1 概述
在历时2个月的不断优化过程中,将数据采集系统的处理能力(kafka一个topic)从2.5万提升到了10万,基本符合对下一次峰值的要求了。
所有日志中,其中广告日志和作品日志量是最大的,所以本次的优化也是针对这两块进行优化。
广告日志接口TPS从之前的不到1k/s,提升到现在的2.1w/s,提升了20倍。
作品日志接口TPS从之前的不到1k/s,提升到现在的1.4w/s,提升了13倍。
在数据采集的优化过程中,设计到很多地方,包含代码的优化,框架的优化,服务的优化,现在将对吞吐率的提高有明显左右的优化点记录下来。
2 面向对象
技术负责人,后端服务开发工程师
3 撰写时间
2020年04月03日
4 技术框架图
5 后端日志ETL程序LogServer的优化
广告日志接口TPS从之前的不到1k/s,提升到现在的2.1w/s,提升了将近20倍。
作品日志接口TPS从之前的不到1k/s,提升到现在的1.4w/s,提升了13倍。
1.广告日志接口的压测结果部分截图
2.作品日志接口的压测结果部分截图
以下TPS的提升是一个大概的值。
5.1 删除代码中不必要的打印日志
例如
System.out.println
System.out.println
logger.info
TPS 1k -> 3k
5.2 将logback.xml文件中的打印日志关闭
例如
TPS 3k -> 5k
5.3 对获取kafka相关的logger的代码优化
例如
之前的代码
public synchronized static Logger getLogger(String topic) {
Logger logger = loggers.get(topic);
try {
if (logger == null) {
logger = LoggerFactory.getLogger(topic);
loggers.put(topic, logger);
}
return logger;
}
优化后的代码
public static Logger getLogger(String topic) {
if (logger == null) {
synchronized(KafkaLoggerFactory.class){
if(logger == null){
logger = LoggerFactory.getLogger(topic);
loggers.put(topic, logger);
}
}
}
}
TPS 5k -> 9k
5.4 对流量广告的逻辑进行简化
之前的做法:
将广告数据当成普通的日志数据来处理,会经历所有的日志判断逻辑,最后经过验证,数据没问题,才会发送到kafka,整个逻辑链条比较长。
现在的做法:
先看代码
ip: String ip = request.getIp();
collection.put("ip", ip);
// 国家、地区、城市: collection.putAll(Constant.getRegionInfo(ip));
server_host: collection.put("srh", Constant.serverHost);
server_time: collection.put("s_t", System.currentTimeMillis());
if( "traffic_view".equals(collection.get("product")) ){
parseAdRecord(collection);
return Constant.RESPONSE_CODE_NORMAL;
}
...
public void parseAdRecord(Map collection){
try {
collection = Constant.clearAdCollection(collection);
log2kafka(Constant.eventTopic, JSONObject.toJSONString(collection));
} catch (Exception e) {
e.printStackTrace();
}
}
从上边的代码可以看出,将广告的逻辑单独处理进行处理,整个链路短了很多,大概总共分3步:
1 必须的公共字段处理
2 判断是否广告日志
3 将广告日志发送到kafka
TPS 9k -> 1.2w
5.5 对广告日志中的字段进行精简
将HDFS上广告日志总共的85个字段,现在精简到45个左右。这一步虽然对LogServer的吞吐率没有太多的提升。但是却可以将kafka的吞吐量提升将近一倍。
5.6 升级和简化依赖
- 首先将非必要的maven依赖全部删除掉了,使得依赖的数量从217个减少到了51个。
- 将maven依赖升级成比较新的版本。
- 有些依赖被删掉了,相关的类也做了调整,比如StringUtils.isEmpty()已经从spring类
org.springframework.util.StringUtils
调整成了commons-lang3包内的org.apache.commons.lang3.StringUtils
org.apache.commons
commons-lang3
3.10
6 服务器硬件层面
从之前的4核8G服务器迁移到8核16G的服务器上。
并且对服务器内核参数做了以下优化:
net.core.somaxconn = 10240
net.core.netdev_max_backlog =262144
net.ipv4.tcp_keepalive_intvl = 5
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 60999
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1
1.2w -> 2w
7 对前端SDK的优化
经过对kafka的写入压测,当日志大小为1024字节的时候的QPS是2048的接近2倍。
1 减少前端上报日志字段数量,将暂时未用的的字段删掉。前端sdk上报的日志字段从71个删减到48个字段,减少了32%的字段数量。
2 不必要的日志不再上报,主要是通过修改前端日志上报的逻辑来实现。
8 对Nginx的优化:
对Nginx的优化主要有2个方面:
- 服务器层面的优化,如上述第5项
- Nginx本身的配置优化
- 增加了ip防刷的机制
8.1 Nginx一部分配置的优化。
worker_connections从20480上调到了102400,调大了5倍。调大之后nginx的吞吐量从2w/s,提升到3.5w/s,设置的时候最好结合业务和服务器的性能先做个压测。
worker_processes 默认是1,官方建议和cpu的核数相同,或者直接设置成auto。有人建议设置成cpu核数的2倍,从我的测试情况来看,不会有明显的提高,也可能是场景覆盖的有限。
worker_cpu_affinity Nginx默认没有开启利用多核cpu,通过worker_cpu_affinity开启nginx利用多核cpu,并将worker绑定到指定的线程上,以此来提高nginx的性能。
multi_accept 默认Nginx没有开启multi_accept。multi_accept可以让nginx worker进程尽可能多地接受请求。它的作用是让worker进程一次性地接受监听队列里的所有请求,然后处理。如果multi_accept的值设为off,那么worker进程必须一个一个地接受监听队列里的请求。
worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;
worker_connections 102400;
multi_accept on;
优化完之后QPS从1万左右上升到3.5万。
8.2 ip防刷
在conf/module/定义了一个blacklist文件:
map $http_x_forwarded_for $ip_action{
default 0;
~123\.123\.29 1;
}
在nginx.conf中增加ip过滤的配置:
location /log.gif {
if ($ip_action) {
return 403;
}
proxy_pass http://big-da/log-server/push;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 128k;
client_body_buffer_size 32k;
proxy_connect_timeout 5;
proxy_send_timeout 5;
proxy_read_timeout 5;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
如果是黑名单中的ip,则直接拒绝请求。
9 对kafka的优化
1.将所有重要topic的Replication从1改成了2,这样保证在kafka有1个节点故障的时候,topic也能正常工作。
2.为每个节点的kafka设置了独用的SSD硬盘。
3.topic分区的分区数量根据业务需求设置,我们设置了6个分区。
3.在producer端写kafka的使用使用snappy压缩格式
4.在producer端合理设置batch.size
batch.size用来控制producer在将消息发送到kafka之前需要攒够多少自己的数据。默认是16kB,经过测试,在32kB的情况下,吞吐量和压测都在接受的接受的范围内。
5.在producer端合理设置linger.ms
默认没有设置,只要有数据就会立即发送。可以将linger.ms设置为100,在流量比较大的时候,可以减少发送请求的数量,从而提高吞吐量。
6.升级版本,将kafka从0.10升到了2.2.1
作者:易企秀工程师 Peace -> 个人主页