Redis常見問題彙總

擊水三千里發表於2019-02-11

問題1:懂Redis事務麼?
Redis Cluster叢集架構,不同的key是有可能分配在不同的Redis節點上的,在這種情況下Redis的事務機制是不生效的。 Redis的事務功能較弱(不支援回滾),而且叢集版本(自研和官方)要求一次事務操作的key必須在一個slot上(可以使用 hashtag 功能解決)

 

 

問題2:Redis的多資料庫機制,瞭解多少?
Redis支援多個資料庫,並且每個資料庫的資料是隔離的不能共享,單機下的redis可以支援16個資料庫(db0 ~ db15)
 在Redis Cluster叢集架構下只有一個資料庫空間,即db0。因此,我們沒有使用Redis的多資料庫功能!

 

問題3:Redis叢集機制中,你覺得有什麼不足的地方嗎?


Redis因為是要提升效能,所以直接採用的非同步複製,當在Master上寫入資料後直接返回,然後把資料快照廣播給
Slave,讓所有的Slaves去執行操作,資料通過非同步複製,不保證資料的強一致性


假設有一個key,對應的value是Hash型別的。如果Hash物件非常大,是不支援對映到不同節點的!只能對映到集
群中的一個節點上!還有就是做批量操作比較麻煩!

 

問題4:懂Redis的批量操作麼?
 我們在生產上採用的是Redis Cluster叢集架構,不同的key會劃分到不同的slot中,因此直接使用mset或者mget等操作是行不通的。

 

問題5:那在Redis叢集模式下,如何進行批量操作的優化?

 redis引入cluster模式後,如果存在的節點異常多的時候,IO的代價已經超過資料傳輸,在這種情況下再增加節點已經沒法再提高效率了。例如批量獲取操作mget,RedisCluster將資料按key雜湊到16384個slot上,每個redis node負責一部分的slot。mget需要執行的操作就是從redis node獲取所有的key-value值,然後進行merge然後返回。

①序列命令:由於n個key是比較均勻地分佈在Redis Cluster的各個節點上,因此無法使用mget命令一次性獲取,所以通常來講要獲取n個key的值,最簡單的方法就是逐次執行n個get命令,這種操作時間複雜度較高,它的操作時間=n次網路時間+n次命令時間,網路次數是n。很顯然這種方案不是最優的,但是實現起來比較簡單。

②序列IO:Redis Cluster使用CRC16演算法計算出雜湊值,再取對16383的餘數就可以算出slot值,同時Smart客戶端會儲存slot和節點的對應關係,有了這兩個資料就可以將屬於同一個節點的key進行歸檔,得到每個節點的key子列表,之後對每個節點執行mget或者Pipeline操作,它的操作時間=node次網路時間+n次命令時間,網路次數是node的個數,很明顯這種方案比第一種要好很多,但是如果節點數太多,還是有一定的效能問題。

Map<String, String> serialIOMget (List<String> keys) {

        //結果集
        Map<String, String> keyValueMap = new HashMap<>();
        //屬於各個節點的key列表,JedisPool要提供基於ip和port的hashcode方法
        Map<JedisPool, List<String>> nodeKeyListMap= new HashMap<>();
        //遍歷所有的key
        for (String key : keys) {
            //使用CRC16本地計算每個key的slot
            int slot = JedisClusterCRC16.getSlot(key);
            //通過iediscluster本地slot->node對映獲取slot對應的node
            JedisPool jedisPool = jedisCluster.getConnectionHandler().getJedisPoolFromSlot(slot);
            //歸檔
            if (nodeKeyListMap.containsKey(jedisPool)) {
                nodeKeyListMap.get(jedisPool).add(key);
            } else {
                List<String> list = new ArrayList<String>();
                list.add(key);
                nodeKeyListMap.put(jedisPool, list);
            }
        }
        //從每個節點上批量獲取,這裡使用mget也可以使用pipeline
        for (Map.Entry<JedisPool, List<String>> entry : nodeKeyListMap.entrySet()) {
            JedisPool jedisPool = entry.getKey();
            List<String> nodeKeyList = entry.getValue();
            //列表變為陣列
            String[] nodeKeyArray = nodeKeyList.toArray(new String[nodeKeyList.size()]);
            //批量獲取,可以使用mget或者Pipeline
            List<String> nodeValueList = jedisPool.getResource().mget(nodeKeyArray);
            //歸檔
            for (int i = 0; i < nodeKeyList.size(); i++) {
                keyValueMap.put(nodeKeyList.get(i), nodeValueList.get(i));
            }
        }
        return keyValueMap;
    }
序列IO

③並行IO:此方案是將方案2中的最後一步改為多執行緒執行,網路次數雖然還是節點個數,但由於使用多執行緒網路時間變為O(1),這種方案會增加程式設計的複雜度。

④hash_tag實現:Redis Cluster的hash_tag功能,它可以將多個key強制分配到一個節點上,它的操作時間=1次網路時間+n次命令時間。hashtag的解決方案:可以使用twitter的 twemproxy

四種批量操作解決方案對比

 

使用Redis Cluster時,pipelining、事務和LUA Script功能涉及的key必須在同一個資料分片上,否則將會返
回錯誤
。如要在Redis Cluster中使用上述功能,就必須通過hash tags來確保一個pipeline或一個事務中操作
的所有key都位於同一個Slot中。

有一些客戶端(如Redisson)實現了叢集化的pipelining操作,可以自動將一個pipeline裡的命令按key所在的
分片進行分組,分別發到不同的分片上執行。但是Redis不支援跨分片的事務,事務和LUA Script還是必須遵循所
有key在一個分片上的規則要求。

 

問題6:你們有對Redis做讀寫分離麼?
不做讀寫分離。我們用的是Redis Cluster的架構,是屬於分片叢集的架構。而Redis本身在記憶體上操作,不會涉及IO吞吐,即使讀寫分離也不會提升太多效能,Redis在生產上的主要問題是考慮容量,單機最多10-20G,key太多降低Redis效能.因此採用分片叢集結構,已經能保證了我們的效能。其次,用上了讀寫分離後,還要考慮主從一致性,主從延遲等問題,徒增業務複雜度。

 

問題7:Redis 序列化方式有哪些?

當我們的資料儲存到Redis的時候,我們的鍵(key)和值(value)都是通過Spring提供的Serializer序列化到資料庫的。RedisTemplate預設使用的是JdkSerializationRedisSerializer,StringRedisTemplate預設使用的是StringRedisSerializer

Spring Data JPA為我們提供了下面的Serializer:GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。

序列化方式對比:

JdkSerializationRedisSerializer: 使用JDK提供的序列化功能。 優點是反序列化時不需要提供型別資訊(class),但缺點是需要實現Serializable介面,還有序列化後的結果非常龐大,是JSON格式的5倍左右,這樣就會消耗redis伺服器的大量記憶體。
Jackson2JsonRedisSerializer: 使用Jackson庫將物件序列化為JSON字串。優點是速度快,序列化後的字串短小精悍,不需要實現Serializable介面。但缺點也非常致命,那就是此類的建構函式中有一個型別引數,必須提供要序列化物件的型別資訊(.class物件)。 通過檢視原始碼,發現其只在反序列化過程中用到了型別資訊。
 

問題8:Redis 如何做非同步佇列?

一般使用list結構作為佇列,rpush生產訊息,lpop消費訊息。當lpop沒有訊息的時候,要適當sleep一會再重試。

如果不用sleep呢?list還有個指令叫blpop,在沒有訊息的時候,它會阻塞住直到訊息到來。

能不能生產一次消費多次呢?

使用pub/sub主題訂閱者模式,可以實現1:N的訊息佇列。pub/sub有什麼缺點?在消費者下線的情況下,生產的訊息會丟失,得使用專業的訊息佇列如rabbitmq等。

redis如何實現延時佇列?使用sortedset,拿時間戳作為score,訊息內容作為key呼叫zadd來生產訊息,消費者用zrangebyscore指令獲取N秒之前的資料輪詢進行處理
 

相關文章