物联网设备性能测试平台,设备通过tcp接入netty后,netty解析处理报文后发布到kafka,数据采集服务消费报文并处理设备上下线,数据转发websocket等业务逻辑。架构 Netty设备接入 + Kafka消息队列 + Alibaba Cloud + Dubbo 微服务架构。
服务器:
- Intel Core i5-7400 @ 3.00HZ 4核4线程
- 16G内存
- Mysql 8 (Docker安装)
- Kafka 2.0.3
性能指标:
- 单机设备同时在线峰值 10000 +
- 设备消息处理 500 并发量
数据流向示意
设备 --> Netty --> Kafka --> 数据采集服务 --> 下游服务
2. 优化过程 2.1 修改服务器文件句柄上限性能测试开始后使用设备模拟程序模拟设备(1s 1000设备握手 + 注册报),开始接入平台,当设备数量达到2000左右时,报错
too many files open
首先想到 Linux文件句柄上限,查看文件句柄上限 ulimit -n
1024
修改服务器文件句柄上限(注意服务器不同conf文件名称可能不同,但目录相同) vim /etc/security/limits.d/20-nproc.conf
# Default limit for number of user's processes to prevent # accidental fork bombs. # See rhbz #432903 for reasoning. * - nproc 65536 * - nofile 65536
测试后问题解决。
2.2 修改dubbo线程池,加dubbo方法调优, 数据库连接池大小继续测试发现 设备上线间隔如果过小,数据采集服务报错
Caused by: java.util.concurrent.RejectedExecutionException: Thread pool is EXHAUSTED!
很明显,dubbo线程池过小,查阅资料发现 dubbo 默认线程池大小是 200 。 对比业务要求的 500 并发明显不足。
考虑注册报和首份数据报间隔可能较短,线程池大小暂定 2000 ,线程池类型为固定,配置如下
dubbo:
consumer:
check: true
protocol:
# dubbo 协议
name: dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
port: -1
host: 127.0.0.1
# 线程池固定,cached可能导致内存溢出
threadpool: fixed
dispatcher: message
threads: 2000
cloud:
# 订阅服务提供方的应用列表,订阅多个服务提供者使用 "," 连接
subscribed-services: device-debug-device-center,device-debug-user-center,device-debug-device-access-broker
由于数据采集服务在进行数据清洗和数据处理后需要进一步转发到下游服务进行业务处理,因而下游服务也需要进行dubbo线程池大小扩充。
配置后发现问题仍然存在。且下游服务出现连接池获取连接失败异常,
Could not open JDBC Connection for transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.CommunicaiotnsException: Communications link failure
Druid连接池没有没有进行配置, 默认配置不匹配dubbo并行调用,导致连接池爆满,连接在等待了MaxWait时长后超时。而由于dubbo是同步调用的,又触发了dubbo的调用超时。
于是配置连接池如下:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ip:port/xxx?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: username
password: password
druid:
#初始化线程数
initial-size: 20
# 最小线程数
min-idle: 20
# 最大线程数
max-active: 200
# 获取连接最大等待时间
max-wait: 60000
# sql执行超时
query-timeout: 3000
# 保持长连接时间
keep-alive-between-time-millis: 600000
# 销毁线程的间隔时间
time-between-eviction-runs-millis: 450000
# 配置一个连接在池中最大空闲时间,单位是毫秒
min-evictable-idle-time-millis: 300000
# 设置从连接池获取连接时是否检查连接有效性,true时,每次都检查;false时,不检查
test-on-borrow: false
#设置往连接池归还连接时是否检查连接有效性,true时,每次都检查;false时,不检查
test-on-return: false
# 设置从连接池获取连接时是否检查连接有效性,true时,如果连接空闲时间超过minEvictableIdleTimeMillis进行检查,否则不检查;false时,不检查
test-while-idle: true
# 检验连接是否有效的查询语句。如果数据库Driver支持ping()方法,则优先使用ping()方法进行检查,否则使用validationQuery查询进行检查。(Oracle jdbc Driver目前不支持ping方法)
validation-query: select 1 from dual
# 是否长链接
keep-alive: true
# 是否删除异常连接
remove-abandoned: true
# 异常连接的判断条件(80毫秒没有使用的连接,防止线程阻塞)
remove-abandoned-timeout: 80
log-abandoned: true
#是否缓存preparedStatement,mysql5.5+建议开启
pool-prepared-statements: true
#要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
max-pool-prepared-statement-per-connection-size: 50
#配置监控统计拦截的filters,去掉后监控界面sql无法统计
filters: stat,wall
#通过connectProperties属性来打开mergeSql功能;慢SQL记录
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#合并多个DruidDataSource的监控数据
use-global-data-source-stat: true
#StatViewServlet配置
stat-view-servlet.enabled: true #是否启用StatViewServlet(监控页面)默认值为false
stat-view-servlet.login-username: admin #设置访问druid监控页的账号,默认没有
stat-view-servlet.login-password: admin #设置访问druid监控页的密码,默认没有
考虑设备端不需要响应,因而暂定连接池最大大小200, 通过调高maxWait来缓冲数据库连接(因为业务逻辑处理时长都较短)。 实际业务场景前期可能没有那么多设备同时在线,初始化连接池大小也设定比较小。 同时添加sql监控。
此外由于不需要对设备报文进行应答,因而dubbo调用可以修改为异步调用,且不关注返回结果。方法调用参考如下
@Component
@Slf4j
public class KafkaConsumer {
@Reference(methods = {
@Method(name = "online", async = true, isReturn = false),
@Method(name = "offline", async = true, isReturn = false),
@Method(name = "data", async = true, isReturn = false),
})
private ISxDeviceDubboService deviceDubboService;
@KafkaListener(topics = KafkaTopic.UP_TOPIC)
public void receiveTcpMessage(String message) {
//业务代码中调用dubbo方法
}
}
2.3 修改mysql最大连接上限
配置后dubbo异常消失,但是下游服务出现了 mysql连接错误 ERROR 1040 (HY000): Too many connections连接池满, 由于并发线程较大,数据库来不及处理。很明显mysql连接上限了
查看mysql 连接上限 show variables like "%max_connections%";
151
而我们线程池最大连接是 200 , 因此直接修改为 1024
修改mysqld配置
[mysqld] max_connections=1024
重启mysql后测试发现问题消失。
连接测试后挂测,发现峰值时内存占用与闲时差距小于20%,数据库连接能够均匀地释放, 但cpu差距大于50%, 服务器也比较卡。单机的情况下能够满足项目要求,唯一的问题是cpu负载较大。
3 总结应用性能调优还是缺少标准公式知识, 更多地按照实际测试进行调整, 兵来将挡水来土掩, 后续还需要进一步系统地学习相关性能调优的知识。



