redis--事务--锁

一、悲观锁和乐观锁

  • 悲观锁

在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作都某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。
悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

悲观锁的流程

1、在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
2、如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。
3、如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
4、其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

优点与不足

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数

  • 乐观锁

在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。

数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。

实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳。

有点与缺点

乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。

二、redis的锁

redis采用了乐观锁。
使用watch命令来监控某一个key,如果事务执行前(exec命令执行前)这个key值发生变化,那么事务将不会执行。如果事务执行后这个key,那么事务继续执行。
!!!!如果事务被取消,那么exec命令会返回nil信息
redis--事务--锁_第1张图片

三、redis锁的命令

UNWATCH:取消对所有key的监控
WATCH:监控某个key,可以监控多个Key

四、redis乐观锁实现秒杀

//获取token
public BuyUser getBuyToken(final BuyUser user) {
        return redisTemplate.execute(new SessionCallback() {
            @Override
            public  BuyUser execute(RedisOperations redisOperations) throws DataAccessException {
                BuyUser result = new BuyUser();
                result.setUsId(user.getUsId());
                result.setUserName(user.getUserName());
                result.setToken(UUID.randomUUID().toString());
                result.setGoodId(-1L);
                try{
                    redisOperations.multi();
                    redisOperations.opsForValue().get("goodNum");          //获取
                    //将token放置到redis中并设置过期时间
                    redisOperations.opsForValue().set("token:" + user.getUsId(),result.getToken(),60,TimeUnit.SECONDS);
                    List redisResult = redisOperations.exec();
                    if(null != redisResult && ! redisResult.isEmpty()) {
                        result.setGoodId(Long.valueOf((String)redisResult.get(0)));
                    }
                }catch (Exception e) {
                    log.error("获取token和商品信息失败",e);
                }
                return result;
            }
        });


//抢购
public boolean buyNow(final BuyUser user) {
        /**
         * 对token的校验
         */
        if(null == user.getToken() || null == user.getUsId()){
            log.error("[token]或[usId]为空");
            return false;
        }
        String token = redisTemplate.opsForValue().get("token:"+user.getUsId());
        if(null == token || !token.equals(user.getToken())){
            log.error("[token:"+user.getUsId()+"]token校验失败:"+token);
            return false;
        }

        /**
         * 对goodId进行过滤
         */
        if(null == user.getGoodId() || 0 >= user.getGoodId()){
            log.error("[goodId]校验失败");
            return false;
        }

        return redisTemplate.execute(new SessionCallback() {
            @Override
            public  Boolean execute(RedisOperations redisOperations) throws DataAccessException {
                redisOperations.watch("goodNum");
                Long redisGoodId = Long.valueOf((String)redisOperations.opsForValue().get("goodNum"));
                /*
                 * 如果商品卖完了或者token不符合,那么就直接返回false
                 */
                if(0 >= redisGoodId || 0 != redisGoodId.compareTo(user.getGoodId())){
                    log.error("redisGoodId不符合规范");
                    return false;
                }
                try {
                    redisOperations.multi();
                    redisOperations.opsForValue().increment("goodNum", -1);
                    redisOperations.opsForList().rightPush("buyNowList", String.valueOf(user.getUsId()));
                    List result = redisOperations.exec();
                    if(null == result){
                        log.error(user.getUsId()+":抢购事务失败");
                        return false;
                    }
                } catch (Exception e) {
                    log.error(user.getUsId()+":抢购事务失败");
                    return false;
                }
                return true;
            }
        }); 
  

                            
                        
                    
                    
                    

你可能感兴趣的:(redis)