迎关注我的头条号: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实现购物车功能:
例如:


redis分布式锁代码具体实现(Redis常见的工作场景使用实战)(1)

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订阅号推送,微博推送


redis分布式锁代码具体实现(Redis常见的工作场景使用实战)(2)

代码实现:

@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的代码,目录在

redis分布式锁代码具体实现(Redis常见的工作场景使用实战)(3)

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

如有侵权请联系删除


,