【灰度发布 (四)】RocketMQ公共组件的灰度方案实现
一.问题背景
服务端灰度发布需要支持RocketMQ消息灰度支持,这里对项目中目前存在得RocketMQ接入方式进行整理和说明,明确不同RocketMQ接入方式下得RocketMQ灰度能力支持
二.实现方案
方案选型
| 灰度tags |
通过MQ提供的tag机制过滤, 可以保证灰度的消息只会被灰度节点消费, 改造简单; |
如果Tag参与的业务过滤,不适合该方案; 如果没有灰度节点订阅关系不一致,会出现消息丢失 必须全链路部署灰度实例 |
需要创建两组GROUP |
| user-property 灰度标记 |
SQL92 的过滤规则支持,服务端过滤 改造简单 |
必须全链路部署灰度实例 |
需要创建两组GROUP 确定使用本方案 |
| 灰度quque分区 | 订阅关系一致,不需要额外维护多一套GROUP | 改动大 | 需要改造负责均衡策略 |

三.生产者接入方式
如果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());
}
}
}
}

浙公网安备 33010602011771号