【灰度发布 (四)】RocketMQ公共组件的灰度方案实现

一.问题背景

服务端灰度发布需要支持RocketMQ消息灰度支持,这里对项目中目前存在得RocketMQ接入方式进行整理和说明,明确不同RocketMQ接入方式下得RocketMQ灰度能力支持

 

二.实现方案

方案选型

 灰度tags

通过MQ提供的tag机制过滤,

可以保证灰度的消息只会被灰度节点消费,

改造简单;

如果Tag参与的业务过滤,不适合该方案;

如果没有灰度节点订阅关系不一致,会出现消息丢失

必须全链路部署灰度实例

需要创建两组GROUP
user-property 灰度标记

SQL92 的过滤规则支持,服务端过滤

改造简单

必须全链路部署灰度实例

需要创建两组GROUP

确定使用本方案

灰度quque分区 订阅关系一致,不需要额外维护多一套GROUP 改动大 需要改造负责均衡策略

 

 

 

image

 

 

 

 

 

 

三.生产者接入方式

如果DefaultMQProducer 被Spring管理,RocketmqGrayExtConfiguration自动增强,相关代码如下

@Override
 public void afterSingletonsInstantiated() {
     String tags = EnvironmentUtils.getTags();
     if (!StringUtils.hasLength(tags)) {
         return;
     }
     log.info("当前实例为否灰度环境:{} tags:{}", StringUtils.hasLength(tags), tags);
     Map<String, DefaultMQProducer> beans = this.applicationContext.getBeansOfType(DefaultMQProducer.class)
             .entrySet().stream().filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey()))
             .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
     GrayTagMessageHook grayTagMessageHook = new GrayTagMessageHook();
     for (DefaultMQProducer producer : beans.values()) {
         producer.setInstanceName(producer.getInstanceName() + "_" + tags);
         log.info("registerSendMessageHook instanceName {} hook {}", producer.getClass().getSimpleName(), grayTagMessageHook.hookName());
         producer.getDefaultMQProducerImpl().registerSendMessageHook(grayTagMessageHook);
     }
 
 }

 

四.消费者接入方式

当前项目中存在两种RocketMQ消费者实现

1. 基于Spring注解方式实现

描述:RocketMQConsumer对象被Spring容器管理,可以在Bean的生命周期中对Bean对象做各种增强

例如

@Slf4j
@Component
@RocketMQMessageListener(topic = StatusRocketMQConstant.CHANNEL_HEART_BEAT_TOPIC,
        consumerGroup = StatusRocketMQConstant.CHANNEL_HEART_BEAT_TOPIC)
public class ChannelHeartBeatConsumer implements RocketMQListener<MessageExt> {
 
    @Override
    public void onMessage(MessageExt message) {
        //。。。。。。。。业务逻辑
    }
 
}

增强方式:RocketMQContainerEnhancer 自动增强

@Component
@Slf4j
public class RocketMQContainerEnhancer implements BeanPostProcessor {
 
    private final RocketMQGrayConsumerManager grayConsumerManager;
 
    public RocketMQContainerEnhancer(RocketMQGrayConsumerManager grayConsumerManager) {
        this.grayConsumerManager = grayConsumerManager;
    }
 
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
 
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof DefaultRocketMQListenerContainer) {
            DefaultMQPushConsumer consumer = ((DefaultRocketMQListenerContainer) bean).getConsumer();
            consumer.getDefaultMQPushConsumerImpl().registerFilterMessageHook(new GrayFilterMessageHook());
            grayConsumerManager.startByGray(consumer);
        }
        return bean;
    }

GrayFilterMessageHook

@Slf4j
@Component
public class GrayFilterMessageHook implements FilterMessageHook {


    @Override
    public String hookName() {
        return "grayFilterHook";
    }

    @Override
    public void filterMessage(FilterMessageContext context) {
        String tags = EnvironmentUtils.getTags();
        //生产流量过滤,没开启灰度模式或者当前是灰度实例无需过滤
        if (!StringUtils.hasText(tags)) {
            if (!EnvironmentUtils.isGrayMode()) {
                return;
            }
            //只消费正常的流量
            List<MessageExt> removeList = context.getMsgList().stream().filter(msg -> msg.getProperties().containsKey(CommonConstants.GRAY_TAG)).collect(Collectors.toList());
            if (!removeList.isEmpty()) {
                context.getMsgList().removeAll(removeList);
            }
            if (log.isDebugEnabled()) {
                log.debug("{} grayMode:{} tags:{} filterMessage:{}", hookName(), EnvironmentUtils.isGrayMode(), tags, removeList.size());
            }
        } else {
            List<MessageExt> removeList = context.getMsgList().stream().filter(msg -> !tags.equals(msg.getProperty(CommonConstants.GRAY_TAG))).collect(Collectors.toList());
            if (!removeList.isEmpty()) {
                context.getMsgList().removeAll(removeList);
            }
            if (log.isDebugEnabled()) {
                log.debug("{} grayTag:{} filterMessage:{}", hookName(), tags, removeList.size());
            }
        }
    }
}

 

posted @ 2025-08-22 20:59  听风是雨  阅读(23)  评论(0)    收藏  举报
/* 看板娘 */