Redis常见的工作场景使用实战,Redisson分布式锁的实现
ztj100 2024-11-04 15:16 58 浏览 0 评论
迎关注我的头条号:Wooola,10 年 Java 软件开发及架构设计经验,专注于 Java、Go 语言、微服务架构,致力于每天分享原创文章、快乐编码和开源技术。
前言
本项目基于springboot+ spring-boot-starter-data-redis+redisson
简单的redis demo可以参考:SpringBoot整合redis
https://www.jianshu.com/p/8e71737a1101
github地址:
Redisson:https://github.com/weiess/redis-and-Redisson.git
源码地址:https://github.com/weiess/redis-and-Redisson.git
redis大家工作的时候都很多,笔者根据自己经验总结下redis的几个数据类型,常用场景,也欢迎大家留言总结自己的经验。
hash
hash在redis里可以存储对象,当然string也可以,只不过hash相比较string,效率更高一点,而且功能也很强大,可以实现简单的购物车:
下面先给大家看下简单的存储对象hash实现:
/*
* hash实现存储对象
* */
@Test
public void testHsetpojo(){
User user = new User();
user.setId(123);
user.setAge(20);
user.setAddr("北京");
user.setName("yang");
Map<String,Object> map = BeanUtils.beanToMap(user);
String key = "user";
redisUtil.hmset(key,map);
System.out.println(redisUtil.hmget(key));
System.out.println("id="+redisUtil.hget(key,"id"));
String key2 = "user:"+user.getId();
redisUtil.hmset(key2,map);
System.out.println(redisUtil.hmget(key2));
}
这里的redisUtil是笔者封好的工具类,源码在文章最底下。
hash存储对象的时候可以把user当作key,例如代码中的key,因为hash是 key item value三种结构,可以把后面的item和value看成一个map,这样结构就是key map<obj,obj>,方便理解。
实际项目中可以给key加个标示符,比如key = userId:a123456,这个大家可以根据项目来定义。
可以用hash实现购物车功能:
例如:
hash.jpg
代码如下:
/*
* hash实现购物车
* */
@Test
public void testcar(){
String key ="carUser:123456";
redisUtil.del(key);
Map map = new HashMap();
map.put("book:a11111",1);
map.put("book:a11112",2);
map.put("book:a11113",3);
boolean b = redisUtil.hmset(key,map);
System.out.println("key = "+redisUtil.hmget(key));
//增加book:a11111的数量
redisUtil.hincr(key,"book:a11111",1);
System.out.println(redisUtil.hmget(key));
//减少book:a11112的数量
redisUtil.hincr(key,"book:a11112",-3);
//或者redisUtil.hdecr(key,"book:a11111",1);
System.out.println(redisUtil.hmget(key));
//获取所有key1的field的值
System.out.println("hegetall="+redisUtil.hmget(key));
//获取key下面的map数量
System.out.println("length="+redisUtil.hlen(key));
//删除某个key下的map
redisUtil.hdel(key,"book:a11112");
System.out.println(redisUtil.hmget(key));
}
hash里的key就是当前用户的购物车,map就是商品(map里的key是商品id,map的v是数量)
功能实现注释都有。注意这里的减操作是可以为负数的,所以大家一定要注意判断负数的情况。
list
常见的list可以分为下面三个数据结构方便大家理解:
stack(栈)= LPUSH + LPOP Queue(队列)= LPUSH + RPOP BlockingMQ(阻塞队列)= LPUSH + BRPOP
@Test
public void testList(){
String key = "a123456";
redisUtil.del(key);
String v1 = "aaaaa";
String v2 = "bbbbb";
String v3 = "ccccc";
List list = new ArrayList();
list.add(v1);
list.add(v2);
list.add(v3);
boolean b1 = redisUtil.lSet(key,list);
System.out.println(redisUtil.lGet(key,0,-1));
System.out.println(redisUtil.lGetIndex(key,0));
System.out.println(redisUtil.lpop(key));
System.out.println(redisUtil.rpop(key));
System.out.println(redisUtil.lGet(key,0,-1));
redisUtil.del(key);
redisUtil.rpush(key,v1);
System.out.println(redisUtil.lGet(key,0,-1));
redisUtil.rpush(key,v2);
System.out.println(redisUtil.lGet(key,0,-1));
redisUtil.lpush(key,v3);
System.out.println(redisUtil.lGet(key,0,-1));
}
实际工作中常用于消息推送,比如vx订阅号推送,微博推送
代码实现:
@Test
public void testVX(){
String key = "VXuser:a123456";
redisUtil.del(key);
String message1 = "a1";
String message2 = "b2";
String message3 = "c3";
//订阅号a发表了一片文章,文章id是a1
redisUtil.lpush(key,message1);
//订阅号b发表了一片文章,文章id是b2
redisUtil.lpush(key,message2);
//订阅号b发表了一片文章,文章id是c3
redisUtil.lpush(key,message3);
//用户获取
System.out.println(redisUtil.lGet(key,0,-1));
}
比如user:a23456订阅了这个订阅号a,订阅号a写了一片内容,只需要加入user:a123456的队列中,user就能查看到,这里的redisUtil.lGet(key,0,-1)表示获取key下的所有v。
set
set常用于一些数学运算,他有求交集,并集,差集的功能:
@Test
public void testset(){
String key1 = "a1";
redisUtil.del(key1);
String key2 = "a2";
redisUtil.del(key2);
redisUtil.sSet(key1,1,2,3,4,5);
System.out.println("key1="+redisUtil.sGet(key1));
redisUtil.sSet(key2,1,2,5,6,7);
System.out.println("key1="+redisUtil.sGet(key2));
//获取key的数量
System.out.println("length="+redisUtil.sGetSetSize(key1));
//取key1和key2的交集
System.out.println("交集="+redisUtil.sIntersect(key1,key2));
//取key1和key2的差集
System.out.println("差集="+redisUtil.sDifference(key1,key2));
//取key1和key2的并集
System.out.println("并集="+redisUtil.sUnion(key1,key2));
//取key1的随机一个数
System.out.println("随机数="+redisUtil.sRandom(key1));
System.out.println("key1="+redisUtil.sGet(key1));
//取key1的随机一个数,并且这个值在key中删除
System.out.println("随机数="+redisUtil.spop(key1));
System.out.println("key1="+redisUtil.sGet(key1));
}
实际工作中可以实现抽奖,共同关注,推荐好友等功能:
@Test
public void testSet2(){
String key ="act:123456";
redisUtil.del(key);
long l = redisUtil.sSet(key,"a1","a2","a3","a4","a5");
System.out.println(redisUtil.sGet(key));
//抽奖
System.out.println(redisUtil.spop(key));
String user1 = "vxuser:a123456";
String user2 = "vxuser:b123456";
String user3 = "vxuser:c123456";
String user4 = "vxuser:d123456";
String user5 = "vxuser:e123456";
String user6 = "vxuser:f123456";
redisUtil.del("gzuser1");
redisUtil.del("gzuser2");
//gzuser1关注user2,user3,user6
redisUtil.sSet("gzuser1",user2,user3,user6);
//gzuser2关注user1,user3,user4,user5
redisUtil.sSet("gzuser2",user1,user3,user4,user5);
//共同好友
System.out.println("共同好友"+redisUtil.sIntersect("gzuser1","gzuser2"));
//你关注的好友也关注了他
Set<String> set = redisUtil.sUnion(user1,user2);
//这里取并集,放在中间表bj里
for (String s:set){
redisUtil.sSet("bj",s);
}
System.out.println(redisUtil.sGet("bj"));
System.out.println("你关注的好友也关注了他"+redisUtil.sDifference("bj","gzuser1"));
}
zset
zset相比较set一个有序,一个无序,具体功能大同小异,我就不多说。
string
简单的string基本功能我就不多说了,这里要说的是分布式常见的setnx命令实现分布式锁:
setnx命令其实表示『SET if Not eXists』(如果不存在,则 SET)的简写。设置成功,返回 1 ;设置失败,返回 0 。
如果set 的key已经在redis里了,你再setnx,就会失败,但是你继续用set命令,是会覆盖当前的key,且返回成功。
/*
* setnx 实现最简单的分布式锁
* */
@Test
public void setlock() {
DefaultRedisScript script = new DefaultRedisScript();
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/deleteLua.lua")));
script.setResultType(Long.class);
String key ="product:001";
String value = Thread.currentThread().getId()+"";
try {
boolean result = redisUtil.setnx(key,value,100);
if(!result){
System.out.println("系统繁忙中");
} else {
System.out.println("这里是你业务代码");
}
}catch (Exception e){
e.printStackTrace();
}finally {
Object result = redisUtil.execute(script,Collections.singletonList(key),value);
System.out.println(result);
// if (value.equals(redisUtil.get(key))){
// redisUtil.del(key);
//
// }
}
}
这是最简单的分布式锁实现,我给大家简单解释下:
首先,DefaultRedisScript这个类是为了读取lua脚本,这里笔者的finally代码块用了lua脚本删除redis的key,其实
Object result = redisUtil.execute(script,Collections.singletonList(key),value);
这行代码等价于下面这行代码
if (value.equals(redisUtil.get(key))){
redisUtil.del(key);
}
为什么要用lua脚本呢,更多的是为了原子性,如果用if操作,要执行两部,在大型的环境下很容易出问题,而lua脚本就不会,他把两个步骤合成一个步骤去执行,这样保证原子性。
我给大家看下lua删除key的代码,目录在
lua代码:
if redis.call('get', KEYS[1]) == ARGV[1]
then
return redis.call('del', KEYS[1])
else
return 0
end
注意这里的lua脚本可以放在static下,但是他的返回值也就是 script.setResultType(Long.class)必须和lua脚本的返回值一致,如果你返回的是string那就是script.setResultType(String.class),数字类型必须是Long,如果你写Integer,会报java.lang.IllegalStateException错误,这个lua脚本执行成功返回1,所以大家要注意。
至于锁的时间这个可以根据业务来定,如果你设置的超时间比较短,但是执行的业务代码所用时间大于你设置的超时时间,你可以开一个线程,再去设置一个新的过期时间。由于分布式锁在实际工作中可能更复杂一点,所以redisson帮我们更好的实现了分布式锁,而且还有单机,哨兵,集群,主从等多个模式。
单机redisson
@Test
public void testsingleRedisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
RedissonClient redisson = Redisson.create(config);
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
boolean res = lock.tryLock(10,100,TimeUnit.SECONDS);
if ( res){
System.out.println("这里是你的业务代码");
}else{
System.out.println("系统繁忙");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
这里注意下几个Rlock 常用的几个方法:
void lock(long leaseTime, TimeUnit unit);
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
常用的以上三个加锁
第一表示lock表示去加锁,加锁成功,没有返回值,继续执行下面代码;但是如果redis已经有这个锁了,它会一直阻塞,直到锁的时间失效,再继续往下执行
第二个两个参数的trylock表示尝试去加锁(第一个参数表示key的失效时间),加锁成功,返回true,继续执行true下面代码;但是如果redis已经有这个锁了,它会返回false,执行false的代码块,且不等待
第三个三个参数的trylock表示尝试去加锁(第一个参数表示等待时间,第二个参数表示key的失效时间),加锁成功,返回true,继续执行true下面代码;如果返回false,它会等待第一个参数设置的时间,然后去执行false下面的代码
当然redisson的功能不仅如此,它还同时还为分布式锁提供了异步执行的相关方法
@Test
public void testsingleRedissonSync(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
RedissonClient redisson = Redisson.create(config);
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
lock.lockAsync();
lock.lockAsync(100,TimeUnit.SECONDS);
Future<Boolean> res = lock.tryLockAsync(3,100, TimeUnit.SECONDS);
if ( res.get()){
System.out.println("这里是你的业务代码");
}else{
System.out.println("系统繁忙");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
上面的代码效果其实和boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException这个方法差不多「尝试去加锁(第一个参数表示等待时间,第二个参数表示key的失效时间),加锁成功,返回true,继续执行true下面代码;如果返回false,它会等待第一个参数设置的时间,然后去执行false下面的代码」。
主从redisson
@Test
public void testMSRedisson(){
Config config = new Config();
config.useMasterSlaveServers()
.setMasterAddress("redis://127.0.0.1:6379")
.addSlaveAddress("redis://127.0.0.1:6380", "redis://127.0.0.1:6381");
RedissonClient redisson = Redisson.create(config);
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
boolean res = lock.tryLock(10,100,TimeUnit.SECONDS);
if (res){
System.out.println("这里是你的业务代码");
}else{
System.out.println("系统繁忙");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
哨兵redisson
@Test
public void testSentineRedisson(){
Config config = new Config();
config.useSentinelServers()
.addSentinelAddress("redis://127.0.0.1:26379")
.addSentinelAddress("redis://127.0.0.1:26389")
.addSentinelAddress("redis://127.0.0.1:26399");
RedissonClient redisson = Redisson.create(config);
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
boolean res = lock.tryLock(10,100,TimeUnit.SECONDS);
if (res){
System.out.println("这里是你的业务代码");
}else{
System.out.println("系统繁忙");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
集群redisson
@Test
public void testClusterRedisson(){
Config config = new Config();
config.useClusterServers()
// 集群状态扫描间隔时间,单位是毫秒
.setScanInterval(2000)
//cluster方式至少6个节点(3主3从,3主做sharding,3从用来保证主宕机后可以高可用)
.addNodeAddress("redis://127.0.0.1:6379" )
.addNodeAddress("redis://127.0.0.1:6380")
.addNodeAddress("redis://127.0.0.1:6381")
.addNodeAddress("redis://127.0.0.1:6382")
.addNodeAddress("redis://127.0.0.1:6383")
.addNodeAddress("redis://127.0.0.1:6384");
RedissonClient redisson = Redisson.create(config);
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
boolean res = lock.tryLock(10,100,TimeUnit.SECONDS);
if (res){
System.out.println("这里是你的业务代码");
}else{
System.out.println("系统繁忙");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
上面就是就是各种模式的redisson实现,锁的代码很简单,主要是就是修改下redisson配置,其实redisson功能远比这个更丰富,大家可以一起去学习学习
来源 简书| 灵_芝
链接:https://www.jianshu.com/p/cd9c55473ddc
如有侵权请联系删除
相关推荐
- sharding-jdbc实现`分库分表`与`读写分离`
-
一、前言本文将基于以下环境整合...
- 三分钟了解mysql中主键、外键、非空、唯一、默认约束是什么
-
在数据库中,数据表是数据库中最重要、最基本的操作对象,是数据存储的基本单位。数据表被定义为列的集合,数据在表中是按照行和列的格式来存储的。每一行代表一条唯一的记录,每一列代表记录中的一个域。...
- MySQL8行级锁_mysql如何加行级锁
-
MySQL8行级锁版本:8.0.34基本概念...
- mysql使用小技巧_mysql使用入门
-
1、MySQL中有许多很实用的函数,好好利用它们可以省去很多时间:group_concat()将取到的值用逗号连接,可以这么用:selectgroup_concat(distinctid)fr...
- MySQL/MariaDB中如何支持全部的Unicode?
-
永远不要在MySQL中使用utf8,并且始终使用utf8mb4。utf8mb4介绍MySQL/MariaDB中,utf8字符集并不是对Unicode的真正实现,即不是真正的UTF-8编码,因...
- 聊聊 MySQL Server 可执行注释,你懂了吗?
-
前言MySQLServer当前支持如下3种注释风格:...
- MySQL系列-源码编译安装(v5.7.34)
-
一、系统环境要求...
- MySQL的锁就锁住我啦!与腾讯大佬的技术交谈,是我小看它了
-
对酒当歌,人生几何!朝朝暮暮,唯有己脱。苦苦寻觅找工作之间,殊不知今日之事乃我心之痛,难道是我不配拥有工作嘛。自面试后他所谓的等待都过去一段时日,可惜在下京东上的小金库都要见低啦。每每想到不由心中一...
- MySQL字符问题_mysql中字符串的位置
-
中文写入乱码问题:我输入的中文编码是urf8的,建的库是urf8的,但是插入mysql总是乱码,一堆"???????????????????????"我用的是ibatis,终于找到原因了,我是这么解决...
- 深圳尚学堂:mysql基本sql语句大全(三)
-
数据开发-经典1.按姓氏笔画排序:Select*FromTableNameOrderByCustomerNameCollateChinese_PRC_Stroke_ci_as//从少...
- MySQL进行行级锁的?一会next-key锁,一会间隙锁,一会记录锁?
-
大家好,是不是很多人都对MySQL加行级锁的规则搞的迷迷糊糊,一会是next-key锁,一会是间隙锁,一会又是记录锁。坦白说,确实还挺复杂的,但是好在我找点了点规律,也知道如何如何用命令分析加...
- 一文讲清怎么利用Python Django实现Excel数据表的导入导出功能
-
摘要:Python作为一门简单易学且功能强大的编程语言,广受程序员、数据分析师和AI工程师的青睐。本文系统讲解了如何使用Python的Django框架结合openpyxl库实现Excel...
- 用DataX实现两个MySQL实例间的数据同步
-
DataXDataX使用Java实现。如果可以实现数据库实例之间准实时的...
- MySQL数据库知识_mysql数据库基础知识
-
MySQL是一种关系型数据库管理系统;那废话不多说,直接上自己以前学习整理文档:查看数据库命令:(1).查看存储过程状态:showprocedurestatus;(2).显示系统变量:show...
- 如何为MySQL中的JSON字段设置索引
-
背景MySQL在2015年中发布的5.7.8版本中首次引入了JSON数据类型。自此,它成了一种逃离严格列定义的方式,可以存储各种形状和大小的JSON文档,例如审计日志、配置信息、第三方数据包、用户自定...
你 发表评论:
欢迎- 一周热门
-
-
MySQL中这14个小玩意,让人眼前一亮!
-
旗舰机新标杆 OPPO Find X2系列正式发布 售价5499元起
-
【VueTorrent】一款吊炸天的qBittorrent主题,人人都可用
-
面试官:使用int类型做加减操作,是线程安全吗
-
C++编程知识:ToString()字符串转换你用正确了吗?
-
【Spring Boot】WebSocket 的 6 种集成方式
-
PyTorch 深度学习实战(26):多目标强化学习Multi-Objective RL
-
pytorch中的 scatter_()函数使用和详解
-
与 Java 17 相比,Java 21 究竟有多快?
-
基于TensorRT_LLM的大模型推理加速与OpenAI兼容服务优化
-
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)