Redis(三)jedis与锁
1 Jedis
引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
</dependency>
测试连通
public static void main(String[] args) {
String host = "192.168.60.100";
int port = 6379;
Jedis jedis = new Jedis(host, port);
String ping = jedis.ping();
System.out.println(ping);
}
输出“PONG”表示连接Redis成功
如果显示连接超时,则检查以下几点:
① 检查配置文件bind以及protectedmodel
② 检查防火墙是否关闭
API 基本是前面的命令 这里略过了
案例:模拟短信验证码
public static void main(String[] args) {
verifyCodeSend("1568887221");
System.out.println(verifyCode("1568887221", "94987"));
}
public static void verifyCodeSend(String phoneNum) {
String host = "192.168.60.100";
int port = 6379;
Jedis jedis = new Jedis(host, port);
String countKey = "VERIFY_CODE_COUNT_" + phoneNum;
String codeKey = "VERIFY_CODE_" + phoneNum;
// 每个手机每天只能发送三次
String count = jedis.get(countKey);
if(count == null) {
jedis.setex(countKey, 24 * 60 * 60, "1");
} else if(Integer.parseInt(count) <= 2) {
jedis.incr(countKey);
} else {
System.out.println("发送三次大于三了");
jedis.close();
return ;
}
// 验证码发送
String code = generateCode();
jedis.setex(codeKey, 5 * 60, code);
System.out.println("发送成功" + code);
jedis.close();
}
public static boolean verifyCode(String phoneNum, String code) {
String host = "192.168.60.100";
int port = 6379;
Jedis jedis = new Jedis(host, port);
String codeKey = "VERIFY_CODE_" + phoneNum;
String codeRedis = jedis.get(codeKey);
if(codeRedis == null) {
System.out.println("手机号错误");
jedis.close();
return false;
} else {
boolean result = codeRedis.equals(code);
jedis.close();
return result;
}
}
public static String generateCode() {
Random random = new Random();
StringBuilder str = new StringBuilder();
for(int i = 0; i < 6; i++) {
str.append(random.nextInt(10));
}
return str.toString();
}
感觉这里老师讲的逻辑好像不大对,上面是进行修改后的
2 SpringBoot整合Redis
引入依赖
<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>
配置redis
spring:
redis:
host: 192.168.60.100
port: 6379
# 默认数据库连接索引
database: 0
timeout: 1800000
# 连接池最大连接数(默认为8,负数表示无限制)
lettuce:
pool:
max-active: 20
jedis:
pool:
# 最大阻塞等待时间(默认为-1)
max-wait: -1
# 最大空闲连接(默认为8)
max-idle: 5
# 最小空闲连接(默认为-1)
min-idle: 0
3 Redis事务和锁操作
3.1 简介
Redis事务是一个单独的隔离操作:事务中所有的命令都会被序列化按照顺序执行。事务在执行过程中,不会被客户端发送来的其他命令打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。
3.2 基本命令
Multi 开启事务
Exec 执行事务
Discard 放弃组队
从输入Multi开始,输入的命令都会依次进入命令队列,但不会执行,直到输入Exec后Redis将会依次执行命令队列中的命令。并在组队的过程中可以使用dicard来放弃组队。
3.3 两个实例
组队期间的错误(即编译的语法错误)
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 value
QUEUED
127.0.0.1:6379(TX)> set key2
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379>
可以看到结果是直接无法排队
执行期间的错误
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 v1
QUEUED
127.0.0.1:6379(TX)> incr key1
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
可以看到组队成功但执行失败,但是第一条语句还是能够执行,也就是Redis
3.4 悲观锁与乐观锁解决事务冲突问题
悲观锁:顾名思义就是很悲观,每次去获取数据的时候都认为别的事务操作会进行修改,因此需要加锁保证别的事务拿不到数据。传统的关系型数据库里面用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都用到了这种锁的机制。
乐观锁:乐观锁则是认为每次获取数据的时候都没有其他事务修改数据,因此不会上锁,只是对数据添加一个版本字段,只有当自己修改数据的时候才去检查当前数据的版本字段和之前自己的版本字段,如果不一致则取消更新。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,Redis就是利用这种check-and-set实现事务的
抢票就是乐观锁的一个典型应用场景,即很多人抢票只能有一个人成功,如果使用悲观锁的话虽然也能够实现但是 单位时间 微观上 只能有一个人在抢票,系统的吞吐量太小
乐观锁基本命令 watch 乐观锁监视数据
127.0.0.1:6379> get balance
"100"
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby balance 10
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 110
127.0.0.1:6379> get balance
"100"
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby balance 10
QUEUED
127.0.0.1:6379(TX)> exec
(nil)
可以看到第二个客户端的事务监测到数据版本和之前的不同终止了事务
乐观锁基本命令 unwatch 停止对所有数据的监视
3.5 Redis事务的三大特性
单独的隔离操作
- 事务中的所有命令都会序列化 按照顺序执行,不会被其他客户端发来的命令所打断
没有隔离级别的概念
- 队列中的命令没有提交之前都不会被实际执行
不保证原子性
- 事务中如果有一条命令执行失败,其他命令依然会被执行,没有回滚
4 事务和锁机制-秒杀案例
案例分析

在redis中使用字段sk:product:qt存储库存,使用set存储购买成功的用户id
原始代码
//秒杀过程
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对象
Jedis jedis = new Jedis("192.168.60.100", 6379);
//3 拼接key
// 3.1 库存key
String kcKey = "sk:" + prodid + ":qt";
// 3.2 秒杀成功用户key
String userKey = "sk:" + uid + ":user";
//4 获取库存,如果库存null,秒杀还没有开始
String kc = jedis.get(kcKey);
if(kc == null) {
System.out.println(uid + "您好,秒杀还没有开始");
jedis.close();
return false;
}
// 5 判断用户是否重复秒杀操作
if(jedis.sismember(userKey, uid)) {
System.out.println(uid + "您好,不能重复购买");
jedis.close();
return false;
}
//6 判断如果商品数量,库存数量小于1,秒杀结束
if(Integer.parseInt(jedis.get(kcKey)) <= 0) {
System.out.println(uid + "您好,秒杀活动已经结束");
jedis.close();
return false;
}
//7 秒杀过程
//7.1 库存-1
jedis.decr(kcKey);
System.out.println(uid+",购买成功");
//7.2 把秒杀成功用户添加清单里面
jedis.sadd(userKey, uid);
jedis.close();
return true;
}
控制台的输出:
34074,购买成功
39830,购买成功
19620,购买成功
9727,购买成功
31847您好,秒杀活动已经结束
此时的redis:
127.0.0.1:6379> keys *
1) "sk:0101:qt"
2) "sk:9727:user"
3) "sk:2618:user"
4) "sk:34074:user"
5) "sk:39446:user"
6) "sk:19620:user"
7) "sk:19756:user"
8) "sk:39830:user"
9) "sk:14215:user"
10) "sk:39589:user"
11) "sk:33691:user"
127.0.0.1:6379> get sk:0101:qt
"0"
使用ab工具进行高并发检测
工具安装
yum install httpd-tools
基本命令
ab -n -c -p -T
-n 表示请求的数量
-c 表示请求中并发的数量
-p 表示请求为post请求的时候的内容
-T 表示请求的类型
'application/x-www-form-urlencoded'
Default is 'text/plain'
测试
ab -n 1000 -c 100 -p ./postfile -T application/x-www-form-urlencoded http://192.168.1.108//Seckill/doseckill
Percentage of the requests served within a certain time (ms)
50% 98
66% 103
75% 107
80% 109
90% 115
95% 118
98% 121
99% 123
100% 128 (longest request)
此时java中的控制台
34074,购买成功
39830,购买成功
19620,购买成功
9727,购买成功
31847您好,秒杀活动已经结束
05-Nov-2022 18:15:06.190 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [D:\software\apache-tomcat-8.5.78\webapps\manager]
05-Nov-2022 18:15:06.222 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Web应用程序目录[D:\software\apache-tomcat-8.5.78\webapps\manager]的部署已在[33]毫秒内完成
49635,购买成功
20104,购买成功
20281,购买成功
38295,购买成功
36060,购买成功
20169,购买成功
16599,购买成功
1757,购买成功
27781,购买成功
37401,购买成功
24778,购买成功
16002您好,秒杀活动已经结束
47051,购买成功
47757,购买成功
47240您好,秒杀活动已经结束
33393您好,秒杀活动已经结束
6163您好,秒杀活动已经结束
4882,购买成功
17448您好,秒杀活动已经结束
19355您好,秒杀活动已经结束
6336您好,秒杀活动已经结束
47492,购买成功
43854您好,秒杀活动已经结束
7106您好,秒杀活动已经结束
44598,购买成功
7747,购买成功
9785,购买成功
25385,购买成功
1499,购买成功
26152,购买成功
31252,购买成功
4589,购买成功
30801,购买成功
13182,购买成功
6426,购买成功
46654,购买成功
45300,购买成功
43455,购买成功
24394您好,秒杀活动已经结束
6417您好,秒杀活动已经结束
14826您好,秒杀活动已经结束
37355,购买成功
22473您好,秒杀活动已经结束
45251,购买成功
30914,购买成功
13849,购买成功
39553您好,秒杀活动已经结束
9444,购买成功
954您好,秒杀活动已经结束
133您好,秒杀活动已经结束
redis中:
127.0.0.1:6379> get sk:0101:qt
"-27"
超卖和超时问题解决
超时问题:使用数据库连接池
public class JedisPoolUtil {
private static volatile JedisPool jedisPool = null;
private JedisPoolUtil() {
}
public static JedisPool getJedisPoolInstance() {
if (null == jedisPool) {
synchronized (JedisPoolUtil.class) {
if (null == jedisPool) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200);
poolConfig.setMaxIdle(32);
poolConfig.setMaxWaitMillis(100*1000);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setTestOnBorrow(true); // ping PONG
jedisPool = new JedisPool(poolConfig, "192.168.44.168", 6379, 60000 );
}
}
}
return jedisPool;
}
public static void release(JedisPool jedisPool, Jedis jedis) {
if (null != jedis) {
jedisPool.returnResource(jedis);
}
}
}

超卖问题 : 使用乐观锁检测数据并使用事务操作
//秒杀过程
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;
}
lua解决乐观锁造成的库存遗留
问题分析:当并发度特别高的时候,会出现没有结束的情况
ab -n 1000 -c 400 -p ./postfile -T application/x-www-form-urlencoded http://192.168.1.108//Seckill/doseckill
然后redis中的库存显示为:
127.0.0.1:6379> get sk:0101:qt
"13"
public class SecKill_redisByScript {
private static final org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;
public static void main(String[] args) {
JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis=jedispool.getResource();
System.out.println(jedis.ping());
Set<HostAndPort> set=new HashSet<HostAndPort>();
// doSecKill("201","sk:0101");
}
static String secKillScript ="local userid=KEYS[1];\r\n" +
"local prodid=KEYS[2];\r\n" +
"local qtkey='sk:'..prodid..\":qt\";\r\n" +
"local usersKey='sk:'..prodid..\":usr\";\r\n" +
"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
"if tonumber(userExists)==1 then \r\n" +
" return 2;\r\n" +
"end\r\n" +
"local num= redis.call(\"get\" ,qtkey);\r\n" +
"if tonumber(num)<=0 then \r\n" +
" return 0;\r\n" +
"else \r\n" +
" redis.call(\"decr\",qtkey);\r\n" +
" redis.call(\"sadd\",usersKey,userid);\r\n" +
"end\r\n" +
"return 1" ;
static String secKillScript2 =
"local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
" return 1";
public static boolean doSecKill(String uid,String prodid) throws IOException {
JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis=jedispool.getResource();
//String sha1= .secKillScript;
String sha1= jedis.scriptLoad(secKillScript);
Object result= jedis.evalsha(sha1, 2, uid,prodid);
String reString=String.valueOf(result);
if ("0".equals( reString ) ) {
System.err.println("已抢空!!");
}else if("1".equals( reString ) ) {
System.out.println("抢购成功!!!!");
}else if("2".equals( reString ) ) {
System.err.println("该用户已抢过!!");
}else{
System.err.println("抢购异常!!");
}
jedis.close();
return true;
}
}
此时的redis:
[root@hadoop100 hikaru]# docker exec -it redis redis-cli
127.0.0.1:6379> keys *
1) "sk:0101:qt"
2) "sk:0101:usr"
127.0.0.1:6379> get sk:0101:qt
"0"
这里也没有讲太清楚。。lua的作用就是把两个操作(减少库存并添加用户)变成了原子性操作,实际上就是变成了使用了悲观锁?只不过因为redis没有悲观锁吗
查了一下网上说:减库存逻辑其实就是先是用lua脚本减redis库存,如果成功再去减数据库中的真实库存,如果减redis库存失败,库存不足,就不会再走后面减真实库存的逻辑了。
5 Redis持久化
5.1 RDB
简介
在指定的时间间隔内,将内存中的数据集快照写入磁盘
Redis(三)jedis与锁的更多相关文章
- Redis实战--Jedis实现分布式锁
echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!!! 分布式 ...
- 五分钟学会悲观乐观锁-java vs mysql vs redis三种实现
1 悲观锁乐观锁简介 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果 ...
- Redis事务和分布式锁
Redis事务 Redis中的事务(transaction)是一组命令的集合.事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行.Redis事务的实现需要用到 MUL ...
- redis客户端、分布式锁及数据一致性
Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...
- 单机Redis实现分布式互斥锁
代码地址如下:http://www.demodashi.com/demo/12520.html 0.准备工作 0-1 运行环境 jdk1.8 gradle 一个能支持以上两者的代码编辑器,作者使用的是 ...
- spring 5.x 系列第7篇 —— 整合Redis客户端 Jedis和Redisson (xml配置方式)
文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件 2.2 单机配置 ...
- 如何用redis正确实现分布式锁?
先把结论抛出来:redis无法正确实现分布式锁!即使是redis单节点也不行!redis的所谓分布式锁无法用在对锁要求严格的场景下,比如:同一个时间点只能有一个客户端获取锁. 首先来看下单节点下一般r ...
- springmvc单Redis实例实现分布式锁(解决锁超时问题)
一.前言 关于redis分布式锁, 查了很多资料, 发现很多只是实现了最基础的功能, 但是, 并没有解决当锁已超时而业务逻辑还未执行完的问题, 这样会导致: A线程超时时间设为10s(为了解决死锁问题 ...
- Redis系列四 - 分布式锁的实现方式
前言 分布式锁一般有3中实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 以下将详细介绍如何正确地实现Redis分布式锁. 可靠性 首先,为了确保分布式锁的可 ...
- Redis 客户端 Jedis、lettuce 和 Redisson 对比
Redis 支持多种语言的客户端,下面列举了部分 Redis 支持的客户端语言,大家可以通过官网查看 Redis 支持的客户端详情. C语言 C++ C# Java Python Node.js PH ...
随机推荐
- node.js 数据模拟
Node: js在服务端的一个运行环境 node框架:express koa egg (本文采用express) express: 是基于node的一个web框架 restful api:是目前流 ...
- 量化交易 - matplotlib画candle图
需要mplfinance包 pip install mplfinance --upgrade from matplotlib import style import pandas as pd im ...
- 【基础知识】C++算法基础(头文件配置、获取输入、输出)
基础的头文件配置.输入输出 <iostream> 和<iostream.h>的区别:加.h是C中的做法,C++里一般不加.h,但相应的,要加using namspace std ...
- http请求的方法
1.OPTIONS 返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送'*'的请求来测试服务器的功能性. 2.HEAD 向服务器索要与GET请求相一致的相应,只不过响应体将不 ...
- C# 高精度定时器
https://blog.gkarch.com/2015/09/high-resolution-timer.html https://www.cnblogs.com/samgk/articles/57 ...
- Hadoop警告信息:WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform.
when键入命令: hadoop fs -ls / 若出现以下警告信息: Hadoop警告问题:WARN util.NativeCodeLoader: Unable to load native-ha ...
- Oracle表主键作为外键都用在哪些表查询
Oracle中,如果设置了外键,删除数据时,必须将外键关联一并删除,但是如果对项目不是很熟悉时,我们无法判断到底都在哪些表中有外键关联,以下提供了一个查询的SQL,可以通过数据库查询,查找到所有的外键 ...
- pytest之运行环境
简介 pytest是Python最流程化的单元测试框架,它具有允许直接使用assert进行断言,而不需要使用self.assert*:可以自动寻找单测文件.类和函数,还可支持执行部分用例:Modula ...
- Swagger UI教程 API 文档神器 搭配Node使用 web api 接口文档 (转)
http://www.68idc.cn/help/makewebs/qitaasks/20160621620667.html 两种方案 一.Swagger 配置 web Api 接口文档美化 二.通过 ...
- P5192 有源汇上下界最大流总结
之前听学长讲解时,只听了大体思路就跑路了,没有听到具体细节.后面在考虑出度多的点具体向虚拟源点连边还是虚拟汇点连边时,只凭直觉直接向源点连边,然后就一直WA,直到后来中午听同学讲解才反应过来,白白浪费 ...