RocketMQ高级原理详解

事务消息
事务消息与消费者关系不大;

代码实现

TransactionListener transactionListener = new TransactionListenerImpl();
        TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
//        producer.setNamesrvAddr("127.0.0.1:9876");
        ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("client-transaction-msg-check-thread");
                return thread;
            }
        });

        producer.setExecutorService(executorService);
        producer.setTransactionListener(transactionListener);
        producer.start();

        String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};
        for (int i = 0; i < 10; i++) {
            try {
                Message msg =
                    new Message("TopicTest", tags[i % tags.length], "KEY" + i,
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.sendMessageInTransaction(msg, null);
                System.out.printf("%s%n", sendResult);

                Thread.sleep(10);
            } catch (MQClientException | UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        for (int i = 0; i < 100000; i++) {
            Thread.sleep(1000);
        }
        producer.shutdown();

使用专有的TransactionMQProducer事务发送者;其他差不多
大体做了什么:定义了一个字符串数组,总共发送10次,每个字符串发送两次;
得到的结果:最先得到两条TagA,然后收到TagC的消息;
原因在producer.setTransactionListener(transactionListener);中
我们本地新建了类TransactionListenerImpl继承自TransactionListener;
并实现方法executeLocalTransaction和checkLocalTransaction;

具体原因:
保证本地事务和mq保持事务一致性,要吗一起成功,要吗一起失败;

举个例子:现在有订单系统(生产者),支付系统(第三方系统),下游服务(消费者)
1、生产者发送 half半消息 至RocketMQ中;
生产者需要确定mq的状态,发送half的半消息,下游服务(消费者)不可见。只是检查mq当前的状态。避免本地执行了事务确发现mq已经挂了。这时mq和本地事务就保持不一致了。
2、RocketMQ回复half消息 至 生产者

3、生产者 执行本地事务
订单系统需要检查本地数据库是否可以执行
4、生产者 返回本地事务状态
把当前数据库的状态发给mq
5、RocketMQ 未确定状态事务进行状态回查
6、生产者 检查本地事务状态
检查第三方系统状态是否正常,支付系统(比如说对接银行了)对账是否可以正常进行。
7、生产者 返回本地事务检查状态。

1、怎么保证half消费不给下游看到。
所谓的half消息,就是普通的消息,放在不同的位置。

如果订单系统,下游服务自己出问题了怎么办?
所以事务消息只保证了分布式事务消息的一半。 性能及吞吐量都会下降。

@Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        String tags = msg.getTags();
        if(StringUtils.contains(tags,"TagA")){
            return LocalTransactionState.COMMIT_MESSAGE;
        }else if(StringUtils.contains(tags,"TagB")){
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }else{
            return LocalTransactionState.UNKNOW;
        }
    }

    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        String tags = msg.getTags();
        if(StringUtils.contains(tags,"TagC")){
            return LocalTransactionState.COMMIT_MESSAGE;
        }else if(StringUtils.contains(tags,"TagD")){
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }else{
            return LocalTransactionState.UNKNOW;
        }
    }

COMMIT_MESSAGE:事务提交
ROLLBACK_MESSAGE:事务回滚
UNKNOW:
在发送消息的时候就会执行监听器的executeLocalTransaction方法;在这里提交了TagA,而回滚了TagB;其他的都是做UNKNOW操作。所以第一次只收到了两个TagA;过了一段时间才执行checkLocalTransaction;所以后面又收到TagC

ACL权限控制
修改配置$ROCKETMQ_HOME/conf/plain_acl.yml文件
配置ip白名单,用户名及密码,这个配置是热加载的。随时配置随时生效;
再修改broker.conf中修改“aclEnable=true” 鉴权规则就生效了;
在生产中用的比较少。知道有这么个东西就好,不详细了解。
springBoot整合
引入相关依赖,添加启动类,添加配置文件;
配置文件:
rocketmq.name-server=192.168.232.128:9876
rocketmq.producer.group=springBootGroup //加了这个可以直接点出我们需要的配置,大部分都是生产者的依赖;
代码实现
使用springboot后,代码实现会变的非常的简单:(这里就举两个典型的案例,其他的太多了)
普通的单向发送代码

 @Resource
    private RocketMQTemplate rocketMQTemplate;

    public void sendMessage(String topic,String msg){
        this.rocketMQTemplate.convertAndSend(topic,msg);
    }

所有的发送者都只需要使用rocketMQTemplate印出来即可,不用记其他的东西,并且像单向发送只要放进目标名和消息就完成了;
发送事务消息:(根据上面那个事务消息来做的)

public void sendMessageInTransaction(String topic,String msg) throws InterruptedException {
        String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};
        for (int i = 0; i < 10; i++) {
            //尝试在Header中加入一些自定义的属性。
            Message<String> message = MessageBuilder.withPayload(msg)
                    .setHeader(RocketMQHeaders.TRANSACTION_ID,"TransID_"+i)
                    //发到事务监听器里后,这个自己设定的TAGS属性会丢失。但是上面那个属性不会丢失。
                    .setHeader(RocketMQHeaders.TAGS,tags[i % tags.length])
                    //MyProp在事务监听器里也能拿到,为什么就单单这个RocketMQHeaders.TAGS拿不到?这只能去调源码了。
                    .setHeader("MyProp","MyProp_"+i)
                    .build();
            String destination =topic+":"+tags[i % tags.length];
            //这里发送事务消息时,还是会转换成RocketMQ的Message对象,再调用RocketMQ的API完成事务消息机制。
            SendResult sendResult = rocketMQTemplate.sendMessageInTransaction(destination, message,destination);
            System.out.printf("%s%n", sendResult);

            Thread.sleep(10);
        }
    }

事务消息的监听器
实现RocketMQLocalTransactionListener接口,封装起来了;也同样实现executeLocalTransaction(执行本地事务)和checkLocalTransaction(检查本地事务)方法;

@RocketMQTransactionListener(rocketMQTemplateBeanName = "rocketMQTemplate")
public class MyTransactionImpl implements RocketMQLocalTransactionListener {

    private ConcurrentHashMap<Object, Message> localTrans = new ConcurrentHashMap<>();
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        Object transId = msg.getHeaders().get(RocketMQHeaders.PREFIX+RocketMQHeaders.TRANSACTION_ID);
        String destination = arg.toString();
        localTrans.put(transId,msg);
        //这个msg的实现类是GenericMessage,里面实现了toString方法
        //在Header中自定义的RocketMQHeaders.TAGS属性,到这里就没了。但是RocketMQHeaders.TRANSACTION_ID这个属性就还在。
        //而message的Header里面会默认保存RocketMQHeaders里的属性,但是都会加上一个RocketMQHeaders.PREFIX前缀
        System.out.println("executeLocalTransaction msg = "+msg);
        //转成RocketMQ的Message对象
        org.apache.rocketmq.common.message.Message message = RocketMQUtil.convertToRocketMessage(new StringMessageConverter(),"UTF-8",destination, msg);
        String tags = message.getTags();
        if(StringUtils.contains(tags,"TagA")){
            return RocketMQLocalTransactionState.COMMIT;
        }else if(StringUtils.contains(tags,"TagB")){
            return RocketMQLocalTransactionState.ROLLBACK;
        }else{
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }
    //延迟检查的时间间隔要有点奇怪。
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        String transId = msg.getHeaders().get(RocketMQHeaders.PREFIX+RocketMQHeaders.TRANSACTION_ID).toString();
        Message originalMessage = localTrans.get(transId);
        //这里能够获取到自定义的transaction_id属性
        System.out.println("checkLocalTransaction msg = "+originalMessage);
        //获取标签时,自定义的RocketMQHeaders.TAGS拿不到,但是框架会封装成一个带RocketMQHeaders.PREFIX的属性
//        String tags = msg.getHeaders().get(RocketMQHeaders.TAGS).toString();
        String tags = msg.getHeaders().get(RocketMQHeaders.PREFIX+RocketMQHeaders.TAGS).toString();
        if(StringUtils.contains(tags,"TagC")){
            return RocketMQLocalTransactionState.COMMIT;
        }else if(StringUtils.contains(tags,"TagD")){
            return RocketMQLocalTransactionState.ROLLBACK;
        }else{
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }
}

需要注意的点:
1、使用springboot发送消息后,消息的格式不再是我们mq的msg的格式了而是org.springframework.messaging的格式;
2、(rocketMQTemplate.convertAndSend(topic,msg);)topic不再是我们之前mq中单纯的topic,目标名的概念;而是融合了tag和topic连接在了一起;String destination =topic+":"+tags[i % tags.length];以冒号连接在一起了;
3、key属性也放在了Header中,Header是头消息;
4、事务消息中每一个监听器需要指定一个rocketMQTemplate,而我们的rocketMQTemplate就只有一个,但是springboot给我们提供了一种方式用于添加新的rocketMQTemplate;
@RocketMQTransactionListener(rocketMQTemplateBeanName = “rocketMQTemplate”)

添加新的rocketMQTemplate 扩展一个即可;

@ExtRocketMQTemplateConfiguration()
public class ExtRocketMQTemplate extends RocketMQTemplate {
}

消费者
这个MyConsumerGroup就是上面配置文件配置的
TestTopic就是目标名

@Component
@RocketMQMessageListener(consumerGroup = "MyConsumerGroup", topic = "TestTopic",consumeMode= ConsumeMode.CONCURRENTLY)
public class SpringConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String message) {
        System.out.println("Received message : "+ message);
        //业务逻辑处理
    }
}

消费者中所有的属性都在RocketMQMessageListener注解中,我们都可以在注解中指定即可;这些属性很重要,但是随时都在变。对消息样例能理解,需要什么版本再去了解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值