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与锁的更多相关文章

  1. Redis实战--Jedis实现分布式锁

    echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!!! 分布式 ...

  2. 五分钟学会悲观乐观锁-java vs mysql vs redis三种实现

    1 悲观锁乐观锁简介 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果 ...

  3. Redis事务和分布式锁

    Redis事务 Redis中的事务(transaction)是一组命令的集合.事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行.Redis事务的实现需要用到 MUL ...

  4. redis客户端、分布式锁及数据一致性

    Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...

  5. 单机Redis实现分布式互斥锁

    代码地址如下:http://www.demodashi.com/demo/12520.html 0.准备工作 0-1 运行环境 jdk1.8 gradle 一个能支持以上两者的代码编辑器,作者使用的是 ...

  6. spring 5.x 系列第7篇 —— 整合Redis客户端 Jedis和Redisson (xml配置方式)

    文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件 2.2 单机配置 ...

  7. 如何用redis正确实现分布式锁?

    先把结论抛出来:redis无法正确实现分布式锁!即使是redis单节点也不行!redis的所谓分布式锁无法用在对锁要求严格的场景下,比如:同一个时间点只能有一个客户端获取锁. 首先来看下单节点下一般r ...

  8. springmvc单Redis实例实现分布式锁(解决锁超时问题)

    一.前言 关于redis分布式锁, 查了很多资料, 发现很多只是实现了最基础的功能, 但是, 并没有解决当锁已超时而业务逻辑还未执行完的问题, 这样会导致: A线程超时时间设为10s(为了解决死锁问题 ...

  9. Redis系列四 - 分布式锁的实现方式

    前言 分布式锁一般有3中实现方式: 数据库乐观锁: 基于Redis的分布式锁: 基于ZooKeeper的分布式锁. 以下将详细介绍如何正确地实现Redis分布式锁. 可靠性 首先,为了确保分布式锁的可 ...

  10. Redis 客户端 Jedis、lettuce 和 Redisson 对比

    Redis 支持多种语言的客户端,下面列举了部分 Redis 支持的客户端语言,大家可以通过官网查看 Redis 支持的客户端详情. C语言 C++ C# Java Python Node.js PH ...

随机推荐

  1. 导入ssm项目时,项目基本的配置文件

    一.ssm框架基本的配置文件 上面的这些配置文件对于比较简单的ssm项目其实大同小异,逻辑上是差不多的. 在config目录下 在lib下就是各种jar包 二.导入ssm项目后,本地环境配置和项目本身 ...

  2. hdu:"红色病毒"问题(指数型母函数用e^x指数函数来计算)

    Problem Description医学界发现的新病毒因其蔓延速度和Internet上传播的"红色病毒"不相上下,被称为"红色病毒",经研究发现,该病毒及其变 ...

  3. 关于Lua中的面向对象实现

    写在前面 最近在琢磨"Lua热重载",在测试中发现我之前对Lua中的面向对象实现有一些理解发生变化,这里记录一下. 本文提到的面向对象实现来自云风. 类实现 <Lua程序设计 ...

  4. 微信小程序 入门总结篇

      页面生命周期 Page({ /** * 页面的初始数据 */ data: { }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { }, ...

  5. Ubuntu 22.04 安装 VMWare 16.2.3 后无法启动

    异常日志: 2022-06-13T03:49:56.019Z In(05) host-29676 In file included from /tmp/modconfig-XR2GVI/vmmon-o ...

  6. Net异步委托-泛型委托Action<T>与Func<T,TResult>及 异步调用AsyncCallback

    1.相同点 Func<ReqMode,ResultModel> 与 Action<ReqMode> 1).都是Net3.5 之后内置的委托方法,作用几乎一致 2).都支持lam ...

  7. Unity泛型单例模式

    using System.Collections; using System.Collections.Generic; using UnityEngine; public class Singleto ...

  8. Python第十章实验报告

    一.实验对象:<零基础学Python>6道实例和2道实战 二.实验环境:IDLE Shell 3.9.7 三.实验目的:学习如何在Python中进行文件和目录的相关操作 四.实验过程: 实 ...

  9. fetch请求方式

    Fetch请求的方式 1:GET 请求 // 未传参数 const getData = async () => { const res = await fetch('http://www.xxx ...

  10. python之tk学习,闲鱼搜索-小记

    (如想转载,请联系博主或贴上本博地址) 编程,逻辑,总是让人如痴如醉. 下面进入正题. 火热的天气配上火热的python,python的入门友好性让门外汉们都看到了希望.当然自己写的程序如果没有GUI ...