RocketMQ学习到使用

一、下载安装RocketMQ

1、下载地址

Release Notes - Apache RocketMQ - Version 4.9.2 | RocketMQ

 2、解压

上传到对应的文件夹下,并解压unzip rocketmq-all-4.9.2-bin-release.zip,若没有unzip命令则下载安装一个 yum install unzip 

解压后文件目录:

 3、配置环境变量

修改环境变量: vim /etc/profile

在文件末尾加上: export NAMESRV_ADDR=阿里云公网IP:9876

我这边已经配置好了,查看看一下效果:

4、若服务器配置不够则修改nameServer运行脚本(服务器配置够则跳过4、5)

进入bin目录下,修改runserver.sh文件,将71行和76行的Xms和Xmx等改小一点

vim runserver.sh

保存退出

5、修改broker的运行脚本

还是在bin目录下修改runbroker.sh文件,修改67行

 保存退出

6、修改broker的配置文件

在conf的目录下,修改broker.conf文件,加入三行配置

#nameSrv地址

namesrvAddr=阿里云公网IP:9876 

#自动创建主题,不然需要手动创建出来

autoCreateTopicEnable=true

#broker也需要一个公网ip,如果不指定,那么是阿里云的内网地址,我们再本地无法连接使用

brokerIP1=阿里云公网IP

效果如下:

traceTopicEnable=true  ---表示开启消息轨迹
aclEnable=true    ---开启账号密码登录,对应的账号密码已经白名单在conf目录下的plain_acl.yml中配置及查看

7、启动

若想要log日志文件,那么就创建一个logs文件夹用于存放日志文件(不要日志文件就跳过当前步骤)

请按照顺序启动,先启动nameSrv在启动broker

启动nameSrv:

nohup sh bin/mqnamesrv > ./logs/namesrv.log &

nohup sh bin/mqnamesrv  & (若不要日志文件则使用此命令)

启动broker:这里的-c是指定使用的配置文件

nohup sh bin/mqbroker -c conf/broker.conf > ./logs/broker.log &

nohup sh bin/mqbroker -c conf/broker.conf  &(若不要日志文件则使用此命令)

查看启动效果:jps -l

 8、安装可视化工具

下载地址:https://github.com/apache/rocketmq-dashboard/archive/refs/tags/rocketmq-dashboard-1.0.0.zip

运行启动:在jar包所在目录,否则jar包目录地址需修改,这里启动时需要修改启动配置

nohup java -jar ./rocketmq-dashboard-1.0.0.jar --server.port=8001 --rocketmq.config.namesrvAddr=公网ip:9876 > ./rocketmq-4.9.3/logs/dashboard.log &

命令拓展:

--server.port指定运行的端口

--rocketmq.config.namesrvAddr=公网ip:9876 指定namesrv地址

可视化工具访问地址:公网ip:8001

二、在spring-boot项目中的使用

1、初步使用rocketMQ,创建一个生产者一个消费者项目

2、修改对应配置文件

#生产者配置文件

rocketmq:
  name-server: 公网ip:9876     # rocketMq的nameServer地址
  producer:
    group: lkx-producer-group        # 生产者组别
    enable-msg-trace: true     #开启发送方的消息轨迹

 #消费者配置文件

server:
  port: 8088
rocketmq:
  name-server: 公网ip:9876     # rocketMq的nameServer地址

 3、引入RocketMQ的maven

<!-- rocketmq的依赖 -->
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

 4、生产者用test生产消息,如下部分示例

@SpringBootTest
class DemoRockerMqProducerApplicationTests {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Test
    public void testSimpleMessage() throws Exception{
        SendResult sendResult = rocketMQTemplate.syncSend("simpleTopic", "我是一个简单的消息");
        System.out.println(sendResult);
    }

    @Test
    public void testASyncMessage() throws Exception{
        rocketMQTemplate.asyncSend("asyncTopic-boot", "我是一个异步消息",new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("异步消息发送成功!");
            }

            @Override
            public void onException(Throwable e) {
                System.out.println("异步消息发送失败!");
            }
        });
        System.out.println("我先执行!");
        System.in.read();
    }


    @Test
    public void testOrderlyMessage() throws Exception{
        List<Order> orderList = Arrays.asList(
                new Order(1, 111, 59D, new Date(), "下订单"),
                new Order(2, 111, 59D, new Date(), "物流"),
                new Order(3, 111, 59D, new Date(), "签收"),
                new Order(4, 112, 89D, new Date(), "下订单"),
                new Order(5, 112, 89D, new Date(), "物流"),
                new Order(6, 112, 89D, new Date(), "拒收")
        );

        for (Order order : orderList) {
            SendResult sendResult = rocketMQTemplate.syncSendOrderly("orderlyTopic-boot", JSON.toJSON(order), order.getOrderNumber().toString());
            System.out.println("消息:"+JSON.toJSON(order)+",发送成功! ");
        }
    }

    @Test
    public void testTagMessage() throws Exception {
        rocketMQTemplate.syncSend("tagTopic-boot:tagA","我是一个带标签A的消息!");
        rocketMQTemplate.syncSend("tagTopic-boot:tagB","我是一个带标签B的消息!");
        System.out.println("带标签的消息发送成功!");
    }

    @Test
    public void testKeyMessage() throws Exception {
        Message<String> message = MessageBuilder.withPayload("我是一个带key的消息!")
                .setHeader(RocketMQHeaders.KEYS, "key1")
                .build();
        rocketMQTemplate.syncSend("keyTopic-boot",message);
        System.out.println("带key的消息发送成功!");
    }

    
    /** 开启轨迹则需要在配置文件中加上,enable-msg-trace: true  */

    @Test
    public void testTraceMessage() throws Exception{
        rocketMQTemplate.syncSend("traceTopic-boot","我是一个带轨迹的消息");
        System.out.println("带key的消息发送成功!");
    }
}

5、消费者消费消息


码示例:

@Component
@RocketMQMessageListener(topic = "keyTopic-boot",consumerGroup = "key-boot-group")
public class KeyListener implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt message) {
        System.out.println(message.getKeys());
        System.out.println(message.getMsgId());
        System.out.println("消费了消息:"+new String(message.getBody()));
    }
}


@Component
@RocketMQMessageListener(topic = "orderlyTopic-boot",consumerGroup = "orderly-boot-group",consumeMode = ConsumeMode.ORDERLY)
public class OrderlyListener implements RocketMQListener<MessageExt> {

    @Override
    public void onMessage(MessageExt message) {
        System.out.println("消费了消息:"+ JSON.parse(new String(message.getBody())));
    }
}


@Component
@RocketMQMessageListener(topic = "simpleTopic",consumerGroup = "simple-consumer-group")
public class SimpleListener implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt message) {
        System.out.println("simpleTopic主题开始消费:");
        System.out.println(message.getMsgId());
        System.out.println(new String(message.getBody()));
        System.out.println("消费结束");
    }
}


@Component
@RocketMQMessageListener(topic = "tagTopic-boot",
        consumerGroup = "tagA-boot-group",
        selectorType = SelectorType.TAG,
        selectorExpression = "tagA"
)
public class TagAListener implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt message) {
        System.out.println(message.getMsgId());
        System.out.println("消费了带标签的消息:"+new String(message.getBody()));
    }
}



@Component
@RocketMQMessageListener(topic = "tagTopic-boot",
        consumerGroup = "tagB-boot-group",
        selectorType = SelectorType.TAG,
        selectorExpression = "tagB"
)
public class TagBListener implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt message) {
        System.out.println(message.getMsgId());
        System.out.println("消费了带标签的消息:"+new String(message.getBody()));
    }
}


@Component
@RocketMQMessageListener(topic = "traceTopic-boot",
        consumerGroup = "trace-boot-group",
        enableMsgTrace = true
)
public class TraceListener implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt message) {
        System.out.println(message.getMsgId());
        System.out.println("消费了带轨迹的消息:"+new String(message.getBody()));
    }
}

三、结合秒杀场景中使用RocketMQ

1、秒杀场景架构图

2、创建两个项目

创建一个接受用户请求并生产消息的demo-seckill

创建一个消费秒杀消息的消费者项目demo-seckill-service项目

 

3、创建数据库

因为是demo所以只需要一张商品表,一张订单记录表。这里自行创建。并且实体已经实现类相关内容也自行创建

4、配置配置文件

demo-seckill:

server:
  port: 8001
rocketmq:
  name-server: 公网ip:9876     # rocketMq的nameServer地址
  producer:
    group: lkx-producer-group        # 生产者组别
    enable-msg-trace: true     #开启发送方的消息轨迹
    access-key: rocketmq2
    secret-key: 12345678
spring:
  redis:
    host: localhost
    port: 6379
    password:
    database: 1

demo-seckill-service:

server:
  port: 8002
rocketmq:
  name-server: 公网ip:9876     # rocketMq的nameServer地址
  consumer:
    enable-msg-trace: true     #开启发送方的消息轨迹
    access-key: rocketmq2
    secret-key: 12345678
spring:
  redis:
    host: localhost
    port: 6379
    password:
    database: 1
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
mybatis:
  mapper-locations: classpath:mapper/*.xml

5、代码

在demo-seckill中创建一个controller接受用户请求:

@RestController
public class SeckillController {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    AtomicInteger ai = new AtomicInteger(0);
    
    @GetMapping("/seckill")
    public String seckill(/*Integer userId,*/Integer goodsId){
        int userId = ai.incrementAndGet();
        String uk = userId + "_" + goodsId;
        //判断库存
        String s = redisTemplate.opsForValue().get("SECKILL_GOODSID:" + goodsId);
        Integer stocks = Integer.valueOf(redisTemplate.opsForValue().get("SECKILL_GOODSID:" + goodsId));
        if(stocks < 1){
            System.out.println("库存不足,抢购失败!");
            return "库存不足,抢购失败!";
        }

        //判断对应商品是否重复抢购
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent("uk:"+uk, "");
        if(!ifAbsent){
            System.out.println("每个用户对应商品只能秒杀一件!");
            return "每个用户对应商品只能秒杀一件!";
        }
        
        //redis库存减少一件
        redisTemplate.opsForValue().decrement("SECKILL_GOODSID:" + goodsId);

        //往mq中存一个消息,等待消费者消费
        rocketMQTemplate.asyncSend("secKillTopic", uk, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("发送成功!");
            }

            @Override
            public void onException(Throwable e) {
                System.out.println("发送失败:"+e.getMessage());
                System.out.println("操作失败。用户id:"+userId + " 商品id:"+goodsId);
            }
        });

        return "秒杀成功,稍后请到订单中查看!";
    }
}

 在demo-seckill-service中写消费者监听器去消费秒杀mq的消息:

@Component
@RocketMQMessageListener(topic = "secKillTopic",
        consumerGroup = "secKill-consumer-group",
        consumeMode = ConsumeMode.CONCURRENTLY,
        consumeThreadNumber = 40
        )
public class SecKillListener implements RocketMQListener<MessageExt> {

        @Autowired
        private ISecKillService secKillService;

        @Override
        public void onMessage(MessageExt message) {
                String uk = new String(message.getBody());
                String[] split = uk.split("_");
                synchronized (this){
                        try {
                                secKillService.seckillGoods(split[0],split[1]);
                        } catch (Exception e) {
                                throw new RuntimeException(e);
                        }
                }
        }
}
@Service
public class SecKillServiceImpl implements ISecKillService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private GoodsMapper goodsMapper;

    @Autowired
    private OrderRecordsMapper orderRecordsMapper;

    @Override
    @Transactional
    public void seckillGoods(String userId, String goodsId) throws Exception {
        //redis 自旋锁
        while (true){
            //redis 加锁
            Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock_seckill:" + goodsId, "", Duration.ofSeconds(60L));
            if(flag){
                try {
                    //商品库存减一
                    int i = goodsMapper.updateGoodsStocksByGoodsId(Integer.valueOf(goodsId));
                    if(i == 1){ //商品库存减一成功
                        //添加订单
                        OrderRecords order = OrderRecords.builder().goodsId(Integer.valueOf(goodsId)).userId(Integer.valueOf(userId)).createTime(new Date()).orderSn(UUID.randomUUID().toString()).build();
                        orderRecordsMapper.insert(order);
                    }else {
                        System.out.println("商品库存更新失败");
                    }
                    return;
                }finally {
                    redisTemplate.delete("lock_seckill:" + goodsId);
                }
            }else {
                try {
                    Thread.sleep(50L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

 更新的时候加上行锁,保证更新时候的原子性。或者加上for update来保证sql的原子性也行

<update id="updateGoodsStocksByGoodsId">
        update goods set stocks = stocks - 1 where id = #{goodsId} and stocks - 1 >= 0
    </update>

  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lin_XXiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值