java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Redis事务和乐观锁

Redis中的事务和Redis乐观锁详解

作者:warybee

这篇文章主要介绍了Redis中的事务和Redis乐观锁详解,Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断,需要的朋友可以参考下

1 Redis事务介绍

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。

事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

1.1 命令介绍

1.2 事务流程

从输入multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入exec命令后,redis会将之前的命令队列中的命令依次执行。

在这里插入图片描述

在这里插入图片描述

上图说明:

命令演示:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set user:001 zhangsan
QUEUED
127.0.0.1:6379> set user:002 lisi
QUEUED
127.0.0.1:6379> get user:001
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "zhangsan"


127.0.0.1:6379> multi
OK
127.0.0.1:6379> set user:001 xiaoming
QUEUED
127.0.0.1:6379> set user:002 xiaozhang
QUEUED
127.0.0.1:6379> discard    # 使用discard命令取消队列
OK
127.0.0.1:6379> exec    # 执行exec报错
(error) ERR EXEC without MULTI

# watch 命令演示

# 客户端2,在客户端1执行exec之前,执行 set user:001 xiaoming,客户端1的事务被打断
127.0.0.1:6379> get user:001
"zhangsan"
127.0.0.1:6379> watch user:001
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set user:001 lisi
QUEUED
127.0.0.1:6379> exec   # 客户端1的事务被打断
(nil)
127.0.0.1:6379> get user:001
"xiaoming"


2 Redis实现乐观锁

2.1 乐观锁与悲观锁介绍

悲观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会被阻塞直到它拿到锁。

传统的关系型数据库里边就用到了很多这种锁机制,比如行锁表锁等,读锁写锁等,都是在做操作之前先上锁。

乐观锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。

乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

2.2 Redis乐观锁实现原理

Redis乐观锁的实现,是利用watch命令特性。数据进行提交更新的时候,对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

Redis通过数据版本(Version)记录机制实现乐观锁,这是乐观锁最常用的一种实现方式。

客户端1客户端2
age字段初始版本为1age字段初始版本为1
watch age multi
set age 25 版本加一,目前数据库版本为2
set age 30 exec 当前操作版本为1,小于数据中版本,提交失败。

客户端2在客户端1提交事务之前,对据库版本version进行更新一次,客户端1事务提交的时候对比版本号,要是此次版本号低于数据库当前版本号,就会提交失败。

2.3 Redis乐观锁秒杀案例

创建Spring boot项目引入以下依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

配置文件

spring:
  redis:
    host: 192.168.235.131
    port: 6379
    database: 0
    connect-timeout: 1800000
    password: 123456
    lettuce:
      pool:
        #连接池最大连接数(使用负值表示没有限制)
        max-active: 20
        #最大阻塞等待时间(负数表示没限制)
        max-wait: -1
        #连接池中的最大空闲连接
        max-idle: 8
        #连接池中的最小空闲连接
        min-idle: 0

service 代码

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    //库存
    private final String STOCK_KEY="stock:num";
    //秒杀成功的用户
    private final String USER_KEY="success:user";
    /**
     * Redis乐观锁秒杀案例
     * @param userId  用户ID
     * @return
     */
    @Override
    public boolean secKill(String userId) {
        Object stockObj = redisTemplate.opsForValue().get(STOCK_KEY);
        if (stockObj==null){
            log.info("库存为空,秒杀还未开始!");
            return false;
        }
        int stockNum=Integer.parseInt(stockObj.toString());
        if (stockNum<=0){
            log.info("库存为0,秒杀已经结束!");
            return false;
        }
        //判断当前用户是否已经秒杀成功
        Boolean member = redisTemplate.opsForSet().isMember(USER_KEY, userId);
        if (member){
            log.info("您已经秒杀成功,不能重复参与!");
            return false;
        }
        List txList =redisTemplate.execute(new SessionCallback<List<Object>>() {
           @Override
           public List<Object> execute(RedisOperations operations) throws DataAccessException {
               //监听库存
                operations.watch(STOCK_KEY);
               //开启事务
               operations.multi();
               //扣减库存
               operations.opsForValue().decrement(STOCK_KEY);
               //把秒杀成功的用户加入到set集合
               operations.opsForSet().add(USER_KEY,userId);
               //执行事务
               List<Object> result=operations.exec();
               return result;
           }
       });
        if (txList==null||txList.size()==0){
            log.info("用户:{},秒杀失败",userId);
            return false;
        }
        log.info("用户:{},秒杀成功",userId);
        return true;
    }
}

Controller代码

/**
 * 秒杀
 * @return
 */
@RequestMapping("secKill")
public String secKill(){
    String userId= UUID.randomUUID().toString();
    boolean res = orderService.secKill(userId);
    if (res){
        return "秒杀成功";
    }else {
        return "秒杀失败";
    }
}

使用linux上的ab进行并发测试:

ab -n 500 -c 100  http://192.168.1.171/order/secKill

到此这篇关于Redis中的事务和Redis乐观锁详解的文章就介绍到这了,更多相关Redis事务和乐观锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文