Redis实现分布式锁+Pub/Sub实现消息队列

最近做了一个业务,类似于美团,饿了吗配送员抢单的功能,简单的总结一下

首先这个功能对于项目来说,并发量不是很大,但是勉强算一个高并发吧

抢单的功能,首先要保证一点,一个单子只能被一个人抢到,哪怕有10w人来抢,也只能有一个人抢到,这里就要用到锁了,当一个人抢到单子时,要立刻保证,别人抢不到,这里选用Redis做

分布式锁实现方式有很多,Redis,zk,等等,至于为什么选用redis,个人原因,对Redis熟悉一点,不管用什么实现,原理都是大同小异的

首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下三个条件:

    互斥性:只有一个客户端能持有锁。

    不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客 户端能加锁。

    加锁和解锁的客户端相同:加锁和解锁必须是同一个客户端

实现流程:

    获取锁的时候,使用 setnx(SETNX key val:当且仅当 key 不存在时,set 一个 key 为 val 的字符串,返 回 1;

    若 key 存在,则什么都不做,返回 0

    在释放锁的时候进行判断

    返回1则成功获取锁。还设置一个获取的超时时间, 若超过这个时间则放弃获取锁。 setex(key,value,expire)过期以秒为单位 

    这样采用setnx,setex有个小问题,因为命令时分开使用的,假如程序在两个命令中出现问题,很有可能出现死锁,所以我这里采用命令连用的方式

    在释放锁的时候进行判断,只能被同一个客户端释放

整个流程包括了,判断,加锁,释放

所以这里用到了try{} catch {} finally{}

public Object deteOrder(RecordTableModel recordTableModel) {
        String key=recordTableModel.getMess_id();
        String value=recordTableModel.getMan_openid();
        boolean nxRet=false;
        try {
            //返回为t时,获取锁成功,f获取失败
            nxRet = redisServer.setLock(key,value);
            if (!nxRet){
                LOGGER.info("获取锁失败");
                return ResultMessage.success(ResultCode.GRAB_NO,"已有人配送");
            }else{
                LOGGER.info("获取锁成功:"+key);
                //一些业务处理
                //放入队列中
                redisServer.sendChannelMess(Const.CHANNEL,JSONObject.toJSONString(recordTableModel));
                return ResultMessage.success(true);
            }
        }catch (Exception e){
            LOGGER.info("错误:{}",e);
        }finally {
            //放锁时要进行判断,加锁,放锁必须同一个客户端
            if (nxRet) {
                redisServer.deleKey(key);
            }
        }
//加锁成功返回T   
public Boolean setLock(String key,String value){
    try{
        Boolean result=(Boolean)redisTemplate.execute(new RedisCallback() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
            //设置超时时间 超过时间自动解锁
            return connection.set(key.getBytes(),value.getBytes(),Expiration.seconds(3600),RedisStringCommands.SetOption.ifAbsent());
            }
        });
        return result;
    }catch (Exception e){
    }
return false;


public void sendChannelMess(String channel,String message){
    try{
        redisTemplate.convertAndSend(channel,message);
    }catch (Exception e){
        LOGGER.info("错误:{}",e);
    }
}

下面实现redis的发布和订阅

//redis的配置文件
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String,String> redisTemplate=new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om=new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }


    /**
     * 初始化监听器
     * @param connectionFactory
     * @param listenerAdapter
     * @return
     */
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // new PatternTopic("这里是监听的通道的名字") 通道要和发布者发布消息的通道一致
        container.addMessageListener(listenerAdapter, new PatternTopic(Const.CHANNEL));
        return container;
    }

    /**
     * 绑定消息监听者和接收监听的方法
     * @param redisReceiver
     * @return
     */
    @Bean
    MessageListenerAdapter listenerAdapter(RedisReceiver redisReceiver) {
        // redisReceiver 消息接收者
        // receiveMessage 消息接收后的方法
        return new MessageListenerAdapter(redisReceiver, "receiveMessage");
    }

    /**
     * 注册订阅者
     * @param latch
     * @return
     */
    @Bean
    RedisReceiver receiver(CountDownLatch latch) {
        return new RedisReceiver(latch);
    }

    /**
     * 计数器,用来控制线程
     * @return
     */
    @Bean
    CountDownLatch latch() {
        //指定了计数的次数 1
        return new CountDownLatch(1);
    }
}
//这里是收到通道的消息之后执行的方法
public void receiveMessage(String message) {
        LOGGER.info("收到的消息:"+message);
        //入库
        try {
            //入库等业务
        }catch (Exception e){
            LOGGER.info("errpr:"+e);
        }
        latch.countDown();
    }

实现到这里,大体的流程都走完了,具体的业务实现就看个人需求了

个人第一次写这种技术文章,有哪些地方不够清楚,欢迎留言,后续补充

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值