Dubbo消费者动态参数报文上加字段唯一ID,通用性的解决方案,解决幂等性问题
Dubbo 中实现通用幂等性 Token 传递方案
在 Dubbo 框架中实现通用的幂等性 Token 传递机制,需要利用 Dubbo 的扩展点机制,特别是 Filter 接口。这种方案可以在不修改业务代码的情况下,为所有 RPC 接口自动添加幂等性支持。
一、Dubbo 幂等性 Token 传递方案
1. 实现原理
通过以下步骤实现通用幂等性:
- 拦截请求:利用 Dubbo 的 Filter 在客户端和服务端拦截所有 RPC 调用。
- 生成 / 验证 Token:
- 客户端生成 Token 并添加到请求中。
- 服务端验证 Token 并防止重复请求。
- 透明集成:对业务代码无侵入,通过配置自动生效。
2. 关键组件
- Client Filter:客户端生成 Token 并附加到请求。
- Server Filter:服务端验证 Token 并处理幂等性逻辑。
- Token 生成器:根据请求参数生成唯一标识。
- Token 存储:使用 Redis 等存储已处理的 Token。
二、具体实现步骤
1. 定义 Token 生成器
java
public interface IdempotentTokenGenerator {
// 根据请求信息生成Token
String generateToken(Invoker<?> invoker, Invocation invocation);
}
// 基于请求参数的Token生成器实现
public class ParamBasedTokenGenerator implements IdempotentTokenGenerator {
@Override
public String generateToken(Invoker<?> invoker, Invocation invocation) {
// 获取服务名和方法名
String serviceName = invoker.getInterface().getName();
String methodName = invocation.getMethodName();
// 获取参数并排序
Object[] args = invocation.getArguments();
String paramStr = normalizeParams(args);
// 组合生成Token
String content = serviceName + "." + methodName + ":" + paramStr;
return DigestUtils.md5DigestAsHex(content.getBytes(StandardCharsets.UTF_8));
}
// 参数规范化处理(排序、过滤动态字段等)
private String normalizeParams(Object[] args) {
// 实现参数序列化和排序逻辑
// ...
}
}
2. 实现客户端 Filter
java
@Activate(group = {Constants.CONSUMER})
public class IdempotentConsumerFilter implements Filter {
@Autowired
private IdempotentTokenGenerator tokenGenerator;
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 生成Token
String token = tokenGenerator.generateToken(invoker, invocation);
// 将Token添加到隐式参数中
RpcContext.getContext().setAttachment("idempotent-token", token);
// 继续调用链
return invoker.invoke(invocation);
}
}
3. 实现服务端 Filter
java
@Activate(group = {Constants.PROVIDER})
public class IdempotentProviderFilter implements Filter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 获取Token
String token = RpcContext.getContext().getAttachment("idempotent-token");
if (StringUtils.isEmpty(token)) {
return invoker.invoke(invocation); // 无Token,正常处理
}
// 检查Token是否已存在(防重放)
String cacheKey = "idempotent:token:" + token;
Boolean exists = redisTemplate.hasKey(cacheKey);
if (Boolean.TRUE.equals(exists)) {
// 重复请求,直接返回空结果或之前的结果
String cachedResult = redisTemplate.opsForValue().get(cacheKey + ":result");
if (cachedResult != null) {
return new RpcResult(JSON.parseObject(cachedResult, Object.class));
}
return new RpcResult(); // 返回空结果
}
// 首次请求,处理业务并存储Token
try {
Result result = invoker.invoke(invocation);
// 存储Token和结果(设置过期时间,如10分钟)
redisTemplate.opsForValue().set(cacheKey, "1", 10, TimeUnit.MINUTES);
if (result.getValue() != null) {
redisTemplate.opsForValue().set(
cacheKey + ":result",
JSON.toJSONString(result.getValue()),
10, TimeUnit.MINUTES
);
}
return result;
} catch (Exception e) {
// 发生异常,不存储Token,允许重试
throw e;
}
}
}
4. 配置 Filter
在
resources/META-INF/dubbo/
目录下创建org.apache.dubbo.rpc.Filter
文件,内容如下:properties
idempotentConsumer=com.example.filter.IdempotentConsumerFilter
idempotentProvider=com.example.filter.IdempotentProviderFilter
5. 在 dubbo 配置中启用 Filter
xml
<!-- 消费者配置 -->
<dubbo:consumer filter="idempotentConsumer" />
<!-- 提供者配置 -->
<dubbo:provider filter="idempotentProvider" />
三、高级优化方案
1. 注解控制幂等性
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Idempotent {
// 幂等性过期时间(秒)
int expireTime() default 600;
// 是否忽略某些参数
String[] ignoreParams() default {};
}
// 修改服务端Filter,支持注解
@Activate(group = {Constants.PROVIDER})
public class IdempotentProviderFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 检查方法是否有@Idempotent注解
Method method = findMethod(invoker.getInterface(), invocation.getMethodName(), invocation.getParameterTypes());
Idempotent idempotent = method.getAnnotation(Idempotent.class);
if (idempotent == null) {
return invoker.invoke(invocation); // 无注解,不做幂等性处理
}
// 有注解,处理幂等性逻辑
// ...
}
}
2. 自定义 Token 生成策略
java
// 支持自定义Token生成策略
public class CustomTokenGenerator implements IdempotentTokenGenerator {
@Override
public String generateToken(Invoker<?> invoker, Invocation invocation) {
// 自定义生成逻辑,例如:只使用特定参数生成Token
// ...
}
}
四、注意事项
-
参数过滤:
- 过滤时间戳、随机数等动态参数,只保留业务关键参数。
- 对参数进行排序,确保相同参数组合生成相同 Token。
-
分布式环境:
- 使用 Redis 等分布式缓存存储 Token,避免单点问题。
- 考虑缓存集群故障转移机制。
-
异常处理:
- 业务处理异常时,不存储 Token,允许重试。
- 设置合理的 Token 过期时间,避免长时间占用缓存。
-
性能影响:
- 幂等性校验会增加一次 Redis 访问,对性能要求极高的场景需优化。
五、扩展应用场景
- 防重复提交:表单重复提交时自动拦截。
- 分布式事务:跨服务调用时保证操作的幂等性。
- 接口限流:结合幂等性实现请求去重和限流。
通过以上方案,可以在 Dubbo 框架中实现通用的幂等性 Token 传递机制,无需修改业务代码即可为所有 RPC 接口提供幂等性保障。