Rpc 优化 - 自动分页/分段工具类
目前的问题
目前在 Rpc 的时候会存在以下两个问题:
-
定时任务刷数据的时候,需要全量拉取其他服务的数据,目前的做法是直接通过列表接口拉取
- 目前拉取数据是从读库拉,不会给主库造成压力,但是在拉取数据的时候,如果数据量比较大,目标服务节点需要一次性序列化大量数据,能用,但不优雅
- 随着日后数据量的增长,如果类似的操作不断增多,将有引发 OOM 的风险,不仅定时任务执行失败,还会导致系统不稳定
-
目前的内网网关对 URL 的限制较短,偶尔会出现 Get 请求传参过多,排查后发现是传了一个比较长的 List,整体 URL 较长,导致 Rpc 请求失败
- 由于该情况只是偶现且业务需求繁多,遇到这种问题时直接粗暴地把 Get 请求换成 Post 请求,通过把参数放在 Body 里面解决,不够优雅,等需求量稍微减少后,需要针对这种情况提供一个解决方案
解决方案的思考
-
对于第一个问题,很自然想到的方案就是分页查询,只需要列表数据有序(比如根据 id 排序),就可以很轻松地分页查询,解决方案呼之欲出:
- 弄一个分页工具类,把分页查询的代码进行封装
- 仅仅是普通的分页还不够,还需要提供并发查询的能力,不然查起来会很慢,因为一次请求变成了多次,除了网络开销以外,需要执行的 SQL 数量也将变成原来的 N 倍
-
对于第二个问题
-
临时的解决方案
通过观察 url 发现,传递
List
时,目前的传参格式为url?param=1¶m=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</