leaf使用dubbo暴露接口

892 阅读2分钟

前提

最近在做公司业务系统的性能优化,分析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";
}

简单测试

测试条件:

  1. 数据量50000
  2. 客户端单线程模式
测试结果

image.png

对照原有的openFegin调用

image.png

提升并优化的还是很多的,本测试存在不太严谨的方向,只是做一个简单的基础对比。

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";
}

这里有一个点要注意,数据获取的方式采用的是同步获取。真实的业务系统中可以采用异步的方式来及进行异步加载,会有一定的性能提升。

测试结果

image.png

暂时没有进行详细分析,主要消耗应该是在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

image.png

提升的还是很明显。

同步也对前面纯dubbo方案来进行异步线程调用测试,耗时在:4S

image.png

CompletabeFuture的优势还是没有发挥出来,客户端调用我还是采用的同步操作。

后续需要研究的点:

  • feign http2调用,有两个方式可以采用jdk11中的httpClient,直接支持http2,或者使用okhttp代替apacheHttpClient调用。就可以同步支持http2
  • dubbo http2,可以直接进行改造,使用reactor异步流操作。