基于RabbitMq实现可回复消息!

2,290 阅读6分钟

Rabbitmq-friend是我开发的一个工具。 基于Rabbitmq client封装了可回复,可重试,可延迟等多种类型消息,客户端可以使用三种类型消息,可轻松实现rpc,重试队列,延迟任务等业务场景

Github地址 github.com/yuhaqiang12…

1.Hello world实现

1.1 几个基础概念

1.交换器,队列,路由键,生产者,消费者

生产者发送消息到指定交换器,根据Rabbitmq根据消息路由键路由到指定队列,消费者绑定到指定的队列,消费该队列消息

BaseExchange :交换器,在系统初始化时需要声明交换器。

BaseQueue: 队列,在系统初始化时,声明队列,并且按照指定路由键将队列绑定到交换器

RoutingKey: 发送消息时需要指定路由键,同时绑定队列到交换器也需要路由键

ProducerCompositor:生产者

2.消息类型

Message:普通消息类型,所有的消息必须继承自Message

NeedReplyMessage 发送该消息类型可通过注册回调接口收到该消息的回复

RetriableMessage 该类型消息提供重试操作,可通过重试完成某些容错逻辑

DefferedMessage 该类型消息可用于延迟队列实现,延迟某一消息处理

可重试,可延迟消息类型,只需要实现以上两个接口即可,可回复消息需要继承NeedReplyMessage

3.Example例子

3.1 定义TestCase基类

public class BaseTest {
    protected RabbitContext context;
    
    protected RabbitConfiguration configuration;

    protected Logger logger = LogManager.getLogger(this.getClass());

    protected BaseExchange exchange;

    protected BaseQueue queue;

    protected RoutingKey routingKey;

    protected Gson gson = GsonUtil.getGson();

    protected CountDownLatch latch = new CountDownLatch(1);

    // 停止线程
    protected void stop() {
        latch.countDown();
    }

    //等待
    protected void waitStop() {
        try {
            latch.await();
        } catch (Exception e) {

        }
    }

    /***
    * 测试Case通用代码在所有的Case之前执行
    *
    **/
    @Before
    public void setup() {
        /****
        * 配置Rabbitmq 基本信息,用户名密码,server ip,channel pool size
        *  默认回复队列名称
        ***/
        configuration = new RabbitConfiguration();
        RabbitFriendUtilExtension extension = new RabbitFriendUtilExtension();
        configuration.setUuidGenerator(extension);
        configuration.setUsername("muppet");
        configuration.setPassword("muppet");
        configuration.setIps(new String[]{"127.0.0.1"});
        configuration.setChannelPoolSize(20);
        configuration.setDefaultReplyToQueue("DefaultReplyQueue");

        /***
         * 通过RabbitContext 注册Producer,Consumer
         */
        context = configuration.getRabbitContext();
        context.start();

        /***
        * 定义交换器
        */
        exchange = new BaseExchange("BaseExchange", ExchangeType.topic);
        context.declareExchange(exchange);

        /****
        * 定义队列
        **/
        queue = context.declareQueueIfAbsent("TestQueue");
        
        /***
        * 定义路由键,并且使用队列名作为路由键绑定到交换器
        **/
        routingKey = new RoutingKey("TestQueue");
        context.bind(exchange, queue, routingKey);
    }


}

3.2 测试NeedReplyMessage消息
public class TestNeedReplyMessage extends BaseTest {

    @Test
    public void testProducer() {
        /**
         * 定义生产者
         */
        ProducerCompositor producerCompositor = context.createProducer(exchange);
        producerCompositor.start();

        /**
         * 可回复消息
         */
        BaseNeedReplyMessage base = new BaseNeedReplyMessage();
        base.setRoutingkey("TestQueue");
        base.a = new A();
        //发送消息,并声明回调
        producerCompositor.send(base, new AsyncMessageReplyCallback(null) {
            @Override
            public void run(MessageReply r) {
                logger.debug(gson.toJson(r));
                stop();
            }
        });
        waitStop();
    }

    @Test
    public void testConsumer() {
        /**
         * 注册消费者,需要指定消费的队列
         */
        context.registerConsumer(new ConsumerCompositor(context) {
            @Override
            public String getQueueName() {
                return queue.getName();
            }

            @Override
            public void handle(Message message) {
                BaseNeedReplyMessage baseNeedReplyMessage = message.cast();
                logger.debug(gson.toJson(baseNeedReplyMessage));
                //回复该消息,回复消息类型为MessageReply类型
                baseNeedReplyMessage.reply(new BaseMessageReply());
                stop();
            }
        });
        waitStop();

    }

    class A {
        public String res = "RESULT";
    }

    /**
     * 可回复消息
     */
    class BaseNeedReplyMessage extends NeedReplyMessage {
        public A a;
    }

    /**
     * 消息回复
     */
    class BaseMessageReply extends MessageReply {
        public String reply = "reply";
    }

}

3.3 测试RetriableMessage

public class TestRetryMessage extends BaseTest {


    @Test
    public void testProducer() {
        ProducerCompositor producerCompositor = context.createProducer(exchange);
        producerCompositor.start();

        BaseRetryMessage message = new BaseRetryMessage();
        message.setRoutingkey(routingKey.getRoutingKey());
        producerCompositor.send(message);
    }


    @Test
    public void testConsumer() {
        context.registerConsumer(new ConsumerCompositor(context) {
            @Override
            public String getQueueName() {
                return queue.getName();
            }

            @Override
            public void handle(Message message) {
                BaseRetryMessage baseRetryMessage = message.cast();
                //获取当前的重试次数
                logger.debug("current retry times[{}]", baseRetryMessage.getCurrentRetryTimes());

                //如果当前重试次数小于最大重试次数则重试,也可以无限重试
                if (baseRetryMessage.getCurrentRetryTimes() < baseRetryMessage.getMaxRetryTimes()) {
                    logger.debug(" retry message[{}]", gson.toJson(baseRetryMessage));
                    baseRetryMessage.retry();
                } else {
                    logger.debug("succeed to handle this message");
                    stop();
                }
            }
        });
        waitStop();
    }

}

//重试消息类型
class BaseRetryMessage extends Message implements RetriableMessage {

    private String name = "Base retry message";

    @Override
    public Integer getMaxRetryTimes() {
        return 4;
    }

    @Override
    public Integer getRetryInterval() {
        return 5000;
    }
}

3.4 可延迟类型消息
public class TestDefferedMessage extends BaseTest {

    @Test
    public void testProducer() {
        ProducerCompositor producerCompositor = context.createProducer(exchange);
        producerCompositor.start();

        for (int i = 0; i < 100; i++) {
            BaseDefferedMessage message = new BaseDefferedMessage();
            message.setRoutingkey(routingKey.getRoutingKey());
            producerCompositor.send(message);
        }
    }

    @Test
    public void testConsumer() {
        context.registerConsuimerCompositor(new ConsumerCompositor(context) {
            @Override
            public String getQueueName() {
                return queue.getName();
            }

            @Override
            public void handle(Message message) {
                logger.debug("time interval:{}", (System.currentTimeMillis() - message.getBasicProperties().getTimestamp().getTime()) / 1000);
                //stop();
            }
        }, 20);
        waitStop();
    }

}

class BaseDefferedMessage extends Message implements DefferedMessage {

    private String name = "延迟消息";

    @Override
    public Integer getDefferedTime() {
        return 10000;
    }
}

3.5 三种类型消息可随意组合

class ARetrableMessage extends NeedReplyMessage implements RetriableMessage, DefferedMessage {


    public ARetrableMessage(String name) {
        this.name = name;
    }

    //@Override
    public TimeoutMessage setTimeout(Long timeout) {
        return this;
    }

    private String name = "组合消息类型";

    @Override
    public Long getTimeout() {
        return 100000L;
    }

    @Override
    public Integer getMaxRetryTimes() {
        return 3;
    }

    @Override
    public Integer getRetryInterval() {
        return 5000;
    }

    @Override
    public Integer getDefferedTime() {
        return 10000;
    }
}

Note

  1. 消息的属性赋值时不能被赋值为匿名内部类型,目前还不能对这种类型反序列化

我的开源项目

最后夹带一点私货,五阳最近花了3个月的时间完成一个开源项目。

开源3周以来,已有近 230 多个关注和Fork

Gitee:gitee.com/juejinwuyan…

GitHub github.com/juejin-wuya…

开源平台上有很多在线商城系统,功能很全,很完善,关注者众多,然而实际业务场景非常复杂和多样化,开源的在线商城系统很难完全匹配实际业务,广泛的痛点是

  • 功能堆砌,大部分功能用不上,需要大量裁剪;
  • 逻辑差异点较多,需要大量修改;
  • 功能之间耦合,难以独立替换某个功能。

由于技术中间件功能诉求较为一致,使用者无需过多定制化,技术中间件开源项目以上的痛点不明显,然而电商交易等业务系统虽然通用性较多,但各行业各产品的业务差异化极大,所以导致以上痛点比较明显

所以我在思考,有没有一个开源系统,能提供电商交易的基础能力,能让开发者搭积木的方式,快速搭建一个完全契合自己业务的新系统呢?

  • 他们可以通过编排和配置选择自己需要的功能,而无需在一个现成的开源系统上进行裁剪
  • 他们可以轻松的新增扩展业务的差异化逻辑,不需要阅读然后修改原有的系统代码!
  • 他们可以轻松的替换掉他们认为垃圾的、多余的系统组件,而不需要考虑其他功能是否会收到影响

开发者们,可以择需选择需要的能力组件,组件中差异化的部分有插件扩展点能轻松扩展。或者能支持开发者快速的重新写一个完全适合自己的新组件然后编排注册到系统中?

memberclub 就是基于这样的想法而设计的。 它的定位是电商类交易系统工具箱, 以SDK方式对外提供通用的交易能力,能让开发者像搭积木方式,从0到1,快速构建一个新的电商交易系统!

image.png

具体介绍可参见

Gitee开源地址gitee.com/juejinwuyan…

GitHub开源地址 : github.com/juejin-wuya…

在这个项目中你可以学习到 SpringBoot 集成 以下框架或组件。

  1. Mybatis、Mybatis-plus 集成多数据源
  2. Sharding-jdbc 多数据源分库分表
  3. redis/redisson 缓存
  4. Apollo 分布式配置中心
  5. Spring Cloud 微服务全家桶
  6. RabbitMq 消息队列
  7. H2 内存数据库
  8. Swagger + Lombok + MapStruct

同时你也可以学习到以下组件的实现原理

  1. 流程引擎的实现原理
  2. 扩展点引擎实现原理
  3. 分布式重试组件实现原理
  4. 通用日志组件实现原理 参考:juejin.cn/post/740727…
  5. 商品库存实现原理: 参考:juejin.cn/post/731377…
  6. 分布式锁组件: 参考:
  7. Redis Lua的使用
  8. Spring 上下文工具类 参考: juejin.cn/post/746927…