花钱的年华

江南白衣,公众号:春天的旁边

Netty高性能编程备忘录(上)

| Filed under 技术

网上赞扬Netty高性能的文章不要太多,但如何利用Netty写出高性能网络应用的文章却甚少,此文权当抛砖引玉。

估计很快就要被拍砖然后修改,因此转载请保持原文链接,否则视为侵权...

http://calvin1978.blogcn.com/articles/netty-performance.html

参考资料:

 

1. 连接篇

1.1 Netty Native

Netty Native用C++编写JNI调用的Socket Transport,是由Twitter将Tomcat Native的移植过来,现在还时不时和汤姆家同步一下代码。

经测试,的确比JDK NIO更省CPU。

也许有人问,JDK的NIO也用EPOLL啊,大家有什么不同? Norman Maurer这么说的:

  • Netty的 epoll transport使用 edge-triggered 而 JDK NIO 使用 level-triggered
  • C代码,更少GC,更少synchronized
  • 暴露了更多的Socket配置参数

第一条没看懂,反正测试结果的确更快更省CPU。

用法倒是简单,只要几个类名替换一下,详见Netty的官方文档1文档2

但要注意,它跟OS相关且基于GLIBC2.10编译,而CentOS 5.8就只有GLIBC2.5(别问为什么,厂大怪事多,我厂就是还有些CentOS5.8的机器),所以最好还是不要狠狠的直接全文搜索替换,而是用System.getProperty(“os.name”)System.getProperty(“os.version”) 取出操作系统名称与版本,做成一个开关。

另外,Netty很多版本都有修复Netty Native相关的bug,看得人心里发毛,好在最近的版本终于不再说了,所以要用就用Netty的新版。

最后,Netty Native还包含了Google的boringssl(A fork of OpenSSL),JDK的原生SSL实现比OpenSSL慢很多很多,而大家把SSL Provider配置成OpenSSL时,又要担心操作系统有没装OpenSSL,或者版本会不会太旧。现在好了。

 

1.2 异步连接,异步传输,告别Commons Pool

异步化最牛头不对马嘴的事情就是,给它配一个类似Commons Pool这样,有借有还的连接池。

在很多异步化的场景里,都用channel.writeAndFlush()原子的发送数据,发完不用同步等response,这时其实不需要独占一条Channel,不需要把它借出去,再还回池里。一来连接池出入之间有并发锁,二来并发请求一多就要狂建连接,到了连接池上限时还要傻傻的等待别人释放连接,而这可能毫无必要。

此时,建议直接建一个连接数组,随机到哪个连接就直接用它发送数据。如果那个连接还没建立或者已经失效,那就建立连接。

顺便说一句,异步的世界里,连建立连接的过程也是异步的,主线程不要等在建连接上,而是把发送的动作封成一个ChannelCallback,等连接建立了,再回调它发送数据,避免因为连接建立的缓慢或网络根本不通,把线程都堵塞了。

Netty4.0.28开始也有ChannelPool了,供需要独占Channel的场景如HTTP1.1,比之Commons Pool的特色之一也是这个异步的建连接过程。

 

1.3 最佳连接数:一条连接打天下?还有传说中的海量连接?

NIO这么神奇,有一种做法是只建一条连接,如Memcached的客户端SpyMemcached。还有一种是既然你能支持海量连接,几千几万的,那我就无节制的可劲的建了。

测试表明,一条连接有瓶颈,毕竟只用到了一个CPU核。 海量连接,CPU和内存在燃烧。。。。

那最佳连接数是传说中的CPU核数么?依然不是。

一切还是看你的场景,连接数在满足传输吞吐量的情况下,越少越好。

举个例子,在我的Proxy测试场景里:

  • 2条连接时,只能有40k QPS。
  • 48条连接,升到62k QPS,CPU烧了28%
  • 4条连接,QPS反而上升到68k ,而且CPU降到20%。

 

1.4 Channel参数设定

TCP/Socket的大路设置,无非 SO_REUSEADDR, TCP_NODELAY, SO_KEEPALIVE 。另外还有SO_LINGER , SO_TIMEOUT, SO_BACKLOG, SO_SNDBUF, SO_RCVBUF。

而用了Native后又加了TCP_CORK和KeepAlive包发送的时间间隔(默认2小时),详见EpoolSocketChannelConfig的JavaDoc

所有这些参数的含义,不一一描述了,自己搜索,比如Linux下高性能网络编程中的几个TCP/IP选项

而Netty自己的参数CONNECT_TIMEOUT_MILLIS,是Netty自己起一个定时任务来监控建立连接是否超时,默认30秒太长谁也受不了,一般会弄短它。

 

2. 线程篇

基本知识:《Netty in Action》中文版—第七章 EventLoop和线程模型

2.1 WorkerGroup 与 Boss Group

大家都知道,Boss Group用于服务端处理建立连接的请求,WorkGroup用于处理I/O。

EventLoopGroup的默认大小都是是2倍的CPU核数,但这并不是一个恒定的最佳数量,为了避免线程上下文切换,只要能满足要求,这个值其实越少越好。

Boss Group每个端口平时好像就只占1条线程,无论配了多少。
 

2.2 上下游线程的绑定

在服务化的应用里,一般处理上游请求的同时,也会向多个下游的服务集群佳节又重阳发送请求,但调优指南里都说,尽量,全部重用同一个EventLoop。否则,处理上游请求的线程,就要把后续任务以Runnable的方式,提交到下游Channel的处理线程。

但,一个EventLoop线程可以处理多个Channel的信息,而一个Channel只能注册一个EventLoop线程。所以没办法保证处理上游的Channel,与下游多个连接的Channel,刚好是属于一个EventLoop?

因此,追求极致的Proxy型应用,可能会放弃前面的固定连接池的做法,而是为每个处理上游请求的线程,对应每一台下游服务器创建一条Channel,而且设定它的工作线程就是本上游线程,然后存到threadLocal里。这样的做法连接数可能会增多,但减少了切换,要自行测试权衡。
 

2.2 业务线程池

Netty线程的数量一般固定且较少,所以很怕线程被堵塞,比如同步的数据库查询,比如下游的服务调用(又来罗嗦,future.get()式的异步在执行future.get()时还是堵住当前线程的啊)。

所以,此时就要把处理放到一个业务线程池里操作,即使要付出线程上下文切换的代价,甚至还有些ThreadLocal需要复制。

 

2.3 定时任务

像发送超时控制之类的一次性任务,不要使用JDK的ScheduledExecutorService,而是如下:

ctx.executor().schedule(new MyTimeoutTask(p), 30, TimeUnit.SECONDS)

首先,JDK的ScheduledExecutorService是一个大池子,多线程争抢并发锁。而上面的写法,TimeoutTask只属于当前的EventLoop,没有任何锁。

其次,如果发送成功,需要从长长Queue里找回任务来取消掉它。现在每个EventLoop一条Queue,明显长度只有原来的N分之一。

 

2.4 快速复习一下Netty的高性能线程池

Netty的线程池理念有点像ForkJoinPool,都不是一个线程大池子并发等待一条任务队列,而是每条线程自己一个任务队列。
不过Netty4的方法是建了N个只有一条线程的线程池,然后用前面说的选择器去选择。而曾经的Netty5 Alpha好像直接就用了ForkJoinPool。

而且Netty的线程,并不只是简单的阻塞地拉取任务,而是非常辛苦命的在每个循环同时做三件事情:

  • 先处理NIO的事件
  • 然后获取2.3里提到的本线程的定时任务,放到本线程的任务队列里
  • 再然后混合2.2里提到的其他线程提交给本线程的任务,一起执行

每个循环里处理NIO事件与其他任务的时间消耗比例,还能通过ioRatio变量来控制,默认是各占50%。

可见,Netty的线程根本没有阻塞等待任务的清闲日子,所以也不使用有锁的BlockingQueue如ArrayBlockingQueue来做任务队列了,而是直接使用下篇里提到的JCTools提供的无锁的MpscLinkedQueue(Mpsc 是Multiple Producer, Single Consumer的缩写)。

 

文章太长没人看,写到这里就停笔了。剩下内容请看 Netty高性能编程备忘录(下)

 

从dstat理解Linux性能监控体系

| Filed under 技术

聪明的同学在性能测试时,一边盯着监控一边自己在想:

“如果有200毫秒的CPU瞬时高峰,会被抓住么?”
“我再加上这个监控项,或者我让采样间隔再密一点,会影响性能么”

dstat用地球人都看得懂的python来写,而且只有寥寥数行,很适合从它入手,了解所有top, vmstat,pidstat们的工作原理,回答上面的问题。

 

1. dstat简介

系统性能的监控工具,我首选dstat,用过的同学也都喜欢,因为:

  • dstat可以认为是vmstat,iostat等的合体,不像vmstat还缺个网络流量数据。
  • dstat有良好的对齐和单位转换,不像vmstat一堆数字看到脖子都歪了。

安装

一般yum install dstat, 或者从官网下载最新版解压即用。

使用

我最喜欢的指令是 "dstat -tamp"

  • t: 时间
  • a: 一个缩写合集,包括CPU(-c), 磁盘IO(-d),网络流量(-n), Swap page in/out(-g), 系统的中断和上下文切换(-y)
    如果用bond0绑定了两块网卡,bond0与eth0+eth1会重复算,需要把值劈一半,或者用-N bond0 这样单拧出来。
    如果想监控不同磁盘,可以-D sda,sdb,total
  • m: 内存
  • p: 进程数 (在运行的,被阻塞的,新增的)

 

2.实现原理

dstat的地球人都看得懂的代码在此:

https://github.com/dagwieers/dstat/blob/master/dstat#L602

 

CPU信息

阅读第一段,关于CPU的采集插件,哦,原来完全是靠读取 /proc/stat 文件的数据。

$ cat /proc/stat
cpu 179165222 1067 67744298 9464596822 89694 31726 17296810 0 0
cpu0 13417559 30 4926156 385849929 39813 5 245082 0 0
cpu1 5972603 19 2536549 395562123 2953 0 70426 0 0
…..

关于CPU的几列数字分别是user,nice,system,idle,iowait....等状态的cpu时间统计,值是从开机到现在的累计值,单位是1/100秒。

顺便再瞄一下,/proc目录下还有/proc/[pid]/stat,那是每个进程的CPU统计。

既然/proc/stat 文件长这样,那top啊,mpstat, pidstat啊,CPU信息估计也是这么读出来的没跑了,《性能之巅》里用strace追踪vmstat也证实了这点。

 

回答问题时间

现在,第一个问题可以回答了,dstat每秒读取一下这个CPU累计值,然后减去前一秒的累计值,就得到这一秒内的平均值——所以200毫秒内的CPU高峰是抓不到的,只有平均值。

第二个问题,这监控的消耗大么?
首先,/proc目录是个伪文件系统,数据其实是在内存之中,只是通过文件形式来暴露,让你可以通过cat命令,或open file这类系统调用来读取(这种风格是Unix的基因,详见《Unix编程的艺术》),读取这么一下内存的消耗很低。

而且,dstat是一开始就打开了/proc/stat文件,不会每秒钟都重复打开。当然,像top,pidstat, 或者dstat里比如top-*插件,就会每次打开每个活动进程的stat文件,那消耗会大些。

通过pidstat的监控,dstat自身的消耗也就是一个CPU核的1%,pidstat也是一个核的1%,top略大,所以有人说压测时不要开top。

有些聪明的同学可能闪电般又有第三个问题,那这个/proc/stat文件什么时候更新?一秒一次么?

因为/proc是个伪文件系统,本质是API接口,所以并不存在“更新”这个概念,每次读取该文件时,调用sysconf(_SC_CLK_TCK)来获取,返回内存中的metrics值(或者如果需要实时统计的就统计一下)

 

/proc目录

关于/proc目录,linux自己已经有详细文档:http://man7.org/linux/man-pages/man5/proc.5.html,《性能之巅》4.2章 观测来源里也有描述。

我够懒的话,写到这里已经可以停笔了,大家自己去读它就可以了。为了凑字数,继续吧。。。。

 

3. 多余的话

其他系统级别信息

新增/运行/堵塞进程数量 ,系统上下文切换,中断次数在/proc/stat,其中新增进程是靠累计进程数的差值得来。

磁盘信息,都在/proc/diskstats, 包括所有iostats用到的详细信息, 详见Documentation/iostatts.txt

内存信息,都在/proc/meminfo,还包括默认没显示的dirty page cached 大小等。

Swap page in/out 信息,在/proc/vmstat

网络流量信息, 在/proc/net/dev

tcp socket状态的统计,在/proc/net/tcp

系统负载的统计(类似uptime),在/proc/loadavg
 

进程级别信息

每个进程的信息,留意下面几个:
/proc/[pid]/stat 与status: TOP看的进程信息多在这里, status对人类友好些。
/proc/[pid]/cmdline: 完整的命令行参数
/proc/[pid]/environ: 完整的实际生效的环境变量

 

线程级别的信息

/proc/[pid]/task/[tid]/stat,如果top 或 pidstat里选择显示进程信息,就会再把所有进程目录下的线程子目录一一打开,消耗会更大。

继续偷懒,直接看参考资料吧。

 
还是那句, 唯品会广州的基础架构部还在继续招人啊,简历请砸 calvin.xiao@vipshop.com

文章持续修订,转载请保留原链接:http://calvin1978.blogcn.com/articles/dstat.html

谈谈服务化体系中的异步(上)

| Filed under 技术

一个懂Akka、RxJava,看得懂《七周七并发》的人,和普通程序员完全是两个世界的人。
那作为一个羞涩的普通程序员,怎么在自己的服务化体系里,满足自己的异步化需求呢 ?我的思路是这样的:

1. 先认清自己的需求,不要一开始就站到某个技术上,再倒叙自己的需求。

2. 然后从浅入深,分析可以用什么样的技术满足该层次的需求,依然避免一下就冲到某个技术上,比如Akka入门到放弃。

3. 最后找出一条相对平稳的技术路线,能够贯穿各个层次的需求,而且最好不那么考验使用者的智商。

 

1. 需求篇

1.1 通过并行的远程调用,缩短总的响应延时。

1.1.1 远程调用分类

服务处理的过程中,总会包含如下几类的远程调用:

服务层:RESTful,RPC框架
数据层:JDBC,Memcached,Redis,ZooKeeper...
消息层:Kafka,RabbitMQ...

有些客户端已经支持异步,比如大部分的RPC框架、RESTful Client、SpyMemcached、ZooKeeper、Kafka 等。 在异步接口之中,又分返回Future 与 用户自定义的CallBack类两种风格。

但有些客户端还是同步的,比如JDBC,Jedis,在设计方案时需要把它们也考虑进去。

1.1.2 并行执行的机会有两种

一是服务提供者没有提供批量接口,比如商品查询,只能以不同的ID分次调用(题外话,始终服务方提供批量接口会更好些)。

一是两个调用之间没有依赖关系,包括参数依赖(一个调用的参数依赖另一个调用的返回结果),时间依赖(一个调用必需在另一个调用成功后执行)。

1.1.3 并行调用的复杂度分两个层次

一是简单并行,把所有可并行的服务一下子都撒出去,然后等待所有的异步调用返回,简单的Future.get()就够用。

还有一种场景是,先调一个异步接口,然后做一些同步的事情,再回头Future.get()拿之前异步调用的结果,这也达到了节省总响应时间的效果。

二是调用编排,比如一开始并行A、B、C、D服务,一旦A与B返回则根据结果调用E,一旦C返回则调用F,最后组装D、E、F的结果返回给客户端。

如果还是简单的并行,没办法做到最高效的调度,必须有一种机制,支持这种多条异步调用链分头并进的场景。此时就需要或Akka,或RxJava,或JDK8的CompletableFuture与Guava的ListenenableFuture,基于Actor,Rx or Callback机制来编排了。再后来,发现其实依然使用Future.get()多起几条线程也一样能做,不要先被某个技术迷住。

 

1.2 希望能用少量的固定线程,处理海量的并发请求。

这个概念并不陌生,NIO就是这个思路。

但是即使你用了Netty的NIO 或 Servlet3.0的异步Servlet 或,也只是解决了传输层面用少量传输线程处理海量并发的传输,并在入口层的编程模式上提供了异步化的可能性。

如果你的服务线程里存在阻塞的远程调用,那线程还是会等待在远程调用上而无法处理海量请求,即使异步化如Future.get(),也依然是等待。

所以,你必须有办法在阻塞等待时把线程给交回去,比如服务线程里采用全异步化的Callback模式,或引入Akka的Actor模式,或基于Quasar引入纤程协程的概念。

 

1.3 小结

在我看来 , 客户端简单并行->客户端并行编排->服务端少量线程处理海量请求。对于大部分普通项目,是由浅入深三个层次的需求。
 

2.第一层,简单并行实现篇

2.1 最简单写法

最简单就是并发的调用一堆返回Future的异步接口,再一个个Get回来,最慢的那个会阻塞住其他:

Future<Product> productFuture = productService.query(id);
Future<Long> stockFuture = stockService.query(id);
.......
Product product = productFuture.get();
Long stock = stockFuture.get();

HttpClient有一个HttpAsyncClient 的子项目提供Http的异步访问:

HttpGet request1 = new HttpGet("http://www.apache.org/");
Future<HttpResponse> future = httpclient.execute(request1, null);
HttpResponse response1 = future.get();

Spring的RestTemplate同样有AsyncRestTemplate:

Future<ResponseEntity<String>> futureEntity = template.getForEntity("http://www.apache.org/" , String.class);
ResponseEntity<String> entity = futureEntity.get();

其他类似的不一一列举。

 

2. 异步接口只有Callback形式时

但如果异步接口只能设置自定义Callback函数,不返回Future呢? 比如Thrift就是这样。

Callback函数在这种并行调用场景里并不好用,因此建议还是要转换回Future接口使用。

转换的方法很简单,自己实现一个默认的Callback类,里面包含一个Future,然后实现Callback接口所定义的onSucess()和onFail()函数,将结果或异常赋值到Future中。

DefaultFutureCallback<Long> callback=new DefaultFutureCallback<Long>();
myService.getPrice(1L, callback);
String result = callback.getFuture().get();

至于Future类的实现,随便抄个HttpClient的BasicFuture就好了。

题外话,Future.get() 接口只声明了ExecutionException一种异常,如果你原来的callback函数里有其他不是Runtime Exception的,就要裹在ExecutionException里了,用户还要自己getCause()把它找出来,唯一不方便的地方。

 

3. 对付同步接口

同步接口如JDBC与Jedis,只能在调用它们的地方实现Callable接口,异步的跑在另一个线程池里来完成并行调度。但这其实引入了线程调度的消耗,不得而为之,不可滥用。

Future<Product> future = executor.submit(new Callable<Product>(){
  @Override
  public Product call() throws Exception {
    return productDao.query(id);
  }
});

题外话,executor中返回的Future的实现类叫FutureTask,它能以“Callable 或 Runnable+预设结果(因为Runnalbe自身没结果)“作为参数构建。executor.submit() 接受“Callable 或 Runnable+预设结果” 做参数,内部构建这个FutureTask。但FutureTask本身又是个Runnable,网上有些例子让大家自己在外部把Callable构造成FutureTask,再以Runnable的身份传给executor,其实不好,也没必要。

如果是JDK8,用Lambda就可以写得短一些,只要一行,和平时同步写法也差不多了。

Future<Product> future = executor.submit(()->productDao.query(id));

最后,同步改异步后,原来存在ThreadLocal中的东西如TraceId就没有了,要用一个Context之类的在进出的时候复制。
 
《浅入浅出,谈谈服务化体系中的异步(上)》,转载请保留链接

另一份Java应用调优指南之-工具篇

| Filed under 技术

Java应用的调优,再不写都要忘光了,先对付着写完,免费的JMC真的好用,大家越早用上越好。

前一篇是三个月前的 另一份Java应用调优指南 - 前菜

 

1. 土法调优两大件

先忆苦思甜,一般人在没有Profile工具的时候,调优的两大件,无非Heap Dump 与 Thread Dump。

 

1.1 Heap Dump

jmap -dump:live,format=b,file=heap.hprof pid

从安全点日志看,从Heap Dump开始,整个JVM都是停顿的,考虑到IO(虽是写到Page Cache,但或许会遇到background flush),几G的Heap可能产生几秒的停顿,在生产环境上执行时谨慎再谨慎。

live的选项,实际上是产生一次Full GC来保证只看还存活的对象。有时候也会故意不加live选项,看历史对象。

Dump出来的文件建议用JDK自带的VisualVM或Eclipse的MAT插件打开,对象的大小有两种统计方式:

  • 本身大小(Shallow Size):对象本来的大小。
  • 保留大小(Retained Size): 当前对象大小 + 当前对象直接或间接引用到的对象的大小总和。

看本身大小时,占大头的都是char[] ,byte[]之类的,没什么意思(用jmap -histo:live pid 看的也是本身大小)。所以需要关心的是保留大小比较大的对象,看谁在引用这些char[], byte[]。

(MAT能看的信息更多,但VisualVM胜在JVM自带,用法如下:命令行输入jvisualvm,文件->装入->堆Dump->检查 -> 查找20保留大小最大的对象,就会触发保留大小的计算,然后就可以类视图里浏览,按保留大小排序了)
 

1.2 Thread Dump

ThreadDump 同样会造成JVM停顿,在生产系统上执行要谨慎。

可以命令行方式执行它,"jstack pid” 或"jstack -l pid" ,其中-l 会同时打印各种lock,但会使得JVM停顿得长久得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),-l 建议不要用。

另一种是直接用代码来打印,比如线程池满了无法添加新任务,在开发或性能测试模式下,可以在代码里捕捉该异常后直接把当前线程池的情况打印出来。

ThreadMXBean threadMBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMBean.dumpAllThreads(false, false);

同样注意,这里threadMBean.dumpAllThreads(false,false)的参数为false,把参数改为true,则打印synchronizers与monitor,同样使得JVM停顿久很多。

线程状态:

  • RUNNABLE: 运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
  • BLOCKED:被某个锁(synchronizers)給block住了。
  • WAITING:等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。
  • TIME_WAITING:和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。

分析工具:

 

2. 你真正要的Java Mission Control

如果你使用过JProfiler,Yourkit,VisualVM还有Eclipse的Profiler插件等一堆Profiler工具,或者用JavaSimion等在代码里打印过metrics,最后会发现免费的JMC才是你想要的。
 

2.1 优点

代替收费的JProfiler的好东西,以前Bea JRockit的宝贝,现在随着JDK7 up40以后的版本免费自带了,不过只在开发环境免费,就是说理论上不能拿它来连生产环境的机器。

另一个让人开心的事情就是JMC采用采样,而不是传统的代码植入的技术,对应用性能的影响非常非常小,完全可以开着JMC来做压测(唯一影响可能是full gc多了,减少一些监控项看看)。不会像以前,开了代码植入型的Profiler,出来的性能测试结果差了一个数量级不说,热点完全可能是错误的,这是一个真实的故事,具体细节就不说了。
 

2.2 功能

JMC里可以看的东西太多了,自己觉得最有用的如下:

  • 内存Tab:分配Tab里的按类、按线程、对象的创建调用栈来归类的对象创建情况,让对象创建无处躲藏。
  • 内存Tab:GC Tab的GC详细情况,以及TLAB外的分配情况(每条线程在Heap里分了一个Thread Local Area,在TLAB里的内存分配不需要线程竞争,所以TLAB之外的分配是不好的)
  • 代码Tab:热点方法类及它的调用栈,超有用的功能。调用树是从线程角度看的方法调用,而按包名分类可以看3PP包的问题。
  • 线程Tab:热点线程,再换个姿势来看热点方法和调用树。
  • 线程Tab:争用,等待时间,锁定实例等。

 

2.3 使用方法简述

JDK7在启动服务时加上-XX:+UnlockCommercialFeatures -XX:+FlightRecorder 。
如果是远程服务器,要开JMX:

“-Dcom.sun.management.jmxremote.port=7001 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=your ip”

JDK自带的jmc命令,文件->连接->设定JMX连接,启动飞行纪录,固定时间选1分钟或更多,事件设置选为profiling,然后进一步修改,自己查看下都Profile了哪些信息,觉得不够的再添加些(下次就直接用上次设定就好了),比如:

  • 加上对象数量的统计:Java Virtual Machine->GC->Detail->Object Count/Object Count after GC
  • 方法调用采样的间隔从10ms改为1ms(但不能低于1ms,否则会影响性能了): Java Virtual Machine->Profiling下的两个选项
  • Socket与File采样, 10ms太久,但即使改为1ms也未必能抓住什么,可以干脆取消掉: Java Application->File Read/FileWrite/Socket Read/Socket Write

然后就开始Profile,到时间后Profile结束,会自动把记录下载回来,在JMC中展示。

其他资料:

 

3. BTrace

神器,在生产环境上,动态监控某些方法的执行时长及其他信息,不再需要自己手工打日志,发版本,部署与重启服务。

据说淘宝就是经常开着BTrace在线上找问题的,我们最近也在生产上试了几把,太爽利了。

使用方法网上一搜大把,就不重复了。

原理就是自己写一个类似AspectJ的,希望监控哪个方法,监控后做什么动作的脚本,然后动态执行btrace命令将这个脚本attach到某个JVM上就行

 
这是一篇严肃的文章,就不配图了。

我的Java后端书架 (2016年暮春3.0版)

| Filed under 技术

书架主要针对Java后端开发。

3.0版把一些后来买的、看的书添补进来,又或删掉或降级一些后来没有再翻开过的书。

更偏爱那些能用简短流畅的话,把少壮不努力的程序员所需的基础补回来的薄书,而有些教课书可能很著名,但干涩枯燥,喋喋不休的把你带回到大学课堂上昏昏欲睡,不录。
 

1. 操作系统与网络的书

《Linux内核设计与实现 第3版》
Robert Love用最薄的篇幅,顺畅的文字将Linux内核主要的算法讲清楚了,《深入理解Linux内核》《深入Linux内核架构》之类厚厚的全是代码,不是专门的内核程序员看这本足够了。

《Linux系统编程 第2版》
继续是Robert Love,比起APUE也是以薄见长,专门针对重要的系统调用讲解。

《性能之巅》
操作系统的性能调优、监控、工具和方法莫道不消魂论,看这本就够了,已经足够厚,可能是书单里最厚的一本。

《TCP/IP详解 卷1:协议》
这么多年过去了,TCP的书好像主要还是只有这一本,有点旧了,看了也还是半懂不懂的。后人在2011年写了第二版,机械工业正在翻译。

《WireShark网络分析就这么简单》《WireShark网络分析的艺术》 new!
多少人,是看了这两本轻松又实战的书,才真正理解TCP的细节。

PS:《UNIX环境高级编程》《UNIX网络编程》,APUE和UNP更多作为一本超厚工具书存在。《Unix 编程艺术》,扯的都是闲篇,厚厚的一本其实略读一下就行。 《现代操作系统 第3版》如果看LKD未尽兴,可以回头看看这本基础概念,感觉比那本枯燥的《操作系统概念》(恐龙书)读起来舒服。

《TCP/IP指南》 前面wireshark书作者的推荐,网上有英文免费版,然后有中文版的卷1卷2,但可能那么多章节那么厚你只关心TCP和HTTP两部分。《HTTP权威指南》,同样是自己从厚厚的目录里挑选感兴趣的章节来看。另外,那些日本韩国人写的《图解XXX》感觉都不喜欢,真的不行。

 

2. 算法的书

《数据结构与算法分析-Java语言描述 第3版》
够薄,数据结构与算法分析的点基本都涵盖了,而且喜欢它的示例代码是Java写的,新出了第3版。

《算法 第4版》
可与上一本对比着读,厚一些,也多些图,但知识点没上面的全,也是Java的。

PS: 《数学之美》《编程珠玑》,都是专栏文章,讲得并不系统,可以当兴趣读物来看。
《算法设计与分析基础 第3版》数学系偏爱无比枯燥很多公式的《算法导论》, 计算机系喜欢这本实用主义的典型。

 

3. 架构设计的书

《软件系统架构:使用视点和视角与利益相关者合作 第2版》
也是教科书,最难得的是,这本老书在十年后的去年升级了第二版,所以感觉鲜活了好多,也许是最鲜活的一本架构书。

《恰如其分的软件架构 - 风险驱动的设计方法》
由于人类与生俱来的惰性,计算机原本科学的、精准的设计方式,有了敏捷的借口之后就很难再维持了。本书就是在这种背景下,提出由风险来决定设计的度。除了开始的风险驱动部分,其余部分就是规规矩矩标标准准的架构师教科书。

《发布!软件的设计与部署 - Release It!: Design and Deploy Production-Ready Software 》
关于高可靠性的软件,学校里不会教,出来社会却要面对的那部分,英文的原标题更清晰。

《大型网站技术架构:核心原理与案例分析》
淘宝出品,大型互联网站的科普入门书。

《高扩展性网站的50条原则》 new!
同是入门级读物,如果还有个高可用50条原则,那就齐了。

《微服务设计》 new!
那么多微服务的书,还是这本比较不像赚快钱的。

《大数据日知录》
前几年参加各种技术会议,CAP,最终一致性,RWN,向量时钟,Paxos,一致性哈希,Gossip什么的能灌你一耳朵。而现在,你只要在家安安静静的看书就够了。不过这个领域发展太快,又一年过去了,期望它可以持续出新版。

PS: 关于设计模式,我以前曾经有过很多很多本,GOF23啦,企业应用架构模式啦,EIP啦, POSA 5卷本啦,反模式啦,JavaEE/SOA/Restful的模式啦。但现在觉得对新人来说,一本Java写的《Head First 设计模式》,知道什么叫设计模式就够了。

《程序员必读之软件架构》作者维护着codingthearchitecture.com 。不过中文书名叫“必读”有点过。

 

4. 语言的书

《Java并发编程实战》
Java并发经典,人手一本不用多说了。

《实战Java高并发程序设计》 new!
国人新作,流畅易读,内容也比上面一本来得新。

《深入理解 Java 虚拟机 第2版》
理解虚拟机并不是那么难,Java程序员来说,很多知识其实是必须的。另外还有几本类似主题的书,忽然一下子都出来了。

《Java性能权威指南》 new!
比起多年前那部调优圣经,讲得更加深入,也更加贴近现在的JDK。可以从里面挑些知识点来,做Java调优的面试题。

《有效的单元测试》 new!
不同于那些动辄BDD的高深书籍,专注于如何写“好”的,可维护的单元测试,拿来给团队看,能省很多口水。

《七周七语言》
《七周七X》系列的开山之作,可能也是最好的一本。

PS:《Effective Java》外界一致推崇,但有点太过誉了。另外《Thinking in Java》有点旧了,而且作者思路随意,译者语言晦涩,新程序员还是建议同时再看两卷《Java核心技术 - Core Java》

 

5. 具体技术的书

《Docker: 容器与容器云》
这本书叫Docker一本就够了,的确够了,在那些Docker操作指南书之上。不想着改Docker代码的看它就够了,别想着什么《Docker源码分析》。

《Redis设计与实现分析》
用Redis的工程师桌面必备吧。

《分布式服务框架:原理与实践》 new!
如果要写一个SOA框架,要留意的方方面面。

《Spark技术内幕》
深度与厚度之间,选了这本200页的薄书,一样有很多的原理与代码解释,但不会像有的书那样贴20行代码只写一行字。

《Netty权威指南 第2版》
虽然网上的吐槽较多,但Netty 快速入门也只有这一本了。

 

6. 程序员的自我修养

PS. 最近没买什么新书,随便说点旧书:

《程序员修佳节又重阳炼之道-从小工到专家》,Pragmatic Programmer-注重实效的程序员开山之作,翻译的马达维文笔也和熊节一样好。

《代码整洁之道》和 《程序员的职业素养》,英文名是很相近的《Clean Code》和 《Clean Coder》,应该接替《代码大全2》成为必看的系列,因为后者太厚了,而且也有不少过时的东西,要自己去过滤。

《重构》很厚,但最有价值就是前面几章的洗东篱把酒黄昏后脑篇,具体实作不如薄薄的《重构手册》

关于敏捷的书,最开始的那本《解析极限编程--拥抱变化》就很好,再随便找本Scrum的流程看看就够了,《敏捷开发的艺术》也不错。

《布道之道 - Driving Technical Change:Why People on Your Team Don't Act on Good Ideas,and How to Convince Them They Should》,经常在组织里推行新技术的同学可以看下,七种怀疑论者模式,脑海中一幅幅熟悉的面孔。

PS. 温伯格的书网上很推崇,《成为技术领佳节又重阳导者》之类的,但我觉得年代太远,读起来其实没多大意思,一两个鸡汤观点还要自己从书里慢慢淘,有那功夫不如看点别的。

 

7. 没有覆盖到的内容

数据库如MySQL,我们DBA太专业,自己没机会搞。

欢迎大家在评论里补充。

文章持续修订,转载请保留原链接: http://calvin1978.blogcn.com/articles/java-bookshelf.html

 

by calvin | tags : | 21

另一份Java应用调优指南之-前菜

| Filed under 工作 技术

每一次成功的调优,都会诞生又一份的调优指南。

一些必须写在前面的军规,虽然与Java应用的调优没直接关联,但是测试同学经常不留神的地方,导致应用的优化变成一场测试环境调优的戏码。
 

1 独占你的测试机器

包括跑JMeter的那些机器。

"top"或者"pidstat 1" 看一下,其他的路人甲乙丙丁的应用都关干净了没。

如果是云主机,确保更多的占有宿主机的资源,比如深夜大家下班了你在家连VPN回来跑。

 

2 了解你的测试机器

必须完完全全的了解你的机器,才知道有没卡在某个瓶颈,或者与线上环境、其他测试结果的比较。

还是那句, 包括跑JMeter的那些机器。

 

2.1 CPU

"cat /proc/cpuinfo", 看最后一条就好,比如

processor : 23
model name : Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz
physical id : 1
cpu cores : 6

所有数字都从零开始,physical id:1即两颗cpu, cpu core: 6即6核,processor : 23即24个处理器。

2 CPU * 6 Core * 2HT(Intel超线程技术) = 24 Processor

不过也有很多同事喜欢说24核,也懒得纠正了。

 

2.2 内存

"free -g" 没什么好说的。

 

2.3 硬盘

  • 查看大小、分区、文件系统类型: "df -hT"
  • 硬盘是否SCSI:/dev/sdX就是scsi的,hdX就是普通的。
  • 硬盘是否SSD : "cat /sys/block/sda/queue/rotational", 0是SSD,1是传统硬盘,但也不一定灵

普通硬盘的写速度大概100M/s,RAID级别的查看不方便,SSD的速度也不定,所以用dd测一下最靠谱:

dd if=/dev/zero of=dd.file bs=8k count=128k conv=fdatasync
dd if=/dev/zero of=./dd.file bs=1G count=1 conv=fdatasync

上面命令测试了分别以每次8k和1g的大小,写入1g文件的速度。

  • if:输入文件名, /dev/zero 设备无穷尽地提供0
  • of:输出文件名
  • bs:块大小
  • count:次数
  • conv=fdatasync :实际写盘,而不是写入Page Cache

硬盘读速度的测试同理,不过要先清理缓存,否则直接从Page Cache读了。

sh -c "sync && echo 3 > /proc/sys/vm/drop_caches”
dd if=./dd.file of=/dev/null bs=8k

 

2.4 网卡

先用ifconfig看看有多少块网卡和bonding。bonding是个很棒的东西,可以把多块网卡绑起来,突破单块网卡的带宽限制。

然后检查每块网卡的速度,比如"ethtool eth0"。

再检查bonding,比如"cat /proc/net/bonding/bond0", 留意其Bonding Mode是负载均衡的,再留意其捆佳节又重阳绑的网卡的速度。

最后检查测试客户机与服务机之间的带宽,先简单ping或traceroute 一下得到RTT时间,iperf之类的可稍后。

 

2.5 操作系统

Linux的内核版本,是否64位: "uname -a"
Redhat/CentOS版本 : "cat /etc/redhat-release"

 

3. 布置好你的机器状态采集工具

实时观察的,我喜欢dstat,详见《从dstat理解Linux性能监控体系》比vmstat,iostat, sar们都好用,起码对得够齐,单位能自动转换。不过dstat需要安装(yum install dstat,或者去它的网站下载解压即用版)

  • dstat -tamp:推荐,打印时间戳,比默认打印多一个memory信息,一个有否进程在block状态的信息
  • dstat -tamN bond0,lo: 如果有bonding,dstat会把bond0和eth0 有时会算双份,还有lo的也算到总量里,所以先用-N指定网卡检查下。

参考资料:后台性能测试不可不知的二三事

 

4. JMeter的调优顶一半的事

JMeter的版本越新越好。

 

4.1 JMeter的JVM参数

它默认连个垃圾收集算法都没有配,对延时要求高的,必须配上CMS或G1,内存也整大点降低GC的频率。其他的,给Server配的啥参数,给JMeter也来上一份。如果想动态改的,不写死在脚本里,可以配置环境变量$JVM_ARGS

 

4.2 测试计划的编写

什么if 语句,以及所有其实用动态语言来实现的都挺慢的。
xPath抽取结果集里的字段之类看着就慢的也别写了。
别加任何监瑞脑消金兽听器和图形。
再配置输出日志的格式,能不要的列都别要了,最极端的其实就延时这列有用。

 

4.3 JMeter的运行

在Linux上用命令行跑,别偷懒用Window开着界面跑。
别开超过200条线程(虚拟机上更少)。
可以在不同机器上起多个JMeter,用集群汇总的模式。

 

4.4 结果的统计

初始连接,Server端热身,JVM编译热点方法等都需要时间,所以建议统计前删掉前面的一些日志。

要配置一下才能看到99.9%, 99.99% 分位数的延时,另外因为之前输出日志时省略了很多列,导入日志的时候配置也要如此。

但如果不能XWindows Forward,还要把日志下载回来再导入本地的JMeter,那还不如自己动动手,用sed, awk, sort配合一下自己写个分析延时的脚本直接在服务器上运行。

 

5. 其他被依赖的节点

比如所依赖的数据库够不够快,Restful API的模拟器够不够快,负载均衡器如HAProxy优化配置了没有。

 
唠叨完这些写给测试同学的话,下篇,就可以正式开始调优了。

更重要的是,唯品会广州的基础架构部还在继续招人啊,简历请砸 calvin.xiao@vipshop.com

另一份Java应用调优指南之前菜 by 江南白衣 转载请保持链接。

备注: pidstat/iostat要用yum install sysstat来装。

StringBuilder在高性能场景下的正确用法

| Filed under 技术

关于StringBuilder,一般同学只简单记住了,字符串拼接要用StringBuilder,不要用+,也不要用StringBuffer,然后性能就是最好的了,真的吗吗吗吗?

还有些同学,还听过三句似是而非的经验:

1. Java编译优化后+和StringBuilder的效果一样;

2. StringBuilder不是线程安全的,为了“安全”起见最好还是用StringBuffer;

3. 永远不要自己拼接日志信息的字符串,交给slf4j来。

 

1. 初始长度好重要,值得说四次。

StringBuilder的内部有一个char[], 不断的append()就是不断的往char[]里填东西的过程。

new StringBuilder() 时char[]的默认长度是16,然后,如果要append第17个字符,怎么办?

用System.arraycopy成倍复制扩容!!!!

这样一来有数组拷贝的成本,二来原来的char[]也白白浪费了要被GC掉。可以想见,一个129字符长度的字符串,经过了16,32,64, 128四次的复制和丢弃,合共申请了496字符的数组,在高性能场景下,这几乎不能忍。

所以,合理设置一个初始值多重要。

但如果我实在估算不好呢?多估一点点好了,只要字符串最后大于16,就算浪费一点点,也比成倍的扩容好。

 

2. Liferay的StringBundler类

Liferay的StringBundler类提供了另一个长度设置的思路,它在append()的时候,不急着往char[]里塞东西,而是先拿一个String[]把它们都存起来,到了最后才把所有String的length加起来,构造一个合理长度的StringBuilder。

 

3. 但,还是浪费了一倍的char[]

浪费发生在最后一步,StringBuilder.toString()

//创建拷贝, 不共享数组
return new String(value, 0, count);

String的构造函数会用 System.arraycopy()复制一把传入的char[]来保证安全性不可变性,如果故事就这样结束,StringBuilder里的char[]还是被白白牺牲了。

为了不浪费这些char[],一种方法是用Unsafe之类的各种黑科技,绕过构造函数直接给String的char[]属性赋值,但很少人这样做。

另一个靠谱一些的办法就是重用StringBuilder。而重用,还解决了前面的长度设置问题,因为即使一开始估算不准,多扩容几次之后也够了。

 

4. 重用StringBuilder

这个做法来源于JDK里的BigDecimal类(没事看看JDK代码多重要),后来发现Netty也同样使用。SpringSide里将代码提取成StringBuilderHolder,里面只有一个函数

public StringBuilder getStringBuilder() {
sb.setLength(0);
return sb;
}

StringBuilder.setLength()函数只重置它的count指针,而char[]则会继续重用,而toString()时会把当前的count指针也作为参数传给String的构造函数,所以不用担心把超过新内容大小的旧内容也传进去了。可见,StringBuilder是完全可以被重用的。

为了避免并发冲突,这个Holder一般设为ThreadLocal,标准写法见BigDecimal或StringBuilderHolder的注释

不过,如果String的长度不大,那从ThreadLocal里取一次值的代价还更大的多,所以也不能把这个ThreadLocalStringBuilder搞出来后,见到StringBuilder就替换。。。
 

5. + 与 StringBuilder

String s = “hello ” + user.getName();

这一句经过javac编译后的效果,的确等价于使用StringBuilder,但没有设定长度。

String s = new StringBuilder().append(“hello”).append(user.getName());

但是,如果像下面这样:

String s = “hello ”;
// 隔了其他一些语句
s = s + user.getName();

每一条语句,都会生成一个新的StringBuilder,这里就有了两个StringBuilder,性能就完全不一样了。如果是在循环体里s+=i; 就更加多得没谱。

据R大说,努力的JVM工程师们在运行优化阶段, 根据+XX:+OptimizeStringConcat(JDK7u40后默认打开),把相邻的(中间没隔着控制语句) StringBuilder合成一个,也会努力的猜长度。

所以,保险起见还是继续自己用StringBuilder并设定长度好了。

 

6. StringBuffer 与 StringBuilder

StringBuffer与StringBuilder都是继承于AbstractStringBuilder,唯一的区别就是StringBuffer的函数上都有synchronized关键字。

那些说StringBuffer “安全”的同学,其实你几时看过几个线程轮流append一个StringBuffer的情况???

 

7. 永远把日志的字符串拼接交给slf4j??

logger.info("Hello {}", user.getName());

对于不知道要不要输出的日志,交给slf4j在真的需要输出时才去拼接的确能省节约成本。

但对于一定要输出的日志,直接自己用StringBuilder拼接更快。因为看看slf4j的实现,实际上就是不断的indexof("{}"), 不断的subString(),再不断的用StringBuilder拼起来而已,没有银弹。

PS. slf4j中的StringBuilder在原始Message之外预留了50个字符,如果可变参数加起来长过50字符还是得复制扩容......而且StringBuilder也没有重用。

 

8. 小结

StringBuilder默认的写法,会为129长度的字符串拼接,合共申请625字符的数组。所以高性能的场景下,永远要考虑用一个ThreadLocal 可重用的StringBuilder。而且重用之后,就不用再玩猜长度的游戏了。当然,如果字符串只有一百几十字节,也不一定要考虑重用,设好初始值就好。

一个简单的StringBuilder,不小心就写了这么多,所以呢,唯品会基础架构部广州招人啊, calvin.xiao@vipshop.com ~~~~

《StringBuilder在高性能场景下的正确用法》 by 江南白衣 ,转载请保留链接。

Java ThreadPool的正确打开方式

| Filed under 技术

线程池应对于突然增大、来不及处理的请求,无非两种应对方式:

  1. 将未完成的请求放在队列里等待
  2. 临时增加处理线程,等高峰回落后再结束临时线程

JDK的Executors.newFixedPool() 和newCachedPool(),分别使用了这两种方式。

不过,这俩函数在方便之余,也屏蔽了ThreadPool原本多样的配置,对一些不求甚解的码农来说,就错过了一些更适合自己项目的选择。

 

1. ThreadPoolExecutor的原理

经典书《Java Concurrency in Pratice(Java并发编程实战)》的第8章,浓缩如下:

1. 每次提交任务时,如果线程数还没达到coreSize就创建新线程并绑定该任务。
所以第coreSize次提交任务后线程总数必达到coreSize,不会重用之前的空闲线程。
在生产环境,为了避免首次调用超时,可以调用executor.prestartCoreThread()预创建所有core线程,避免来一个创一个带来首次调用慢的问题。

2. 线程数达到coreSize后,新增的任务就放到工作队列里,而线程池里的线程则努力的使用take()阻塞地从工作队列里拉活来干。

3. 如果队列是个有界队列,又如果线程池里的线程不能及时将任务取走,工作队列可能会满掉,插入任务就会失败,此时线程池就会紧急的再创建新的临时线程来补救。

4. 临时线程使用poll(keepAliveTime,timeUnit)来从工作队列拉活,如果时候到了仍然两手空空没拉到活,表明它太闲了,就会被解雇掉。

5. 如果core线程数+临时线程数 >maxSize,则不能再创建新的临时线程了,转头执行RejectExecutionHanlder。默认的AbortPolicy抛RejectedExecutionException异常,其他选择包括静默放弃当前任务(Discard),放弃工作队列里最老的任务(DisacardOldest),或由主线程来直接执行(CallerRuns),或你自己发挥想象力写的一个。

 

2. FixedPool 与 CachedPool

FixedPool默认用了一条无有暗香盈袖界的工作队列 LinkedBlockingQueue, 所以只去到上面的第2步就不会继续往下走了,coreSize的线程做不完的任务不断堆积到无限长的Queue中。
所以只有coreSize一个参数,其他maxSize,keepAliveTime,RejectHandler的配置都不会实际生效。

CachedPool则把coreSize设成0,然后选用了一种特殊的Queue -- SynchronousQueue,只要当前没有空闲线程,Queue就会立刻报插入失败,让线程池增加新的临时线程,默认的KeepAliveTime是1分钟,而且maxSize是整型的最大值,也就是说只要有干不完的活,都会无限增增加线程数,直到高峰过去线程数才会回落。

 

3. 对FixedPool的进一步配置

3.1 设置QueueSize

如果不想搞一条无限长的Queue,避免任务无限等待显得像假死,同时占用太多内存,可能会把它换成一条有界的ArrayBlockingQueue,那就要同时关注一下这条队列满了之后的场景,选择正确的rejectHanlder。

此时,最好还是把maxSize设为coreSize一样的值,不把临时线程及其keepAlive时间拉进来,Queue+临时线程两者结合听是好听,但很难设置好。

3.2 有界队列选LinkedBlockingQueue 还是ArrayBlockingQueue?

按Executors的JavaDoc上说是ArrayBlockingQueue,起码ArrayBlockingQueue每插入一个Runnable就直接放到内部的数组里,而LinkedBlockingQueue则要 new Node(runnable),无疑会产生更多对象。而性能方面有兴趣的同学可以自己测一下。

allowCoreThreadTimeOut(true)

允许core线程也在完全没流量时收缩到0,但因为JDK的算法,只要当前线程数低于core,请求一来就会创建线程,不管现在有没有空闲的线程能服务这个请求,所以这个选项的作用有限,仅在完全没流量时有效。 但都完全没流量了,怎么滴其实也没所谓了。除非是同时有很多个线程池的情况。

 

4. 对CachedPool的进一步配置

4.1 设置coreSize

coreSize默认为0,但很多时候也希望是一个类似FixedPool的固定值,能处理大部分的情况,不要有太多加加减减的波动,等待和消耗的精力。

4.2 设置maxSize及rejectHandler

同理,maxSize默认是整形最大值,但太多的线程也很可能让系统崩溃,所以建议还是设一下maxSize和rejectHandler。

4.3 设置keepAliveTime

默认1分钟,可以根据项目再设置一把。

4.4 SynchronousQueue的性能?

高并发下,SynchronousQueue的性能绝对比LinkedBlockingQueue/ArrayBlockingQueue低一大截。虽然JDK6的实现号称比JDK5的改进很多,但还是慢,据文章说只在20线程并发下它才是快的。

 

5. SpringSide的ThreadPoolBuilder

广告时间,SpringSide的ThreadPoolBuilder能简化上述的配置。

此文太科普太水,主要就是为了帮SpringSide-Utils项目打广告:)

AWK处理日志入门

| Filed under 技术

前言

这两天自己挽起袖子处理日志,终于把AWK给入门了。其实AWK的基本使用,学起来也就半天的时间,之前总是靠同事代劳,惰性呀。

此文仅为菜鸟入门,运维们请勿围观。

下面是被处理的日志的示例,不那么标准,但不标准的日志正是标准的情况。

[2015-08-20 10:00:55.600] - [192.168.0.73/192.168.0.75:1080 com.vip.xxx.MyService_2.0 0 106046 100346 90ms 110ms]

 

基本语句

最基本的语句,以空格做分割,提取所需的列:

awk '{print $0,$1,$2,$(NF-1),$NF,$NF-$(NF-1)}’ access.log

 

1. 输入

AWK是针对文件或管道中每行输入的处理语言。所以也可以从管道输入:

grep “xxx” access.log | awk '{print $1}’

但下面这样写就会成为一个Linux老梗的主角,awk不需要不需要cat的。

cat access.log | awk '{print $1}'

2.语句定义

可以快速的用单引号’ ’,把所有语句写成一行。

也可以用-f 指定文件,文件里可以任意换行,增加可读性和重用性。

所有执行语句用{}括起来,{}的外面是一些高级的东西比如过滤条件,见后。

另外如果打印整列,{print} 可省略不写。

3. 列引用

$0代表整行所有数据,$1代表第一列(终于不是程序员数数从0开始了)。

NF是个代表总列数的系统变量,所以$NF代表最后一列,还支持$(NF-1)来表示倒数第二列。

还支持列之间的运算,如$NF-$(NF-1)是最后两列的值相减。

只写一个print 是 print $0的简写,打印整行所有数据。

4. 输入的列分隔符

默认以空格做分割符,也可以重新指定,下例指定了':'

awk -F ':' '{print $1,$2}’ access.log

也可以正则表达式定义多个分割符,下例指定了 '-' 和 ':'

awk -F '[-:]' '{print $1,$2}’ access.log

5. 输出的列间隔

print $1,$2 中间的','逗号,代表打印时第1与第2列之间使用默认分隔符号也就是空格,也可以用” ”来定义其他任意的字符:

awk '{print $1 "\t" $2 " - " $3$4xxxxx$5}’ access.log

上例,在第1第2列之间用 tab 分隔,第2第3列之间用" - "分隔,

也可以什么都不写代表中间没分隔,比如第3第4列之间,或者乱写一些字符没用" "括起来,也等于没写,比如第4第5列之间。

 

数字类型,字符串类型

虽然上例最后两列的值是字符串类型的,带着ms字样,看起来不能做算术运算。

但其实两个列相减时,AWK就会神奇地把它们转换为纯数字。同样,做累计的时候,sum=sum+$NF,也能自动转换为数字。

如果想对某个字符列比较是否大于阀值,先把它转回数字就行了,上一篇文章里的

sed "s|ms]||g" access.log | awk ' $NF>100'

其实可以简写成下面的样子,性能还比使用sed略快,:

awk ' $NF*1>100’ access.log

awk ' int($NF)>100’ access.log

 

BEGIN与END语句

BEGIN与END后的语句定义在处理全部文本内容之前与之后的语句。

1.计算累计值和平均值

awk '{sum+=$NF} END {print sum, sum/NR}'

上例对每行输入内容进行最后一列的值的累计,而END后的语句,打印累计结果 和平均值,NR是系统变量代表总行数。

2.打印表头

还可以定义BEGIN语句打印表头,定义变量什么的。

awk 'BEGIN{print "Date\t\tTime\t\tCost”} {print $1 "\t"$2 "\t" $NF}’ access.log

上例表头用两个制表符分隔,内容则用一个制表符分隔,有良好的对齐效果。
 

过滤行

1. 简单字符匹配

先用grep过滤也是可以的,也可以用awk简单在执行语句之外的/ /之间定义正则表达式

awk '/192.168.0.4[1-5]/ {print $1}’ access.log

等价于

grep "192.168.0.4[1-5]” access.log| awk ‘{print $1}

2. 针对某一列的字符匹配

针对第4列的地址段匹配,~ 是字符匹配,!~则是不匹配的意思。

awk '$4 ~ /192.168.0.4[1-5]/'

3. 针对数值的过滤

支持==, !=, <, >, <=, >=

awk '$(NF-1)*1==100'

awk '$NF-$(NF-1)>100'

见前,对于非纯数字的字段,可以用算术运算让它转回数字。

4. 多条件同时存在

awk '($12 >150 || $(13)>250)'

5. 使用if语句

如果逻辑更复杂,可以考虑使用if,else等语句

awk '{ if ($(NF-1)*1>100) print}'

 

其他

1.外部传入参数

比如从外面传入超时的阀值,注意threshold在命令行中的位置。

awk '{if($(NF)*1>threshold) print}' threshold=20 access.log

2.常用函数

最有用是gsub和sub,match,index等。其中gsub将一个字符串替换为目标字符串,可选定整行替换或只替换某一列。

awk '{gsub("ms]","",$NF); if( $NF>100 ) print}' access.log

 

一些例子

1.截取日期段内段数据

方式有很多,都是随着日志格式不同自由发挥。

比如下段截取17:30:30 秒到 17.31:00的数据,先抽取出时分秒三列,再拼成一个数字进行比较

awk -F "[ :.]" '$2$3$4>=173030 && $2$3$4<173100'

也可以匹配某个整点时间, 下例取11点的日志:

awk '/[2015-08-20 11:/ {print $1}’ access.log

取11点01分到05分的数据:

awk '/[2015-08-20 11:0[1-5]:/ {print $1}’ access.log

2. 找出超时的数据集中发生的时间

第一段找出超时记录,第二段过滤掉时间戳里的微秒,然后按秒来合并,并统计该秒超时的次数。

awk '$(NF)*1>100’ access.log | awk -F"." '{print $1}' | sort | uniq -c

------------- 一条安全的分隔线 --------------------

by calvin | tags : | 3

Sonar + Jacoco,强悍的UT, IT 双覆盖率统计

| Filed under 技术

以前做统计代码测试覆盖,一般用Cobertura。以前统计测试覆盖率,一般只算Unit Test,或者闭上眼睛把Unit Test和Integration Test一起算。

但是,我们已经过了迷信UT的时代:

  • UT不支持大幅度重构,如果对类和方法进行重构拆分,UT就失去了保障重构后代码仍然正确的作用,还要花时间按新的类和方法重写,其他用例对旧类和方法的mock改起来也是噩梦。
  • UT不支持基于用户故事的测试,即使覆盖率100%了,也不保证就是产品经理想要的东西。
  • UT对输入参数和Mock对象行为的假设,其实存在潜在的风险
  • 多线程,网络等等难于测试的地方。

在我看来,使用嵌入式容器的集成测试,如Spring Boot所倡导的基于嵌入式Jetty,H2等等的一整套集成测试体系,集合了UT(可本地快速运行,可直接assert应用内部属性,可统计覆盖率,如果CI失败了可以本地单独运行、debug、修复失败的case再提交)与FT(基于用户故事黑盒测试)的优点,对项目质量保证的地位一点不比UT低,所以同样需要计算覆盖率,而不是传统测试金字塔模型,只依赖UT的覆盖率。

所以Sonar + Jacoco 这种同时显示UT和IT测试覆盖率的组合非常实用。

照抄Sonar自带的Maven UT/IT示例项目,用maven插件,很容易就能跑出效果来,略。

花了我半天时间的,是如何用Jenkins上的SonarQubeRunner,跑出相同的效果,因为SonarQubeRunner不认识Maven是谁。

网上都是半新半旧,不咸不淡的文章,自己又摸索了一轮,得出一个只要一条不漏,便保证能跑的Jenkins + Maven + Sonar + Jacoco配置

在Jenkins上使用最新的SonarQube Runner 2.4,填入下面的配置


sonar.projectKey=xxx
sonar.projectName=xxx
sonar.projectVersion=xxx
sonar.modules=moduleA,moduleB,IT module C

#这里假设moduleA,moduleB 在根目录下的一层目录,Module C在二层目录下,需额外定义
#moduleC.projectBaseDir=xxx/moduleC

sonar.sourceEncoding=UTF-8
sonar.language=java
sonar.sources=src/main/java
sonar.tests=src/test/java
sonar.binaries=target/classes

#排除一些不想统计的类
#sonar.exclusions=**/*IDL.java

sonar.java.coveragePlugin=jacoco
sonar.jacoco.itReportPath=xxx/moduleC/target/jacoco-it.exec,最好写成绝对路径
sonar.junit.reportsPath=target/surefire-reports
sonar.surefire.reportsPath=target/surefire-reports

参考资料: JAVA代码覆盖率工具JaCoCo-原理篇

广州今天继续热的要命

by calvin | tags : | 2