前提
最近在做公司业务系统的性能优化,分析cpu火焰图时,逐个查找程序中cpu耗时较高的部分,发现在调用leaf服务时,存在一定的耗时,想着能否对该微服务进行优化。大致的优化思路在不改变整体调用结构的情况下进行服务的优化。有如下几个修改思路:
| 修改思路 | |
|---|---|
| dubbo | 协议由http转为dubbo协议 |
| dubbo + completableFuture | 使用dubbo进行异步处理 |
| dubbo + http2 + protobuf | 即使用dubbo中tri协议 |
| feign + http2 | 服务端客户端都进行改造http2 |
实践
dubbo
前提:leaf改造成dubbo服务
核心就是segementService的暴露
dubbo服务提供端
package cn.db.provider.service.leaf;
import com.tencent.devops.leaf.common.Result;
import com.tencent.devops.leaf.segment.SegmentIDGenImpl;
import com.tencent.devops.leaf.segment.dao.IDAllocDao;
import com.tencent.devops.leaf.segment.dao.impl.IDAllocDaoImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
/**
* @author:zooooooooy
* @date: 2022/9/11 - 23:55
*/
@Service
@Slf4j
public class SegmentService implements BeanFactoryAware {
private BeanFactory beanFactory;
private SegmentIDGenImpl segmentIDGenImpl;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public Result getId(String key) {
return segmentIDGenImpl.get(key);
}
@PostConstruct
public void init() {
if(segmentIDGenImpl == null) {
synchronized (this) {
if(segmentIDGenImpl == null) {
DataSource dataSource = beanFactory.getBean(DataSource.class);
// Config Dao
IDAllocDao dao = new IDAllocDaoImpl(dataSource);
// Config ID Gen
this.segmentIDGenImpl = new SegmentIDGenImpl();
segmentIDGenImpl.setDao(dao);
segmentIDGenImpl.init();
}
}
}
}
}
去掉了原有的datasource取法,直接通过beanFactory去获取springboot自动配置的DataSource,基本代码都是参照leaf-server写法来进行实现。
dubbo服务消费端
比较简单,通过reference注解直接调用对应的service即可
@RequestMapping("/nextId/{key}")
public String nextId(@PathVariable String key) throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
String.valueOf(leafService.nextId(key));
}
System.out.println("cost time:" + (System.currentTimeMillis() - startTime));
return "success";
}
简单测试
测试条件:
- 数据量50000
- 客户端单线程模式
测试结果
对照原有的openFegin调用
提升并优化的还是很多的,本测试存在不太严谨的方向,只是做一个简单的基础对比。
dubbo + completableFuture
测试结论:在单线程模式下,虽然用了异步业务线程进行了解耦,还是比一般的会慢一些
dubbo服务端改造
- 接口部分
public interface LeafService {
CompletableFuture<Long> nextId(String key);
}
- 实现部分
@Service
public class LeafServiceImpl implements LeafService {
@Autowired
private SegmentService segmentService;
@Override
public CompletableFuture<Long> nextId(String key) {
// 获取数据库连接
// 构造idGen,分布式id生成器
return CompletableFuture.supplyAsync(() -> segmentService.getId(key).getId());
}
}
- 调用端
@RequestMapping("/nextId/{key}")
public String nextId(@PathVariable String key) throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
String.valueOf(leafService.nextId(key).get());
}
System.out.println("cost time:" + (System.currentTimeMillis() - startTime));
return "success";
}
这里有一个点要注意,数据获取的方式采用的是同步获取。真实的业务系统中可以采用异步的方式来及进行异步加载,会有一定的性能提升。
测试结果
暂时没有进行详细分析,主要消耗应该是在CompletableFuture对象的构建和线程的切换上多余了一些耗时。 模拟了一些异步调用:客户端需要进行一些改造
@RequestMapping("/nextId/{key}")
public String nextId(@PathVariable String key) throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(4);
CountDownLatch cdl = new CountDownLatch(50000);
for (int i = 0; i < 50000; i++) {
executorService.execute(() -> {
try {
String.valueOf(leafService.nextId(key).get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
cdl.countDown();
});
}
cdl.await();
System.out.println("cost time:" + (System.currentTimeMillis() - startTime));
return "success";
}
引入了countDownLatch,开了4个异步线程来调用,耗时在:6S
提升的还是很明显。
同步也对前面纯dubbo方案来进行异步线程调用测试,耗时在:4S
CompletabeFuture的优势还是没有发挥出来,客户端调用我还是采用的同步操作。
后续需要研究的点:
- feign http2调用,有两个方式可以采用jdk11中的httpClient,直接支持http2,或者使用okhttp代替apacheHttpClient调用。就可以同步支持http2
- dubbo http2,可以直接进行改造,使用reactor异步流操作。