Redis常見的工作場景使用實戰(zhàn),redisson分布式鎖的實現(xiàn)

本項目基于springboot+ spring-boot-starter-data-redis+redisson

簡單的redis demo可以參考:SpringBoot整合redis

github地址:https://github.com/weiess/redis-and-Redisson.git
redis大家工作的時候都很多蒜哀,筆者根據(jù)自己經(jīng)驗總結(jié)下redis的幾個數(shù)據(jù)類型斩箫,常用場景,也歡迎大家留言總結(jié)自己的經(jīng)驗撵儿;

hash

hash在redis里可以存儲對象乘客,當然string也可以,只不過hash相比較string淀歇,效率更高一點寨典,而且功能也很強大,可以實現(xiàn)簡單的購物車:
下面先給大家看下簡單的存儲對象hash實現(xiàn):

/*
    *   hash實現(xiàn)存儲對象
    * */
    @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三種結(jié)構(gòu)井氢,可以把后面的item和value看成一個map,這樣結(jié)構(gòu)就是key map<obj,obj>岳链,方便理解花竞。

實際項目中可以給key加個標示符,
比如key = userId:a123456掸哑,這個大家可以根據(jù)項目來定義约急。

可以用hash實現(xiàn)購物車功能:
例如:


hash.jpg

代碼如下:

    /*
     *   hash實現(xiàn)購物車
     * */
    @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的數(shù)量
        redisUtil.hincr(key,"book:a11111",1);
        System.out.println(redisUtil.hmget(key));
        //減少book:a11112的數(shù)量
        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數(shù)量
        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是數(shù)量)
功能實現(xiàn)注釋都有厌蔽。注意這里的減操作是可以為負數(shù)的,所以大家一定要注意判斷負數(shù)的情況摔癣。

list

常見的list可以分為下面三個數(shù)據(jù)結(jié)構(gòu)方便大家理解:

  • 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訂閱號推送,微博推送


Wechat.jpeg

代碼實現(xiàn):

    @Test
    public void testVX(){
        String key = "VXuser:a123456";
        redisUtil.del(key);
        String message1 = "a1";
        String message2 = "b2";
        String message3 = "c3";

        //訂閱號a發(fā)表了一片文章择浊,文章id是a1
        redisUtil.lpush(key,message1);
        //訂閱號b發(fā)表了一片文章戴卜,文章id是b2
        redisUtil.lpush(key,message2);
        //訂閱號b發(fā)表了一片文章,文章id是c3
        redisUtil.lpush(key,message3);

        //用戶獲取
        System.out.println(redisUtil.lGet(key,0,-1));

    }

比如user:a23456訂閱了這個訂閱號a琢岩,訂閱號a寫了一片內(nèi)容投剥,只需要加入user:a123456的隊列中,user就能查看到担孔,這里的redisUtil.lGet(key,0,-1)表示獲取key下的所有v江锨。

set

set常用于一些數(shù)學運算,他有求交集攒磨,并集泳桦,差集的功能:

    @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的數(shù)量
        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的隨機一個數(shù)
        System.out.println("隨機數(shù)="+redisUtil.sRandom(key1));
        System.out.println("key1="+redisUtil.sGet(key1));
        //取key1的隨機一個數(shù),并且這個值在key中刪除
        System.out.println("隨機數(shù)="+redisUtil.spop(key1));
        System.out.println("key1="+redisUtil.sGet(key1));
    }

實際工作中可以實現(xiàn)抽獎娩缰,共同關(guān)注灸撰,推薦好友等功能:

     @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關(guān)注user2,user3,user6
        redisUtil.sSet("gzuser1",user2,user3,user6);
        //gzuser2關(guān)注user1,user3,user4,user5
        redisUtil.sSet("gzuser2",user1,user3,user4,user5);
        //共同好友
        System.out.println("共同好友"+redisUtil.sIntersect("gzuser1","gzuser2"));
        //你關(guān)注的好友也關(guān)注了他
        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("你關(guān)注的好友也關(guān)注了他"+redisUtil.sDifference("bj","gzuser1"));
    }

zset

zset相比較set一個有序拼坎,一個無序浮毯,具體功能大同小異,我就不多說泰鸡。

string

簡單的string基本功能我就不多說了债蓝,這里要說的是分布式常見的setnx命令實現(xiàn)分布式鎖:

setnx命令其實表示『SET if Not eXists』(如果不存在,則 SET)的簡寫盛龄。設置成功饰迹,返回 1 芳誓;設置失敗,返回 0 啊鸭。
如果set 的key已經(jīng)在redis里了锹淌,你再setnx,就會失敗赠制,但是你繼續(xù)用set命令赂摆,是會覆蓋當前的key,且返回成功钟些。

/*
    *   setnx 實現(xiàn)最簡單的分布式鎖
    * */
    @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("系統(tǒng)繁忙中");
            } else {
                System.out.println("這里是你業(yè)務代碼");
            }

        }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);
//
//            }
        }
    }

這是最簡單的分布式鎖實現(xiàn)烟号,我給大家簡單解釋下:
首先,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操作喷楣,要執(zhí)行兩部,在大型的環(huán)境下很容易出問題鹤树,而lua腳本就不會铣焊,他把兩個步驟合成一個步驟去執(zhí)行,這樣保證原子性罕伯。

我給大家看下lua刪除key的代碼曲伊,目錄在


lua.jpg

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)坟募,數(shù)字類型必須是Long,如果你寫Integer邑狸,會報java.lang.IllegalStateException錯誤懈糯,這個lua腳本執(zhí)行成功返回1,所以大家要注意单雾。

至于鎖的時間這個可以根據(jù)業(yè)務來定赚哗,如果你設置的超時間比較短,但是執(zhí)行的業(yè)務代碼所用時間大于你設置的超時時間硅堆,你可以開一個線程屿储,再去設置一個新的過期時間。由于分布式鎖在實際工作中可能更復雜一點渐逃,所以redisson幫我們更好的實現(xiàn)了分布式鎖够掠,而且還有單機,哨兵茄菊,集群疯潭,主從等多個模式赊堪。

單機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("這里是你的業(yè)務代碼");
            }else{
                System.out.println("系統(tǒng)繁忙");
            }
        }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表示去加鎖,加鎖成功竖哩,沒有返回值雹食,繼續(xù)執(zhí)行下面代碼;但是如果redis已經(jīng)有這個鎖了期丰,它會一直阻塞,直到鎖的時間失效吃挑,再繼續(xù)往下執(zhí)行

第二個兩個參數(shù)的trylock表示嘗試去加鎖(第一個參數(shù)表示key的失效時間)钝荡,加鎖成功,返回true,繼續(xù)執(zhí)行true下面代碼舶衬;但是如果redis已經(jīng)有這個鎖了埠通,它會返回false,執(zhí)行false的代碼塊逛犹,且不等待

第三個三個參數(shù)的trylock表示嘗試去加鎖(第一個參數(shù)表示等待時間端辱,第二個參數(shù)表示key的失效時間),加鎖成功,返回true虽画,繼續(xù)執(zhí)行true下面代碼舞蔽;如果返回false,它會等待第一個參數(shù)設置的時間码撰,然后去執(zhí)行false下面的代碼

當然redisson的功能不僅如此渗柿,它還同時還為分布式鎖提供了異步執(zhí)行的相關(guān)方法

     @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("這里是你的業(yè)務代碼");
            }else{
                System.out.println("系統(tǒng)繁忙");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

上面的代碼效果其實和boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException這個方法差不多「嘗試去加鎖(第一個參數(shù)表示等待時間,第二個參數(shù)表示key的失效時間)脖岛,加鎖成功,返回true朵栖,繼續(xù)執(zhí)行true下面代碼;如果返回false柴梆,它會等待第一個參數(shù)設置的時間陨溅,然后去執(zhí)行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("這里是你的業(yè)務代碼");
            }else{
                System.out.println("系統(tǒng)繁忙");
            }
        }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("這里是你的業(yè)務代碼");
            }else{
                System.out.println("系統(tǒng)繁忙");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

集群redisson

    @Test
    public void testClusterRedisson(){
        Config config = new Config();
        config.useClusterServers()
                // 集群狀態(tài)掃描間隔時間绍在,單位是毫秒
                .setScanInterval(2000)
                //cluster方式至少6個節(jié)點(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("這里是你的業(yè)務代碼");
            }else{
                System.out.println("系統(tǒng)繁忙");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

上面就是就是各種模式的redisson實現(xiàn)揣苏,鎖的代碼很簡單悯嗓,主要是就是修改下redisson配置,其實redisson功能遠比這個更豐富卸察,大家可以一起去學習學習

ok
源碼地址:https://github.com/weiess/redis-and-Redisson.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脯厨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坑质,更是在濱河造成了極大的恐慌合武,老刑警劉巖临梗,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異稼跳,居然都是意外死亡盟庞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門汤善,熙熙樓的掌柜王于貴愁眉苦臉地迎上來什猖,“玉大人,你說我怎么就攤上這事红淡〔皇ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵在旱,是天一觀的道長摇零。 經(jīng)常有香客問我,道長桶蝎,這世上最難降的妖魔是什么驻仅? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮登渣,結(jié)果婚禮上噪服,老公的妹妹穿的比我還像新娘。我一直安慰自己绍豁,他們只是感情好芯咧,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著竹揍,像睡著了一般敬飒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芬位,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天无拗,我揣著相機與錄音,去河邊找鬼昧碉。 笑死英染,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的被饿。 我是一名探鬼主播四康,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼狭握!你這毒婦竟也來了闪金?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哎垦,沒想到半個月后囱嫩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡漏设,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年墨闲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郑口。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸳碧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出犬性,到底是詐尸還是另有隱情杆兵,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布仔夺,位于F島的核電站,受9級特大地震影響攒砖,放射性物質(zhì)發(fā)生泄漏缸兔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一吹艇、第九天 我趴在偏房一處隱蔽的房頂上張望惰蜜。 院中可真熱鬧,春花似錦受神、人聲如沸抛猖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽财著。三九已至,卻和暖如春撑碴,著一層夾襖步出監(jiān)牢的瞬間撑教,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工醉拓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留伟姐,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓亿卤,卻偏偏與公主長得像愤兵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子排吴,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容