2019年10月

Tomcat 7 NIO handling request on Linux

围绕这个图, 做几点解说(左边的虚线框表示 Tomcat):
Tomcat_Request_handling.png

  1. 从操作系统层面来说, 收到 TCP 的 SYN 之后, 放到 TCP SYN backlog 队列, 然后发送 (syn + ack) 包, 等待对方的 ack 包;
  2. SYN backlog 长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 设置, 还有文章说最终值有几个因素共同决定, 不过在 Linux Kernel 4.3 之后,这个 syn backlog 不在由 net.ipv4.tcp_max_syn_backlog 决定, 而是由 net.core.somaxconn 决定

    eric@host:~$ sysctl net.core.somaxconn
    net.core.somaxconn = 4096
    eric@host:~$ sysctl net.ipv4.tcp_max_syn_backlog
    net.ipv4.tcp_max_syn_backlog = 4096
  3. 如何查看一个监听端口的 SYN backlog 当前的队列长度?
    没有直接看当前长度的命令, 不过可以自己手工计算:

    # 查看连到当前 host 8080 端口上并且处于 sync-recv 状态的连接
    eric@host:~$ ss -n state syn-recv sport = :8080
  4. 如果 SYN backlog 已经满了, 那么操作系统层面会自动丢弃接收到的 syn 包, 那么客户端以为对端没收到, 会继续尝试.
  5. 当操作系统回复 syn+ack 之后, 对端返回 ack, 完成三次包握手, 这时, 这个连接变成 Established 的状态, 该连接从 syn backlog 进入 listen backlog;
  6. listen backlog 的长度由 listen 系统调用的参数决定, 在 Java 里由 ServerSocket(int port, int backlog)的构造函数的 backlog 参数决定. java 默认是 50. Tomcat 7 里面由 Connector 参数 acceptCount 决定, 默认是 100;
  7. 如果这个 listen backlog 满了, OS 收到对端为了完成握手的 ack 时, 选择 ignore 这个 ack, 那么客户端会以为丢包, 会继续尝试发送 ack;
  8. 如何查看一个监听端口的 listen backlog 队列的当前长度? Linux -> ss -ltn 看 Send-Q 列
    xiatian_lvsbastion200_____ssh_.png
  9. Tomcat 7 NIO 处理这些 socket 连接有 2 类 background 线程, 分别是 Acceptor 线程和 Poller 线程;
  10. Acceptor 线程 通过 serverSock.accept() 方法接受新连接, 就是 listen backlog 里面已经建立的连接;
  11. Acceptor 接受之后, 就把这个接受的 socket 送给 Poller 线程去处理;
  12. Poller 线程把 socket 封装之后放入 TaskQueue;
  13. Poller 线程还负责 从 socket 的 Channel 搬运数据到对应的 Buffer, 也就是负责 NIO 的 Selector 的处理任务;
  14. Acceptor 线程和 Poller 线程的数量分别由 Tomcat Connector 的 acceptorThreadCount 和 pollerThreadCount 决定;
  15. Tomcat 的 TaskQueue 是一个 LinkedBlockingQueue;
  16. TaskQueue队列里的 Task 由 Executor 里的线程读取并执行;
  17. TaskQueue队列里的 Task 由 Poller 放入;
  18. TaskQueue的长度默认是 Int 的最大值, 不过基本不会放这么多, 如果TaskQueue的任务和在处理的超过 maxConnection 的值, Poller 就会决绝新的任务;

参考: https://blog.cloudflare.com/syn-packet-handling-in-the-wild/

Java NIO & NIO2 里面的 attachment 是怎么回事?

在 Java NIO 和 NIO2 里面我们经常看到attach 和 attachment 相关的 API, 如:

Objcet SelectionKey.attch(Object obj)
Object SelectionKey.attachment()
SelectionKey AbstractSelector.register(AbstractSelectableChannel ch,int ops, Object att);

这里的 Attachment 是一个 Object 对象, 也就是说它可以 attach 任何对象, 为什么要 attach 一个对象呢?
我们从 Blocking IO 说起, 当 Blocking IO 的时候, 读字节流或字符流的的线程一直 block 在读操作上, 不去干其它事情, 当来一个字节/字符的时候, 它就读一个, 直到返回 -1 代表读完(EOF). 可是当时 NIO 的时候, 读线程直接返回, 是由一个专门的 IO 线程操作 Selector 去读取的, 当有部分数据到达的时候, 它就帮你读取, 读完来的数据之后, 还没读取到 EOF, 那么要继续监听这个 socket, 再次等待它接下来的数据, 那么刚才读取的数据放哪里呢? 之后来的数据怎么拼接到原来的数据之后呢? 那么就需要有一个容器放置还没有读完的半成品, 在下次同一个 socket 来数据的时候, 再次拿出这个容器, 继续放数据, 那么这个容器怎么可以容易的每次都有数据的时候拿到呢? 那就放到 SelectionKey 上面吧, 反正每次都是通过 Selectionkey 知道有数据的, 一旦 SelectionKey 有数据, 就通过它拿到之前装有半成品的容器, 那么可以继续放了.

每次读完通过 attach(Object) 方法附在上面, 下次当有数据来的时候, 通过 attachment 方法拿到之前的容器.

参考: https://jfarcand.wordpress.com/2006/07/06/tricks-and-tips-with-nio-part-ii-why-selectionkey-attach-is-evil/

在 chrome 以屏幕模式打印, 而不是打印模式

有时候我们浏览网页的时候, 想打印该页面, 可是打开打印预览, 发现和正常模式差别很大, 实际上是差很多. 这时候, 我们想以正常模式打印.

  1. 使用命令 Command+Shift+P (Mac) or Control+Shift+P (Windows, Linux, Chrome OS) 打开命令提示菜单;
  2. 选择 Show Rendering, 回车;
  3. 再最下面 选择 Emulate CSS Midea, 选择 Screen.

照抄: https://developers.google.com/web/tools/chrome-devtools/css/print-preview

本文是遇到像打印 oreilly 书的页面的时候, 出现的这个问题. https://learning.oreilly.com

    let classes = [
                "sbo-site-nav",
                "sbo-reading-menu sbo-menu-top",
                "interface-controls interface-controls-top",
                "t-sbo-prev sbo-prev sbo-nav-top",
                "t-sbo-next sbo-next sbo-nav-top",
                "t-sbo-prev sbo-prev sbo-nav-bottom",
                "t-sbo-next sbo-next sbo-nav-bottom",
                "pagefoot t-pagefoot"
            ];

classes.forEach(function(className, i){
    var ele = document.getElementsByClassName(className)[0];
    if (ele) {
        ele.parentNode.removeChild(ele);
    } 
});

var ctt = document.getElementById("sbo-rt-content");
ctt.setAttribute("style", "max-width:700px");

var ct = document.getElementsByClassName("annotator-wrapper")[0];
if (ct) {
    ct.setAttribute("style", "font-size:180%!important");
} 

使用 strace 解决 twagent 狂耗 CPU 问题

最近一段时间, 经常发现生产环境上有些服务器的 CPU 使用率比其它高很多. 检查下来, 都不是我们自己的应用程序导致的, 细分下来有两种, 一种是 puppet agent 导致的(诊断 puppet agent 导致的 CPU 使用率非常高的问题), 另外一种就是 twagent 导致的.

  1. 首先, 通过 top -i -H 可以看到这个进程十分耗 CPU
    twagent.png
  2. 然后, 通过 ps aux | grep twagent | less -w 可以看到它是怎么被启动的

    xiatian 28604 0.0 0.0 10476 2084 pts/7 S+ 03:33 0:00 grep --color=auto twagent
    root 31695 31.9 0.0 494272 11340 ? Ssl May22 60485:45 /opt/tripwire/agent/twagent --service.mode=true --service.type=SysV --agent.dir=/opt/tripwire/agent --plugins.dir=/opt/tripwire/agent/plugins --tools.dir="/opt/tripwire/agent/tools" --data.dir="/var/cache/tripwire" --lock.dir="/var/lock/tripwire" --log.dir="/var/log/tripwire" --spool.dir="/var/spool/tripwire"

  3. 然后, 执行 sudo strace -C -e trace=all -p 31794 (这里一定是要那个耗CPU 的线程, 不是进程号), 可以看到N多这样的 event:

    statfs("/var/spool/tripwire", 0x7fe61a247450) = -1 ENOENT (No such file or directory)
    statfs("/var/spool/tripwire", 0x7fe61a247450) = -1 ENOENT (No such file or directory)

  4. 然后执行 添加文件夹 (我这里是 root 起的这个进程, 所以用 root 加一个文件夹)

    sudo mkdir /var/spool/tripwire

  5. 然后呢, 问题消失了