Dubbo消费者动态参数报文上加字段唯一ID,通用性的解决方案,解决幂等性问题

Dubbo 中实现通用幂等性 Token 传递方案

在 Dubbo 框架中实现通用的幂等性 Token 传递机制,需要利用 Dubbo 的扩展点机制,特别是 Filter 接口。这种方案可以在不修改业务代码的情况下,为所有 RPC 接口自动添加幂等性支持。

一、Dubbo 幂等性 Token 传递方案

1. 实现原理

通过以下步骤实现通用幂等性:

  1. 拦截请求:利用 Dubbo 的 Filter 在客户端和服务端拦截所有 RPC 调用。
  2. 生成 / 验证 Token:
    • 客户端生成 Token 并添加到请求中。
    • 服务端验证 Token 并防止重复请求。
  3. 透明集成:对业务代码无侵入,通过配置自动生效。

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
        // ...
    }
}
 
 

四、注意事项

  1. 参数过滤:
    • 过滤时间戳、随机数等动态参数,只保留业务关键参数。
    • 对参数进行排序,确保相同参数组合生成相同 Token。
  2. 分布式环境:
    • 使用 Redis 等分布式缓存存储 Token,避免单点问题。
    • 考虑缓存集群故障转移机制。
  3. 异常处理:
    • 业务处理异常时,不存储 Token,允许重试。
    • 设置合理的 Token 过期时间,避免长时间占用缓存。
  4. 性能影响:
    • 幂等性校验会增加一次 Redis 访问,对性能要求极高的场景需优化。

五、扩展应用场景

  1. 防重复提交:表单重复提交时自动拦截。
  2. 分布式事务:跨服务调用时保证操作的幂等性。
  3. 接口限流:结合幂等性实现请求去重和限流。

通过以上方案,可以在 Dubbo 框架中实现通用的幂等性 Token 传递机制,无需修改业务代码即可为所有 RPC 接口提供幂等性保障。
posted @ 2025-06-06 18:09  飘来荡去evo  阅读(36)  评论(0)    收藏  举报