第八章Redis_ 事务_ 锁机制_ 秒杀
第八章Redis_ 事务_ 锁机制_ 秒杀
文章目录
- 第八章Redis_ 事务_ 锁机制_ 秒杀
- 8.1事物的定义
- 8.2Multi、Exec、discard
- 8.3为什么要做成事务
- 8.4事物冲突的例子
- 8.4.1悲观锁
- 8.4.2乐观锁
- 8.4.3乐观锁在redis中的使用
- 8.4.4unwatch
- 8.5Redis事务三特性
- 8.6Redis_ 事务_秒杀案例
- 8.6.1解决计数器和人员记录的事务操作
- 8.6.2 Redis 事务--秒杀并发模拟
8.1事物的定义
Redis 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事
务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis 事务的主要作用就是串联多个命令防止别的命令插队。
8.2Multi、Exec、discard
从输入 Multi 命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入
Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过 discard 来放弃组队。
案例
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
8.3为什么要做成事务
想想一个场景:有很多人有你的账户,同时去参加双十一抢购
8.4事物冲突的例子
一个请求想给金额减 8000
一个请求想给金额减 5000
一个请求想给金额减 1000
8.4.1悲观锁
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 block直到它拿到锁。
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
想拿10000元会先被上锁,只能被一个操作执行,假设第一个操作需要取8000元,最后面还剩2000元,200
8.4.2乐观锁
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
乐观锁适用于多读的应用类型,这样可以提高吞吐量。
Redis 就是利用这种 check-and-set机制实现事务的。
8.4.3乐观锁在redis中的使用
WATCH key [key ...]
在执行 multi 之前,先执行 watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
8.4.4unwatch
取消 WATCH 命令对所有 key 的监视。
如果在执行 WATCH 命令之后,EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。
http://doc.redisfans.com/transaction/exec.html
8.5Redis事务三特性
➢ 单独的隔离操作
◼ 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
➢ 没有隔离级别的概念
◼ 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
➢ 不保证原子性
◼ 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
8.6Redis_ 事务_秒杀案例
8.6.1解决计数器和人员记录的事务操作
public static boolean doSecKill(String uid,String prodid) throws IOException {
//1 uid和prodid非空判断
if(uid == null || prodid == null) {
return false;
}
//2 连接redis
//Jedis jedis = new Jedis("192.168.44.168",6379);
//通过连接池得到jedis对象
JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = jedisPoolInstance.getResource();
//3 拼接key
// 3.1 库存key
String kcKey = "sk:"+prodid+":qt";
// 3.2 秒杀成功用户key
String userKey = "sk:"+prodid+":user";
//监视库存
jedis.watch(kcKey);
//4 获取库存,如果库存null,秒杀还没有开始
String kc = jedis.get(kcKey);
if(kc == null) {
System.out.println("秒杀还没有开始,请等待");
jedis.close();
return false;
}
// 5 判断用户是否重复秒杀操作
if(jedis.sismember(userKey, uid)) {
System.out.println("已经秒杀成功了,不能重复秒杀");
jedis.close();
return false;
}
//6 判断如果商品数量,库存数量小于1,秒杀结束
if(Integer.parseInt(kc)<=0) {
System.out.println("秒杀已经结束了");
jedis.close();
return false;
}
//7 秒杀过程
//使用事务
Transaction multi = jedis.multi();
//组队操作
multi.decr(kcKey);
multi.sadd(userKey,uid);
//执行
List<Object> results = multi.exec();
if(results == null || results.size()==0) {
System.out.println("秒杀失败了....");
jedis.close();
return false;
}
//7.1 库存-1
//jedis.decr(kcKey);
//7.2 把秒杀成功用户添加清单里面
//jedis.sadd(userKey,uid);
System.out.println("秒杀成功了..");
jedis.close();
return true;
}
8.6.2 Redis 事务–秒杀并发模拟
使用工具 ab 模拟测试
CentOS6 默认安装
CentOS7 需要手动安装
联网: yum install httpd-tools
测试及结果
vim postfile 模拟表单提交参数,以&符号结尾;存放当前目录。
内容:prodid=0101&ab -n 2000 -c 200 -k -p ~/postfile -T application/xwww-form-urlencodedhttp://192.168.2.115:8081/Seckill/doseckill
超卖
超卖问题
利用乐观锁淘汰用户,解决超卖问题。
继续增加并发测试
已经秒光,可是还有库存
连接超时,通过连接池解决
节省每次连接 redis 服务带来的消耗,把连接好的实例反复利用。
通过参数管理连接的行为代码见项目中
链接池参数
◼ MaxTotal:控制一个 pool 可分配多少个 jedis实例,通过 pool.getResource()来获取;如果赋值为-1,则表示不限制;如果 pool已经分配了 MaxTotal 个 jedis实例,则此时 pool 的状态为 exhausted。
◼ maxIdle:控制一个 pool最多有多少个状态为 idle(空闲)的 jedis实例;
◼ MaxWaitMillis :表示当 borrow 一个 jedis 实例时,最大的等待毫秒数,如果超过等待时间,则直 接抛 JedisConnectionException;
◼ testOnBorrow:获得一个 jedis实例的时候是否检查连接可用性(ping());如
果为 true,则得到的 jedis实例均是可用的;
解决库存遗留问题
LUA 脚本
LUA 在 脚本在 Redis 中的优势