背景
文章将占用您宝贵的五分钟
理论上来讲,我们日常中使用的电脑性能上来已经满足大部分场景,那为什么在将代码编译部署到服务器上,不久就会出现CPU告警?到底是因为服务器性能低,还是说我们的代码在与业务的兜兜转转后给CPU打造了很大的负担?如何排查定位是迫在眉睫的任务,那么回溯上边的疑问,我将给出个人的见解,希望可以抛砖引玉,共同进步!
高CPU占用率用例
一名业务开发人员在日常开发中,经常无法较好的衡量自己的代码瓶颈在哪里,只能一股脑的提测上线告警回滚,而在这每次重新发布的过程中,损失的不仅仅是技术人员的精力,更重要的也是你在团队的内部的可信度,我们要尽可能的保证自己的代码高质量的发布,低频次有目的性的回滚,毕竟在这个内卷的环境机会不是每次都有的,下面我将给出遇到过的案例,供大家分析补充!
MQ消费线程数
在很多业务节点中,无需同步返回业务结果,或者是允许短时间内的延迟,这种情况我们首先想到的是在提高的吞吐量的同时完成业务目标,那么给到的方案就是异步通知或者创建新一个线程去做非主线外的事情,我看到过很多代码比方说发送通知、消息中间件等都是通过线程池的方式去做这个事情,在低频次场景当然没有大问题,不过在高并发的情况下会有怎么样的表现呢?OK,铺垫已做好,进入代码环节!
(1)工欲利其事必先利其器
(2)安装RocketMQ
此处以4.3.0版本作为测试目标,此版本需要使用jdk8版本,过高版本JDK会导致启动MQ失败,且JAVA环境变量需要安装OK
- 下载MQ,并且适当调小runserver.cmd、runbroker.cmd的内存设置
set "JAVA_OPT=%JAVA_OPT% -server -Xms512m -Xmx512m -Xmn256m"
- 更改MQ的日志存储位置,将logback_broker.xml、logback_nameser.xml中user.home替换为logdir
<configuration>
<property name="logdir" value="D:\rocketmq-all-4.3.0-bin-release" />
...
- 启动MQ服务
mqnameser.cmd
mqbroker.cmd -n 0.0.0.0:9876 -c D:\rocketmq-all-4.3.0-bin-release\conf\broker.conf
看到图下的样式就代表已经启动成功了!
(3)打包SpringBootMQ应用
- 初始化Springboot web应用
- 集成rocketmq依赖
- 初始化topic
- 生产者消费者相关测试用例代码
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.9.1</version>
</dependency>
</dependencies>
public class MultiThreadCpuApplication {
public static void main(String[] args) {
SpringApplication.run(MultiThreadCpuApplication.class, args);
}
@GetMapping("/sendTestMsg")
public String sendTestMsg() throws MQClientException, UnsupportedEncodingException, MQBrokerException, RemotingException, InterruptedException {
// mqadmin updateTopic -c DefaultCluster -n 127.0.0.1:9876 -t TopicTest
// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
// 设置NameServer的地址
producer.setNamesrvAddr("172.18.3.8:9876");
// 启动Producer实例
producer.start();
for (int i = 0; i < 50000000; i++) {
// for (int i = 0; i < 100; i++) {
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
// 发送消息到一个Broker
SendResult sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.printf("%s%n", sendResult);
}
return "hello!";
}
@Bean
public DefaultMQPushConsumer consumerMsg() throws MQClientException {
// 实例化消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name");
// 设置NameServer的地址
consumer.setNamesrvAddr("172.18.3.8:9876");
consumer.setConsumeThreadMin(300);
consumer.setConsumeThreadMax(300);
// 订阅一个或者多个Topic,以及Tag来过滤需要消费的消息
consumer.subscribe("TopicTest", "*");
// 注册回调实现类来处理从broker拉取回来的消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 标记该消息已经被成功消费
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动消费者实例
consumer.start();
System.out.printf("Consumer Started.%n");
return consumer;
}
}
(4)上边的代码利用MQ发送500W消息,消费端应用开启300个消费线程消费,消费逻辑阻塞500ms模拟业务处理过程,观察服务器表现
(5)安装宝塔服务器监控软件
- 安装宝塔
- 安装Java项目管理工具&Web服务器
- 上传Jar包至宝塔服务器中,创建Java项目&启动应用
- 启动应用立马100%,好家伙,服务器卡死了!直到我把jvm停止后,才恢复到10%以内的正常水平
- 此处可以抛出一个问题,为什么一个消费线程数会导致这么高的CPU利用率,这样的设置难道没有边缘界定限制吗?
CPU负载就线程池会影响吗
答案当然是否定的,这里我在列举一个,比如图片上传后进行文件大小压缩处理、尺寸缩放处理,不仅仅会对CPU存在计算压力,也会将图像读入到内存中,较大的文件会立马将服务器烤起来,不多说了,线上服务器又在嗷嗷待哺了,期待下次相见,感谢观看!
2022.7.20 代码仓库位置:gitee.com/a807966224/…
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。