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</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值