Rpc 优化 - 自动分页/分段工具类

博客介绍了Rpc优化中遇到的问题,如全量数据拉取可能导致的性能和稳定性问题,以及URL过长导致的请求失败。解决方案是创建一个自动分页/分段的工具类,支持并发查询和参数为List的场景,适用于不同类型的查询需求。文章详细讲解了工具类的适用场景、配置选项,并提供了特殊场景的处理方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Rpc 优化 - 自动分页/分段工具类

目前的问题

目前在 Rpc 的时候会存在以下两个问题:

  • 定时任务刷数据的时候,需要全量拉取其他服务的数据,目前的做法是直接通过列表接口拉取

    • 目前拉取数据是从读库拉,不会给主库造成压力,但是在拉取数据的时候,如果数据量比较大,目标服务节点需要一次性序列化大量数据,能用,但不优雅
    • 随着日后数据量的增长,如果类似的操作不断增多,将有引发 OOM 的风险,不仅定时任务执行失败,还会导致系统不稳定
  • 目前的内网网关对 URL 的限制较短,偶尔会出现 Get 请求传参过多,排查后发现是传了一个比较长的 List,整体 URL 较长,导致 Rpc 请求失败

    • 由于该情况只是偶现且业务需求繁多,遇到这种问题时直接粗暴地把 Get 请求换成 Post 请求,通过把参数放在 Body 里面解决,不够优雅,等需求量稍微减少后,需要针对这种情况提供一个解决方案

解决方案的思考

  • 对于第一个问题,很自然想到的方案就是分页查询,只需要列表数据有序(比如根据 id 排序),就可以很轻松地分页查询,解决方案呼之欲出:

    • 弄一个分页工具类,把分页查询的代码进行封装
    • 仅仅是普通的分页还不够,还需要提供并发查询的能力,不然查起来会很慢,因为一次请求变成了多次,除了网络开销以外,需要执行的 SQL 数量也将变成原来的 N 倍
  • 对于第二个问题

    • 临时的解决方案

      通过观察 url 发现,传递 List 时,目前的传参格式为 url?param=1&param=2,参数名会重复很多遍,但这是 Feign 默认的传参格式,且没有提供全局修改的能力,只能在接口上面通过 @CollectionFormat(feign.CollectionFormat.CSV) 注解把传参格式修改为 url?paran=1,2,3,以此减少 url 长度

    • 比较整体的解决方案

      在进行 Rpc 查询时,先把 List 进行分段,分段以后再根据每个段发送查询请求

总结来说,最后的解决方案是编写一个工具类,提供分页/分段的功能,编写业务需求的时候直接使用工具类来进行查询即可

工具类介绍

适用场景

该工具类主要适用于以下三种场景:

  • 参数为一个分页查询 DTO,自动分页查询

    RpcUtils.listWithAutoPage(new UserQueryDTO(), feignClient::listUser)
    
  • 参数为一个 List,自动对 List 进行分段

    RpcUtils.listByIdsWithAutoPartition(userIdList, feignClient::listUserById)
    
  • 参数为一个 Query 对象,对象里有一个很长的 List,自动对 List 分段

    • 如果参数只有这个 List 的话,可以这样:

      RpcUtils.listByIdsWithAutoPartition(
        userIdList, 
        UserQueryDTO::createWithIdList, 
        feignClient::listUser);
      
    • 如果参数比较多,可以这样:

      RpcUtils.listByIdsWithAutoPartition(
        userIdList, 
        // 注意, 这里必须的 DTO 不能在外面 new 出来
        // 必须保证每次调用这个 Function 都得到一个新对象, 否则会有线程安全问题
        (idList) -> UserQueryDTO.builder()
            .type(1L)
            .idList(idList)
            .build(), 
        feignClient::listUser);
      

分页/分段大小与多线程选项

上面只展示了最简单的用法,除了以上的必传参数以外,还支持设置两个可选项:

  • 分页大小 pageSize / 分段大小 partitionSize,这两个大小都已经定义了一些常量,比如 PAGE_SIZE_128、PARTITION_SIZE_256 等等,建议直接使用常量

  • 是否多线程 isConcurrent

    下面的代码中,默认值是走单线程,业务场景对响应时长有要求时可以走多线程

    这个是出于对数据库性能的考虑,这个工具类至少要保证 CV 到不同公司、不同项目后可以安全使用,如果数据库性能比较孱弱,默认走多线程可能会数据库打爆

两个特殊场景

  • 场景 1

    特殊的,你可能会遇到这样的场景,参数为 PageQuery,但是要传一个特别长的参数,暂时还没有 API 能直接解决这个问题,但可以采用曲线救国的方式:

    直接把分页的大小设成 Integer.MAX_VALUE,时间复杂度为 O(n),但是失去了 PageQuery 本身的意义,变成了一个类似第三种场景的状态

  • 场景 2

    如果有两个及以上很长的 List,本工具暂时无法解决,因为参数之间的关联关系一般为“与”, a in [1,2] and b in [3,4] 没有办法简单地转换为 (a in [1] and b in [3]) + (a in [2] and b in [4])

​ 当然它可以做笛卡尔积来解决这个问题, 但是这个场景目前比较稀缺, 后续有需要再做

上代码

下面的导包隐去了 jdk 自带的一些包,以及 PageQuery、CommonResponse 等类的导入,CV 的到时候自行替换即可

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import com.alibaba.fastjson.JSON;

import com.google.common.collect.Lists;
import org.springframework.core.ResolvableType;

public class RpcUtils {
   
   

    /**
     * 分页大小 - 64
     */
    public static final int PAGE_SIZE_64 = 64;

    /**
     * 分页大小 - 128
     */
    public static final int PAGE_SIZE_128 = 128;

    /**
     * 分页大小 - 256
     */
    public static final int PAGE_SIZE_256 = 256;

    /**
     * 分页大小 - 512
     */
    public static final int PAGE_SIZE_512 = 512;

    /**
     * 分页大小 - 1024
     */
    public static final int PAGE_SIZE_1024 = 1024;

    /**
     * 分页大小 - 2048
     */
    public static final int PAGE_SIZE_2048 = 2048;

    /**
     * 分页大小 - 4096
     */
    public static final int PAGE_SIZE_4096 = 4096;

    /**
     * 分页大小 - 8192
     */
    public static final int PAGE_SIZE_8192 = 8192;

    /**
     * 分页大小 - 16384
     */
    public static final int PAGE_SIZE_16384 = 16384;

    /**
     * 默认分页大小
     */
    public static final int DEFAULT_PAGE_SIZE = PAGE_SIZE_128;

    /**
     * 分段大小 - 64
     */
    public static final int PARTITION_SIZE_64 = 64;

    /**
     * 分段大小 - 128
     */
    public static final int PARTITION_SIZE_128 = 128;

    /**
     * 分段大小 - 256
     */
    public static final int PARTITION_SIZE_256 = 256;

    /**
     * 分段大小 - 512
     */
    public static final int PARTITION_SIZE_512 = 512;

    /**
     * 分段大小 - 1024
     */
    public static final int PARTITION_SIZE_1024 = 1024;

    /**
     * 分段大小 - 2048
     */
    public static final int PARTITION_SIZE_2048 = 2048;

    /**
     * 分段大小 - 4096
     */
    public static final int PARTITION_SIZE_4096 = 4096;

    /**
     * 分段大小 - 8192
     */
    public static final int PARTITION_SIZE_8192 = 8192;

    /**
     * 分段大小 - 16384
     */
    public static final int PARTITION_SIZE_16204 = 16384;

    /**
     * 默认分段大小 (内部网关的请求参数限制太小了, 256 都可能会报错, 128 目前还没有报错过)
     */
    public static final int DEFAULT_PARTITION_SIZE = PARTITION_SIZE_128;

    /**
     * 带自动分页的 RPC
     *
     * @param pageQuery rpc查询条件
     * @param fetcher   rpc 方法
     * @param <T>       rpc查询条件的类型
     * @param <R>       rpc 方法返回值的类型
     * @return
     */
    public static <T extends PageQuery, R> List<R> listWithAutoPage(T pageQuery, Function<T, CommonResponse<CommonPage<R</
1. boot默认扫描包:Spring Boot默认会扫描启动类所在的包及其子包下的所有类。可以通过在启动类上添加@ComponentScan注解来改变默认扫描的包路径。 2. Aop注解:AOP(面向切面编程)是Spring框架的一个重要特性,用于在不修改原有代码的情况下,实现对系统中某些关键流程进行增强或控制。常见的AOP注解包括@Aspect、@Pointcut、@Before、@After、@Around等。 3. volatile线程问题:volatile是Java中的一个关键字,用于保证变量的可见性和禁止指令重排序。在多线程环境下,使用volatile修饰的变量可以保证多个线程对变量的修改可以及时被其他线程所感知,从而避免了数据不一致的问题。 4. ConcurrentHashMap为什么线程安全:ConcurrentHashMap是Java中的一个线程安全的Map实现,其内部使用了分段锁的机制,不同的线程可以同时对不同的段进行操作,从而实现了高效的并发访问。 5. mybatis-plus多表查询:Mybatis-Plus是Mybatis的扩展工具包,提供了很多方便的功能。在Mybatis-Plus中进行多表查询可以使用Mybatis-Plus提供的Wrapper接口和QueryWrapper类。 6. limit分页:在MySQL中,LIMIT是用来限制查询结果集的行数的关键字。通过设置LIMIT offset, limit可以实现分页的功能,其中offset表示从查询结果集的第几行开始,limit表示返回的行数。 7. Linux创建文件:在Linux中,可以使用touch命令来创建一个空文件。例如,要在当前目录下创建一个名为test.txt的空文件,可以使用命令touch test.txt。 8. ES存储、修改、如果修改失败怎么办:ES(Elasticsearch)是一款基于Lucene的分布式搜索引擎,支持快速搜索、分析和存储大量数据。在ES中,可以使用API来存储和修改数据。如果修改失败,可以根据具体的错误信息进行排查,比如数据类型不匹配、索引不存在等。 9. Cloud基于什么协议,Dubbo呢?:Spring Cloud是一款基于Spring Boot的微服务框架,基于HTTP协议实现了服务注册、服务发现、服务调用等功能。Dubbo是阿里巴巴开源的一款高性能RPC框架,基于TCP协议实现了服务注册、服务发现、服务调用等功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值