百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

Redis常见的工作场景使用实战,Redisson分布式锁的实现

ztj100 2024-11-04 15:16 34 浏览 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

如有侵权请联系删除


相关推荐

如何将数据仓库迁移到阿里云 AnalyticDB for PostgreSQL

阿里云AnalyticDBforPostgreSQL(以下简称ADBPG,即原HybridDBforPostgreSQL)为基于PostgreSQL内核的MPP架构的实时数据仓库服务,可以...

Python数据分析:探索性分析

写在前面如果你忘记了前面的文章,可以看看加深印象:Python数据处理...

CSP-J/S冲奖第21天:插入排序

...

C++基础语法梳理:算法丨十大排序算法(二)

本期是C++基础语法分享的第十六节,今天给大家来梳理一下十大排序算法后五个!归并排序...

C 语言的标准库有哪些

C语言的标准库并不是一个单一的实体,而是由一系列头文件(headerfiles)组成的集合。每个头文件声明了一组相关的函数、宏、类型和常量。程序员通过在代码中使用#include<...

[深度学习] ncnn安装和调用基础教程

1介绍ncnn是腾讯开发的一个为手机端极致优化的高性能神经网络前向计算框架,无第三方依赖,跨平台,但是通常都需要protobuf和opencv。ncnn目前已在腾讯多款应用中使用,如QQ,Qzon...

用rust实现经典的冒泡排序和快速排序

1.假设待排序数组如下letmutarr=[5,3,8,4,2,7,1];...

ncnn+PPYOLOv2首次结合!全网最详细代码解读来了

编辑:好困LRS【新智元导读】今天给大家安利一个宝藏仓库miemiedetection,该仓库集合了PPYOLO、PPYOLOv2、PPYOLOE三个算法pytorch实现三合一,其中的PPYOL...

C++特性使用建议

1.引用参数使用引用替代指针且所有不变的引用参数必须加上const。在C语言中,如果函数需要修改变量的值,参数必须为指针,如...

Qt4/5升级到Qt6吐血经验总结V202308

00:直观总结增加了很多轮子,同时原有模块拆分的也更细致,估计为了方便拓展个管理。把一些过度封装的东西移除了(比如同样的功能有多个函数),保证了只有一个函数执行该功能。把一些Qt5中兼容Qt4的方法废...

到底什么是C++11新特性,请看下文

C++11是一个比较大的更新,引入了很多新特性,以下是对这些特性的详细解释,帮助您快速理解C++11的内容1.自动类型推导(auto和decltype)...

掌握C++11这些特性,代码简洁性、安全性和性能轻松跃升!

C++11(又称C++0x)是C++编程语言的一次重大更新,引入了许多新特性,显著提升了代码简洁性、安全性和性能。以下是主要特性的分类介绍及示例:一、核心语言特性1.自动类型推导(auto)编译器自...

经典算法——凸包算法

凸包算法(ConvexHull)一、概念与问题描述凸包是指在平面上给定一组点,找到包含这些点的最小面积或最小周长的凸多边形。这个多边形没有任何内凹部分,即从一个多边形内的任意一点画一条线到多边形边界...

一起学习c++11——c++11中的新增的容器

c++11新增的容器1:array当时的初衷是希望提供一个在栈上分配的,定长数组,而且可以使用stl中的模板算法。array的用法如下:#include<string>#includ...

C++ 编程中的一些最佳实践

1.遵循代码简洁原则尽量避免冗余代码,通过模块化设计、清晰的命名和良好的结构,让代码更易于阅读和维护...

取消回复欢迎 发表评论: