redis實(shí)戰(zhàn)筆記

1.redis數(shù)據(jù)結(jié)構(gòu)

  1. string
    string可以存儲(chǔ)字符串、整數(shù)或者浮點(diǎn)數(shù)。
    當(dāng)存儲(chǔ)整數(shù)時(shí),可以進(jìn)行自增或者自減操作



一個(gè)鍵對(duì)應(yīng)一個(gè)值


  1. list列表
    一個(gè)列表結(jié)構(gòu)可以有序的存儲(chǔ)多個(gè)字符串,且可以重復(fù)。
    列表可以從序列的兩端推入(RPUSH、LPUSH)或者彈出元素(RPOP、LPOP),也可以返回某下標(biāo)的元素(lindex(key,index))還可以存儲(chǔ)任務(wù)信息、最近瀏覽過(guò)的文章信息或常用聯(lián)系人信息。
    還可以使用阻塞彈出和推入命令實(shí)現(xiàn)消息傳遞和任務(wù)隊(duì)列。




  2. set集合
    集合和列表都可以存儲(chǔ)多個(gè)字符串,可以刪除集合中的某個(gè)或多個(gè)元素(srem),可以返回集合的所有元素(smembers(key)),不同在于set集合存儲(chǔ)的字符串是不可重復(fù)且無(wú)序的。
    集合真正厲害的地方是組合和處理多個(gè)集合
    sdiff:兩個(gè)集合的差集
    sinter:交集
    sunion:并集



  1. hash散列
    redis的散列可以存儲(chǔ)多個(gè)鍵和值得映射,各個(gè)鍵不相同,且無(wú)序




  2. zset有序集合
    zset有序集合和散列一樣,都是存儲(chǔ)多個(gè)鍵值對(duì),有序集合的鍵被稱為成員(member),值被稱為分?jǐn)?shù)(score),既可以根據(jù)鍵來(lái)訪問(wèn)元素,也可以根據(jù)值和值得排列順序來(lái)訪問(wèn)元素。
    可以根據(jù)鍵獲取排名(zrank),根據(jù)鍵獲取分值(zscore)
    也可以獲取范圍內(nèi)的排名(zrange)
    取并集(zunionstore),兩個(gè)集合相同元素的score取最小值
    取交集(zinterstore),兩個(gè)集合相同元素的score取和




2.redis的發(fā)布和訂閱

發(fā)送者負(fù)責(zé)向頻道發(fā)送消息,頻道內(nèi)的所有訂閱者都會(huì)獲得消息
subscribe channel --訂閱一個(gè)頻道
unsubscribe channel --退訂給定頻道
public channel msg --向給定頻道發(fā)送消息

3.基本的redis事務(wù)

redis的基本事務(wù)需要用到multi命令和exec命令,這種事務(wù)可以讓一個(gè)客戶端在不被其他客戶端打斷的情況下執(zhí)行多個(gè)命令,在redis中,被multi命令和exec命令包圍的命令會(huì)依次執(zhí)行,直到所用的命令執(zhí)行完畢為止。當(dāng)一個(gè)事務(wù)執(zhí)行完畢之后,redis才會(huì)執(zhí)行其他客戶端的命令。
在客戶端中使用piepline()方法創(chuàng)建一個(gè)事物,在一切正常的情況下,客戶端會(huì)自動(dòng)的使用multi和exec命令包裹用戶輸入的多個(gè)命令,為了提升性能,會(huì)在事務(wù)執(zhí)行時(shí)一次性地將所有命令都發(fā)送給redis,減少連接次數(shù),提高性能。
當(dāng)使用piepline(false)時(shí),將使用非事務(wù)型流水線,被包裹的命令一次連接執(zhí)行全部命令,但不以事務(wù)方式運(yùn)行。



使用流水線性能提高多倍。

    public void benchmarkUpdateToken(Jedis conn, int duration) {
        try{
            @SuppressWarnings("rawtypes")
            Class[] args = new Class[]{
                Jedis.class, String.class, String.class, String.class};
            Method[] methods = new Method[]{
                this.getClass().getDeclaredMethod("updateToken", args),
                this.getClass().getDeclaredMethod("updateTokenPipeline", args),
            };
            for (Method method : methods){
                int count = 0;
                long start = System.currentTimeMillis();
                long end = start + (duration * 1000);
                while (System.currentTimeMillis() < end){
                    count++;
                    method.invoke(this, conn, "token", "user", "item");
                }
                long delta = System.currentTimeMillis() - start;
                System.out.println(
                        method.getName() + ' ' +
                        count + ' ' +
                        (delta / 1000) + ' ' +
                        (count / (delta / 1000)));
            }
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
    public void updateToken(Jedis conn, String token, String user, String item) {
        long timestamp = System.currentTimeMillis() / 1000;
        conn.hset("login:", token, user);
        conn.zadd("recent:", timestamp, token);
        if (item != null) {
            conn.zadd("viewed:" + token, timestamp, item);
            conn.zremrangeByRank("viewed:" + token, 0, -26);
            conn.zincrby("viewed:", -1, item);
        }
    }

    public void updateTokenPipeline(Jedis conn, String token, String user, String item) {
        long timestamp = System.currentTimeMillis() / 1000;
        Pipeline pipe = conn.pipelined();
        pipe.multi();
        pipe.hset("login:", token, user);
        pipe.zadd("recent:", timestamp, token);
        if (item != null){
            pipe.zadd("viewed:" + token, timestamp, item);
            pipe.zremrangeByRank("viewed:" + token, 0, -26);
            pipe.zincrby("viewed:", -1, item);
        }
        pipe.exec();
    }

//updateToken 52558 5 10511
//updateTokenPipeline 476595 5 95319

watch命令:
在使用watch命令對(duì)鍵進(jìn)行監(jiān)視之后,直到執(zhí)行exec命令的這段時(shí)間里,如果其他客戶端對(duì)被監(jiān)視的鍵進(jìn)行了修改,那么在執(zhí)行exec時(shí),事務(wù)將失敗。(樂(lè)觀鎖)
比如在執(zhí)行購(gòu)買商品事務(wù)時(shí)監(jiān)視商品,當(dāng)商品發(fā)生變化時(shí),事務(wù)執(zhí)行失敗。

   public boolean purchaseItem(
            Jedis conn, String buyerId, String itemId, String sellerId, double lprice) {

        String buyer = "users:" + buyerId;
        String seller = "users:" + sellerId;
        String item = itemId + '.' + sellerId;
        String inventory = "inventory:" + buyerId;
        long end = System.currentTimeMillis() + 10000;

        while (System.currentTimeMillis() < end){
            conn.watch("market:", buyer); //對(duì)市場(chǎng)中買家的商品進(jìn)行監(jiān)視

            double price = conn.zscore("market:", item);
            double funds = Double.parseDouble(conn.hget(buyer, "funds"));
            if (price != lprice || price > funds){
                conn.unwatch();
                return false;
            }

            Transaction trans = conn.multi(); // 事務(wù)中轉(zhuǎn)移商品和錢
            trans.hincrBy(seller, "funds", (int)price);
            trans.hincrBy(buyer, "funds", (int)-price);
            trans.sadd(inventory, itemId);
            trans.zrem("market:", item);
            List<Object> results = trans.exec(); //如果exec方法沒(méi)有引發(fā)watchError錯(cuò)誤,說(shuō)明該鍵沒(méi)有被更改,事務(wù)執(zhí)行成功,并且watch執(zhí)行結(jié)束
            // null response indicates that the transaction was aborted due to
            // the watched key changing.
            if (results == null){
                continue;
            }
            return true;
        }

        return false;
    }

4.鍵的過(guò)期時(shí)間

        // 設(shè)置過(guò)期時(shí)間,對(duì)于list,set,hash來(lái)說(shuō),只能為鍵設(shè)置過(guò)期時(shí)間,而無(wú)法為鍵里的單個(gè)元素設(shè)置過(guò)期時(shí)間
        conn.expire("tran", 2);
        // 查看鍵距離過(guò)期還有多長(zhǎng)時(shí)間
        Long tran = conn.ttl("tran");

5.持久化策略

  • 快照持久化
    redis可以通過(guò)創(chuàng)建快照來(lái)獲得存儲(chǔ)在內(nèi)存的數(shù)據(jù)的某個(gè)時(shí)間點(diǎn)上的副本。
save 60 1000    //60秒內(nèi)有1000次寫入則執(zhí)行快照
stop-writes-on-bgsave-error no //創(chuàng)建快照失敗后是否執(zhí)行寫命令
rdbcompression yes //是否對(duì)快照文件進(jìn)行壓縮
dbfilename dump.rdb //快照創(chuàng)建的副本文件名

創(chuàng)建快照的幾種方式

  1. BGSAVE,會(huì)創(chuàng)建子線程
  2. SAVE
  3. 設(shè)置save配置選項(xiàng),觸發(fā)條件后會(huì)執(zhí)行BGSAVE命令

快照的缺陷

  1. 在只使用快照持久化保存數(shù)據(jù)時(shí),如果系統(tǒng)真的發(fā)生崩潰,用戶將丟失最近一次生成快照之后更改的所有數(shù)據(jù)。
  2. 當(dāng)數(shù)據(jù)量太大時(shí),BGSAVE創(chuàng)建的子線程要將數(shù)據(jù)保存到硬盤中耗費(fèi)的時(shí)間越來(lái)越多。
  • AOF持久化
    簡(jiǎn)單來(lái)說(shuō),AOF持久化會(huì)將被執(zhí)行的寫命令寫到AOF文件的末尾,以此來(lái)記錄文件的變化。因此,redis只要從頭到尾重新執(zhí)行一次AOF文件的寫命令,就可以恢復(fù)AOF文件所記錄的數(shù)據(jù)集,AOF文件可能會(huì)很大,可以使用bgrewriteaof命令消除aof文件中的冗余命令。


    image.png
appendonly no //是否使用AOF持久化
appendfsync everysec //多久才將寫入的內(nèi)容同步到硬盤,可選always、everysec、no
no-appendfsync-on-rewrite no 
auto-aof-rewrite-percentage 100 //AOF文件體積上次重寫大100%時(shí)重新執(zhí)行bgrewriteaof
auto-aof-rewrite-min-size 64mb //只有在AOF文件體積大于64M時(shí)才執(zhí)行bgrewriteaof,這個(gè)命令會(huì)移除AOF文件中的冗余命令來(lái)重寫AOF文件,使AOF文件盡可能的小
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容