<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>拾光 - 原文</title>
<link>https://www.ishiguang.cn/tag/%E5%8E%9F%E6%96%87/</link>
<atom:link href="https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/" rel="self" type="application/rss+xml" />
<language>zh-CN</language>
<description></description>
<lastBuildDate>Sat, 05 Feb 2022 06:12:00 +0800</lastBuildDate>
<pubDate>Sat, 05 Feb 2022 06:12:00 +0800</pubDate>
<item>
<title>减字木兰花（送赵令）</title>
<link>https://www.ishiguang.cn/14398.html</link>
<guid>https://www.ishiguang.cn/14398.html</guid>
<pubDate>Sat, 05 Feb 2022 06:12:00 +0800</pubDate>
<dc:creator>ssr</dc:creator>
<description><![CDATA[减字木兰花（送赵令）宋代 · 苏轼春光亭下。流水如今何在也。岁月如梭。白首相看拟奈何。故人重见。世事年来千万变。官况阑珊。惭愧青松守岁寒。人生如细，岁月如梭。只叹今朝无能为力，心不甘，奈可何!看...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p>减字木兰花（送赵令）</p><p>宋代 · 苏轼</p><p>春光亭下。流水如今何在也。岁月如梭。白首相看拟奈何。</p><p>故人重见。世事年来千万变。官况阑珊。惭愧青松守岁寒。</p><p><img src="https://up.ishiguang.cn/blog/typecho/co200301223228-0.jpg?imageMogr2/format/webp" alt="co200301223228-0.jpg" title="co200301223228-0.jpg"></p><p><img src="https://up.ishiguang.cn/blog/typecho/co200301223228-2.jpg?imageMogr2/format/webp" alt="co200301223228-2.jpg" title="co200301223228-2.jpg"></p><p><img src="https://up.ishiguang.cn/blog/typecho/co200301223228-6.jpg?imageMogr2/format/webp" alt="co200301223228-6.jpg" title="co200301223228-6.jpg"></p><p>人生如细，岁月如梭。只叹今朝无能为力，心不甘，奈可何!看年年岁岁，比比皆是楼上楼，人上人。愿子能懂半是非。</p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://www.ishiguang.cn/14398.html#comments</comments>
<wfw:commentRss>https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/</wfw:commentRss>
</item>
<item>
<title>Java 异步 I/O</title>
<link>https://www.ishiguang.cn/12061.html</link>
<guid>https://www.ishiguang.cn/12061.html</guid>
<pubDate>Sat, 22 Jan 2022 19:43:27 +0800</pubDate>
<dc:creator>admin</dc:creator>
<description><![CDATA[(adsbygoogle = window.adsbygoogle || []).push({});		Java 中的异步 I/O 简称 AIO， A 即 Asynchronous。AIO 在 ...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p>Java 中的异步 I/O 简称 AIO， A 即 <strong>A</strong>synchronous。AIO 在 JDK1.7 时引入，基于操作系统提供的异步 I/O 通信模型，封装了一些进行异步 I/O 操作的 API。</p><h2>1. 异步 I/O 模型</h2><p>学习 Java I/O 相关操作之前应该先了解其背后的 I/O 模型。Java 典型的基于流的文件操作和网络通信都是基于同步阻塞 I/O 模型，JDK1.4 引入的 NIO 基于多路复用 I/O 模型，而 AIO 则基于异步 I/O 模型。在 Linux 操作系统中，异步模型从 I/O 设备读取数据的流程如下图所示。</p><p>应用程序内核aio_read数据未准备好程序继续执行等待数据数据已准备好复制完成复制数据到用户空间处理数据系统调用传递信号立即返回</p><ul>
  <li>应用程序向内核发起 <code>aio_read</code> 系统调用，传递缓存区信息，要读取的文件信息；</li> 
  <li>内核接收请求之后立即返回，应用程序未阻塞；</li> 
  <li>内核等待 CPU 或者 DMA 设备将数据从 I/O 设备复制到内核缓冲区；</li> 
  <li>内核将数据复制到用户空间缓冲区；</li> 
  <li>内核发送一个信号给用户程序，告知数据已复制完成；</li> 
  <li>应用程序处理用户空间缓冲区中的数据。</li>
 </ul><h2>2. 异步通道</h2><p>基于异步 I/O 模型，JDK 提供了面向通道和缓冲区编程的 API。事实上，Java 为基于同步阻塞 I/O 模型的 “旧I/O” 和基于多路复用 I/O 模型的 “新I/O” 也提供了面向通道和缓冲区的 API。异步 I/O 的核心接口是 <code>AsynchronousChannel</code>，这个接口有文件 I/O 的实现和网络 I/O 的实现。</p><p>java.nio.channels<<interface>>AsynchronousChannelAsynchronousServerSocketChannelAsynchronousSocketChannel<<interface>>AsynchronousByteChannelAsynchronousFileChannel</p><ul>
  <li><code>AsynchronousFileChannel</code> 异步文件通道，用于异步操作文件；</li> 
  <li><code>AsynchronousSocketChannel</code> 异步套接字通道，用于 TCP 通信；</li> 
  <li><code>AsynchronousServerSocketChannel</code> 异步套接字监听通道，作为服务端，接收 TCP 连接并创建 <code>AsynchronousSocketChannel</code>。</li>
 </ul><h2>3 异步操作的两种形式</h2><p>异步通道 <code>AsynchronousChannel</code> 并没有显式提供一些必须实现的异步操作的抽象方法（事实上，这个接口仅提供了抽象方法 <code>close()</code>），但是它在注释中给出了异步操作 API 的两种形式。一种异步操作是返回一个 <code>Future</code>，另一种是往异步方法中传递一个回调函数，也就是一个 <code>CompletionHandler</code> 的对象，这两种形式一般的异步编程框架中很常见。</p><h3>3.1 返回 Future 形式</h3><pre class="prettyprint linenums"><code>Future<V> operation(...)<br />void operation(... A attachment, CompletionHandler<V,? super A> handler)<br /></code></pre><p>其中 operation() 代表异步操作，比如从 I/O 设备中读取数据 read，往 I/O 设备中写入数据 write。</p><p>第一种是异步操作返回 <code>Future<V></code>，其中 <strong>V</strong> 是异步操作返回值的类型。开发人员可以调用 <code>Future#isDone()</code> 或者 <code>Future#isCancelled()</code> 查询异步操作的状态，也可以调用 <code>Future#get()</code>，阻塞当前线程，直到异步操作完成。</p><p><strong>读取数据</strong></p><pre class="prettyprint linenums"><code>Future<Integer> future = readChan.read(buff); // 异步读取数据，并立即返回<br />future.get(); // 阻塞，等到异步操作完成，效率低<br /></code></pre><p><strong>写入数据</strong></p><pre class="prettyprint linenums"><code>Future<Integer> future = writeChan.write(buff, position); // 异步写入数据，并立即返回<br />len = future.get(); // 阻塞等待异步操作完成，效率低<br /></code></pre><p>当然，为了提高效率，开发过程中也可以不调用 Future#get() 方法来阻塞代码，可以通过轮询的方式检查 Future 是否已经完成，完成之后再调用 Future#get() 来获取结果。</p><h3>3.2 回调形式</h3><p>第二种操作是往异步函数中传递一个 <code>A attachment</code> 和 <code>CompletionHandler<V, ? super A></code>。其中 A 表示附件的类型，附件通常用来往 <code>CompletionHandler</code> 对象中传入一些上下文信息，V 表示异步操作返回值类型。<code>CompletionHandler</code> 提供了两个抽象方法：completed(V result, A attachment) 和 failed(Throwable t, A attachment)。当异步操作成功，completed 会被调用；当异步操作失败，failed 会被调用。读取到一个数据块就会调用回调代码，不会阻塞。</p><p>可以采用匿名内部类的方式去实现回调接口，也可以采用一般实现类，通过 attachment 传递上下文的形式实现回调逻辑。</p><p><strong>匿名内部类方式</strong></p><pre class="prettyprint linenums"><code>readChan.read(buff, 0, null, new CompletionHandler<>() { // 从位置 0 开始读取数据，数据读取到缓冲区 buff 中<br />long readSize = 0; // 已经读取的字节数<br />@Override<br />public void completed(Integer result, Object attachment) {<br />// 打印读取到的数据<br />System.out.println(Thread.currentThread() + new String(buff.array(), 0, result));<br />try {<br />if ( (readSize = readSize + result) < readChan.size()) { // 已读取字节数少于文件总字节数，继续读取<br />buff.clear(); // 将 buff 的 position 移动到起始位置，使其变为可写状态<br />readChan.read(buff, readSize, null, this); // 递归，继续读取，注意改变读取位置，Handler 直接使用 this。<br />} else {<br />semaphore.release();<br />}<br />} catch (IOException e) {<br />e.printStackTrace();<br />}<br />}<br /></code></pre><p><strong>传递上下文(attachment)方式</strong></p><p>一般的调用逻辑。</p><pre class="prettyprint linenums"><code>Context context = new Context();          // 自定义类，存放上下文信息，上下文信息可根据需要设定<br />context.asyncFileChan = asyncFileChan;<br />context.buffer = ByteBuffer.allocate(4);<br />AsyncReadDataHandler callback = new AsyncReadDataHandler(); // 创建一个处理器对象<br />asyncFileChan.read(context.buffer, 0, context, callback); // 执行异步读取数据<br /></code></pre><p>AsyncReadDataHandler 和 Context 的实现。</p><pre class="prettyprint linenums"><code>/** 定义上下文类 */<br />class Context {<br />AsynchronousFileChannel asyncFileChan;<br />ByteBuffer buffer;<br />}

/** 回调实现类 */<br />class AsyncReadDataHandler implements CompletionHandler<Integer, Context> {

  private long readSize = 0;

  private Semaphore semaphore = new Semaphore(0);

  @Override<br />public void completed(Integer size, Context context) {<br />System.out.print(new String(context.buffer.array(), 0, size));<br />context.buffer.clear();<br />try {<br />if ( (readSize = readSize + context.buffer.limit()) < context.asyncFileChan.size()) {<br />// 还有数据，继续读。数据放入到 context.buffer 中，从 readSize 位置开始读，附件是 context，处理器是当前对象<br />context.asyncFileChan.read(context.buffer, readSize, context, this);<br />} else {<br />semaphore.release();<br />}<br />} catch (IOException e) {<br />e.printStackTrace();<br />semaphore.release();<br />}<br />}

  @Override<br />public void failed(Throwable cause, Context context) {<br />cause.printStackTrace();<br />semaphore.release();<br />}

  // 等待结束<br />public void waitForEnd() throws InterruptedException {<br />semaphore.acquire();<br />}<br />}<br /></code></pre><h2>4. 异步文件通道</h2><p>异步文件通道和文件通道的大部分 API 相同，不同的是异步文件通道支持异步读取和写入数据。这里仅介绍这两类异步 API，其它 API 以及内存映射相关的内容可以参考Java NIO 文件通道 FileChannel 用法。</p><pre class="prettyprint linenums"><code>public class AsyncFileChannel {

  public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {<br />Path path = Paths.get("data.txt"); // 准备一些数据

    /* 异步写入数据 */<br />byte[] data = "This is an example of AsynchronousFileChannel".getBytes(StandardCharsets.UTF_8);<br />ByteBuffer buff = ByteBuffer.allocate(4); // 分配一个大小为 4 的字节缓冲区<br />AsynchronousFileChannel writeChan = AsynchronousFileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE);<br />long position = 0; // 记录写入数据在文件中的起始位置<br />for (int i = 0; i<data.length; i+=buff.capacity()) {<br />buff.put(data, i, Math.min(buff.capacity(), data.length - i)); // 将数据放入缓冲区<br />buff.flip(); // 将缓冲区变为读模式<br />int len;     // 记录成功写入的字节长度<br />while (buff.hasRemaining()) {<br />Future<Integer> future = writeChan.write(buff, position); // 异步写入数据，并立即返回<br />len = future.get(); // 阻塞等待异步操作完成，效率低<br />position += len;    // 更新 position 位置<br />}<br />buff.clear(); // 清空缓冲区，将缓冲区变为写模式<br />}<br />writeChan.force(false);<br />writeChan.close();

    /* 异步读取数据 */<br />Semaphore semaphore = new Semaphore(0);<br />AsynchronousFileChannel readChan = AsynchronousFileChannel.open(path, StandardOpenOption.READ); // 打开一个异步文件通道<br />readChan.read(buff, 0, null, new CompletionHandler<>() { // 从位置 0 开始读取数据，数据读取到缓冲区 buff 中<br />long readSize = 0; // 已经读取的字节数<br />@Override<br />public void completed(Integer result, Object attachment) {<br />// 打印读取到的数据<br />System.out.println(Thread.currentThread() + new String(buff.array(), 0, result));<br />try {<br />if ( (readSize = readSize + result) < readChan.size()) { // 已读取字节数少于文件总字节数，继续读取<br />buff.clear(); // 将 buff 的 position 移动到起始位置，使其变为可写状态<br />readChan.read(buff, readSize, null, this); // 递归，继续读取，注意改变读取位置，Handler 直接使用 this。<br />} else {<br />semaphore.release();<br />}<br />} catch (IOException e) {<br />e.printStackTrace();<br />}<br />}

      @Override<br />public void failed(Throwable exc, Object attachment) {<br />exc.printStackTrace();<br />}<br />});

    // 主线程等待文件数据读取结束。<br />semaphore.acquire();<br />}

}<br /></code></pre><h2>5. 异步套接字通道与异步套接字监听通道</h2><p>面向通道的 Socket 通信需要有客户端和服务端的参与，涉及到监听套接字连接的通道和收发数据的套接字通道。服务端通过套接字监听通道接收客户端的连接，并产生一个套接字通道与客户端通信，而客户端需要主动创建套接字通道去连接服务端。在 Java 实现的同步阻塞 I/O 和多路复用 I/O 中，套接字通道和套接字监听通道分别是 SocketChannel 和 ServerSocketChannel，而 AIO 中的通道则分别是 AsynchronousSocketChannel 和 AsynchronousServerSocketChannel。</p><h3>5.1 TCP 服务端 —— 异步套接字监听通道</h3><p>异步套接字监听通道 AsynchronousServerSocketChannel 的异步操作是异步监听连接，调用 accept 方法之后会立即返回，异步操作的结果是一个异步套接字通道 AsynchronousSocketChannel；连接建立成功之后，服务端即可与客户端进行通信。</p><p>异步套接字监听通道一次性只能够接受一个连接，一个连接接受成功之后再接收下一个，连续接收连接会抛出 AcceptPendingException。例如，下面两段代码将会抛出异常。</p><pre class="prettyprint linenums"><code>serverSocketChannel.accept(null, handler); // 附件为空，传入一个 CompletionHandler 实现类的对象。<br />serverSocketChannel.accept(null, handler);<br /></code></pre><p>或者</p><pre class="prettyprint linenums"><code>future = serverSocketChannel.accept();<br />future = serverSocketChannel.accept();<br /></code></pre><p>正确的使用方式是</p><pre class="prettyprint linenums"><code>serverSocketChannel.accept(null, new CompletionHandler<>() { // 异步建立连接<br />@Override<br />public void completed(AsynchronousSocketChannel socketChannel, Object attachment) { // 成功建立连接<br />serverSocketChannel.accept(null, this);  // 接收下一个<br />...... 其它逻辑<br />}<br />}<br /></code></pre><p>或者</p><pre class="prettyprint linenums"><code>future = serverSocketChannel.accept();<br />future.get(); // 阻塞，等待前一个连接完成<br />future = serverSocketChannel.accept();<br /></code></pre><p>下面是一个 TCP 服务端异步套接字监听通道的一段完整示例代码。服务端接收来自于客户端的连接，连接成功之后继续等待下一个；然后以异步的方式接收客户端发来的数据并打印出来。这里可能会有一个疑问，“接收下一个连接” 处对 accept 方法的调用算递归吗？长时间运行会不会造成 Stack Overflow？严格来讲，这不算是递归，也不会造成栈溢出错误，因为外层的 accept 方法会立即返回，释放虚拟机栈的空间，栈的深度不会超过虚拟机允许的最大深度。</p><pre class="prettyprint linenums"><code>public class AsyncServerSocketChannel {

  public static void main(String[] args) throws IOException, InterruptedException {<br />AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();<br />serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 9090));<br />serverSocketChannel.accept(null, new CompletionHandler<>() { // 异步建立连接<br />@Override<br />public void completed(AsynchronousSocketChannel socketChannel, Object attachment) { // 成功建立连接<br />serverSocketChannel.accept(null, this);           // 接收下一个连接

        ByteBuffer buf = ByteBuffer.allocate(8); // 分配一个 8 字节的缓冲区<br />socketChannel.read(buf, null, new CompletionHandler<>() { // 异步读取数据<br />@Override<br />public void completed(Integer len, Object attachment) {           // 成功读取到数据<br />if (-1 != len) { // 客户端未关闭通道<br />System.out.print(new String(buf.array(), 0, len));<br />buf.clear();    // 清除缓冲区，为下一次写入数据做准备<br />socketChannel.read(buf, null, this);        // 继续读取下一批数据<br />} else {<br />try {<br />socketChannel.close(); // 关闭通道<br />} catch (IOException e) {<br />e.printStackTrace();<br />}<br />System.out.println();<br />}<br />}

          @Override<br />public void failed(Throwable exc, Object attachment) {<br />exc.printStackTrace();<br />}<br />});<br />}

      @Override<br />public void failed(Throwable exc, Object attachment) {<br />exc.printStackTrace();<br />}<br />});

    new Semaphore(0).acquire(); // 阻塞主线程<br />}

}<br /></code></pre><h3>5.2 TCP 客户端 —— 异步套接字通道</h3><p>在上面的异步套接字监听通道的例子中其实已经包含了异步套接字通道读取数据的方式，下面给出的例子是往异步套接字通道写入数据（即向 TCP 服务端发送数据）的例子。</p><p>回调操作方式。</p><pre class="prettyprint linenums"><code>public class AsyncSocketChannel {

  public static void main(String[] args) throws IOException, InterruptedException {<br />AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open(); // 打开一个异步的 Socket 通道<br />InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1", 9090); // 服务端地址<br />Semaphore semaphore = new Semaphore(0); // 定义一个信号量，用来确保主线程等待 Socket 将数据完成再退出<br />socketChannel.connect(serverAddress, null, new CompletionHandler<>() {<br />@Override<br />public void completed(Void result, Object attachment) { // 成功建立连接之后触发<br />String msg = "Hello, this is a TCP Client.";<br />ByteBuffer data = ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));  // 将要发送的数据放到缓冲区中<br />socketChannel.write(data, null, new CompletionHandler<>() {     // 往通道中写（发）数据给服务端

          @Override<br />public void completed(Integer result, Object attachment) {  // 成功写完一批数据后触发<br />if (data.hasRemaining()) { // 缓冲区还有数据<br />socketChannel.write(data, null, this); // 继续（写）发给服务端<br />} else { // 缓冲区数据已经全部发送给了客户端<br />try {<br />socketChannel.shutdownOutput();   // 关闭输出，服务端调用 read 时收到返回值 -1<br />socketChannel.close();            // 关闭通道<br />semaphore.release();              // 释放信号量许可，让主线程可以继续往下走<br />} catch (IOException e) {<br />e.printStackTrace();<br />}<br />}<br />}

          @Override<br />public void failed(Throwable exc, Object attachment) {<br />exc.printStackTrace();<br />}<br />});

      }

      @Override<br />public void failed(Throwable exc, Object attachment) {<br />exc.printStackTrace();<br />}<br />});

    semaphore.acquire(); // 等到异步线程工作完成<br />}<br />}<br /></code></pre><p>Future 操作方式。</p><pre class="prettyprint linenums"><code>/**<br />* 异步 Socket，返回 Future。<br />*/<br />class AsyncSocketChannel2 {<br />public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {<br />AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();<br />InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1", 9090);<br />Future<Void> connect = socketChannel.connect(serverAddress); // 连接到服务端<br />connect.get();  // 阻塞，等待连接建立成功

    byte[] data = "AsyncSocketChannel with Future.".getBytes(StandardCharsets.UTF_8);<br />ByteBuffer buf = ByteBuffer.allocate(4);<br />for (int i = 0; i < data.length; i += buf.capacity()) {<br />buf.put(data, i, Math.min(buf.capacity(), data.length - i));<br />buf.flip();                   // 使缓冲区变为可读状态<br />while (buf.hasRemaining()) {  // 缓冲区中还有数据（缓冲区的数据不一定能够一次性就被发送出去）<br />Future<Integer> future = socketChannel.write(buf); // 非阻塞发送数据<br />future.get(); // 阻塞等待数据发送成功<br />}<br />buf.clear();    // 清空缓冲区，变为可写状态<br />}<br />socketChannel.shutdownOutput();<br />socketChannel.close();<br />}<br />}<br /></code></pre><h2>6. 小结</h2><p>Java AIO 的操作模式和一般的异步代码编写模式类似，都支持返回 Future 的操作和回调操作；但这并不是 AIO 的核心，基于其它 I/O 模型（如：同步阻塞I/O模型）也可以提供类似的异步操作 API。AIO 的厉害之处在于它调用了操作系统内核提供的异步 I/O 接口，提高了 I/O 的效率。</p><p>无论是访问文件还是网络，AIO 的操作步骤和一般基于通道的 I/O 操作步骤类似，包括打开通道，关闭通道，接收连接，读取（接收）数据，写入（发送）数据。这些步骤当中，读/写数据以及接收连接是异步的，其它步骤都是同步。这一点与一些 API 全盘异步的框架（如 Vert.X）不同。</p><h2>7. 参考</h2><p>[1] I/O Multiplexing<br /><br />[2] Java NIO 缓冲区 Buffer<br /><br />[3] Java NIO 文件通道 FileChannel 用法<br /><br />[4] Java NIO 通道 Channel</p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p><h2>Java 异步 I/O的</p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://www.ishiguang.cn/12061.html#comments</comments>
<wfw:commentRss>https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/</wfw:commentRss>
</item>
<item>
<title>JavaFx 实现按钮防抖</title>
<link>https://www.ishiguang.cn/12059.html</link>
<guid>https://www.ishiguang.cn/12059.html</guid>
<pubDate>Sat, 22 Jan 2022 19:40:18 +0800</pubDate>
<dc:creator>admin</dc:creator>
<description><![CDATA[(adsbygoogle = window.adsbygoogle || []).push({});		原文地址：JavaFx 实现按钮防抖 | Stars-One的杂货小窝Android平台的...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p>原文地址：JavaFx 实现按钮防抖 | Stars-One的杂货小窝</p><blockquote><p>Android平台的APP，一般是有需要进行设置按钮的防抖（即在短时间内无法多次点击），我想在JavaFx项目中也是实现防抖功能，便是研究了下</p></blockquote><h2>实现原理</h2><p>点击按钮前，需要记录当前点击的时间，在按钮下一次点击的时候，与之前记录的点击时间进行计算，判断两者的间隔时间是否大于设定的条件值</p><p>这里思路没有是嘛难度，主要是点击时间的历史记录该如何记录？有以下几种方法</p><ol>
  <li>使用Map存储（按钮过多占用资源较大）</li> 
  <li>使用控件对象提供的某个无用字段进行存储</li>
 </ol><p>在Android中，可以使用View中的tag来保存信息</p><p>而在JavaFx中，<strong>所有的控件都有userData的字段</strong>，有了这个，我们即可去存储时间</p><h2>实现代码</h2><h3>Kotlin版：</h3><p>kotlin版我是使用了扩展方法，对BUtton类进行了扩展，不太明白扩展方法的可以查下资料，简单来说即是给Button类新增了个方法</p><pre class="prettyprint linenums"><code>//注意Button是javafx包的<br />fun Button.isFastClick(): Boolean {<br />val lastClickTime = userData as Long?<br />val currentTime = System.currentTimeMillis()

    userData = currentTime<br />//这里我设置为1s内多次点击无效，可以根据需要调整<br />if (lastClickTime != null && currentTime - lastClickTime <= 1000) {<br />return true<br />}<br />return false<br />}<br /></code></pre><p><strong>使用：</strong></p><pre class="prettyprint linenums"><code>button{<br />action {<br />//不是快速点击才进入点击逻辑<br />if (!isFastClick()) {<br />println("hello")<br />}<br />}<br />}<br /></code></pre><p><strong>PS:后续会收录到common-controls的库中</strong></p><h3>Java版</h3><pre class="prettyprint linenums"><code>class ButtonUtils {<br />public static boolean isFastClick(Button button) {<br />Object userData = button.getUserData();

        long currentTime = System.currentTimeMillis();<br />button.setUserData(currentTime);<br />if (userData instanceof Long) {<br />Long lastClickTime = (Long) userData;<br />return currentTime - lastClickTime <= 1000;<br />}<br />return false;<br />}<br />}<br /></code></pre><h2>测试效果</h2><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/664429457324132975.jpg?imageMogr2/format/webp" class="aligncenter"></p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p><h2>JavaFx 实现按钮防抖的</p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://www.ishiguang.cn/12059.html#comments</comments>
<wfw:commentRss>https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/</wfw:commentRss>
</item>
<item>
<title>新手如何入门linux，linux原来还可以这么学</title>
<link>https://www.ishiguang.cn/12058.html</link>
<guid>https://www.ishiguang.cn/12058.html</guid>
<pubDate>Sat, 22 Jan 2022 19:40:17 +0800</pubDate>
<dc:creator>admin</dc:creator>
<description><![CDATA[(adsbygoogle = window.adsbygoogle || []).push({});		前言在这个只有cangls和小白两人的小房间中，展开了一次关于学习方法的讨论。小白：can...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><h1>前言</h1><p>在这个只有cangls和小白两人的小房间中，展开了一次关于学习方法的讨论。</p><p><strong>小白</strong>：cangls啊，我想请教一个问题，您是如何记住那么多linux命令的。</p><p><strong>cangls</strong>：我啊，别人都看我的小电影，我也不知道啊！可能是举的例子多了，就记住了一些呗。</p><p>此次写作灵感源于一本心理学方面的书籍，对话体的方式运用得当，确实很吸引人。</p><p>灵魂拷问？新手如何学linux？如何从新手到熟手？如何玩转linux？linux大神如何掌握那么多命令？趣味学习linux，了解一下。基于实战演练，同时突出重点。2万5千字的长文，是否真材实料，不妨先收藏再评论。</p><p>换个角度看待这个问题，<strong>linux大神都是从小白开始的，没有人一开始就是大神</strong>。</p><p>只是别人<strong>运用的比较多</strong>，自然而然记住了更多的命令，<strong>从而更擅长总结学习方法</strong>。</p><p>如果你是Linux运维人员，或者是刚入门的编程人员。根据自己的工作经历以及自学总结的内容，篇幅很长，加入了不少示例代码。但很详细哟！相信你一定有所收获的。</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/5555157997167242683.jpg?imageMogr2/format/webp" class="aligncenter"></p><h1>正文</h1><p>7年前，我还是一个连linux是个啥都不知道的笨小孩，然而现在靠着自学积累的知识也能在日常工作中满足刚需，还教会了新来的同事不少小技巧。先上点趣味性的知识，俗称扫盲，拥有一颗爱学习（闷骚）的心。</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/9114597185578706808.jpg?imageMogr2/format/webp" class="aligncenter"></p><h2>01 第一夜</h2><p>以linux为基础将知识串联起来，加入了一些个人经验总结，仅供参考哟！当然，你可以将此篇文章推送你的亲朋好友，当做是学习linux的入门课程。</p><p><strong>个人建议，如果感觉建议不到位，可以忽略掉。请直接看正文，相信会有收获的</strong></p><p>在给别人建议的时候，希望能从他人的角度去思考。不要上来直接甩一个教程或者视频过去，这样对新人是很不友好的。</p><p>首先，我们应该养成自学的良好习惯。<strong>最优的方法往往是去官网查阅文档</strong>，其次通过各大论坛，再就是搜索引擎。但官方文档大体都是英文版的，小白往往也是心有余而力不足，<strong>记得多用词典查阅并积累</strong>。但是，现在linux有简体中文版支持，我们在搭建学习环境的时候可以选择简体中文。当你学到深处的时候，发现还是官方文档最靠谱。</p><p><strong>搭建环境</strong>，<strong>建议是真机配合虚拟机工具部署linux服务器</strong>(Redhat系列、Ubuntu、Fedora、Debian等等)使用，放心大胆的测试，再也不用担心服务器被自己弄崩溃了。终端管理工具推荐tabby（github上有），虽然目前终端不支持中文字符输入，但它是开源免费的。关于以何种linux的发行版进行学习，希望不要去跟风，听风就是雨，要根据自身实际情况而定。如果非要我推荐，那就是推荐Redhat系列的Centos或者Ubuntu系列，教程和资源多呗。给出我之前写的稀烂的教程<strong>linux养成达人之入门实践图文超详细</strong>（工欲善其事，必先利其器篇），希望对你有所帮助（<strong>基于centos6.5的</strong>）：</p><p>https://blog.csdn.net/Tolove_dream/article/details/103823216</p><p>关于centos7的可以参考这篇文章：</p><p>https://blog.csdn.net/Tolove_dream/article/details/116085467</p><p><strong>接着说linux简体中文版</strong>，优势在于我看帮助命令的时候有一部分是中文的，这就对我们的学习有大大大的好处。当你提这个问题的时候，应该是对linux这个极具魅力的操作系统有很大的兴趣。有兴趣那就好办了，你会思考如何学习对自己更为有利。此时就会去翻阅文档，<strong>最后发现还是官方文档最为有用，学会积累经验</strong>。慢慢的从小白到老手，这之间有一段艰难的路需要独自走完，享受这一过程的美妙之处。</p><p>曾经你是不是在各大论坛或者博客网站都能看到这样一些内容，从删库到跑路。全球绝大部分的服务器啊，基本上都是采用linux服务器的，没有权限你还想删库到跑路？<strong>当然只是开个玩笑，咱都是遵纪守法的五好青年</strong>。</p><p>前面的删库到跑路，只是为了引出linux另一大魅力所在权限足够透明。如果在权限这方面玩的相当熟练，<strong>那你可以存不少cang老师的作品哟</strong>。我能自己欣赏，别人却看不到，想想就刺激。谈到cang老师，大家满眼放光聚精会神直呼内行。咱就皮一下，这不是为了引起你学习的兴趣吗。</p><p>做了很多铺垫，这里就详细的聊聊我自己的学习过程。将以对话形式展示，小白与cangls日日夜夜的对话。cangls是啥？别问我，问就是不知道，只可意会不可言传。</p><h3>一、学习方法的探讨</h3><p>在这个只有cangls和小白两人的小房间中，展开了一次关于学习方法的讨论。</p><p><strong>小白</strong>：cangls啊，我想请教一个问题，您是如何记住那么多linux命令的。</p><p><strong>cangls</strong>：我啊，别人都看我的小电影，我也不知道啊！可能是举的例子多了，就记住了一些呗。</p><p><strong>小白</strong>：是这样啊！那能具体说说吗，我很感兴趣。</p><p><strong>cangls</strong>：好啊，那咱就互相探讨探讨。</p><p>这这这，啥情况？我怀疑你两不仅在开车，而且还搞颜色，还超速了。好了，玩归玩闹归闹，言归正传。</p><p><strong>小白</strong>：我想查看一下以前放的学习资料(小电影)</p><p><strong>cangls</strong>：使用ls命令即可查看目录和文件<strong>。</strong></p><pre class="prettyprint linenums"><code># 不带参数<br />$ ls<br /># -a参数，查询所有，包含带.隐藏的<br />$ ls -a<br /># -l参数，长格式显示：显示所属组、所有者，修改时间以及文件名<br />$ ls -l<br /></code></pre><p><strong>小白</strong>：我想给文件夹改个名字，羞羞。</p><p><strong>cangls</strong>：好办，使用mv命令就能处理。</p><pre class="prettyprint linenums"><code>$ mv cangls  bols<br /></code></pre><p><strong>小白</strong>：那我想移动到另一个文件夹呢？</p><p><strong>cangls</strong>：同样可以使用mv命令，但要接路径哟！</p><pre class="prettyprint linenums"><code>$ mv /opt/cangls  /home/cangls/av<br /></code></pre><p><strong>小白</strong>：cangls呀，我目前存的文件过多，想集中分类处理。</p><p><strong>cangls</strong>：你是想，将多个文件存放到同一个目录吧。mkdir命令新建文件夹可以满足你，记得用上面的mv命令转移学习资料。</p><pre class="prettyprint linenums"><code>$ mkdir  /home/cangls/av<br /></code></pre><p>此时的小白看了看时间，已经记不起这是多少次来请教cangls了。cangls教了小白一个很鸡肋的但很实用的命令。</p><p><strong>cangls</strong>：小白啊，知道今天是今年的第多少天吗？</p><p><strong>小白</strong>：我不记得了，看看手机就知道了。</p><p><strong>cangls</strong>：不用那么麻烦，用linux自带的命令cal即可查看。</p><pre class="prettyprint linenums"><code>$ cal -jy<br /></code></pre><p><strong>cangls此时看小白兴趣不减反增，于是介绍了帮助命令help和man来方便小白自学。</strong></p><p>上面的对话形式，是不是很有趣。当你心烦意乱的时候，不妨静下心来试试。找一个自己感兴趣的方向，去验证这些命令。如果你感兴趣的话，我之前写的部分文章也许会对你有一定的帮助。</p><p>cangls和小白的对话并没有结束，在这个只有两人的小房间中，到底发生了啥，请接着看。</p><h2>02 第二夜</h2><p>第一夜对学习方法进行了探讨，如何一步步提升自己学习的兴趣。第二夜cangls与小白继续展开关于linux基本命令的讨论。</p><p><strong>cangls</strong>：小白啊，你来了，看来还是有不少疑问吧！</p><p><strong>小白</strong>：是的，虽然上次你说了一些关于linux的帮助命令，我还是有些摸不着头脑。</p><p><strong>cangls</strong>：没关系，下面我给你带来了详细的帮助命令的讲解，学习方法很重要哟！</p><p>小白此时拿出了自己的小本本，开始记录了起来...</p><h3>二、基本命令的探讨</h3><p><strong>注意</strong>：#符号表示root用户登录，$符号表示普通用户登录。</p><h4>1、帮助命令（重点）</h4><p>这种方法是其中一种手段，将其输出到某个文件，然后总结起来。再<strong>通过scp命令传到本地记录到小本本上</strong>。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# ls --help > helpcmd.txt<br />[root@cnwangk ~]# man ls >> helpcmd.txt<br />#借助本地的终端管理工具，使用scp命令取到Windows本地桌面，即从远程传回本地<br />$ scp root@192.168.245.131:/root/helpcmd.txt ~/Desktop/<br />root@192.168.245.131's password:<br />helpcmd.txt                                                                                              100%   21KB   3.8MB/s   00:00<br /></code></pre><p><strong>注意</strong>：>符号是重定向输入会覆盖原始文件的内容，>>符号也是重定向输入到指定文件，但是是追加进去。</p><h5>1.1、help命令</h5><p>如下所示，直接输入help命令就会输出很多提示，或者在使用的命令后面加上参数<code>--help</code>进行操作。</p><pre class="prettyprint linenums"><code>$ help<br />$ ls --help<br /></code></pre><p>上面的第一条命令代表着直接输入help命令，也会反馈一些帮助文档出来。第二种方式，则是以具体的命令<code>ls</code>使用<code>--help</code>帮助命令获取指定命令的帮助文档。</p><h5>1.2、man命令</h5><h6>1.2.1、man的级别作用</h6><table> 
  <thead> 
   <tr> 
    <th>参数</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>1</td> 
    <td>查看命令的帮助</td> 
   </tr> 
   <tr> 
    <td>2</td> 
    <td>查看可被内核调用的函数的帮助</td> 
   </tr> 
   <tr> 
    <td>3</td> 
    <td>查看函数和函数库的帮助</td> 
   </tr> 
   <tr> 
    <td>4</td> 
    <td>查看特殊件的帮助（主要是/dev目录下的文件）</td> 
   </tr> 
   <tr> 
    <td>5</td> 
    <td>查看配置文件的帮助</td> 
   </tr> 
   <tr> 
    <td>6</td> 
    <td>查看游戏的帮助</td> 
   </tr> 
   <tr> 
    <td>7</td> 
    <td>查看其它杂项的帮助</td> 
   </tr> 
   <tr> 
    <td>8</td> 
    <td>查看系统管理员可用命令的帮助</td> 
   </tr> 
   <tr> 
    <td>9</td> 
    <td>查看和内核相关文件的帮助</td> 
   </tr> 
  </tbody> 
 </table><p>例如：<strong>查看passwd的配置文件帮助、查看null的特殊件的帮助、查看ifconfig系统管理员可用命令的帮助</strong>。</p><pre class="prettyprint linenums"><code>$ man 5 passwd<br />$ man 4 null<br />$ man 8 ifconfig<br /></code></pre><p><code>man</code> 命令，<strong>获取指定命令的帮助</strong>，展示ls命令全部内容帮助手册。</p><pre class="prettyprint linenums"><code>$ man ls<br /></code></pre><p>查看命令拥有哪个级别的帮助,<code>man -f</code> 命令相当于<code>whatis</code>命令。</p><pre class="prettyprint linenums"><code>$ man -f<br />[root@cnwangk ~]# man -f<br />whatis 什么？<br />$ whatis<br /></code></pre><p>查看命令相关的所有帮助。<code>man -k</code> 命令，相当于<code>apropos</code> 命令。例如：</p><pre class="prettyprint linenums"><code>$ man -k<br />$ apropos<br />#查看passwd的帮助命令<br />$ apropos passwd<br /></code></pre><h6>1.2.2、man命令的详细展示</h6><p>直接在终端输入man命令，会提示您需要什么手册页？这里以查看ls命令的帮助手册为例子进行讲解示例说明，列举了部分示例。</p><p><strong>tips</strong>：输入q直接退出帮助手册。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# man<br />您需要什么手册页？<br />#进入ls命令的帮助文档<br />$ man ls<br />LS(1)                              General Commands Manual                                                   LS(1)<br />NAME<br />ls, dir, vdir - 列目录内容<br />提要<br />ls [选项] [文件名...]<br />POSIX 标准选项: [-CFRacdilqrtu1]<br />GNU 选项 (短格式):<br />[-1abcdfgiklmnopqrstuxABCDFGLNQRSUX] [-w cols] [-T cols] [-I pattern] [--full-time] [--format={long,verbose,commas,across,verti‐<br />cal,single-column}] [--sort={none,time,size,extension}] [--time={atime,access,use,ctime,status}]  [--color[={none,auto,always}]]<br />[--help] [--version] [--]<br />描述（ DESCRIPTION ）<br />程序ls先列出非目录的文件项，然后是每一个目录中的“可显示”文件。如果  没有选项之外的参数【译注：即文件名部分为空】出现，缺省为 "."（当前目录）。 选项“ -d ”使得目录与非目录项同样对待。除非“ -a ” 选项出现，文 件名以“.”开始的文件不属“可显示”文件。<br />以当前目录为准，每一组文件（包括非目录文件项，以及每一内含文件的目录）分别按文件名比较顺序排序。如果“-l”选项存在，每组文件前显示一摘要行: 给出该组文件长度之和（以 512 字节为单位）。<br />输出是到标准输出（stdout）。除非以“-C”选项要求按多列输出，输出将是一行一个。然而，输出到终端时，单列输出或多列输出是不确定的。可以分别 用选项“ -1 ” 或“ -C ”来强制按单列或多列输出。<br />-C     多列输出，纵向排序。<br />-F     每个目录名加“ / ”后缀，每个 FIFO 名加“ | ”后缀， 每个可运行名加“ * ”后缀。<br />-R     递归列出遇到的子目录。<br />-a     列出所有文件，包括以 "." 开头的隐含文件。<br /></code></pre><p>以上就是<code>man</code>帮助命令的讲解，相信你会爱上linux中的帮助命令的。这回该明白了，linux大神是如何记住那么多命令的吧！</p><h4>2、常见目录作用的探讨</h4><table> 
  <thead> 
   <tr> 
    <th>选项</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td><strong>/</strong></td> 
    <td><strong>根目录</strong></td> 
   </tr> 
   <tr> 
    <td>/<code>bin</code></td> 
    <td>命令保存目录（普通用户就可以读取的命令）</td> 
   </tr> 
   <tr> 
    <td>/boot</td> 
    <td>启动目录，启动相关文件</td> 
   </tr> 
   <tr> 
    <td>/<code>dev</code></td> 
    <td>设备文件保存目录</td> 
   </tr> 
   <tr> 
    <td><strong>/etc</strong></td> 
    <td><strong>配置文件保存目录</strong></td> 
   </tr> 
   <tr> 
    <td><strong>/home</strong></td> 
    <td><strong>普通用户的家目录</strong></td> 
   </tr> 
   <tr> 
    <td>/lib</td> 
    <td>系统库保存目录</td> 
   </tr> 
   <tr> 
    <td>/mnt</td> 
    <td>系统挂载目录</td> 
   </tr> 
   <tr> 
    <td>/media</td> 
    <td>挂载目录</td> 
   </tr> 
   <tr> 
    <td><strong>/<code>root</code></strong></td> 
    <td><strong>超级用户的家目录</strong></td> 
   </tr> 
   <tr> 
    <td>/tmp</td> 
    <td>临时目录</td> 
   </tr> 
   <tr> 
    <td>/<code>sbin</code></td> 
    <td>命令保存目录（超级用户才能使用的目录）</td> 
   </tr> 
   <tr> 
    <td>/proc</td> 
    <td>直接写入内存</td> 
   </tr> 
   <tr> 
    <td>/sys</td> 
    <td></td> 
   </tr> 
   <tr> 
    <td><strong>/usr</strong></td> 
    <td><strong>系统软件资源目录</strong></td> 
   </tr> 
   <tr> 
    <td>/usr/bin</td> 
    <td>系统命令（普通用户）</td> 
   </tr> 
   <tr> 
    <td>/usr/sbin</td> 
    <td>系统命令（超级用户）</td> 
   </tr> 
   <tr> 
    <td>/<code>var</code></td> 
    <td>系统相关文档内容</td> 
   </tr> 
  </tbody> 
 </table><h4>3、最常用的命令总结</h4><h5>3.1、学习方法小结</h5><p>最常用的命令往往也是最基本的命令，这里也同样以增删改查（CURD）进行讲解。如果要人为的细分，这里给出一种学习的思路，可以按照如思维导图所示：</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/2086500669488468929.jpg?imageMogr2/format/webp" class="aligncenter"></p><p>当然也可以按照如下方式去总结：</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/3516828129502260719.jpg?imageMogr2/format/webp" class="aligncenter"></p><p>列举一些常用的命令，先做简单的介绍，在脑海中有个印象。下面继续做详细介绍。</p><pre class="prettyprint linenums"><code>$ ls #查看文件和目录<br />$ ll #等价于ls -l<br />$ cat #查看具体内容<br />$ cd #切换目录<br />$ mkdir # 新建文件夹<br />$ touch # 新建文件<br />$ cp #复制文件<br />$ mv #移动或者重命名文件<br />$ vim #linux下的一种编辑文件的手段，文中会详细介绍。<br />$ rm #删除文件<br />$ chmod #赋予权限<br />$ chown #改变文件所有者<br /></code></pre><h5>3.2、命令详细介绍</h5><p><strong>ls命令</strong>，查看目录以及文件命令，下面不会全部展示出来，只<strong>展示一部分内容</strong>。</p><pre class="prettyprint linenums"><code>#不带任何参数<br />[root@cnwangk ~]# ls<br />anaconda-ks.cfg  av  cangls.av  history.txt  scp_test<br />#加上参数-l，使用较长格式列出信息<br />[root@cnwangk ~]# ls -l<br />drwxr-xr-x  2 root root    23 1月   4 20:21 av<br />-rw-r--r--  1 root root    10 1月   4 20:16 cangls.av<br /># -a参数，不隐藏任何以. 开始的项目<br />[root@cnwangk ~]# ls -a<br />.  .bash_history  .bash_profile  .cache  .config ..  av  .bash_logout   .bashrc   cangls.av  .ssh  .viminfo<br /></code></pre><p><strong>ll命令</strong>，是ls -l的缩略形式，相当于起了别名。以长格式列出信息，包含权限、文件所有者、日期、文件名。</p><pre class="prettyprint linenums"><code>$ ll<br />drwxr-xr-x  2 root root    23 1月   4 20:21 av<br />-rw-r--r--  1 root root    10 1月   4 20:16 cangls.av<br />-rwxr-xr-x  1 root root    78 1月  19 21:23 hello.sh<br /></code></pre><p><strong>cat命令</strong>，查看文件的内容，新建了一个hello.sh脚本作为演示，展示脚本的内容。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# cat hello.sh<br />#!/bin/bash<br />echo "hello cangls"<br />echo hello linux<br />echo create btrfs filesystem<br /></code></pre><p><strong>cd命令</strong>，这个就不用做过多介绍，大家都很熟悉这个命令了。</p><pre class="prettyprint linenums"><code>#切换到opt目录下<br />$ cd /opt<br />#返回上一层<br />$ cd ..<br />#进入当前用户家目录<br />$ cd ~<br /></code></pre><p><strong>mkdir命令</strong>，新建目录，新建一个cangls的合集目录。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# mkdir canglsList<br />[root@cnwangk ~]# ls<br />anaconda-ks.cfg  cangls.av  canglsList  hello.sh<br /></code></pre><p><strong>touch命令</strong>，新建一个cangls.avi文件</p><pre class="prettyprint linenums"><code>$ touch cangls.avi<br /></code></pre><p><strong>cp命令</strong>，复制命令，可以是单个文件也可以是目录。将cangls.avi文件复制到新建的canglsList目录中。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# cp cangls.avi /root/canglsList/<br />[root@cnwangk ~]# ls /root/canglsList/ #查看复制后cangls文件集合目录<br />cangls.avi<br /></code></pre><p><strong>mv命令</strong>，移动或者重命名。当文件路径相同时，我们就会修改重命名；不同路径时，则为剪切。我将cangls.avi文件重命名为acngls.mp4。然后将cangls.mp4文件移动到canglsList目录下。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# mv cangls.avi cangls.mp4<br />[root@cnwangk ~]# ls<br />anaconda-ks.cfg  av  cangls.av  canglsList  cangls.mp4  hello.sh<br />[root@cnwangk ~]# mv cangls.mp4 /root/canglsList/<br />[root@cnwangk ~]# ls<br />anaconda-ks.cfg  av  cangls.av  canglsList  hello.sh  history.txt  scp_test<br />[root@cnwangk ~]# ls /root/canglsList/<br />cangls.avi  cangls.mp4<br /></code></pre><p>最终查看在/root/目录下cangls.mp4部件了，此时已经被我移动到canglsList集合目录中了。</p><p><strong>vim命令</strong>，这里做简单演示，下面会做详细的说明。输入hello cangls，使用cat展示内容。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# vim /root/canglsList/cangls.avi<br />[root@cnwangk ~]# cat /root/canglsList/cangls.avi<br />hello cangls<br /></code></pre><p><strong>rm命令</strong>，删除命令。演示，删除cangls.av文件。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# ls #查看我有cangls.av这个文件<br />anaconda-ks.cfg  av  cangls.av  canglsList  hello.sh<br />[root@cnwangk ~]# rm cangls.av #删除，需要输入确认<br />rm：是否删除普通文件 "cangls.av"？y<br />[root@cnwangk ~]# rm -rf cangls.av  #强制删除，并递归删除，不需要确认<br />[root@cnwangk ~]# ls<br />anaconda-ks.cfg  av  canglsList  hello.sh<br /></code></pre><p><strong>chmod命令</strong>，简单的介绍赋予权限命令，一般755和644比较常用的。给cangls.sh输入点内容，然后赋予权限。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# echo echo "hello cangls" > cangls.sh<br />[root@cnwangk ~]# cat cangls.av<br />hello cangls<br />[root@cnwangk ~]# ll #查看cangls.sh脚本权限<br />-rw-r--r--  1 root root    13 1月  19 22:50 cangls.sh<br />[root@cnwangk ~]# chmod 755 cangls.sh<br />[root@cnwangk ~]# ll #对比权限变为了rwx-rx-rx，对应数字就是755<br />-rwxr-xr-x  1 root root    13 1月  19 22:50 cangls.sh<br />#执行脚本cangls.sh<br />[root@cnwangk ~]# ./cangls.sh<br />hello cangls<br /></code></pre><p><strong>chown命令</strong>，改变文件所有者，将cangls.sh文件所以者从root改为test用户。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# chown test cangls.sh<br />[root@cnwangk ~]# ll<br />-rwxr-xr-x  1 test root    18 1月  19 22:55 cangls.sh<br /></code></pre><p>个人根据多年经验总结，认为工作中最最最常用的一些命令，以上就列举这么多了。还有其它常用命令，请接着往下阅读。</p><h4>4、其它常用命令</h4><h5>4.1、 挂载命令格式</h5><p><code>mount</code> [-t 文件系统] [-o 特殊选项] 设备文件名 挂载点</p><table> 
  <thead> 
   <tr> 
    <th>参数</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>-t</td> 
    <td>文件系统，加入系统文件类型来指定挂载的类型，可以是ext3、ext4、iso9660、<strong>xfs</strong>、<strong>btrfs</strong>等文件系统</td> 
   </tr> 
   <tr> 
    <td>-o</td> 
    <td>特殊选项，可以指定挂载的额外选项</td> 
   </tr> 
  </tbody> 
 </table><h5>4.2、查询与挂载</h5><p>查询系统中已经挂载的设备，<code>mount</code>命令。</p><pre class="prettyprint linenums"><code>#查询系统中已经挂载的设备<br />$ mount<br />#列举我自己测试环境下已经挂载的部分设备，Redhat7系列<br />#我测试使用是btrfs文件系统<br />/dev/sdb2 on /data type btrfs (rw,relatime,space_cache,subvolid=5,subvol=/)<br />#系统默认挂载所使用文件系统格式xfs<br />/dev/sda2 on /home type xfs (rw,relatime,attr2,inode64,noquota)<br />/dev/sda1 on /boot type xfs (rw,relatime,attr2,inode64,noquota)<br /></code></pre><p>上面列举我自己测试环境下已经挂载的部分设备，Redhat7系列。顺带一提，Redhat7开始推荐使用xfs文件系统，我所演示的也包含了<code>xfs</code>文件系统挂载的，同样也有上面介绍过的<code>btrfs</code>文件系统。</p><p>部分参数说明，如下表格所示：</p><table> 
  <thead> 
   <tr> 
    <th>参数</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>mount -a</td> 
    <td>依据配置文件<code>/etc/fstb</code>的内容，自动挂载</td> 
   </tr> 
   <tr> 
    <td>atime/noatime</td> 
    <td>更新访问时间/不更新访问时间。访问分区文件时，是否更新文件的访问时间，默认为更新。</td> 
   </tr> 
   <tr> 
    <td>async/sync</td> 
    <td>异步/同步，默认为异步</td> 
   </tr> 
   <tr> 
    <td>auto/noauto</td> 
    <td>自动/手动，执行mount -a命令时，是否会自动安装/etc/fstb文件内容挂载，默认自动。</td> 
   </tr> 
   <tr> 
    <td>defaults</td> 
    <td>定义默认值，相当于<code>rw</code>，<code>suid</code>，<code>dev</code>，<code>exec</code>，<code>auto</code>，<code>nouser</code>，<code>async</code>这七个选项。</td> 
   </tr> 
   <tr> 
    <td>exec/noexec</td> 
    <td>执行/不执行，设定是否允许在文件系统中执行可执行文件，默认exec允许。</td> 
   </tr> 
   <tr> 
    <td>remount</td> 
    <td>重新挂载已挂载的文件系统，一般用于指定修改特殊权限。</td> 
   </tr> 
   <tr> 
    <td>rw/ro</td> 
    <td>读写/只读，文件系统挂载时，是否具有读写权限，<strong>默认</strong>rw。</td> 
   </tr> 
   <tr> 
    <td>suid/nosuid</td> 
    <td>具有/不具有suid权限，设定文件系统是否具有suid和sgid的权限，<strong>默认具有</strong>。</td> 
   </tr> 
   <tr> 
    <td>user/nouser</td> 
    <td>允许/不允许普通用户挂载，设定文件系统是否允许普通用户挂载，默认不允许，只有<code>root</code>可以挂载分区。</td> 
   </tr> 
   <tr> 
    <td>usrquota</td> 
    <td>写入代表文件系统支持用户磁盘配额，默认不支持。</td> 
   </tr> 
   <tr> 
    <td>grpquota</td> 
    <td>写入代表文件系统支持组磁盘配额，默认不支持。</td> 
   </tr> 
  </tbody> 
 </table><p><strong>关于磁盘挂载，如果感兴趣的话</strong>，可以参考我在github或者gitee上整理的文章。在代码库中的linux文件夹中，同样整理了PDF文件格式的文章便于阅读，目前还在整理完善中。</p><p><strong>个人github仓库地址</strong>，一般会先更新PDF文件，然后再上传markdown文件。如果访问github太慢，可以访问gitee进行克隆。</p><p>https://github.com/cnwangk/SQL-study</p><p><strong>4.2.1、挂载光盘</strong></p><p>首先建立挂载点，命令如下：</p><pre class="prettyprint linenums"><code>$ mkdir /mnt/cdrom/<br /></code></pre><p>挂载光盘，这里说明下：只是习惯在mnt下建立，media下建立也行。</p><p>简单介绍：/dev/sr0是要挂载的文件，/mnt/cdrom是挂载存储的磁盘路径</p><pre class="prettyprint linenums"><code>$ mount -t iso9660 /dev/cdrom /mnt/cdrom/<br />$ mount /dev/sr0  /mnt/cdrom/<br /></code></pre><p>卸载命令，设备文件名或者挂载点，比如卸载新增磁盘挂载的sdb</p><pre class="prettyprint linenums"><code>$ umount /dev/sdb<br /></code></pre><p><strong>4.2.2、挂载U盘</strong></p><p>查看U盘设备文件名，<code>fdisk -l</code>查看磁盘列表</p><pre class="prettyprint linenums"><code>$ fdisk -l<br />$ mount -t vfat /dev/sdb1 /mnt/usb/<br /></code></pre><h5>4.3、用户登录查看命令</h5><p>查看用户登录信息</p><pre class="prettyprint linenums"><code>$ w<br />[root@cnwangk ~]# w<br />21:31:12 up  1:39,  1 user,  load average: 0.00, 0.04, 0.05<br />USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT<br />root     pts/0    192.168.245.1    19:52    0.00s  0.28s  0.02s w<br /></code></pre><p>如上所示，输入w命令查看到我的登录信息。只有1个用户，我用ssh登录的，本机虚拟机中并没有登录。命令输出如下所示：</p><p><strong>4.3.1、命令输出</strong></p><p>参数说明，作用如下表格所示：</p><table> 
  <thead> 
   <tr> 
    <th>参数</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>USER</td> 
    <td>登录的用户名</td> 
   </tr> 
   <tr> 
    <td>TTY</td> 
    <td>登录终端</td> 
   </tr> 
   <tr> 
    <td>FROM</td> 
    <td>从哪个IP地址登录</td> 
   </tr> 
   <tr> 
    <td>LOGIN@</td> 
    <td>登录时间</td> 
   </tr> 
   <tr> 
    <td>DILE</td> 
    <td>用户闲置时间</td> 
   </tr> 
   <tr> 
    <td>JCPU</td> 
    <td>与该终端连接的所有进程占用的时间。这个时间里并不包括过去的后台作业时间，但包括当前正在运行的后台作业占用时间</td> 
   </tr> 
   <tr> 
    <td>PCPU</td> 
    <td>当前进程所占用时间</td> 
   </tr> 
   <tr> 
    <td>WHAT</td> 
    <td>当前正在运行的命令</td> 
   </tr> 
   <tr> 
    <td>w</td> 
    <td>查询登录用户，显示系统时间和运行时间，用户个数以及平均负载。</td> 
   </tr> 
  </tbody> 
 </table><p><strong>4.3.2、查看登录用户信息</strong></p><p>命令输出：用户名，登录终端，登录时间（登录来源IP地址）</p><pre class="prettyprint linenums"><code>#查看用户信息<br />$ who<br />[root@cnwangk ~]# who  #查看当前用户信息<br />root     pts/0        2022-01-19 19:52 (192.168.245.1)<br />#查看当前用户<br />$ whoami #查看当前用户身份是root<br />[root@cnwangk ~]# whoami<br />root<br /></code></pre><p>查看到当前用户为root，登录终端pts/0，登录时键与IP：2022-01-19 19:52 (192.168.245.1)，<strong>虚拟机搭建的环境</strong>。</p><p><strong>查询当前登录和过去登录的用户信息</strong></p><p>last命令默认读取<code>/var/log/wtmp</code>文件数据 。命令输出：用户名，登录终端，登录IP，登录时间，退出时间（在线时间）</p><pre class="prettyprint linenums"><code>$ last<br />root     tty1                          Sun Jan  5 21:47 - 21:50  (00:02)<br />reboot   system boot  3.10.0-514.el7.x Sun Jan  5 21:44 - 21:59 (7+00:15)<br /></code></pre><p>可以看到记录的我最久远的一次登录信息，时间确实有点久了，基本上很晚才使用的。</p><p><strong>查看所有用户最后一次登录时间</strong></p><pre class="prettyprint linenums"><code>#lastlog命令默认读取/var/log/lastlog文件内容<br />#命令输出：用户名，登录终端，登录IP，最后一次登录时间<br />$ lastlog<br />[root@cnwangk ~]# lastlog<br />用户名           端口     来自               最后登陆时间<br />root            pts/0   192.168.245.1      三 1月 19 22:52:46 +0800 2022<br />bin                                        **从未登录过**<br /></code></pre><p>上面的中文显示，我在安装的时候选择了简体中文版，<strong>对于初学者来说简直太友好了</strong>。</p><p><strong>查看网络状态</strong> ，一般比较关注的是<code>ESTABLISHED</code>状态</p><pre class="prettyprint linenums"><code>$ netstat -an | grep ESTABLISHED<br />[root@cnwangk ~]# netstat -an | grep ESTABLISHED<br />tcp        0      0 192.168.245.131:22      192.168.245.1:3579      ESTABLISHED<br /></code></pre><h5>4.4、解压缩命令</h5><p>只介绍一些常用的，比如压缩命令zip、gzip；解打包命令tar。</p><h6>4.4.1、压缩命令</h6><p>例如：zip、gzip。将canglsAVList压缩成canglsAVList.zip格式，将bolsAVList压缩成bolsAVList.gz格式。</p><pre class="prettyprint linenums"><code>zip -r canglsAVList > cangls.zip<br />gzip -c bolsAVList > bolsAVList.gz<br /></code></pre><h6>4.4.2、解打包命令tar</h6><p>解压一个<code>redis</code>的源码包</p><pre class="prettyprint linenums"><code>$ tar -zxvf redis-6.0.8.tar.gz<br /></code></pre><p>打包命令tar -zcvf，将redis-6.0.8-bak打包成tar包</p><pre class="prettyprint linenums"><code>$ tar -zcvf redis-6.0.8-bak > redis-6.0.8-bak.tar<br /></code></pre><p>然后再将redis-6.0.8-bak.tar压缩成.gz格式</p><pre class="prettyprint linenums"><code>$ gzip redis-6.0.8-bak.tar > redis-6.0.8-bak.tar.gz<br /></code></pre><h5>4.5、搜索命令</h5><h6>4.5.1、locate命令</h6><p><strong>locate命令</strong>后面只能接文件名，例如，咱搜索一下cangls的文件。嚯，还不少啊：</p><pre class="prettyprint linenums"><code>locate cangls<br />[root@cnwangk opt]# locate cangls<br />/root/cangls.av<br />/root/cangls.sh<br />/root/canglsList<br />/root/canglsList/cangls.avi<br />/root/canglsList/cangls.mp4<br /></code></pre><p>locate命令所搜索的后台命令，不是及时更新，这时可以使用<strong>updatedb命令</strong>更新：</p><pre class="prettyprint linenums"><code>$ updatedb<br /></code></pre><p>locate命令配置文件<code>/etc/updatedb.conf</code>配置文件</p><ul>
  <li>PRUNE_BIND_MOUNTS：开启搜索限制</li> 
  <li>PRUNEFS=""：不搜索的系统文件</li> 
  <li>PRUNENAMES=""：不搜索的文件类型</li> 
  <li>PRUNEPAEHS=""：不搜索的路径</li>
 </ul><h6>4.5.2、命令搜索命令</h6><p>命令搜索命令，比如<strong>wheris、which以及find</strong>命令。</p><p><strong>此处，着重讲一下find命令的使用</strong>：</p><p><strong>不区分大小写</strong>，搜索cangls.sh脚本</p><pre class="prettyprint linenums"><code>$ find /root -iname cangls.sh<br /></code></pre><p>按照所有者搜索</p><pre class="prettyprint linenums"><code>$ find /root -user root<br /></code></pre><p>查找10天前修改的文件</p><pre class="prettyprint linenums"><code>find /var/log  -mtime +10<br /></code></pre><p>查找<code>/etc</code>目录下大于1M的文件</p><pre class="prettyprint linenums"><code>find /etc -size +1M<br /></code></pre><p><strong>查找i节点为26267295的文件</strong>，直呼内行，啥时候新增了个bols的小电影。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# find . -inum 26267295<br />./bolsList<br /></code></pre><p>查找/etc/目录下大于100KB且小于200KB的文件</p><pre class="prettyprint linenums"><code>find /etc -size +100k -a -size -200k<br /></code></pre><ul>
  <li>-a相当于and，逻辑与，两个条件都满足</li> 
  <li>-o相当于or，逻辑或，两个条件满足一个即可</li>
 </ul><p><strong>查找/etc/目录下大于100KB且小于200KB的文件，并且显示详细信息</strong></p><pre class="prettyprint linenums"><code>#-exec/-ok 命令<br />#{}\;对搜索结果执行操作<br />find /etc -size +100k -a -size -200k -exec ls -lh {} \;<br /></code></pre><p><strong>grep</strong>字符串搜素命令</p><ul>
  <li>grep [选项] 字符串 文件名，在文件当中匹配符合的字符串</li> 
  <li>-i，忽略大小写</li> 
  <li>-v，排除指定字符串</li>
 </ul><p><strong>根据文件大小匹配</strong>，anaconda-ks.cfg文件时Redhat系列安装就自带的文件。</p><pre class="prettyprint linenums"><code>$ grep "size" anaconda-ks.cfg<br />part swap --fstype="swap" --ondisk=sda --size=2000<br />part /boot --fstype="xfs" --ondisk=sda --size=200<br />part / --fstype="xfs" --ondisk=sda --size=16278<br />part /home --fstype="xfs" --ondisk=sda --size=2000<br /></code></pre><p><strong>find与grep的区别</strong></p><ul>
  <li>find命令：在系统当中搜索符合条件的文件名，如需匹配，<strong>使用通配符，通配符是完全匹配</strong>。</li> 
  <li>grep命令：在文件当中搜索符合条件的字符串，如需匹配，<strong>使用正则表达式进行匹配，正则表达式是包含匹配</strong>。</li>
 </ul><h4>5、关机与重启命令</h4><h5>5.1、关机命令</h5><p>一般而言关机和重启命令都不会赋予权限给普通用户，只有root用户才有权限执行。</p><pre class="prettyprint linenums"><code>$ shutdown<br /># shutdown -h now #立即关机<br /></code></pre><table> 
  <thead> 
   <tr> 
    <th>参数</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>-c</td> 
    <td>取消前一个关机命令</td> 
   </tr> 
   <tr> 
    <td>-h</td> 
    <td>关机</td> 
   </tr> 
   <tr> 
    <td>-r</td> 
    <td>重启</td> 
   </tr> 
  </tbody> 
 </table><p>其它关机命令：halt，poweroff，init 0</p><p><strong>注意</strong>：使用服务器时，不要随便去使用关机命令。一旦使用了，会造成不必要的麻烦。</p><h5>5.2、重启命令</h5><p>一般而言关机和重启命令都不会赋予权限给普通用户，只有root用户才有权限执行。</p><pre class="prettyprint linenums"><code>#重启命令1,立即重启，同样可以接指定世界以及间隔多久重启<br />$ shutdown -r now<br />#重启命令2,立即重启<br />$ reboot<br /></code></pre><p><strong>注意</strong>：使用<code>logout</code>命令登出shell，<strong>养成良好的习惯退出登录</strong></p><p>其它重启命令：reboot，init 6</p><h2>03 第三夜</h2><p>第二夜的知识有点多，小白还有点没消化过来。</p><p><strong>cangls</strong>：小白，你来啦。我白吗？我大吗？我好看吗？别走了，我这里...</p><p><strong>小白</strong>：啥？一脸懵逼...</p><p>此时小白的内心真实想法：不要以为我不懂，咱为学习忍了。真的又大又白，天天诱惑我。</p><p><strong>cangls</strong>：不逗你玩了，咱回到正题，昨天的知识点有点多，希望下去好好消化。今天会带来linux进阶方面的小技巧。</p><p><strong>小白</strong>：点了点头，认真的聆听着。</p><h3>三、进阶常用</h3><h4>1、scp命令</h4><h5>1.1、语法</h5><pre class="prettyprint linenums"><code>usage: scp [-12346BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]<br />[-l limit] [-o ssh_option] [-P port] [-S program]<br />[[user@]host1:]file1 ... [[user@]host2:]file2<br /></code></pre><h5>1.2、使用方法</h5><p><strong>简单来看</strong>：scp [可选参数] 本地文件 目标目录</p><pre class="prettyprint linenums"><code>scp /root/av/local_file.av  remote_username@ip:/root/av<br /></code></pre><p>scp [可选参数] 本地目录 目标目录</p><pre class="prettyprint linenums"><code>scp -r /root/av/  remote_username@ip:/root/<br /></code></pre><p><strong>命令格式介绍</strong></p><pre class="prettyprint linenums"><code>#复制文件格式,本地到远程服务器<br />scp local_file remote_username@remote_ip:remote_directory<br />#或者<br />scp local_file remote_username@remote_ip:remote_file<br />#或者<br />scp local_file  remote_ip:remote_directory<br />#或者<br />scp local_file remote_ip:remote_file<br /></code></pre><p><strong>复制目录命令格式</strong></p><pre class="prettyprint linenums"><code>#复制命令格式,本地到远程服务器<br />scp -r local_directory remote_username@remote_ip:remote_directory<br />#或者<br />scp -r local_directory remote_ip:remote_directory<br /></code></pre><p>详细操作，请参考的博文：<strong>【SCP命令】安全又快捷的linux小技巧scp命令</strong></p><h4>2、路由命令</h4><p>以下是对一些常用的路由命令（网络配置工具）进行简单的介绍。</p><p>2.1、<strong>ifconfig命令</strong>，展示内容如下：</p><p><strong>tips</strong>：我们判断网络环境的时候，<strong>dropped</strong>参数值是很重要的，<strong>一般正常状态是0，如果掉包数字则会上升</strong>。</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# ifconfig<br />docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500<br />inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255<br />ether 02:42:38:38:ab:fe  txqueuelen 0  (Ethernet)<br />RX packets 0  bytes 0 (0.0 B)<br />RX errors 0  dropped 0  overruns 0  frame 0<br />TX packets 0  bytes 0 (0.0 B)<br />TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500<br />inet 192.168.245.131  netmask 255.255.255.0  broadcast 192.168.245.255<br />inet6 fe80::b314:8248:917a:d808  prefixlen 64  scopeid 0x20<link><br />ether 00:0c:29:47:be:5f  txqueuelen 1000  (Ethernet)<br />RX packets 14160  bytes 1053042 (1.0 MiB)<br />RX errors 0  dropped 0  overruns 0  frame 0<br />TX packets 7652  bytes 713027 (696.3 KiB)<br />TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536<br />inet 127.0.0.1  netmask 255.0.0.0<br />inet6 ::1  prefixlen 128  scopeid 0x10<host><br />loop  txqueuelen 1  (Local Loopback)<br />RX packets 1474  bytes 91198 (89.0 KiB)<br />RX errors 0  dropped 0  overruns 0  frame 0<br />TX packets 1474  bytes 91198 (89.0 KiB)<br />TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0<br /></code></pre><p><strong>ifconfig最常见的作用就是看设置的ip地址以及dns和网关</strong>，其实就是看网卡设置的参数。当你看到docker的时候，没错，我安装了docker环境。</p><p>2.2、<strong>ip命令使用</strong>，如下所示：</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# ip addr list<br />1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1<br />link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00<br />inet 127.0.0.1/8 scope host lo<br />valid_lft forever preferred_lft forever<br />inet6 ::1/128 scope host<br />valid_lft forever preferred_lft forever<br />2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000<br />link/ether 00:0c:29:47:be:5f brd ff:ff:ff:ff:ff:ff<br />inet 192.168.245.131/24 brd 192.168.245.255 scope global noprefixroute ens33<br />valid_lft forever preferred_lft forever<br />inet6 fe80::b314:8248:917a:d808/64 scope link noprefixroute<br />valid_lft forever preferred_lft forever<br />3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default<br />link/ether 02:42:38:38:ab:fe brd ff:ff:ff:ff:ff:ff<br />inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0<br />valid_lft forever preferred_lft forever<br />#ip命令同样可以配合route命令使用<br />[root@cnwangk ~]# ip route list<br />default via 192.168.245.2 dev ens33 proto static metric 100<br />172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1<br />192.168.245.0/24 dev ens33 proto kernel scope link src 192.168.245.131 metric 100<br /></code></pre><p>ip命令同样可以配合route命令使用，单独使用route命令同样可以起到配置作用。</p><p>下图是我之前制作的一个简单的思维导图，本来是要放在防火墙知识里面的，现在同样适用。没有放原图，原图太大了，缩小了一点也同样能看清楚哟！</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/759870844929863194.jpg?imageMogr2/format/webp" class="aligncenter"></p><h4>3、防火墙命令</h4><p>这里，我只介绍firewalld命令行模式，关于详细操作说明请参考博文：<strong>【Redhat系列linux防火墙工具】firewalld与iptables防火墙工具的激烈碰撞</strong></p><h5>1.1、区域选择</h5><p>当前操作系统安装完成后，防火墙会设置一个默认区域，将接口加入到默认区域中。用户配置防火墙的第一步是获取默认区域并修改，关于操作如下：</p><p>查看当前系统中所有区域</p><pre class="prettyprint linenums"><code>firewall-cmd --get-zones<br /></code></pre><p>查看当前默认的区域</p><pre class="prettyprint linenums"><code>firewall-cmd --get-default-zone<br /></code></pre><p>查看当前已激活的区域</p><pre class="prettyprint linenums"><code>firewall-cmd --get-active-zones<br /></code></pre><p>获取接口ens33所属区域</p><pre class="prettyprint linenums"><code>firewall-cmd --get-zone-of-interface=ens33<br /></code></pre><p>修改接口所属区域</p><pre class="prettyprint linenums"><code>firewall-cmd --permanent --zone=internal --change-interface=ens33<br /></code></pre><h5>1.2、firewalld服务管理</h5><p>重新加载防火墙配置</p><pre class="prettyprint linenums"><code>firewall-cmd --reload<br /></code></pre><p>重启防火墙(redhat系列)</p><pre class="prettyprint linenums"><code>systemctl restart firewalld.service<br /></code></pre><p>临时关闭防火墙</p><pre class="prettyprint linenums"><code>systemctl stop firewalld.service<br /></code></pre><p>开机启用防火墙</p><pre class="prettyprint linenums"><code>systemctl enable firewalld.service<br /></code></pre><p>开机禁止防火墙</p><pre class="prettyprint linenums"><code>systemctl disable firewalld.service<br /></code></pre><p>查看firewalld的运行状态</p><pre class="prettyprint linenums"><code>firewall-cmd --state<br /></code></pre><h5>1.3、firewalld开放端口</h5><p>公共区域（public）设置开放21端口永久生效并写入配置文件（参数：--permanent）</p><pre class="prettyprint linenums"><code>#参数：--permanent，设置即立刻生效并且写入配置文件<br />firewall-cmd --zone=public --add-port=21/tcp --permanent<br /></code></pre><p>查询防火墙端口21是否开放</p><pre class="prettyprint linenums"><code>firewall-cmd --zone=public --query-port=21/tcp<br /></code></pre><p>移除开放的端口21</p><pre class="prettyprint linenums"><code>firewall-cmd --zone=public --remove-port=21/tcp --permanent<br /></code></pre><h5>1.4、区域规则修改</h5><p>查询防火墙规则列表</p><pre class="prettyprint linenums"><code>firewall-cmd --zone=public --list-all<br /></code></pre><p>新增一条区域规则httpd服务</p><pre class="prettyprint linenums"><code>firewall-cmd --permanent --zone=internal --add-service=http<br /></code></pre><p>验证规则</p><pre class="prettyprint linenums"><code>firewall-cmd  --zone=internal --list-all<br /></code></pre><h4>4、进程相关命令</h4><p>进程相关命令主要介绍四个</p><ul>
  <li>ps -aux | grep [服务名]</li> 
  <li>ps -ef | grep [服务名]</li> 
  <li>ps -le | grep [服务名]</li> 
  <li>top</li>
 </ul><p>如果没有安装httpd服务，Redhat系列可以使用yum命令进行安装，在Ubuntu系列可以通过<code>apt install</code>。apt 是 Debian 和 Ubuntu 中的 Shell 前端软件包管理器。</p><pre class="prettyprint linenums"><code>$ yum -y install httpd<br /></code></pre><p>查看进程常用的ps命令，以httpd进程进行演示。这里只介绍工作中比较实用的。</p><pre class="prettyprint linenums"><code>#使用-ef参数查看httpd进程<br />$ ps -ef | grep httpd<br />[root@cnwangk ~]# ps -ef | grep httpd<br />root      1329     1  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2216  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2218  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2219  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2220  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2221  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2222  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />root      2276  2226  0 13:38 pts/0    00:00:00 grep --color=auto httpd<br />#使用-aux参数查看httpd进程<br />$ ps -aux | grep httpd<br />[root@cnwangk ~]# ps -ef | grep httpd<br />root      1329     1  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2216  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2218  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2219  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2220  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2221  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />apache    2222  1329  0 13:37 ?        00:00:00 /usr/sbin/httpd -DFOREGROUND<br />root      2276  2226  0 13:38 pts/0    00:00:00 grep --color=auto httpd<br />#以长格式查询httpd进程<br />$ ps -le | grep httpd<br />[root@cnwangk ~]# ps -le | grep httpd<br />4 S     0  1329     1  0  80   0 - 104198 poll_s ?       00:00:00 httpd<br />5 S    48  2216  1329  0  80   0 - 70677 poll_s ?        00:00:00 httpd<br />5 S    48  2218  1329  0  80   0 - 105278 SYSC_s ?       00:00:00 httpd<br />5 S    48  2219  1329  0  80   0 - 105278 ep_pol ?       00:00:00 httpd<br />5 S    48  2220  1329  0  80   0 - 105278 SYSC_s ?       00:00:00 httpd<br />5 S    48  2221  1329  0  80   0 - 105278 SYSC_s ?       00:00:00 httpd<br />5 S    48  2222  1329  0  80   0 - 105278 SYSC_s ?       00:00:00 httpd<br /></code></pre><p>查看进程，当然<strong>top命令</strong>也应该了解，不带任何参数就能看到一些进程相关的。比如服务器当前时间、运行天数、用户数量、负载均衡、任务总数、多少进程在运行和休眠、停止以及僵尸进程<strong>zombie</strong>、cpu的利用率、内存占用情况以及缓存、缓冲的情况。查看直接使用top即可，退出按q键。</p><pre class="prettyprint linenums"><code>$ top<br />top - 13:41:24 up 5 min,  1 user,  load average: 0.06, 0.28, 0.16<br />Tasks: 193 total,   1 running, 192 sleeping,   0 stopped,   0 zombie<br />%Cpu(s):  0.3 us,  0.7 sy,  0.0 ni, 98.7 id,  0.3 wa,  0.0 hi,  0.0 si,  0.0 st<br />KiB Mem :  1877588 total,   940396 free,   481804 used,   455388 buff/cache<br />KiB Swap:  2047996 total,  2047996 free,        0 used.  1235960 avail Mem<br /></code></pre><p>查看进程，<strong>pstree命令</strong>也必不可少，以树状结构展示出了我部署的<strong>vsftpd</strong>以及监控系统服务<strong>zabbix</strong>。</p><pre class="prettyprint linenums"><code>$ pstree<br />├─vsftpd<br />├─zabbix_agentd───5*[zabbix_agentd]<br />├─zabbix_proxy<br />└─zabbix_server───33*[zabbix_server]<br /></code></pre><p>进程实在无法正常结束，那就采取暴力手段<strong>kill命令</strong>，强行关闭。</p><pre class="prettyprint linenums"><code>$ kill -1 3033 #重启进程<br />$ kill -9 3034   #强制杀死进程<br /></code></pre><p>关于进程的就讲这么多，讲太多了不好消化，更多详细描述可以参考本人的历史博文。</p><h4>5、系统权限</h4><p>系统权限，如果非要分类，大致可分为：<strong>基本权限、特殊权限</strong>。</p><p>权限分配原则：<strong>对文件来讲：最高权限为<code>x</code>（执行）</strong>；<strong>对目录来讲：最高权限为<code>w</code>（写）</strong>。接着以cangls这个文件进行演示，分别带你看权限用数字的代表含义，rwx加起来就是7：</p><ul>
  <li>r 对应的数字权限为：4，读权限</li> 
  <li>w 对应的数字权限为：2， 写权限</li> 
  <li>x 对应的数字权限为：1，执行权限</li>
 </ul><pre class="prettyprint linenums"><code>[root@cnwangk ~]# chmod 711 cangls.av<br />[root@cnwangk ~]# ll cangls.av<br />-rwx--x--x 1 root root 13 1月  19 22:49 cangls.av<br />[root@cnwangk ~]# chmod 722 cangls.av<br />[root@cnwangk ~]# ll cangls.av<br />-rwx-w--w- 1 root root 13 1月  19 22:49 cangls.av<br />[root@cnwangk ~]# chmod 744 cangls.av<br />[root@cnwangk ~]# ll cangls.av<br />-rwxr--r-- 1 root root 13 1月  19 22:49 cangls.av<br /></code></pre><p>通过上面的演示对cangls.av这个文件赋予权限711、722、744进行测试。711对应的权限<code>-rwx--x--x</code>、722对应的权限<code>-rwx-w--w-</code>、744对应的权限：<code>-rwxr--r--</code>，当然这样设置是没有意义的，为了演示进行测试演示数字代表的含义。</p><p><strong>tips</strong>：还有一种方式是ugo模式，作为了解就行。</p><h2>04 第四夜</h2><p><strong>cangls</strong>：还记得前三天我们探讨了哪些linux方面的知识吗？</p><p><strong>小白</strong>：当然记得，我都记在笔记本上了，下面进行了复述：</p><ul>
  <li>学习方法；</li> 
  <li>linux基本命令，最重要的是帮助命令的运用；</li> 
  <li>linux进阶的小技巧，比如：scp安全文件传输命令、路由命令、firewalld防火墙命令等等；</li> 
  <li>linux进程相关命令：ps -ef、ps -aux、top；</li> 
  <li>linux系统权限命令：chmod。</li>
 </ul><p><strong>cangls</strong>：接下来会有一点小插曲，对服务器部署项目的一些知识进行讲解，比如Javaweb项目。</p><p><strong>小白</strong>：好耶，我很感兴趣！</p><h3>四、Java相关</h3><p><strong>基于实际工作中的运用，介绍一些web项目和服务器相关的知识</strong>。当你看到我的文章或多或少会出现Java相关的知识的时候，请不要惊讶。我其实是一个蹩脚Java码农，就是这么神奇。好吧，我承认自己连半吊子都算不上，只是有一段时间的CURD经验。而在Linux上操作的经验反而比我主学的Java经验还要丰富，得益于多年的自学以及与服务器和虚拟机打交道。</p><h4>1、jar包服务和war包服务</h4><p>将Java程序通过中间件在后台运行，&符号就是将程序放入后台执行</p><pre class="prettyprint linenums"><code>$ java -jar demo-1.0.jar &<br /></code></pre><p>jar包或者war包在中间件后台运行，优化版本，将日志输出到指定文件demo-1.0.log。<strong>jvm在client模式下运行，默认Xmx大小为64M，而在server模式下默认Xmx大小为1024M</strong>，<strong>默认Xms大小为1M，而在server模式下默认Xms大小为128M</strong>。</p><pre class="prettyprint linenums"><code>$ nohup java -server -Xms256M -Xmx2048M -jar demo-1.0.jar &> demo-1.0.log &<br /></code></pre><p>当你遇到控制台<strong>日志输出乱码</strong>的时候，此时不要慌，请冷静思考。当然有<strong>解决方案</strong>，那就是在控制台执行<code>Java -jar</code>命令时指定固定的编码，比如utf-8编码。</p><pre class="prettyprint linenums"><code>$ java -jar -Dfile.encoding=utf-8 demo-1.0.jar &<br />$ nohup java -server -Xms256M -Xmx2048M -jar -Dfile.encoding=utf-8 demo-1.0.jar &> demo-1.0.log &<br /></code></pre><p><strong>个人建议</strong>：基于实际工作处理。再怎么去优化，机器始终是机器，内存始终要消耗。如果不能释放，需要采取人为的干预手段，比如定期重启服务器。很多问题找了很多原因依旧解决不掉，但是重启服务器就迎刃而解了，就是这么神奇。</p><h4>2、tomcat中间件优化</h4><p><strong>限制堆内存</strong>，具体根据服务器和真机的内存计算优化，下面知识给出示例值。</p><pre class="prettyprint linenums"><code>$ set JAVA_OPS = -Xms800m -Xmx1024m -XX:PermSize=800m -XX:MaxPermSize=1024m<br /></code></pre><p><strong>https自签证书配置</strong></p><pre class="prettyprint linenums"><code><!--自签证书配置，证书密码keystorePass,key包含文件keystoreFile=""--><br /><!-- <Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"<br />maxThreads="150" SSLEnabled="true" scheme="https" secure="true"<br />clientAuth="false" sslProtocol="TLS"<br />keystoreFile="D:\\company-workspace\\mykeys\\tomcat.keystore"<br />keystorePass="123456"<br />truststoreFile="D:\\company-workspace\\mykeys\\tomcat.keystore"<br />truststorePass="123456"<br />/>--><br /></code></pre><p>谈到tomcat中间件，一般自然而然的就联想到nginx中间件。一般不建议直接在tomcat中直接配置，反而使用nginx做一层反向代理，然后在nginx中设置ssl。</p><p><strong>nginx中间件配置简单介绍</strong></p><p>负载均衡，可以通过加权重实现轮询。</p><pre class="prettyprint linenums"><code>   upstream tomcat {<br />server 192.168.0.233:8080;<br />server 192.168.0.233:8888;<br />server 192.168.0.233:9999;<br />}<br /></code></pre><p><strong>配置ssl参考</strong></p><pre class="prettyprint linenums"><code>server<br />{<br />listen 80;<br />listen 443 ssl;<br />server_name 192.168.245.130;<br />#ssl set begin<br />#优雅的编写rewrite规则<br />#rewrite ^ https://www.nginx.org$request_uri?;<br />#重定向转发到https<br />#proxy_redirect http:// $scheme://;<br />#301重定向<br />#return  301 https://$server_name$request_uri;<br />#http请求重定向到https请求,非标准433端口采用<br />#error_page 497  https://$host$uri?$args;<br />#ssl 设置off或者屏蔽<br />ssl off;<br />ssl_certificate ssl/server.crt;#服务端证书<br />ssl_certificate_key ssl/server.key;#服务端私钥<br />ssl_session_cache shared:SSL:1m;#设置共享会话缓存大小<br />ssl_session_timeout 5m;#配置session有效时间5分钟<br />ssl_prefer_server_ciphers on;#优先采取服务器算法<br />ssl_protocols TLSv1 TLSv1.1 TLSv1.2;#启用指定协议<br />#加密算法<br />ssl_ciphers  EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;<br />#ssl_verify_client on;# 开启客户端证书校验<br />#ssl_client_certificate ssl/ca.crt;#设置验证客户端证书<br />#ssl_verify_depth 6; #校验深度<br />#ssl_trusted_certificate ssl/ca.crt;#设置CA为受信任证书<br />#ssl set end<br />location / {<br />#配置proxy_set_header请求头<br />proxy_pass_header User-Agent;<br />proxy_set_header Host $http_host;<br />proxy_set_header X-Real-IP $remote_addr;<br />proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<br />proxy_set_header X-Forwarded-Proto https;<br />proxy_pass http://tomcat;<br />proxy_redirect http:// https://;<br />}<br /></code></pre><h4>3、国产中创中间件优化</h4><p><strong>解决JDK不完整的两种方案</strong>：</p><ul>
  <li>第一种：下载完整版的JDK；</li> 
  <li>第二种：使用as内部自带的编译器，如下所示进行调整。</li>
 </ul><p>找到<code>java-config</code>配置那一栏，在<jvm-options>加入如下参数。</p><pre class="prettyprint linenums"><code><jvm-options>-Dorg.apache.jasper.compiler.disablejsr199=true</jvm-options><br /></code></pre><p>或者修改<code>as/domains/domain1/config/</code>目录下的domain.xml，将false修改为<strong>true</strong>。</p><pre class="prettyprint linenums"><code><jvm-options>-Dorg.apache.jasper.compiler.disablejsr199=false</jvm-options><br /></code></pre><h2>05 第五夜</h2><p>经过前四夜的深入探讨，此时的小白已经具备了一些linux基本知识的运用，对于项目上线和维护也有了一定的了解。</p><p><strong>cangls</strong>：相信小白同学现在已经对linux基本知识有了一定的了解，并能够运用到工作中。</p><p><strong>小白</strong>：cangls，这还是得感谢您的细心教导，是有那么一丢丢的成就感。</p><p><strong>cangls</strong>：学习就是要有这种劲头，好样的！今天再给你传授我多年珍藏的独家秘笈：vim工具的使用。</p><p><strong>小白</strong>：好耶，独家秘笈？莫非是九阳神功，还是玉女心经？表现出满怀期待的样子。</p><p><strong>cangls</strong>：想啥呢？是linux中的编辑神器，用好了vim对你的工作有大大大的好处哟！</p><p><strong>小白</strong>：原来如此，开始拿出了自己的小本本记录...</p><h3>五、vim的使用</h3><h4>1、vim简单使用</h4><p>接着上面的例子hello.sh脚本继续讲。</p><pre class="prettyprint linenums"><code>$ vim hello.sh  #开始编辑脚本<br /></code></pre><p>在hello.sh脚本中写入一段内容：</p><p><strong>tips</strong>：进入后按 i 键进行插入内容。</p><pre class="prettyprint linenums"><code>#!/bin/bash<br />echo "hello cangls"<br />echo hello linux<br />echo create btrfs filesystem<br /></code></pre><p>按ESC，输入:wq保存退出，然后执行脚本：</p><pre class="prettyprint linenums"><code>[root@cnwangk ~]# ./hello.sh<br />hello cangls<br />hello linux<br />create btrfs filesystem<br /></code></pre><p>执行脚本，会发现刚刚通过<code>vim</code>输入的<code>hello cangls</code>已经生效了。</p><p><strong>如果没有权限，通过root用户赋予权限</strong>：</p><pre class="prettyprint linenums"><code>#赋予读和执行权限<br />chmod 755 /hello.sh<br /></code></pre><h4>2、玩转vim</h4><p>凭个人使用经验总结一些常用到的快捷键命令</p><table> 
  <thead> 
   <tr> 
    <th>参数</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>i</td> 
    <td>进行编辑，插入内容</td> 
   </tr> 
   <tr> 
    <td>:wq</td> 
    <td>保存并退出</td> 
   </tr> 
   <tr> 
    <td>:q!</td> 
    <td>不保存并退出</td> 
   </tr> 
   <tr> 
    <td>shirt + pgup</td> 
    <td>上翻页</td> 
   </tr> 
   <tr> 
    <td>shift + pgdown</td> 
    <td>下翻页</td> 
   </tr> 
   <tr> 
    <td>/</td> 
    <td>搜索内容</td> 
   </tr> 
   <tr> 
    <td>:set number</td> 
    <td>显示行数</td> 
   </tr> 
   <tr> 
    <td>ESC</td> 
    <td>退出编辑</td> 
   </tr> 
  </tbody> 
 </table><p>更多命令参数可以参考，<strong>man vim</strong>，强大的帮助命令man。</p><p>附上一个vim的键盘图，<strong>来自菜鸟教程</strong>，可以去菜鸟教程下载原图。</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/8805386600381527282.jpg?imageMogr2/format/webp" class="aligncenter"></p><h2>06 第六夜</h2><p>经过前五夜的连续战斗，小白依旧斗志昂扬。</p><p><strong>cangls</strong>：小白同学啊，看你今天似乎遇到什么难题了吧！</p><p><strong>小白</strong>：是的，我最近阅读了linux shell相关的文章，但是有很多疑惑。</p><p><strong>cangls</strong>：能具体描述一下吗？</p><p><strong>小白</strong>：是这样的，对基本知识和用法有些困惑。</p><p><strong>cangls</strong>：没关系，今天正是要给你传授shell方面的基本知识，希望对你有所帮助。</p><p><strong>小白</strong>：此时拿出了小本本开始记录着...</p><h3>六、shell脚本</h3><h3>1、shell概述</h3><ul>
  <li>shell的两种主要语法类型有<code>Bourne</code>和<code>C</code>，这两种语法彼此不兼容。Bourne家族主要包括：sh、ksh、Bash、psh、zsh；</li> 
  <li>C家族主要包括：csh、tcsh。</li>
 </ul><p><strong>Bash中其它特殊符号</strong>，如下表格所示：</p><table> 
  <thead> 
   <tr> 
    <th>选项</th> 
    <th>符号含义</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>' '</td> 
    <td>在单引号中所有特殊符号，如"$"和"`"(反引号)都没有特殊含义</td> 
   </tr> 
   <tr> 
    <td>" "</td> 
    <td>双引号。在双引号中所有特殊符号除"<code>$</code>"、"、"、"\"外都无特殊含义。"$"、"、"、""拥有"调用变量的值"、"引用命令"和"转义符"的含义</td> 
   </tr> 
   <tr> 
    <td>``</td> 
    <td>反引号括起来的是系统命令，在Bash中会优先执行它。和$()作用一样，推荐使用$()，反引号容易误导。</td> 
   </tr> 
   <tr> 
    <td>$()</td> 
    <td>与反引号作用相同，用来引用系统命令。</td> 
   </tr> 
   <tr> 
    <td>#</td> 
    <td>在shell脚本中，#代表注释。</td> 
   </tr> 
   <tr> 
    <td>$</td> 
    <td>用于调用变量的值，如果需要调用name的值时，需要使用$name获取变量的值。</td> 
   </tr> 
   <tr> 
    <td>\</td> 
    <td>转义符，跟在\之后的特殊符号将失去特殊含义，变为普通字符。例如：$将输出"$"符号，则不会作为变量引用。</td> 
   </tr> 
  </tbody> 
 </table><h3>2、shell脚本基本编写</h3><pre class="prettyprint linenums"><code>#编写一个简单的linux脚本，使用vim命令<br />vim /home/hello.sh<br />#注意一个标准的shell脚本必须加上：#!/bin/bash<br />#!/bin/bash<br />echo "hello world"  >> /root/hello.log #将hello world追加到hello.log这个文件中<br />#赋予读和执行权限<br />chmod 755 /hello.sh<br />#使用 sh hello.sh 命令执行<br />sh hello.sh && ./hello.sh<br /></code></pre><h3>3、别名与快捷键</h3><p>3.1、查看与设定别名</p><p><code>alias</code> 别名 = '原命令'，设定命令别名，当然这样设置只会<strong>临时生效</strong>。</p><p><code>alias</code> 命令，查看系统命令中所有的命令别名，例如我个人安装的系统别名查看如下：</p><pre class="prettyprint linenums"><code>alias cp='cp -i'<br />alias egrep='egrep --color=auto'<br />alias fgrep='fgrep --color=auto'<br />alias grep='grep --color=auto'<br />alias l.='ls -d .* --color=auto'<br />alias ll='ls -l --color=auto'<br />alias ls='ls --color=auto'<br />alias mv='mv -i'<br />alias rm='rm -i'<br />alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'<br /></code></pre><p><strong>别名永久生效与删除别名</strong></p><p>写入环境变量配置文件，<strong>永久生效</strong>。</p><pre class="prettyprint linenums"><code>#写入当前用户的环境变量，Ubuntu下可以这样设置<br />$ vi ~/.bashrc<br />#Redhat7系列<br />[root@cnwangk ~]# vi ~/.bash_profile<br /></code></pre><p>上面分别介绍了Ubuntu下的环境变量和Redhat7系列的当前用户环境变量修改。我之前也有写过博文，在这篇文章中：<strong>【linux环境变量】秒懂linux配置全局与当前用户环境变量</strong>，同样也上传到了我的github和gitee仓库，微信公众号也有发布哟。</p><p><strong>删除别名</strong></p><pre class="prettyprint linenums"><code>$ unalias 接别名<br /></code></pre><p><strong>命令生效顺序</strong></p><ul>
  <li>第一顺位执行用绝对路径或相对路径执行的命令</li> 
  <li>第二顺位执行别名</li> 
  <li>第三顺位执行Bash的内部命令</li> 
  <li>第四顺位执行按照$PATH环境变量定义的目录查找顺序找到的第一个命令</li>
 </ul><h3>4、 历史命令</h3><p>直接在终端输入history命令，就可以看到自己输入的历史命令。</p><pre class="prettyprint linenums"><code>$ history<br /></code></pre><p><code>history</code> [选项][历史命令保存文件]</p><ul>
  <li>-c：清空历史命令</li> 
  <li>-w：把缓存中的历史命令写入历史命令保存文件~/.bash_history</li>
 </ul><p><strong>历史命令的调用</strong></p><ul>
  <li>使用上、下箭头调用以前的历史命令</li> 
  <li>使用"!n"重复执行第n条命令</li> 
  <li>使用"!!"重复执行上一条命令</li> 
  <li>使用"!字符串"重复执行最后一条以该字符串开头的命令</li>
 </ul><h3>5、输出重定向</h3><p>最基本的<code>echo</code>用法演示，将hello world输出到hello.sh脚本中，如果对echo命令不熟悉，可以使用我上面介绍过的<code>man</code>帮助命令进行查找。例如：</p><pre class="prettyprint linenums"><code>$ man echo<br /></code></pre><p><strong>-e参数介绍</strong>：支持反斜线控制的字符转换</p><pre class="prettyprint linenums"><code>$ echo "hello world" > hello.sh<br />[root@cnwangk ~]# cat hello.sh<br />hello world<br />#输入一段字符串到hello.sh脚本<br />[root@cnwangk ~]# echo -e "echo "hello linux"\necho "create btrfs filesystem"" > hello.sh<br />[root@cnwangk ~]# chmod 755 hello.sh  #赋予755权限<br />[root@cnwangk ~]# sh hello.sh  #执行脚本<br />hello linux<br />create btrfs filesystem<br /></code></pre><p><strong>控制字符作用</strong>，如下表格所示：</p><table> 
  <thead> 
   <tr> 
    <th>参数</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>\a</td> 
    <td>输出警告音</td> 
   </tr> 
   <tr> 
    <td>\b</td> 
    <td>退格键，向左删除</td> 
   </tr> 
   <tr> 
    <td>\n</td> 
    <td>换行符</td> 
   </tr> 
   <tr> 
    <td>\r</td> 
    <td>回车键</td> 
   </tr> 
   <tr> 
    <td>\t</td> 
    <td>制表符，TAB键</td> 
   </tr> 
   <tr> 
    <td>\v</td> 
    <td>垂直制表符</td> 
   </tr> 
   <tr> 
    <td>\0nnn</td> 
    <td>按照八进制ASCII码输出字符。其中0为数字零，nnn为三位八进制数。</td> 
   </tr> 
   <tr> 
    <td>\xhh</td> 
    <td>按照十六进制ASCII码输出字符。hh是两位十六进制数。</td> 
   </tr> 
  </tbody> 
 </table><p>例如，表示以追加形式将苍老师输出到<code>av</code>文件中。嘿嘿，咱就骚一下。</p><pre class="prettyprint linenums"><code>echo "cangls" >> av.avi<br /></code></pre><p><strong>标准输出重定向</strong>，作用如下表格所示：</p><table> 
  <thead> 
   <tr> 
    <th>命令格式</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>命令 > 文件</td> 
    <td>以覆盖的方式，把命令的正确输出到指定的文件或设备当中</td> 
   </tr> 
   <tr> 
    <td>命令 >> 文件</td> 
    <td>以追加的方式，把命令的正确输出到指定的文件或设备当中</td> 
   </tr> 
   <tr> 
    <td>错误命令 2> 文件</td> 
    <td>以覆盖的方式，把命令的错误输出到指定的文件或设备当中</td> 
   </tr> 
   <tr> 
    <td>错误命令 2>> 文件</td> 
    <td>以追加的方式，把命令的错误输出到指定的文件或设备当中</td> 
   </tr> 
  </tbody> 
 </table><p><strong>正确输出和错误输出同时保存</strong>，作用如下表格所示：</p><table> 
  <thead> 
   <tr> 
    <th>命令格式</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>命令 > 文件 2>&1</td> 
    <td>以覆盖的方式，把命令的正确和错误输出保存在同一个文件中</td> 
   </tr> 
   <tr> 
    <td>命令 >> 文件 2>&1</td> 
    <td>以追加的方式，把命令的正确和错误输出保存在同一个文件中</td> 
   </tr> 
   <tr> 
    <td>命令 &> 文件</td> 
    <td>以覆盖的方式，把命令的正确和错误输出保存在同一个文件中</td> 
   </tr> 
   <tr> 
    <td>命令 &>>文件</td> 
    <td>以追加的方式，把命令的正确和错误输出保存在同一个文件中</td> 
   </tr> 
   <tr> 
    <td>命令 >> 文件1 2>> 文件2</td> 
    <td>把正确的输出到文件1中，错误的输出到文件2中</td> 
   </tr> 
  </tbody> 
 </table><p><strong>wc命令</strong></p><p><code>wc</code> [选项][文件名]，输出文件中的行数、单词数、行数</p><p>示例，<strong>统计hello.sh的行数</strong>，统计有两行，与上面的演示对应起来了。</p><pre class="prettyprint linenums"><code>$ wc -l<br />[root@cnwangk ~]# wc -l hello.sh  #统计hello.sh的行数，统计有两行，与上面的演示对应起来了。<br />2 hello.sh<br /></code></pre><p><strong>最后总结出实用的几种</strong>，以追加的形式把错误和正确的结果输出到文件中：</p><ul>
  <li>命令 >> file 2>&1</li> 
  <li>命令 &>> file</li> 
  <li>命令 >> file1 2>> file2</li>
 </ul><h3>6、管道符</h3><p>6.1、命令格式，如下表格所示：</p><table> 
  <thead> 
   <tr> 
    <th>命令格式</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>命令1 | 命令2</td> 
    <td>命令1的正确输出作为命令2的操作对象</td> 
   </tr> 
   <tr> 
    <td>;，格式1：命令2</td> 
    <td>多个命令顺序执行，命令之间没有任何逻辑关系</td> 
   </tr> 
   <tr> 
    <td>&&，命令1&&命令2</td> 
    <td>逻辑与，当命令1正确执行，命令2才会执行；命令1无法正确执行，命令2不会执行</td> 
   </tr> 
   <tr> 
    <td>||，命令1||命令2</td> 
    <td>逻辑或，当命令1非正确执行，命令2才会执行；命令1正确执行，命令2不会执行</td> 
   </tr> 
  </tbody> 
 </table><p>6.2、linux中的通配符，部分整理如下表格所示：</p><table> 
  <thead> 
   <tr> 
    <th>参数</th> 
    <th>作用</th> 
   </tr> 
  </thead> 
  <tbody> 
   <tr> 
    <td>*</td> 
    <td>匹配任意内容</td> 
   </tr> 
   <tr> 
    <td>?</td> 
    <td>匹配任意一个字符</td> 
   </tr> 
   <tr> 
    <td>[]</td> 
    <td>匹配任意一个中括号内的字符，例如[abc]代表匹配一个字符，可能是a，也可能是b或c。</td> 
   </tr> 
   <tr> 
    <td>[-]</td> 
    <td>匹配中括号里的任意一个字符，-代表一个范围，例如：[a-z]代表匹配一个小写字母。</td> 
   </tr> 
   <tr> 
    <td>[ ^ ]</td> 
    <td>逻辑非，匹配不是中括号里的一个字符，例如：[^0-9]代表匹配一个非数字的字符。</td> 
   </tr> 
  </tbody> 
 </table><p>续更新优化中...</p><h1>总结</h1><p><strong>能看到这里的，都是帅哥靓妹</strong>。以上就是此次文章的所有内容的，希望能对你的工作有所帮助。感觉写的好，就拿出你的一键三连。如果感觉总结的不到位，也希望能留下您宝贵的意见，我会在文章中进行调整优化。</p><p><strong>原创不易，转载也请标明出处和作者，尊重原创</strong>。本文会不定期上传到gitee或者github以及发布到微信公众平台。<strong>我的微信公众号与其他平台昵称同样是龙腾万里sky</strong>。认准龙腾万里sky，如果看见其它平台不是这个ID发出我的文章，就是转载的。</p><p><strong>linux系列文章：linux小技巧scp命令、linux磁盘管理</strong>已经上传至github和gitee。</p><p>个人github仓库地址，一般会先更新PDF文件，然后再上传markdown文件。如果访问github太慢，可以访问gitee进行克隆。</p><p>https://github.com/cnwangk/SQL-study</p><p>个人gitee仓库地址，一般会先更新PDF文件，然后再上传markdown文件。</p><p>https://gitee.com/dywangk/SQL-study</p><p>cangls和小白的对话并没有结束，在这个只有两人的操作间之中，到底发生了啥，请接着看。</p><h5>by 龙腾万里sky</h5><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p><h2>新手如何入门linux，linux原来还可以这么学的</p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://www.ishiguang.cn/12058.html#comments</comments>
<wfw:commentRss>https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/</wfw:commentRss>
</item>
<item>
<title>【Alibaba中间件技术系列】「Nacos技术专题」服务注册与发现相关的原理分析</title>
<link>https://www.ishiguang.cn/12056.html</link>
<guid>https://www.ishiguang.cn/12056.html</guid>
<pubDate>Sat, 22 Jan 2022 19:40:16 +0800</pubDate>
<dc:creator>admin</dc:creator>
<description><![CDATA[(adsbygoogle = window.adsbygoogle || []).push({});		背景介绍前几篇文章介绍了Nacos配置中心服务的能力机制，接下来，我们来介绍Nacos另一...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><h3>背景介绍</h3><p>前几篇文章介绍了Nacos配置中心服务的能力机制，接下来，我们来介绍Nacos另一个非常重要的特性就是服务注册与发现，说到服务的注册与发现相信大家应该都不陌生，在微服务盛行的今天，服务是非常重要的，而在 Nacos 中服务更被称为他的一等公民。Nacos 支持几乎所有主流类型的 “服务” 的发现、配置和管理。</p><h3>服务 (Service)</h3><p>服务是指一个或一组软件功能（例如特定信息的检索或一组操作的执行），其目的是不同的客户端可以为不同的目的重用（例如通过跨进程的网络调用）。Nacos 支持主流的服务生态，如 Kubernetes Service、gRPC|Dubbo RPC Service 或者 Spring Cloud RESTful Service。</p><h3>nacos的架构图所示</h3><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/2277138022543008428.jpg?imageMogr2/format/webp" class="aligncenter"></p><h3>(dubbo) RPC微服务的基础架构</h3><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/1430348252045234886.jpg?imageMogr2/format/webp" class="aligncenter"></p><p>图中的6个步骤的含义解释如下：</p><ol>
  <li>服务容器负责启动，加载，运行服务提供者。</li> 
  <li>服务提供者在启动时，向注册中心注册自己提供的服务。</li> 
  <li>服务消费者在启动时，向注册中心订阅自己所需的服务。</li> 
  <li>注册中心返回服务提供者地址列表给消费者，如果有变更，注册中心将基于长连接推送变更数据给消费者。</li> 
  <li>服务消费者，从提供者地址列表中，基于软负载均衡算法，选一台提供者进行调用，如果调用失败，再选另一台调用。</li> 
  <li>服务消费者和提供者，在内存中累计调用次数和调用时间，定时每分钟发送一次统计数据到监控中心。</li>
 </ol><h4>服务注册中心 (Service Registry)</h4><p>上图中Registry就是注册中心，负责服务的注册与发现，Dubbo服务体系之前使用Zookeeper或者自己的Registry 实现，而 Nacos 则是另一种 Registry的实现。</p><p>服务注册中心，它是服务，其实例及元数据的数据库（Dubbo3已经将源数据中心、配置服务全部提取独立出来了），服务实例在启动时注册到服务注册表，并在关闭时注销。</p><p>服务和路由器的客户端查询服务注册表以查找服务的可用实例，服务注册中心可能会调用服务实例的健康检查 API 来验证它是否能够处理请求。</p><h4>服务元数据 (Service Metadata)</h4><p>服务元数据是指包括服务端点(endpoints)、服务标签、服务版本号、服务实例权重、路由规则、安全策略等描述服务的数据。</p><h3>逻辑架构及其组件介绍</h3><ul>
  <li>服务管理：实现服务CRUD，域名CRUD，服务健康状态检查，服务权重管理等功能</li> 
  <li>配置管理：实现配置管CRUD，版本管理，灰度管理，监听管理，推送轨迹，聚合数据等功能</li> 
  <li>元数据管理：提供元数据CURD 和打标能力</li> 
  <li>插件机制：实现三个模块可分可合能力，实现扩展点SPI机制</li> 
  <li>事件机制：实现异步化事件通知，sdk数据变化异步通知等逻辑</li> 
  <li>日志模块：管理日志分类，日志级别，日志可移植性（尤其避免冲突），日志格式，异常码+帮助文档</li> 
  <li>回调机制：sdk通知数据，通过统一的模式回调用户处理。接口和数据结构需要具备可扩展性</li> 
  <li>寻址模式：解决ip，域名，nameserver、广播等多种寻址模式，需要可扩展</li> 
  <li>推送通道：解决server与存储、server间、server与sdk间推送性能问题</li> 
  <li>容量管理：管理每个租户，分组下的容量，防止存储被写爆，影响服务可用性</li> 
  <li>流量管理：按照租户，分组等多个维度对请求频率，长链接个数，报文大小，请求流控进行控制</li> 
  <li>缓存机制：容灾目录，本地缓存，server缓存机制。容灾目录使用需要工具</li> 
  <li>启动模式：按照单机模式，配置模式，服务模式，dns模式，或者all模式，启动不同的程序+UI</li> 
  <li>一致性协议：解决不同数据，不同一致性要求情况下，不同一致性机制</li> 
  <li>存储模块：解决数据持久化、非持久化存储，解决数据分片问题</li> 
  <li>Nameserver：解决namespace到clusterid的路由问题，解决用户环境与nacos物理环境映射问题</li> 
  <li>CMDB：解决元数据存储，与三方cmdb系统对接问题，解决应用，人，资源关系</li> 
  <li>Metrics：暴露标准metrics数据，方便与三方监控系统打通</li> 
  <li>Trace：暴露标准trace，方便与SLA系统打通，日志白平化，推送轨迹等能力，并且可以和计量计费系统打通</li> 
  <li>接入管理：相当于阿里云开通服务，分配身份、容量、权限过程</li> 
  <li>用户管理：解决用户管理，登录，sso等问题</li> 
  <li>权限管理：解决身份识别，访问控制，角色管理等问题</li> 
  <li>审计系统：扩展接口方便与不同公司审计系统打通</li> 
  <li>通知系统：核心数据变更，或者操作，方便通过SMS系统打通，通知到对应人数据变更</li> 
  <li>OpenAPI：暴露标准Rest风格HTTP接口，简单易用，方便多语言集成</li> 
  <li>Console：易用控制台，做服务管理、配置管理等操作</li> 
  <li>SDK：多语言sdk</li> 
  <li>Agent：dns-f类似模式，或者与mesh等方案集成</li> 
  <li>CLI：命令行对产品进行轻量化管理，像git一样好用</li>
 </ul><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/4313088184114031821.jpg?imageMogr2/format/webp" class="aligncenter"></p><h3>Nacos 的服务注册与发现</h3><h4>服务提供方 (Service Provider)</h4><p>是指提供可复用和可调用服务的应用方。</p><h5>模拟服务注册</h5><p>服务注册最重要的就是将服务注册到哪里，在注册中心服务端，肯定有一个用来管理服务的容器，他保存着所有服务的实例。不需要知道该容器具体的实现细节，只需要知道有这样一个概念。</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/5019227391820796126.jpg?imageMogr2/format/webp" class="aligncenter"></p><ul>
  <li>将同一个服务的两个实例注册到 Nacos 中：</li>
 </ul><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/5727086463556371696.jpg?imageMogr2/format/webp" class="aligncenter"></p><ul>
  <li>双注册模式注入进入</li>
 </ul><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/5642184726875959110.jpg?imageMogr2/format/webp" class="aligncenter"></p><ul>
  <li>维持服务在线状态</li>
 </ul><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/4636794625994996784.jpg?imageMogr2/format/webp" class="aligncenter"></p><h6>demo代码如下：</h6><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/5137657661322360841.jpg?imageMogr2/format/webp" class="aligncenter"></p><p>通过 NamingService 接口的 registerInstance 方法就可以将服务进行注册了，该方法有很多重载的方法，这里我们选择一个简单的来调用就好了。注册完成后，通过调用 getAllInstances 方法，立即获取所有可用的实例，然后让主线程等待，打印如下：</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/1989411987075137601.jpg?imageMogr2/format/webp" class="aligncenter"></p><p>可以发现naming客户端成功获取到了两个实例。</p><h4>服务消费方 (Service Consumer)</h4><p>服务注册到注册中心后，服务的消费者就可以进行服务发现的流程了，消费者可以直接向注册中心发送获取某个服务实例的请求，这种情况下注册中心将返回所有可用的服务实例给消费者，但是一般不推荐这种情况。另一种方法就是服务的消费者向注册中心订阅某个服务，并提交一个监听器，当注册中心中服务发生变更时，监听器会收到通知，这时消费者更新本地的服务实例列表，以保证所有的服务均是可用的。</p><h5>Nacos消费服务机制</h5><p>是指会发起对某个服务调用的应用方。服务注册之后，服务的消费者就可以向注册中心订阅自己所需要的服务了，注册中心会将所有服务的实例“推送”给消费者，实际上获取服务是客户端主动轮询的，跟客户端获取配置中心的配置项的原理一样。</p><p>现在我创建一个服务消费者，然后向注册中心订阅一个服务，当接收到注册中心返回的服务列表之后，执行5次 select 服务实例的操作，相当于进行一个模拟的服务请求，具体的代码如下图所示：</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/950768243272645732.jpg?imageMogr2/format/webp" class="aligncenter"></p><p>其中的 printInstances 方法主要是打印出所有服务的实例，将 ServiceConsumer 类启动之后，打印出如下的日志：</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/7130428006769874326.jpg?imageMogr2/format/webp" class="aligncenter"></p><h5>Nacos机制负载均衡</h5><p>负载均衡有很多中实现方式，包括轮询法，随机方法法，对请求ip做hash后取模等等，从负载的维度考虑又分为：服务端负载均衡和客户端负载均衡。Nacos 的客户端在获取到服务的完整实例列表后，会在客户端进行负载均衡算法来获取一个可用的实例，模式使用的是随机获取的方式。</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/2836221679246777574.jpg?imageMogr2/format/webp" class="aligncenter"></p><h3>Nacos 服务注册与订阅的完整流程</h3><ul>
  <li><p>Nacos客户端进行服务注册有两个部分组成，一个是将服务信息注册到服务端，另一个是像服务端发送心跳包，这两个操作都是通过NamingProxy 和服务端进行数据交互的。</p></li> 
  <li><p>Nacos客户端进行服务订阅时也有两部分组成，一个是不断从服务端查询可用服务实例的定时任务，另一个是不断从已变服务队列中取出服务并通知 EventListener 持有者的定时任务，更新服务订阅列表。</p></li>
 </ul><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/3925251373107476508.jpg?imageMogr2/format/webp" class="aligncenter"></p><h3>参考资料</h3><p>https://nacos.io/zh-cn/docs/feature-list.html</p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p><h2>【Alibaba中间件技术系列】「Nacos技术专题」服务注册与发现相关的原理分析的</p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://www.ishiguang.cn/12056.html#comments</comments>
<wfw:commentRss>https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/</wfw:commentRss>
</item>
<item>
<title>轻量级orm框架——gzero指南</title>
<link>https://www.ishiguang.cn/12057.html</link>
<guid>https://www.ishiguang.cn/12057.html</guid>
<pubDate>Sat, 22 Jan 2022 19:40:16 +0800</pubDate>
<dc:creator>admin</dc:creator>
<description><![CDATA[(adsbygoogle = window.adsbygoogle || []).push({});		开发过web系统人一定对大量的curd不陌生，为了提高效率我们通常会使用一些orm框架做辅...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p>开发过web系统人一定对大量的curd不陌生，为了提高效率我们通常会使用一些orm框架做辅助，而不会直接操作数据库。但是现有的orm框架往往有两个通病（各种语言的都一样）：1. API复杂：即使是有经验的开发人员在使用前还是需要先写出原始的SQL语句，再调用api。效率不高，排查问题也不够透明。如果使用原生SQL，通常就需要自己再写代码来处理结果集。2. 使用orm框架对数据库的操作粒度比较粗，有时候为了提高数据库的查询效率，明明一条语句能够完成的工作，通过框架会发送多条语句。这样也无形中增加了系统的负载，降低了系统的并发吞吐量。</p><p>所谓我才想能够自己做一个框架，既能够让开发人员自由编写sql，同时又保持了orm的特性，将查询的结果集自动处理成我预先定义好的格式。关键，要使用方便，无外部依赖。经过了大概半个月的尝试，我终于写出了gzero。</p><p>一、如何引入</p><div><pre class="prettyprint linenums">go get gitee.com/learnhow/gzero@v0.8.1</pre></div><p>目前的最新版本是v0.8.1，源码只有400行，做了一些简单的单元测试。后面也许会根据大家的反馈意见在这个版本的基础上进一步完善。</p><p>二、开始使用</p><p>1. 定义模型</p><div><pre class="prettyprint linenums">// 定义员工结构体，内部包含一个身份证信息和登录信息<br />type Emp struct {<br />ID        uint `zero:"primaryKey"`<br />Name      string<br />JobNumber *string<br />ILogin    Login         `zero:"prefix:login_"`<br />IdCard    *IdentityCard `zero:"prefix:idcard_"`<br />}

// 登录信息<br />type Login struct {<br />ID    uint<br />Code  string<br />EmpFk uint<br />}

// 身份证信息<br />type IdentityCard struct {<br />Code string<br />}</pre></div><p>既然是orm就应该在我们的结构体内部体现出模型之间的关联关系，gzero通过primaryKey标识主键，在处理结果集的时候会根据主键判断，同时支持复合主键。主键字段不支持指针类型。</p><p>2. 创建连接</p><div><pre class="prettyprint linenums">ctx := Open("mysql", "root:123456@tcp(127.0.0.1:13306)/helloworld?charset=utf8&parseTime=True&loc=Local", Cfg{})</pre></div><p>gzero内部调用database/sql，Ctx不是单例对象，你可以反复调用Open方法。但是*sql.DB只会被创建一次，目前我只是做了简单处理。</p><p>3. 编写sql并安装进对象</p><div><pre class="prettyprint linenums">ctx.Sql("SELECT name, job_number FROM t_emp WHERE id = 1").Install(&emp)</pre></div><p>gzero默认结构体字段是驼峰类型，映射为表结构的蛇形字段。例如：JobNumber在数据库中对应job_number。你也可以通过“column”重新定义映射的字段名。如果多个结构体都包含同名的字段，你完全可以使用"prefix“为内部的结构体添加统一前缀（这个特点参考了gorm）。</p><p>结果示例：</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/7069245740433280610.jpg?imageMogr2/format/webp" class="aligncenter"></p><p>三、获取多条结果集</p><p>编写sql并安装进Slice</p><div><pre class="prettyprint linenums">var emps []Emp<br />ctx.Sql("SELECT id, name FROM t_emp").Install(&emps)</pre></div><p>多条结果集需要使用数组或切片来接收，结果示例：</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/2731057186264804114.jpg?imageMogr2/format/webp" class="aligncenter"></p><p>四、根据对象的层次结构处理数据</p><p>代码：</p><div><pre class="prettyprint linenums">var emp []Emp<br />ctx.Sql("SELECT e.id, e.name, e.job_number, l.emp_fk, l.code AS login_code FROM t_emp e, t_login l WHERE e.id = l.emp_fk").Install(&emp)</pre></div><p>查询出来的数据，gzero会根据结构体的层次对数据进行自动处理</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/1384006157339939867.jpg?imageMogr2/format/webp" class="aligncenter"></p><p>五、根据主键自动合并数据</p><p>定义结构体：</p><div><pre class="prettyprint linenums">type Dept struct {<br />ID   uint `zero:"primaryKey"`<br />Name string<br />Emps []Emp `zero:"prefix:emp_"`<br />}</pre></div><p>查询并安装数据：</p><div><pre class="prettyprint linenums">var depts []Dept<br />ctx.Sql("SELECT d.id, d.name, e.name AS emp_name, l.code AS login_code FROM t_dept d, t_emp e, t_login l WHERE d.id = e.dept_fk AND l.emp_fk = e.id").Install(&depts)</pre></div><p>结构体的前缀能够在sql语句中通过别名指定，查询结果如下：</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/4427912858156452389.jpg?imageMogr2/format/webp" class="aligncenter"></p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/6274423539532401351.jpg?imageMogr2/format/webp" class="aligncenter"></p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/3789465601368050212.jpg?imageMogr2/format/webp" class="aligncenter"></p><p>六、总结</p><p>目前gzero功能非常简单，就是根据开发人员预先定义好的层次关系将查询的结果集进行处理。之后，我打算增加对象到sql生成的功能，进一步提升crud的开发效率。</p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p><h2>轻量级orm框架——gzero指南的</p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://www.ishiguang.cn/12057.html#comments</comments>
<wfw:commentRss>https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/</wfw:commentRss>
</item>
<item>
<title>Markdown最新使用说明</title>
<link>https://www.ishiguang.cn/12055.html</link>
<guid>https://www.ishiguang.cn/12055.html</guid>
<pubDate>Sat, 22 Jan 2022 19:40:15 +0800</pubDate>
<dc:creator>admin</dc:creator>
<description><![CDATA[(adsbygoogle = window.adsbygoogle || []).push({});		Markdown基本语法说明markdown 和 typora关系：typora是一款实时...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><h1>Markdown基本语法说明</h1><blockquote><p>markdown 和 typora关系：typora是一款实时预览markdown的文本编辑器<br /><br />本文推荐使用的Markdown编辑器为：Typora (用了多款编辑器，觉得还是Typora更加简洁和实用)<br /><br />Typora下载地址在文章末尾，有需要的伙伴自取哦~</p></blockquote><blockquote><p>整篇文章说明：</p><ol>
   <li>若要使用markdown的标记符号则需在前加上一个反斜杠符号</li> 
   <li>符号和文本之间基本都需要一个空格来分开</li> 
   <li>全文的符号都必须是英文状态下的才行</li>
  </ol></blockquote><h2>基本语法</h2><h3>标题</h3><p>格式：</p><p><code>#+空格+文字</code></p><p><code>##+空格+文字</code></p><p><code>###+空格+文字</code></p><p>……</p><p><strong>【最多支持六个#，一个#相当与html中h1的大小，两个##相当与html中h2的大小，以此类推】</strong></p><p><hr></p><h3>粗体</h3><p>格式：</p><p><code>**文本**</code></p><p><hr></p><h3>斜体</h3><p>格式：</p><p><code>*文本*</code></p><blockquote><p><strong>若要斜体加粗则用三个星号 例：<code>***文本***</code></strong></p></blockquote><p><hr></p><h3>引用</h3><p>格式：</p><p><code>>文本</code></p><blockquote><p>注意是一个大于号</p></blockquote><p><hr></p><h3>列表</h3><h4>有序列表</h4><p>格式：</p><ol>
  <li><p>A</p></li> 
  <li><p>B</p></li> 
  <li><p>C</p><p>【数字+.+空格+文本】</p></li>
 </ol><h4>无序列表</h4><p>格式：</p><ul>
  <li><p>first</p></li> 
  <li><p>second</p></li> 
  <li><p>third</p><p>【减号+空格+文本】</p></li>
 </ul><h4>自定义列表</h4><p>格式：</p><p>冒号+自定义的标记符</p><p>例：<code>:apple</code>+文本</p><p>one</p><p>two</p><h4>任务列表</h4><p>格式：</p><p>三个点号+test+回车 再在文本框内输入：- [] 文本</p><p>例子：</p><pre class="prettyprint linenums"><code>- [x] Write the press release<br />- [√] Update the website<br />- [ ] Contact the media<br /></code></pre><p><hr></p><h3>代码</h3><h4>单行代码</h4><p>格式：</p><p>一个点号+单行代码+一个点号</p><p>【点号是英文输入下的tab键上的那个键】</p><p>例：<code>代码</code></p><blockquote><p>有时候可以用单行代码的格式作为注释方式</p></blockquote><h4>多行代码</h4><p>格式：</p><ol>
  <li>三个点号+多行代码+三个点号</li> 
  <li>三个点号+代码语言（java or python or and so on) +回车</li>
 </ol><p>【点号是英文输入下的tab键上的那个键】</p><p>例：</p><ol>
  <li><p><code>多行代码</code></p></li> 
  <li><p><code> ```java</code></p></li>
 </ol><p><hr></p><h3>分割线</h3><p>格式：</p><p>三个减号</p><p>例：<code>---</code></p><p><hr></p><h3>超链接</h3><p>格式：<br /><br /><code>[标题](网址)</code></p><p><hr></p><h3>图片</h3><p>格式：</p><p><code>![图片名字](图片的本地路径 or 网络路径)</code></p><blockquote><p>图片网络路径获取方法：</p><ol>
   <li>对网页上的图片右击选择复制 ” 图像链接 “</li> 
   <li>对网页上的图片右击选择 “ 审查元素 ” 从中找到所要的图片地址</li>
  </ol></blockquote><p><hr></p><h2>拓展语法</h2><h3>表格</h3><p>格式：</p><ol>
  <li><p>| 文本|文本|</p><p>|-- |-- |</p><p>| 文本|文本|</p><p>| 文本|文本|</p></li>
 </ol><blockquote><p>第二行的--是为了分割第一行的标题行和下面的子行</p></blockquote><ol>
  <li>直接在typora中右键选择所要的表格规格</li>
 </ol><p><hr></p><h3>删除线</h3><p>格式：</p><p>两个波浪线+文本+两个波浪线</p><p>例：<code>~~文本~~</code> 效果：文本</p><p><hr></p><h3>Emoji表情</h3><p>格式：</p><p>：英文：</p><p>例子：</p><p><hr></p><h3>自动网址链接</h3><p>在默认情况下，markdown会自动将你输入的网址变成url链接</p><p>禁用自动url链接方式：用反引号来保住这个链接</p><p><code>http://www.example.com</code></p><p>【这里的反引号就是前文所说的点号】</p><p><hr></p><h1>Typora下载路径</h1><p>多台设备测试进入Typora官网 https://typora.io/ 下载发现已经进不去了，而且前不久就宣布1.0版本开始要收费所以就连忙下载了最新的免费版本。想要下载免费版本的小伙伴可自行下载：</p><p>https://blog.csdn.net/ityoukown/article/details/122126625</p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p><h2>Markdown最新使用说明的</p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://www.ishiguang.cn/12055.html#comments</comments>
<wfw:commentRss>https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/</wfw:commentRss>
</item>
<item>
<title>Flink State Rescale性能优化</title>
<link>https://www.ishiguang.cn/12054.html</link>
<guid>https://www.ishiguang.cn/12054.html</guid>
<pubDate>Sat, 22 Jan 2022 19:40:13 +0800</pubDate>
<dc:creator>admin</dc:creator>
<description><![CDATA[(adsbygoogle = window.adsbygoogle || []).push({});		背景今天我们来聊一聊flink中状态rescale的性能优化。我们知道flink是一个支持...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><h1>背景</h1><p>今天我们来聊一聊flink中状态rescale的性能优化。我们知道flink是一个支持带状态计算的引擎，其中的状态分为了operator state和 keyed state两类。简而言之operator state是和key无关只是到operator粒度的一些状态，而keyed state是和key绑定的状态。而Rescale，意味着某个状态节点发生了并发的缩扩。在任务不修改并发重启的情况下，我们只需要按照task，将先前job的各个并发的state handle重新分发处理下载远程的持久化的state文件即可恢复。而发生rescale时，状态的数据分布将发生变化，因此存在一个reshuffle的过程，那么我们就来看看这个rescale的实现是怎么做的，以及其问题和优化手段。<br /><br />​</p><h1>Rescale的实现</h1><p>2017年社区有一篇博客就比较深入的介绍了Operator 和 keyed state的rescale的实现，感兴趣的话可以去了解下。<br /><br /><img src="https://up.ishiguang.cn/blog/typecho/2022/01/8172552557468483822.jpg?imageMogr2/format/webp" class="aligncenter"> <img src="https://up.ishiguang.cn/blog/typecho/2022/01/5377491584587060027.jpg?imageMogr2/format/webp" class="aligncenter"><br /><br />这两张图对比了是否基于keyGroup来划区的一个差别，社区中的版本使用的是基于keygroup的版本实现的，可以看到可以减少对于数据的random的访问。但是从B中我们看到，以rescale后的subtask为例:<br /><br />​</p><ul>
  <li><strong>subtask-0</strong>: 需要将原先subtask-0的dfs文件下载后将KG-3的数据剔除掉。<em>这里需要剔除的原因是: 虽然我们任务启动后由于keyshuffle的原因，subtask-0不会再接收到KG-3的数据，但是后续如果继续做checkpoint，会导致这部分数据重新被上传到DFS文件中，而如果继续发生rescale，就可能导致和其他subtask-1上的KG-3的数据发生冲突导致数据问题</em></li> 
  <li><strong>subtask-1</strong>: 需要download原先subtask-0和subtask-1的数据dfs文件，并将subtask-0中的KG-1和KG-2的数据删除，以及原先subtask-1中的 KG-5 和 KG-6删除，并将其导入到新的RocksDB实例中。</li>
 </ul><p>因此我们可以总结出rescale的大致流程中，首先会将当前task所涉及的db文件恢复到本地，并从中挑选出属于当前keygroup的数据重新构建出新的db。<br /><br />​</p><p>从理论上分析，在不同的并发调整场景下，其rescale的代价也不尽相同<br /><br /><img src="https://up.ishiguang.cn/blog/typecho/2022/01/1336737495263768608.jpg?imageMogr2/format/webp" class="aligncenter"><br /><br />并发翻倍<br /><br />​</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/6379958550973361475.jpg?imageMogr2/format/webp" class="aligncenter"><br /><br />1.5倍扩并发<br /><br />​</p><p><img src="https://up.ishiguang.cn/blog/typecho/2022/01/737510827361480157.jpg?imageMogr2/format/webp" class="aligncenter"><br /><br />并发减半<br /><br />​</p><p>接下来，我们在代码中确认相关的逻辑（代码基于Flink1.15版本）。</p><h2>根据stateHandle元信息判断是否是rescale</h2><p>我们可以看到当restoreStateHandles的数量大于1，或者stateHandle的keyGroupRange和当前task的range不一致时就是rescale的过程<br /><br /><img src="https://up.ishiguang.cn/blog/typecho/2022/01/7968426227811471285.jpg?imageMogr2/format/webp" class="aligncenter"><br /><br />在不是rescale的场景下，恢复的流程只需要将相应<code>IncrementalRemoteKeyedStateHandle</code>对应的文件下载到本地或者是直接使用local recovery中的 <code>IncrementalLocalKeyedStateHandle</code>所对应的本地文件的目录，直接执行 <code>RocksDB.open()</code>就可以将db数据恢复。</p><h2>initialDB</h2><p>首先根据keygroup的重叠比较，挑选出和当前keygroup有最大重叠范围的stateHandle作为initial state handle。这样的好处是可以尽可能利用最大重叠部分的数据，减少后续数据遍历的过程。在挑选出initial state handle 创建db之后，首先需要将db中不属于当前的task的keygroup的数据进行遍历删除。<br /><br /><img src="https://up.ishiguang.cn/blog/typecho/2022/01/5077967857007824708.jpg?imageMogr2/format/webp" class="aligncenter"><br /><br />因为flink中存储的keyed state的数据已经按照keygroup作为前缀作为排序，所以只需要删除头部和尾部的数据即可，这样就不用遍历全量的数据。<br /><br /><img src="https://up.ishiguang.cn/blog/typecho/2022/01/7073494730947310416.jpg?imageMogr2/format/webp" class="aligncenter"><br /><br />在当前的deleteRange的实现中是依赖遍历db，通过writeBatch的方式进行批量执行删除，这种方式当需要删除的key的基数较大时会比较耗时，并且都会触发io和compaction的开销，而rocksdb提供了deleteRange的接口，可以通过指定start和end key来进行快速的删除，经过测试下来基本只要ms级别就可以完成。参考 FLINK-21321</p><h2>Bulk load</h2><p>在完成base db裁剪之后，就需要将其他db的数据导入到base db中，目前的实现还是通过writeBatch来加速写入<br /><br /><img src="https://up.ishiguang.cn/blog/typecho/2022/01/2896822888509750275.jpg?imageMogr2/format/webp" class="aligncenter"><br /><br />在 FLINK-17971 中作者提供了sst ingest 写入的实现，本质上是利用rocksdb 的sst writer的工具，通过sst writer能直接构建出sst 文件，避免了直接写的过程中的compaction的问题，然后通过 <code>db.ingestExternalFile</code>直接将其导入db中。实际测试的过程中这样的写入性能有2-3倍的提升。</p><p>Rescale的优化应该迭代优化了很多次，最开始的实现应该是将所有的statehandle的数据download下来，将其遍历写入新的db，在 FLINK-8790 中首先将其优化成 base db + delete Range + bulk load的方式，后续的两个pr又通过Rocksdb提供的deleteRange + SSTIngest 特性加速。虽然这些优化应用上只有rescale的提速很明显，但是当我们遇到key的基数非常大时，就会出现我们遍历原先的db next调用和 写入的耗时也非常的大，因此rescale的场景可能还需要继续优化。<br /><br />​</p><p>关于RocksDB中deleteRange和SST Ingest功能笔者也做了一些研究，在后续的文章中会陆续更新出来，敬请期待</p><h1>参考</h1><p>https://blog.csdn.net/Z_Stand/article/details/115799605 sst ingest 原理<br /><br />http://rocksdb.org/blog/2017/02/17/bulkoad-ingest-sst-file.html<br /><br />https://rocksdb.org/blog/2018/11/21/delete-range.html delete range原理</p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p><h2>Flink State Rescale性能优化的</p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://www.ishiguang.cn/12054.html#comments</comments>
<wfw:commentRss>https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/</wfw:commentRss>
</item>
<item>
<title>Mybatis插件，能做的事情真的很多</title>
<link>https://www.ishiguang.cn/12053.html</link>
<guid>https://www.ishiguang.cn/12053.html</guid>
<pubDate>Sat, 22 Jan 2022 19:40:12 +0800</pubDate>
<dc:creator>admin</dc:creator>
<description><![CDATA[(adsbygoogle = window.adsbygoogle || []).push({});		大家好，我是架构摆渡人。这是实践经验系列的第九篇文章，这个系列会给大家分享很多在实际工作中...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p>大家好，我是架构摆渡人。这是实践经验系列的第九篇文章，这个系列会给大家分享很多在实际工作中有用的经验，如果有收获，还请分享给更多的朋友。</p><p>Mybatis是我们经常用的一款操作数据库的框架，它的插件机制设计的非常好，能够在很多需求场景下派上用场。如果你还没用过Mybatis的插件（Mybatis 插件实际是一种拦截器），那么需要仔细阅读这篇文章。</p><h1>SQL监控埋点</h1><p>说句大实话，大部分性能问题都发生在存储层。当然对于优化我们需要有足够的样本，这些样本也就是慢SQL。数据库本身就有慢SQL的日志，但不方便我们跟具体的接口和trace进行关联。</p><p>如果要跟trace进行关联那么就必须自己进行埋点上报性能数据，像我们之前用Cat做监控的时候，就是基于Mybatis的插件进行埋点，这样可以在Cat中看到每次请求下执行了哪些SQL，以及SQL的执行时间。</p><h1>SQL校验</h1><p>如果你们的表做了水平拆分，也就是分库分表。当然有可能是用开源框架实现的，也有可能是自研的框架。对于SQL的写法没有过多要求，但是如果是分库分表，而你的查询语句中没有带分片的字段，这个时候一般都是会进行所有表的查询，然后合并结果返回。</p><p>假如你想打破这个规则，查询的SQL中必须带分片的字段，否则就直接报错。当然可以对你们的分库分表框架进行改造，如果是开源的改起来比较麻烦，那是否可以简单点实现呢？</p><p>可以的，答案就是用Mybatis插件进行扩展，拿到执行的SQL进行分析，然后做校验。</p><h1>SQL改写</h1><p>SQL改写相对来说用的比较少，主要是改写是个很危险的事情，稍有不慎，线上就要炸锅了。那么改写会有哪些场景下需要呢？</p><p>之前有遇到过的场景就是，我们接了一个新的分库分表的框架，老框架是支持分表场景下update分片字段的。比如你user表的分片字段是id，那么update语句中可以带上这个id，只不过值还是原来的，不影响数据，这种也经常会在用一些自动生成SQL的场景下会有。</p><p>自从接了新框架后，对SQL更新有要求啦，分片字段是不允许更新的，不能出现在update的SQL中，如果有就直接报错。所以这个时候改写SQL就排上用场了，当然为了稳定性，我们还是采用了最原始的方式，将所有SQL都改了一遍。</p><h1>SQL中透传信息</h1><p>SQL中透传信息这个很实用，问题在于透传的信息给谁使用呢？</p><p>其实这个要归根于你们有没有使用Proxy方式的分库分表中间件，如果有的话就需要透传信息。</p><p>比如读写分离场景，Proxy怎么知道你的SQL要走主库还是走从库呢？</p><p>比如在做压测的时候，Proxy怎么知道你的SQL要走正常库还是影子库呢？当然这个你也可以把影子库控制放在客户端。</p><p>那么如何通过SQL进行信息透传呢？如下：</p><pre class="prettyprint linenums"><code>/*master:true*/ SELECT * FROM table<br /></code></pre><p>其实就是在SQL的前面加一些特殊的信息，然后中间件去解析做对应的处理。</p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p><h2>Mybatis插件，能做的事情真的很多的</p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://www.ishiguang.cn/12053.html#comments</comments>
<wfw:commentRss>https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/</wfw:commentRss>
</item>
<item>
<title>花了半年时间，我把Pink老师的HTMLCSS视频课程，整理成了10万字的Markdown笔记！</title>
<link>https://www.ishiguang.cn/12051.html</link>
<guid>https://www.ishiguang.cn/12051.html</guid>
<pubDate>Sat, 22 Jan 2022 19:40:11 +0800</pubDate>
<dc:creator>admin</dc:creator>
<description><![CDATA[(adsbygoogle = window.adsbygoogle || []).push({});		说明：本文内容真实！！！不是推广！！！学习前端的同学应该都或多或少听说过 Pink 老师，...]]></description>
<content:encoded xml:lang="zh-CN"><![CDATA[
<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><h1>说明：本文内容真实！！！不是推广！！！</h1><p>学习前端的同学应该都或多或少听说过 Pink 老师，我个人觉得 Pink 老师的前端视频教程应该说是目前B站上最好的了，没有之一！<br /><br />Pink老师 HTML CSS B站视频课程链接<br /><br />我花了整整半年的时间细致认真的学习完了 Pink 老师的 HTML CSS 教程，受益匪浅！<br /><br />我总结整理了一份包含该视频课程 全部知识点 近10万字 的 Markdown 笔记！笔记中包含所有的知识点归纳！并且配套有视频中对应的代码案例，最重要的是：每个代码案例都配套对应的效果图及批注，而且对于动画等页面效果所配套的都是GIF动态图片！！！，而且文档经过了精美的排版，同时加入了许多重要的知识点补充，更NB的是所有知识点都配套对应的思维导图！！！<br /><br />整理这份资料花了我半年所有的业余时间！！！如果只看视频，不做记录的话，效率非常低，看完就忘了！希望我提供的这份资料能帮助到大家！！！感谢！！！<br /><br />Markdown文档<br /><br />思维导图</p><blockquote><p>制作不易，希望大家可以去 GitHub 收藏一下！这对我有非常大的帮助！感谢大家！</p></blockquote><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-9051032955849697" data-ad-slot="3737842878"></ins><script>
		     (adsbygoogle = window.adsbygoogle || []).push({});
		</script><p><h2>花了半年时间，我把Pink老师的HTMLCSS视频课程，整理成了10万字的Markdown笔记！的</p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://www.ishiguang.cn/12051.html#comments</comments>
<wfw:commentRss>https://www.ishiguang.cn/feed/tag/%E5%8E%9F%E6%96%87/</wfw:commentRss>
</item>
</channel>
</rss>