面试总结之[分布式锁]

分布式锁应用最广的应该就是秒杀时库存的-1;此时我们可以采用redis的乐观锁和悲观锁实现:

需要考虑的问题:

1、用什么操作redis?幸亏redis已经提供了jedis客户端用于java应用程序,直接调用jedis API即可。
2、怎么实现加锁?“锁”其实是一个抽象的概念,将这个抽象概念变为具体的东西,就是一个存储在redis里的key-value对,key是于商品ID相关的字符串来唯一标识,value其实并不重要,因为只要这个唯一的key-value存在,就表示这个商品已经上锁。
3、如何释放锁?既然key-value对存在就表示上锁,那么释放锁就自然是在redis里删除key-value对。
4、阻塞还是非阻塞?笔者采用了阻塞式的实现,若线程发现已经上锁,会在特定时间内轮询锁。
5、如何处理异常情况?比如一个线程把一个商品上了锁,但是由于各种原因,没有完成操作(在上面的业务场景里就是没有将库存-1写入数据库),自然没有释放锁,这个情况笔者加入了锁超时机制,利用redis的expire命令为key设置超时时长,过了超时时间redis就会将这个key自动删除,即强制释放锁(可以认为超时释放锁是一个异步操作,由redis完成,应用程序只需要根据系统特点设置超时时间即可)。

1:乐观锁
redis中可以使用watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了exec,discard,unwatch命令都会清除连接中的所有监视。

Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。Redis的事务是下面4个命令来实现 :

1.multi,开启Redis的事务,置客户端为事务态。 
//      2.exec,提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。 
//      3.discard,取消事务,置客户端为非事务态。 
//      4.watch,监视键值对,作用是如果事务提交exec时发现监视的键值对发生变化,事务将被取消。 
        @Override
        public void run() {
            try {
                Thread.sleep((int)Math.random()*5000);//随机睡眠一下
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            while(true){
                System.out.println("顾客:" + clientName + "开始抢购商品");
                jedis = RedisUtil.getInstance().getJedis();
                try {
                    jedis.watch(key);//监视商品键值对,作用时如果事务提交exec时发现监视的键值对发生变化,事务将被取消
                    int prdNum = Integer.parseInt(jedis.get(key));//当前商品个数
                    if (prdNum > 0) {
                        Transaction transaction = (Transaction) jedis.multi();//开启redis事务
                        ((Jedis) transaction).set(key,String.valueOf(prdNum - 1));//商品数量减一
                        List<Object> result = ((redis.clients.jedis.Transaction) transaction).exec();//提交事务(乐观锁:提交事务的时候才会去检查key有没有被修改)
                        if (result == null || result.isEmpty()) {
                            System.out.println("很抱歉,顾客:" + clientName + "没有抢到商品");// 可能是watch-key被外部修改,或者是数据操作被驳回
                        }else {
                            jedis.sadd(clientList, clientName);//抢到商品的话记录一下
                            System.out.println("恭喜,顾客:" + clientName + "抢到商品");  
                            break; 
                        }
                    }else {
                         System.out.println("很抱歉,库存为0,顾客:" + clientName + "没有抢到商品");  
                         break; 
                    }
                } catch (Exception e) {
                    // TODO: handle exception
                }finally{
                    jedis.unwatch();
                    RedisUtil.returnResource(jedis);
                }
            }

        }

2:悲观锁

@Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            while (true) {
                if (Integer.parseInt(jedis.get(key)) <= 0) {
                    break;//缓存中没有商品,跳出循环,消费者线程执行完毕
                }
                //缓存中还有商品,取锁,商品数目减一
                 System.out.println("顾客:" + clientName + "开始抢商品");  
                 if (redisBasedDistributedLock.tryLock(3,TimeUnit.SECONDS)) {//等待3秒获取锁,否则返回false(悲观锁:每次拿数据都上锁)
                    int prdNum = Integer.parseInt(jedis.get(key));//再次取得商品缓存数目
                    if (prdNum > 0) {
                        jedis.decr(key);//商品数减一
                        jedis.sadd(clientList, clientName);//将抢购到商品的顾客记录一下
                        System.out.println("恭喜,顾客:" + clientName + "抢到商品");  
                    } else {  
                        System.out.println("抱歉,库存为0,顾客:" + clientName + "没有抢到商品");  
                    }
                    redisBasedDistributedLock.unlock0();//操作完成释放锁
                    break;
                }
            }
            //释放资源
            redisBasedDistributedLock = null;
            RedisUtil.returnResource(jedis);
        }
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值