月度归档:2014年09月

mac tips

1) 开启locate的数据检索

sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist

2) 终端下的拷贝粘贴用 pbcopy, pbpaste,为避免pbcopy的时候多一个换行符,可以截去:

$ alias copy='tr -d "\n" | pbcopy'
$ uname | copy
$ pbpaste
Darwin

3) 在linux下为了更好的看man文档,可以用vim来看:

$ man ps | col -b | vi -

mac下,可以用preview预览程序来看:

$ man -t ps | open -fa /Applications/Preview.app

前面的man -t是使用/usr/bin/groff -Tps -mandoc -c来格式化

4) 终端下安装dmg或iso

$ hdid diskimage.dmg

5) 查看cpu的信息:

$ sysctl -a hw | grep -i cpu

6) 查看某个进程在执行什么文件操作:这个和lsof有点像,可以对比一下

$ sudo fs_usage 1204

7) 查看系统版本

$ sw_vers

8) system_profiler也是查看软硬件的工具

$ system_profiler -detailLevel full  SPHardwareDataType

或直接:

$ system_profiler SPHardwareDataType

查看内存条的硬件信息

$ system_profiler -listDataTypes  | grep -i mem
$ system_profiler -detailLevel full SPMemoryDataType 

9) 修改截屏保存的格式,默认是png格式,修改为jpg的话:

$ defaults write com.apple.screencapture type jpg

mac下控制上网的脚本

livejournal的创始人(也是memcached的作者),说有次他坐飞机,无聊拿出笔记本写程序,写着写着就习惯性的打开浏览器访问互联网,当然访问不了,这时他突然意识到自己平 时写代码也是这样不专注,随意上网浏览都成了一种习惯。后来他为此专门找了个iptables的脚本来控制自己。对于控制力不强的同学确实需要。在mac 上可以用ipfw来替代iptables,下面的脚本用来控制我在家里只能访问局域网不能访问外网:

$ cat quiet
#!/bin/sh
sudo ipfw -f flush
#home
sudo ipfw add allow tcp from me to 192.168.1.0/16
sudo ipfw add allow ip from me to 192.168.1.0/16
#company
sudo ipfw add allow tcp from me to 10.0.0.0/8
sudo ipfw add allow ip from me to 10.0.0.0/8
sudo ipfw add allow tcp from me to 119.38.218.129
sudo ipfw add allow ip from me to 119.38.218.129
sudo ipfw add allow tcp from me to 110.75.167.30
sudo ipfw add allow ip from me to 110.75.167.30
#aliwangwang
sudo ipfw add allow tcp from me to 110.75.161.98/24
sudo ipfw add allow ip from me to 110.75.161.98/24
#evernote
#dict.youdao.com
sudo ipfw add allow tcp from me to 220.181.76.13/24
#block
sudo ipfw add deny tcp from me to any
sudo ipfw add deny ip from me to any

恢复的话是:

$ sudo ipfw -f flush

另一个,比较简单的,控制sns网站的dns的:

$ cat disable-sns.sh
#!/bin/sh
cat << EOF >> /etc/hosts
### sns-sites
127.0.0.1   www.laiwang.com
127.0.0.1   laiwang.com
127.0.0.1   www.cnbeta.com
127.0.0.1   cnbeta.com
127.0.0.1   www.weibo.com
127.0.0.1   weibo.com
127.0.0.1   news.sina.com.cn
127.0.0.1   www.douban.com
127.0.0.1   douban.com
127.0.0.1   www.facebook.com
127.0.0.1   facebook.com
### sns-sites
EOF

tomcat-connector的微调(3): processorCache与socket.processorCache

tomcat在处理每个连接时,Acceptor角色负责将socket上下文封装为一个任务SocketProcessor然后提交给线程池处理。在BIO和APR模式下,每次有新请求时,会创建一个新的SocketProcessor实例(在之前的tomcat对keep-alive的实现逻辑里也介绍过可以简单的通过SocketProcessorSocketWrapper实例数对比socket的复用情况);而在NIO里,为了追求性能,对SocketProcessor也做了cache,用完后将对象状态清空然后放入cache,下次有新的请求过来先从cache里获取对象,获取不到再创建一个新的。

这个cache是一个ConcurrentLinkedQueue,默认最多可缓存500个对象(见SocketProperties)。可以通过socket.processorCache来设置这个缓存的大小,注意这个参数是NIO特有的。

接下来在SocketProcessor执行过程中,真正的业务逻辑是通过一个org.apache.coyote.Processor的接口来封装的,默认这个Processor的实现是org.apache.coyote.http11.Http11Processor。我们看一下SocketProcessor.process(...)方法的大致逻辑:

public SocketState process(SocketWrapper<S> wrapper, SocketStatus status) {
    ...
    // 针对长轮询或upgrade情况
    Processor<S> processor = connections.get(socket);
    ...

    if (processor == null) {
        // 1) 尝试从回收队列里获取对象
        processor = recycledProcessors.poll();
    }
    if (processor == null) {
        // 2) 没有再创建新的
        processor = createProcessor();
    }

    ...
    state = processor.process(wrapper);

    ...
    release(wrapper, processor, ...);

    ...
    return SocketState.CLOSED;
}

上面的方法是在AbstractProtocol模板类里,所以BIO/APR/NIO都走这段逻辑,这里使用了一个回收队列来缓存Processor,这个回收队列是ConcurrentLinkedQueue的一个子类,队列的长度可通过server.xml里connector节点的processorCache属性来设置,默认值是200,如果不做限制的话可以设置为-1,这样cache的上限将是最大连接数maxConnections的大小。

在原有的一张ppt上加工了一下把这两个缓存队列所在位置标示了一下,图有点乱,重点是两个绿颜色的cache队列:

图中位于上面的socket.processorCache队列是NIO独有的,下面的processorCache是三种连接器都可以设置的。processorCache这个参数在并发量比较大的情况下也蛮重要的,如果设置的太小,可能引起瓶颈。我们模拟一下,看看这个瓶颈是怎么回事。先修改server.xml里的connector节点,把processorCache设置为0:

    <Connector port="7001"
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           connectionTimeout="20000"
           redirectPort="8443" 
           processorCache="0"/>

启动tomcat后,使用ab模拟并发请求:

$ ab -n100000 -c10 http://localhost:7001/main

然后在ab的执行过程中立刻执行jstack观察堆栈信息,会发现一大半线程阻塞在AbstractConnectionHandler.registerAbstractConnectionHandler.unregister方法上:

"http-nio-7001-exec-11" #34 daemon prio=5 os_prio=31 tid=0x00007fd05ab05000 nid=0x8903 waiting for monitor entry [0x000000012b3b7000]
 java.lang.Thread.State: BLOCKED (on object monitor)
 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.register(AbstractProtocol.java:746)
 - waiting to lock <0x00000007403b8950> (a org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler)
 at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.createProcessor(Http11NioProtocol.java:277)
 at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.createProcessor(Http11NioProtocol.java:139)
 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585)
 at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1720)
 at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)

 ...
"http-nio-7001-exec-4" #27 daemon prio=5 os_prio=31 tid=0x00007fd0593e3000 nid=0x7b03 waiting for monitor entry [0x000000012aca2000]
 java.lang.Thread.State: BLOCKED (on object monitor)
 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.unregister(AbstractProtocol.java:773)
 - locked <0x00000007403b8950> (a org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler)
 at org.apache.coyote.AbstractProtocol$RecycledProcessors.offer(AbstractProtocol.java:820)
 at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.release(Http11NioProtocol.java:219)
 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:690)
 at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1720)

registerunregister分别是在创建和回收processor的时候调用的;看一下createProcessor方法里的大致逻辑:

public Http11NioProcessor createProcessor() {
    Http11NioProcessor processor = new Http11NioProcessor(...);
    processor.setXXX(...);
    ...

    // 这里,注册到jmx
    register(processor);
    return processor;
}

tomcat对jmx支持的非常好,运行时信息也有很多可以通过jmx获取,所以在每个新连接处理的时候,会在创建processor对象的时候注册一把,然后在processor处理完回收的时候再反注册一把;但这两个方法的实现都是同步的,同步的锁是一个全局的ConnectionHandler对象,造成了多个线程会在这里串行。

绝大部分应用没有特别高的访问量,通常并不需要调整processorCache参数,但对于网关或代理一类的应用(尤其是使用servlet3的情况)这个地方可以设置的大一些,比如调到1000或者-1。

tomcat-connector的微调(2): maxConnections, maxThreads

1) 最大连接数

tomcat的最大连接数参数是maxConnections,这个值表示最多可以有多少个socket连接到tomcat上。BIO模式下默认最大连接数是它的最大线程数(缺省是200),NIO模式下默认是10000,APR模式则是8192(windows上则是低于或等于maxConnections的1024的倍数)。如果设置为-1则表示不限制。

在tomcat里通过一个计数器来控制最大连接,比如在Endpoint的Acceptor里大致逻辑如下:

while (running) {
    ...    
    //if we have reached max connections, wait
    countUpOrAwaitConnection(); //计数+1,达到最大值则等待

    ...
    // Accept the next incoming connection from the server socket
    socket = serverSock.accept();

    ...
    processSocket(socket);

    ...
    countDownConnection(); //计数-1
    closeSocket(socket);
}

计数器是通过LimitLatch锁来实现的,它内部主要通过一个java.util.concurrent.locks.AbstractQueuedSynchronizer的实现来控制。

我们在server.xml里对Connector增加maxConnections="1"这个参数,然后模拟2个连接:

for i in {1..2}; do ( 
    {
        echo -ne "POST /main HTTP/1.1\nhost: localhost:7001\n\n"; 
        sleep 20
    } | telnet localhost 7001
)&;  done

然后通过jstack可以看到acceptor线程阻塞在countUpOrAwaitConnection方法上:

"http-bio-7001-Acceptor-0" #19 daemon prio=5 os_prio=31 tid=0x00007f8acbcf1000 nid=0x6903 waiting on condition [0x0000000129c58000]
 java.lang.Thread.State: WAITING (parking)
 at sun.misc.Unsafe.park(Native Method)
 - parking to wait for  <0x0000000740353f40> (a org.apache.tomcat.util.threads.LimitLatch$Sync)    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
 at org.apache.tomcat.util.threads.LimitLatch.countUpOrAwait(LimitLatch.java:115)
 at org.apache.tomcat.util.net.AbstractEndpoint.countUpOrAwaitConnection(AbstractEndpoint.java:755)
 at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:214)
 at java.lang.Thread.run(Thread.java:745)

对于NIO和APR的最大连接数默认值比较大,适合大量连接的场景;如果是BIO模式线程池又设置的比较小的话,就需要注意一下连接的处理是否够快,如果连接处理的时间较长,或新涌入的连接量比较大是不太适合用BIO的,调大BIO的线程数也可能存在利用率不高的情况

2) 最大线程数

如果没有对connector配置额外的线程池的话,maxThreads参数用来设置默认线程池的最大线程数。tomcat默认是200,对一般访问量的应用来说足够了。

tomcat-connector的微调(1): acceptCount参数

对于acceptCount这个参数,含义跟字面意思并不是特别一致(个人感觉),容易跟maxConnections,maxThreads等参数混淆;实际上这个参数在tomcat里会被映射成backlog:

static {
    replacements.put("acceptCount", "backlog");
    replacements.put("connectionLinger", "soLinger");
    replacements.put("connectionTimeout", "soTimeout");
    replacements.put("rootFile", "rootfile");
}

backlog表示积压待处理的事物,是socket的参数,在bind的时候传入的,比如在Endpoint里的bind方法里:

public void bind() throws Exception {

    serverSock = ServerSocketChannel.open();
    ...
    serverSock.socket().bind(addr,getBacklog());
    ...
}

这个参数跟tcp底层实现的半连接队列和完全连接队列有什么关系呢?我们在tomcat默认BIO模式下模拟一下它的效果。

模拟的思路还是简单的通过shell脚本,建立一个长连接发送请求,持有20秒再断开,好有时间观察网络状态。注意BIO模式下默认超过75%的线程时会关闭keep-alive,需要把这个百分比调成100,这样就不会关闭keep-alive了。修改后的connector如下,最后边的三行参数是新增的:

<Connector port="8080" protocol="HTTP/1.1"
    connectionTimeout="20000"
    redirectPort="8443"        

    maxThreads="1"
    disableKeepAlivePercentage="100"
    acceptCount="2"
/>

上面的配置里我们把tomcat的最大线程数设置为1个,一直开启keep-alive,acceptCount设置为2。在linux上可以通过ss命令检测参数是否生效:

$ ss -ant  
State       Recv-Q Send-Q     Local Address:Port     Peer Address:Port
LISTEN      0      2          :::7001                :::*

可以看到7001端口是LISTEN状态,send-q的值是2,也就是我们设置的backlog的值。如果我们不设置,tomcat默认会设置为100,java则默认是50。

然后用下面的脚本模拟一次长连接:

$ { 
    echo -ne "POST /main HTTP/1.1\nhost: localhost:7001\n\n";
    sleep 20
  } | telnet localhost 7001

这个时候看服务器端socket的状况,是ESTABLISHED,并且Recv-QSend-Q都是没有堆积的,说明请求已经处理完

$ netstat -an | awk 'NR==2 || $4~/7001/'
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.7001         127.0.0.1.54453        ESTABLISHED

现在我们模拟多个连接:

$ for i in {1..5}; do 
    ( 
        {
          echo -ne "POST /main HTTP/1.1\nhost: localhost:7001\n\n"; 
          sleep 20
        } | telnet localhost 7001
    )&;  
  done 

上面发起了5个链接,服务器端只有1个线程,只有第一个连接上的请求会被处理,另外4次连接,有2个连接还是完成了建立(ESTABLISHED状态),还有2个连接则因为服务器端的连接队列已满,没有响应,发送端处于SYN_SENT状态。下面列出发送端的tcp状态:

$ netstat -an | awk 'NR==2 || $5~/7001/'
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.51389        127.0.0.1.7001         SYN_SENT
tcp4       0      0  127.0.0.1.51388        127.0.0.1.7001         SYN_SENT
tcp4       0      0  127.0.0.1.51387        127.0.0.1.7001         ESTABLISHED
tcp4       0      0  127.0.0.1.51386        127.0.0.1.7001         ESTABLISHED
tcp4       0      0  127.0.0.1.51385        127.0.0.1.7001         ESTABLISHED

再看tomcat端的状态:

$ netstat -an | awk 'NR==2 || $4~/7001/'
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4      45      0  127.0.0.1.7001         127.0.0.1.51387        ESTABLISHED
tcp4      45      0  127.0.0.1.7001         127.0.0.1.51386        ESTABLISHED
tcp4       0      0  127.0.0.1.7001         127.0.0.1.51385        ESTABLISHED

有3个链接,除了第一条连接请求的Recv-Q是0,另外两个连接的Recv-Q则有数据堆积(大小表示发送过来的字节长度)。注意,在ESTABLISHED状态下看到的Recv-QSend-Q的大小与在LISTEN状态下的含义不同,在LISTEN状态下的大小表示队列的长度,而非数据的大小。

从上面的模拟可以看出acceptCount参数是指服务器端线程都处于busy状态时(线程池已满),还可接受的连接数,即tcp的完全连接队列的大小。对于完全队列的计算,在linux上是:

min(backlog,somaxconn) 

backlog参数和proc/sys/net/core/somaxconn这两个值哪个小选哪个。

不过acceptCount/backlog参数还不仅仅决定完全连接队列的大小,对于半连接队列也有影响。参考同事飘零的blog,在linux 2.6.20内核之后,它的计算方式大致是:

table_entries = min(min(somaxconn,backlog),tcp_max_syn_backlog)
roundup_pow_of_two(table_entries + 1)

第二行的函数roundup_pow_of_two表示取最近的2的n次方的值,举例来说:假设somaxconn为128,backlog值为50,tcp_max_syn_backlog值为4096,则第一步计算出来的为50,然后roundup_pow_of_two(50 + 1),找到比51大的2的n次方的数为64,所以最终半连接队列的长度是64。

所以对于acceptCount这个值,需要慎重对待,如果请求量不是很大,通常tomcat默认的100也ok,但若访问量较大的情况,建议这个值设置的大一些,比如1024或更大。如果在tomcat前边一层对synflood攻击的防御没有把握的话,最好也开启syn cookie来防御。