基于rocketMQ延时队列+redis分布式锁 实现自动支付

业务需求:默认情况下符合条件的订单都需要进行人工支付,耗费大量时间成本,产生需求:在指定时间内对符合支付条件的订单进行系统自动支付
实现思路:
rocketMQ 生成消息时,定义消息队列为延时队列,指定时间。
分布式情况下可能多台服务器同时执行产生消息,所以消费消息时,需要通过redis 分布式锁来保证同一时刻,只有一台服务器在进行执行消费消息的操作,并通过业务查询判断是否已经支付成功,今在为消费成功的情况下,才进行消费,从而解决消息的重复消费幂等性。

yml

rocketmq:
  # 是否开启自动配置
  isEnable: true
  # nameserver 地址
  namesrvAddr: 172.16.170.70:9876
  # 设置一次消费消息的条数,默认为 1 条
  consumerMessageBatchMaxSize: 1
  # 消费者线程数量
  consumerThreadMax: 32
  consumerThreadMin: 5
  # 最大消费重试次数
  maxReconsumeTimes: 3
  # 发送同一类消息设置为同一个 group,保证唯一默认不需要设置,rocketmq 会使用 ip@pid(pid代表 jvm 名字)作为唯一标识
  groupName: dipaoGroup
  # 消息最大长度 默认 1024*4(4M)
  producerMaxMessageSize: 4096
  # 发送消息失败重试次数,默认 2
  producerRetryTimesWhenSendFailed: 2
  # 发送消息超时时间,默认 3000
  producerSendMsgTimeOut: 3000
  autoPay:
    topic: autoPay
    # 最大消费重试次数
    maxReconsumeTimes: 3
    groupName: autoPayGroup

RocketMQ-延时消息Demo及实现原理分析:
https://blog.csdn.net/hosaos/article/details/90577732

RocketMQConfiguation :

@Configuration
@EnableConfigurationProperties({RocketMQProperties.class})
@ConditionalOnProperty(prefix = "rocketmq", value = "isEnable", havingValue = "true")
@Slf4j
public class RocketMQConfiguation implements InitializingBean {

    @Autowired
    private RocketMQProperties properties;

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 注入一个默认的生产者
     *
     * @return
     * @throws MQClientException
     */
    @Bean
    public DefaultMQProducer getRocketMQProducer() throws MQClientException {
        if (StringUtils.isEmpty(properties.getGroupName())) {
            throw new MQClientException(-1, "groupName is blank");
        }

        if (StringUtils.isEmpty(properties.getNamesrvAddr())) {
            throw new MQClientException(-1, "nameServerAddr is blank");
        }
        DefaultMQProducer producer;
        producer = new DefaultMQProducer(properties.getGroupName());

        producer.setNamesrvAddr(properties.getNamesrvAddr());
        // producer.setCreateTopicKey("AUTO_CREATE_TOPIC_KEY");

        // 如果需要同一个 jvm 中不同的 producer 往不同的 mq 集群发送消息,需要设置不同的 instanceName
        // producer.setInstanceName(instanceName);
        producer.setMaxMessageSize(properties.getProducerMaxMessageSize());
        producer.setSendMsgTimeout(properties.getProducerSendMsgTimeout());
        // 如果发送消息失败,设置重试次数,默认为2次
        producer.setRetryTimesWhenSendFailed(properties.getProducerRetryTimesWhenSendFailed());

        try {
            producer.start();
            log.info("producer is start,groupName:{},namesrvAddr:{}", properties.getGroupName(), properties.getNamesrvAddr());
        } catch (MQClientException e) {
            log.error(String.format("producer is error {}", e.getMessage(), e));
            throw e;
        }
        return producer;
    }

    /**
     * SpringBoot 启动时加载所有消费者
     */
    @Override
    public void afterPropertiesSet() {
        Map consumers = applicationContext.getBeansOfType(AbstractRocketConsumer.class);
        if (consumers == null || consumers.size() == 0) {
            log.info("init rocket consumer 0");
        }
        Iterator beans = consumers.keySet().iterator();
        while (beans.hasNext()) {
            String beanName = (String) beans.next();
            AbstractRocketConsumer consumer = consumers.get(beanName);
            consumer.init();
            createConsumer(consumer);
            log.info("init success consumer title {} , topics {} , tags {}", consumer.consumerTitle,
                    consumer.topics, consumer.tags);
        }
    }

    /**
     * 通过消费者信息创建消费者
     */
    public void createConsumer(AbstractRocketConsumer arc) {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(arc.groupName);
        consumer.setNamesrvAddr(this.properties.getNamesrvAddr());
        consumer.setConsumeThreadMin(this.properties.getConsumerThreadMin());
        consumer.setConsumeThreadMax(this.properties.getConsumerThreadMax());
        consumer.registerMessageListener(arc.getMessageListener());
        consumer.setMaxReconsumeTimes(this.properties.getMaxReconsumeTimes());
        /**
         * 设置 Consumer 第一次启动是从队列头部开始消费还是队列尾部开始消费 如果非第一次启动,那么按照上次消费的位置继续消费
         */
        // consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        /**
         * 设置消费模型,集群还是广播,默认为集群
         */
        // consumer.setMessageModel(MessageModel.CLUSTERING);

        /**
         * 设置一次消费消息的条数,默认为 1 条
         */
        consumer.setConsumeMessageBatchMaxSize(this.properties.getConsumerMessageBatchMaxSize());
        try {
            consumer.subscribe(arc.topics, arc.tags);
            consumer.start();
            arc.mqPushConsumer = consumer;
        } catch (MQClientException e) {
            log.error("info consumer title {}", arc.getConsumerTitle(), e);
        }

    }
}

ConfigurationProperties:

@Data
@ConfigurationProperties(prefix = "rocketmq")
public class RocketMQProperties {
    private boolean isEnable = false;
    private String namesrvAddr = "localhost:9876";
    private String groupName = "default";
    private int producerMaxMessageSize = 1024;
    private int producerSendMsgTimeout = 2000;
    private int producerRetryTimesWhenSendFailed = 2;
    private int consumerThreadMin = 5;
    private int consumerThreadMax = 30;
    private int consumerMessageBatchMaxSize = 1;
    private int maxReconsumeTimes = 3;
}


生产者:
@Service
@Slf4j
public class RocketProducer {

    @Autowired
    private DefaultMQProducer defaultMQProducer;

    public void sendMessage(String msg, String topics, String tags, String key,int delayTimeLevel) {
        try {
            Message message = new Message(topics, tags, key, msg.getBytes(RemotingHelper.DEFAULT_CHARSET));
            if (delayTimeLevel > 0){
                message.setDelayTimeLevel(delayTimeLevel);
            }
            // 发送消息到一个 Broker
            SendResult sendResult = defaultMQProducer.send(message);
            // 通过 sendResult 返回消息是否成功送达
            log.info("发送MQ消息:" + sendResult.toString());
        } catch (Exception e) {
            log.error("发送MQ消息异常:" + e.getMessage(), e);
        }
    }

    public void sendMessage(String msg, String topics) {
        sendMessage(msg, topics, "*", "", 0);
    }

    public void sendMessage(String msg, String topics, String key) {
        sendMessage(msg, topics, "*", key, 0);
    }

    public void sendMessage(String msg, String topics, String key, int delayTimeLevel) {
        sendMessage(msg, topics, "*", key, delayTimeLevel);
    }
}

RocketProducer 生产者生产消息

// 当前订单所属基地需要进行自动支付尾款 并且未进行人工支付 才进行系统自动支付
DriverConfigEntity autoPayPreAmountBase = driverConfigDao.findOneByKey("autoPayPreAmountBase");
boolean autoPayPreAmountFlag = Objects.nonNull(autoPayPreAmountBase) && autoPayPreAmountBase.getValue().contains(orderEntity.getCustomerFullName())
        && DriverPayStatusEnum.PAY_SUCCESS.getCode() != orderEntity.getPrePayStatus();
if (autoPayPreAmountFlag) {
    int delayTimeLevel = 0;
    // 设置消息延时等级
    DriverConfigEntity delayTimeLevelSetting = driverConfigDao.findOneByKey("delayTimeLevel");
    if (Objects.nonNull(delayTimeLevelSetting)) {
        delayTimeLevel = Integer.valueOf(delayTimeLevelSetting.getValue());
    }
    log.info("===生产消息===autoPayOrderPreAmount===系统自动支付司机id为[{}]订单号为[{}]的预付款", driverId, orderNo);
    rocketProducer.sendMessage(orderNo, topics, "", delayTimeLevel);
}

消费者父接口:

public interface RocketConsumer {
    /**
     * 初始化消费者
     */
    void init();
    /**
     * 注册监听
     * @param messageListener
     */
    void registerMessageListener(MessageListener messageListener);
}


加载消费者基本信息
@Data
public abstract class AbstractRocketConsumer implements RocketConsumer {

    protected String topics;
    protected String tags;
    protected MessageListener messageListener;
    protected String consumerTitle;
    protected MQPushConsumer mqPushConsumer;
    protected String groupName;

    /**
     * 必要的信息
     *
     * @param topics
     * @param tags
     * @param consumerTitle
     */
    public void necessary(String topics, String tags, String consumerTitle,String groupName) {
        this.topics = topics;
        this.tags = tags;
        this.consumerTitle = consumerTitle;
        this.groupName = groupName;
    }

    @Override
    public abstract void init();

    @Override
    public void registerMessageListener(MessageListener messageListener) {
        this.messageListener = messageListener;
    }

}

消费者消费消息

@Component
@Slf4j
@Getter
public class AutoPayAmountConsumerMQ extends AbstractRocketConsumer {

    @Value("${rocketmq.autoPayAmount.topic}")
    public String topic;
    @Value("${rocketmq.autoPayAmount.groupName}")
    public String groupName;
    @Autowired
    private MainOrderDao mainOrderDao;
    @Autowired
    private RedisLocker redisLocker;
    @Autowired
    private OrderPayV2Service orderPayService;
    @Autowired
    private OperationLogService operationLogService;
    @Autowired
    private TransactionManager transactionManager;
    private static final String AUTO_PAY_ORDER_PRE_AMOUNT = "autoPayOrderPreAmount:";

    @Override
    public void init() {
        // 设置主题,标签与消费者标题
        super.necessary(topic, "*", "报警error日志消息", groupName);
        // 消费者具体执行逻辑
        registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            // 一次消费一条消息
            MessageExt msg = msgs.get(0);
            String orderNo = new String(msg.getBody());
            // 获取redis分布式锁
            String lockKey = AUTO_PAY_ORDER_PRE_AMOUNT + orderNo;
            String lockValue = redisLocker.lock(lockKey);
            try {
                // 锁判断
                if (StringUtils.isNotBlank(lockValue)) {
                    MainOrderEntity mainOrderEntity = mainOrderDao.findByOrderNo(orderNo, Lists.newArrayList("id", "orderNo", "prePayStatus", "orderStatus"));
                    // 订单未支付预付款并且未取消才进行支付预付款
                    boolean autoPayOrderPreAmountFlag = Objects.nonNull(mainOrderEntity) && mainOrderEntity.getOrderStatus() != OrderStatusEnum.CANCELED.getCode()
                            && DriverPayStatusEnum.PAY_SUCCESS.getCode() != mainOrderEntity.getPrePayStatus();
                    if (autoPayOrderPreAmountFlag) {
                        Resp resp = orderPayService.checkOrderPay(FeeItemTypeEnum.PRE_PAY, mainOrderEntity.getId());
                        if (resp.hasSuccess()) {
                            Pair pair = (Pair) resp.getData();
                            transactionManager.doInTransaction(() -> {
                                // 订单支付
                                // 订单支付日志
                                // 修改订单支付方式
                                // 设置订单支付方式
                                return null;
                            });
                            log.info("===消费消息===autoPayOrderPreAmount===系统自动支付订单号为[{}]的预付款", orderNo);
                            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                        }
                    }
                }
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            } catch (Exception e) {
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            } finally {
                redisLocker.unlock(lockKey, lockValue);
            }
        });
    }
} 

redis分布式锁

@Slf4j
@Component
public class RedisLocker {
    /**
     * 锁的key前缀,整个lock key是applicationName:lock:key
     */
    public static final String LOCK_KEY_PREFIX = "lock" + RedisConfig.KEY_DELIMITER;
    /**
     * 默认请求锁成功后的有效期,60秒
     */
    public static final long DEFAULT_LOCK_EXPIRE = 60 * 1000;
    @Autowired
    private RedisTemplateWarpper redisTemplateWarpper;

    /**
     * 加锁,使用默认的锁有效时间
     *
     * @param key - key名称
     * @return return null is lock failed Otherwise return uuid value of lock-key
     */
    public String lock(String key) {
        return this.lock(key, DEFAULT_LOCK_EXPIRE);
    }

    /**
     * 加锁
     *
     * @param key               - key名称
     * @param expireMillisecond - 锁成功后的有效期,毫秒
     * @return return null or empty string is lock failed Otherwise return uuid value of lock-key
     */
    public String lock(String key, long expireMillisecond) {
        Preconditions.checkArgument(StringUtils.isNotBlank(key));
        Preconditions.checkArgument(expireMillisecond > 0L);

        String lockKey = LOCK_KEY_PREFIX + key;
        String lockValue = UUID.randomUUID().toString();
        boolean keySet = redisTemplateWarpper.vSetIfAbsent(lockKey, lockValue, expireMillisecond);
        if (keySet) {   //锁成功
            return lockValue;
        }
        return null;
    }

    /**
     * 解锁
     *
     * @param key
     */
    public void unlock(String key, String value) {
        if (StringUtils.isBlank(value)) {
            return;
        }

        String lockKey = LOCK_KEY_PREFIX + key;
        String lockValueRedis = redisTemplateWarpper.vGet(lockKey);
        if (StringUtils.equals(lockValueRedis, value)) {
            redisTemplateWarpper.kDelete(lockKey);
        }
    }
}

你可能感兴趣的:(分布式,redis)