JVM参数设置及优化

本文中一些参数基于JDK8,我们在优化过程中,需要同时关注FullGC和YoungGC,尽可能避免stop-the-world。

JVM内存分区

JVM进程占用内存 = 堆内存 + 线程数*线程栈 + 元空间 + 堆外内存

线程数可以利用jstack $PID | more观察当前JVM中所有线程,可以优化部分不必要的线程,降低内存使用同时也避免线程切换的开销。

栈Stack

存储局部变量等,默认为1M

堆Heap

存储对象实例,包含新生代,老年代

元Metaspace

存储类定义

堆外Off-heap

java.nio.*等使用

全文

Protocol Buffers 3

由于接口同时支持application/x-protobufapplication/json两种content-type,所以protobuf-java提供的JSON序列化方案性能也是我们比较关注的。

最近注意到Protocol Buffers 3(下称proto3)中对JSON的处理调整为利用Gson实现,尝试升级到proto3,这里简单总结下中间遇到的一些问题。

proto3语法规则

  • 描述文件头部增加语法声明syntax = "proto3" ,这样protoc会正确编译生成代码。

  • 移除required描述关键字,字段默认是optional

  • 移除default选项,proto2可以使用default为某一字段指定默认值,但proto3字段的默认值只能根据字段类型由系统决定。

  • enum类型第一个字段的字段编号tags必须为0。

  • 添加新的字段选项json_name。默认在proto3的JSON格式中字段名将被转换为lowerCamelCase,这个选项被用于自定义字段名。

全文

HTTP连接状态总结

TCP连接的生命周期,通常会有

三个阶段

  1. TCP三次握手
  2. 数据传送
  3. TCP四次挥手

从Google上扒了一张详细介绍各个阶段的产生的连接状态图
TCP状态转换图

三种数据包

  1. SYN(同步序列编号,Synchronize Sequence Numbers)该标志仅在三次握手建立TCP连接时有效
  2. ACK(确认编号,Acknowledgement Number)是对TCP请求的确认标志
  3. FIN(结束标志,FINish)用来结束一个TCP连接

三个问题

SYN Flood攻击

SYN攻击是一个典型的DDoS攻击。
在三次握手过程中,服务器发送SYN-ACK之后,收到客户端的ACK之前的TCP连接称为半连接(half-open connect)。
SYN攻击就是客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复ACK包,并等待客户的ACK从而建立连接。
由于源地址是不存在的,不会再发送ACK确认包,所以服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,资源耗尽(CPU满负荷或内存不足),让正常的业务请求连接不进来。
参考TCP洪水攻击(SYN Flood)的诊断和处理

CLOSE_WAIT

主动关闭的一方发出FIN包,被动关闭的一方响应ACK包,此时被动关闭的一方就进入了CLOSE_WAIT状态。
通常,CLOSE_WAIT状态在服务器停留时间很短,如果你发现大量的CLOSE_WAIT状态,那么就意味着被动关闭的一方没有及时发出FIN包,一般有可能是程序没有及时关闭socket。
注意 CLOSE_WAIT没有超时参数控制,如果不重启进程,那么这些状态的连接会永远占用资源。

TIME_WAIT

在关闭连接的时候,先发FIN包的一方执行的是主动关闭,后发FIN包的一方执行的是被动关闭。主动关闭的一方会进入TIME_WAIT状态,并且在此状态停留两倍的MSL时长。
MSL指的是报文段的最大生存时间,如果报文段在网络活动了MSL时间,还没有被接收,那么会被丢弃。关于MSL的大小,RFC 793协议中给出的建议是两分钟,不过实际上不同的操作系统可能有不同的设置,以Linux为例,通常是半分钟,两倍的MSL就是一分钟,也就是60秒。
比较有效的方案是连接采用keep-alive,尽可能不让服务端主动关闭连接。

Nexus私服搭建

Nexus是Maven仓库管理工具,Team内部之前存在一套,但管理比较混乱,这段时间腾出手来重新搭了一套,把整个过程记录下。

下载解压

我这里出现ssl链接问题,所以加了—no-check-certificate

1
2
3
4
cd /opt/
wget --no-check-certificate https://sonatype-download.global.ssl.fastly.net/nexus/oss/nexus-2.14.1-01-bundle.zip
unzip nexus-2.14.1-01-bundle.zip
ln -s nexus-2.14.1-01/ nexus
修改.bashrc文件增加环境变量
1
NEXUS_HOME="/opt/nexus"
配置为Service
1
2
3
4
ln -s $NEXUS_HOME/bin/nexus /etc/init.d/nexus
cd /etc/init.d/
sudo chkconfig --add nexus
sudo chkconfig --levels 345 nexus on

全文

利用OpenResty解决IP反作弊

广告投放项目中Nginx起初有通过ngx_http_limit_req_modulengx_stream_limit_conn_module配置来限制单个IP在同一时间段的访问和并发次数来防止CC攻击,但在解决一些作弊流量上有些力不从心了。

比如有些IP的在没有展示或者展示时间过短的的情况下,模拟客户端(点击触摸参数宏替换)请求广告点击链接,之前这些非法流量是在离线处理才会过滤掉,没有及时的禁止访问,直接导致这些流量可能会被上游DSP反作弊命中影响收入,所以最近着手处理这个问题。

目前的解决思路是,首先收集到异常IP列表入Redis并设置过期时间,在Nginx上利用Lua判断当前IP是否在黑名单中,从而达到反作弊功能。

全文

初次接触Go编程踩到的坑

最近想利用Go来写切图服务,没什么经验,遇到的问题比较多,下面就简单总结下,看到的同学们可以参考下吧。

设置HTTP response header不生效

由于响应的数据格式为JSON,所以想设置下Content-Type : application/json,代码如下

1
2
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")

结果在Header始终看不到期望结果,跟踪源码发现response中有wroteHeader字段标示。
得出结论,如果这两行代码都需要调用,就必须让 w.WriteHeader 在所有的 w.Header.Set 之后执行,才能生效。

全文

Tomcat优化

更新:建议部署APM工具Pinpoint,比较直观分析性能。

分析

排查JVM

1
2
3
4
# jstat -gcutil $PID            注意GC次数,过多的GC会影响延迟
# jmap -heap $PID               观察各个分区的内存占用情况
# jmap -histo $PID | more       注意大对象或者对象实例较多的情况
# jstack $PID > jstack.txt      观察线程执行状态,出现线程Block,需要特别注意

ThreadDump没什么问题,GC没发现严重问题,只是觉得Eden区内存设置有点紧张

排查负载

1
2
3
4
5
6
# uptime        观察最近的负载
# free -g       还有可用内存,注意swap
# ps auxxwf     各个进程,包括JVM占用资源,可能其他进程的CPU负载过高也会影响响应的延迟,比如日志压缩脚本
# top           注:按下数字1,观察每个CPU核的负载
# ss -an | grep :8080 | wc -l   当前端口下的连接数,不区分状态
# netstat -natl | awk '{print $6}' | sort | uniq -c | sort -n   注意各个状态下的连接数,过多的wait连接影响新的请求进来

发现TIME_WAIT状态的连接比较多,接近配置的连接数上限了,怀疑没有配置keep-alive导致Tomcat主动关闭Connection。

执行主动关闭的一端进入TIME_WAIT,需要等2倍的MSL时间(通常1分钟)之后才能把状态改为CLOSED。具体参考HTTP连接状态总结

再结合Nginx和Tomcat的日志,开始着手调整。

优化

Nginx配置keep-alive

1
2
3
4
5
6
location / {
    proxy_pass tomcat-upstream;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    ...
}

JVM参数

在脚本中头部增加一行参数,这里针对JDK8版本的JVM,具体参考JVM参数设置及优化

1
2
3
# vim /usr/local/tomcat/bin/catalina.sh
JAVA_OPTS="$JAVA_OPTS -Xms4g -Xmx4g -Xmn2g -XX:SurvivorRatio=16 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m"
JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:MaxTenuringThreshold=4"

Connector协议由BIO调整为NIO

配置Connector

同时也参考 Tomcat 8 Configuration Reference 修改了线程数和Logs格式,便于统计平均响应时长。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# vim /usr/local/tomcat/conf/server.xml
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
            keepAliveTimeout="60000"
            maxKeepAliveRequests="1024"
            <!-- 非异步Servlet时与maxThreads保持一致 -->
            processorCache="128"
            maxThreads="128"
            <!-- 队列大小 -->
            acceptCount="64"
            <!-- 单位毫秒,请求连接超时,keepAliveTimeout参数未设置按此参数 -->
            connectionTimeout="2000"
            redirectPort="8443" />

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
            prefix="localhost_access_log" suffix=".txt"
            pattern="%h %l %u %t "%r" %s %D" />

这里通过jstack结果分析,决定线程maxThreads参数配置,同时也需要考虑栈空间的分配问题。
调整过程中也建议使用ab简单压测下,及时观察调整效果。

排查Java进程CPU占有率高

最近尝试在项目中利用com.sun.management.OperatingSystemMXBean增加些调试信息,发现测试环境Java进程CPU占有率很高,Google后总结一些解决过程。

1.根据top命令,发现占有率高的Java的PID。

1
#top

2.找到该PID后,首先显示线程列表,并按照CPU占用高的线程排序。

1
#ps -mp $PID -o THREAD,tid,time | sort -rn

3.然后找到耗时最多的线程TID,将需要的TID转换为16进制格式。

1
#printf "%x\n" $TID

4.打印线程的堆栈信息。

1
#jstack $PID |grep $0xTID -A 30

MySQL JDBC Driver版本Bug

上周发现线上的CTR信息不准确,影响到广告投放概率。

经过同事的排查发现是数据库实例连接错了,在jdbcUrl中指定的端口是是3307,但实际上是读写3306默认端口的数据库。

跟踪确定是Driver本身的Bug,Google了下官方已经有相关Bug反馈了,解决方案就是把mysql-connector-java的版本从5.1.9调整为5.1.18。