国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Redis Cluster基于客戶端對mget的性能優(yōu)化

這篇具有很好參考價值的文章主要介紹了Redis Cluster基于客戶端對mget的性能優(yōu)化。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

1 背景

Redis是知名的、應用廣泛的NoSQL數(shù)據(jù)庫,在轉(zhuǎn)轉(zhuǎn)也是作為主要的非關(guān)系型數(shù)據(jù)庫使用。我們主要使用Codis來管理Redis分布式集群,但隨著Codis官方停止更新和Redis Cluster的日益完善,轉(zhuǎn)轉(zhuǎn)也開始嘗試使用Redis Cluster,并選擇Lettuce作為客戶端使用。但是在業(yè)務(wù)接入過程中發(fā)現(xiàn),使用Lettuce訪問Redis Cluster的mget、mset等Multi-Key命令時,性能表現(xiàn)不佳。

2 分析原因

2.1 現(xiàn)象

業(yè)務(wù)在從Codis遷移到Redis Cluster的過程中,在Redis Cluster和Codis雙寫了相同的數(shù)據(jù)。結(jié)果Codis在比Redis Cluster多一次連接proxy節(jié)點的耗時下,同樣是mget獲取相同的數(shù)據(jù),使用Lettuce訪問Redis Cluster還是比使用Jeds訪問Codis耗時要高,于是我們開始定位性能差異的原因。

2.2 定位問題

2.2.1 Redis Cluster的架構(gòu)設(shè)計

導致Redis Cluster的mget性能不佳的根本原因,是Redis Cluster在架構(gòu)上的設(shè)計導致的。Redis Cluster基于smart client和無中心的設(shè)計,按照槽位將數(shù)據(jù)存儲在不同的節(jié)點上

Redis Cluster基于客戶端對mget的性能優(yōu)化,redis,性能優(yōu)化,數(shù)據(jù)庫

如上圖所示,每個主節(jié)點管理不同部分的槽位,并且下面掛了多個從節(jié)點。槽位是Redis Cluster管理數(shù)據(jù)的基本單位,集群的伸縮就是槽和數(shù)據(jù)在節(jié)點之間的移動。

通過CRC16(key) % 16384計算key屬于哪個槽位和哪個Redis節(jié)點。而且Redis Cluster的Multi-Key操作受槽位限制,例如我們執(zhí)行mget,獲取不同槽位的數(shù)據(jù),是限制執(zhí)行的:

Redis Cluster基于客戶端對mget的性能優(yōu)化,redis,性能優(yōu)化,數(shù)據(jù)庫

2.2.2 Lettuce的mget實現(xiàn)方式

lettuce對Multi-Key進行了支持,當我們調(diào)用mget方法,涉及跨槽位時,Lettuce對mget進行了拆分執(zhí)行和結(jié)果合并,代碼如下:

public RedisFuture<List<KeyValue<K, V>>> mget(Iterable<K> keys) {
    //將key按照槽位拆分
    Map<Integer, List<K>> partitioned = SlotHash.partition(codec, keys);

    if (partitioned.size() < 2) {
        return super.mget(keys);
    }

    Map<K, Integer> slots = SlotHash.getSlots(partitioned);
    Map<Integer, RedisFuture<List<KeyValue<K, V>>>> executions = new HashMap<>();
	   //對不同槽位的keys分別執(zhí)行mget
    for (Map.Entry<Integer, List<K>> entry : partitioned.entrySet()) {
        RedisFuture<List<KeyValue<K, V>>> mget = super.mget(entry.getValue());
        executions.put(entry.getKey(), mget);
    }

    // 獲取、合并、排序結(jié)果
    return new PipelinedRedisFuture<>(executions, objectPipelinedRedisFuture -> {
        List<KeyValue<K, V>> result = new ArrayList<>();
        for (K opKey : keys) {
            int slot = slots.get(opKey);

            int position = partitioned.get(slot).indexOf(opKey);
            RedisFuture<List<KeyValue<K, V>>> listRedisFuture = executions.get(slot);
            result.add(MultiNodeExecution.execute(() -> listRedisFuture.get().get(position)));
        }

        return result;
    });
}

mget涉及多個key的時候,主要有三個步驟:

1、按照槽位 將key進行拆分;

2、分別對相同槽位的key去對應的槽位mget獲取數(shù)據(jù);

3、將所有執(zhí)行的結(jié)果按照傳參的key順序排序返回。

所以Lettuce客戶端,執(zhí)行mget獲取跨槽位的數(shù)據(jù),是通過槽位分發(fā)執(zhí)行mget,并合并結(jié)果實現(xiàn)的。而Lettuce基于Netty的NIO框架實現(xiàn),發(fā)送命令不會阻塞IO,但是處理請求是單連接串行發(fā)送命令:

Redis Cluster基于客戶端對mget的性能優(yōu)化,redis,性能優(yōu)化,數(shù)據(jù)庫

所以Lettuce的mget的key數(shù)量越多,涉及的槽位數(shù)量越多,性能就會越差。Codis也是拆分執(zhí)行mget,不過是并發(fā)發(fā)送命令,并使用pipeline提高性能,進而減少了網(wǎng)絡(luò)的開銷。

3 解決問題

3.1使用hashtag

我們首先想到的是 客戶端分別執(zhí)行分到不同槽位的請求,導致耗時增加。我們可以將我們需要同時操作到的key,放到同一個槽位里去。我們是可以通過hashtag來實現(xiàn)

hashtag用于Redis Cluster中。hashtag 規(guī)定以key里{}里的內(nèi)容來做hash,比如 user:{a}:zhangsan和user:{a}:lisi就會用a去hash,保證帶{a}的key都落到同一個slot里

利用hashtag對key進行規(guī)劃,使得我們mget的值都在同一個槽位里。

Redis Cluster基于客戶端對mget的性能優(yōu)化,redis,性能優(yōu)化,數(shù)據(jù)庫

但是這種方式需要業(yè)務(wù)方感知到Redis Cluster的分片的存在,需要對Redis Cluster的各節(jié)點存儲做規(guī)劃,保證數(shù)據(jù)平均的分布在不同的Redis節(jié)點上。對業(yè)務(wù)方使用上太不友好,所以舍棄了這種方案。

3.2 客戶端改造

另一種方案是在客戶端做改造,這樣做成本較低。不需要業(yè)務(wù)方感知和維護hashtag。

我們利用pipeline對Redis節(jié)點批量發(fā)送get命令,相對于Lettuce串行發(fā)送mget命令來說,減少了多次跨槽位mget發(fā)送命令的網(wǎng)絡(luò)耗時。具體步驟如下:

1、把所有key按照所在的Redis節(jié)點拆分;

2、通過pipeline對每個Redis節(jié)點批量發(fā)送get命令;

3、獲取所有命令執(zhí)行結(jié)果,排序、合并結(jié)果,并返回。

這樣改造,使用pipeline一次發(fā)送批量的命令,減少了串行批量發(fā)送命令的網(wǎng)絡(luò)耗時。

3.2.1 改造JedisCluster

由于Lettuce沒有原生支持pipeline批量提交命令,而JedisCluster原生支持pipeline,并且JedisCluster沒有對Multi-Key進行支持,我們對JedisCluster的mget進行了改造,代碼如下:

public List<String> mget(String... keys) {
        List<Pipeline> pipelineList = new ArrayList<>();
        List<Jedis> jedisList = new ArrayList<>();
        try {
            //按照key的hash計算key位于哪一個redis節(jié)點
            Map<JedisPool, List<String>> pooling = new HashMap<>();
            for (String key : keys) {
                JedisPool pool = connectionHandler.getConnectionPoolFromSlot(JedisClusterCRC16.getSlot(key));
                pooling.computeIfAbsent(pool, k -> new ArrayList<>()).add(key);
            }

            //分別對每個redis 執(zhí)行pipeline get操作
            Map<String, Response<String>> resultMap = new HashMap<>();
            for (Map.Entry<JedisPool, List<String>> entry : pooling.entrySet()) {
                Jedis jedis = entry.getKey().getResource();
                Pipeline pipelined = jedis.pipelined();
                for (String key : entry.getValue()) {
                    Response<String> response = pipelined.get(key);
                    resultMap.put(key, response);
                }
                pipelined.flush();
                //保存所有連接和pipeline 最后進行close
                pipelineList.add(pipelined);
                jedisList.add(jedis);
            }
            //同步所有請求結(jié)果
            for (Pipeline pipeline : pipelineList) {
                pipeline.returnAll();
            }
            //合并、排序結(jié)果
            List<String> list = new ArrayList<>();
            for (String key : keys) {
                Response<String> response = resultMap.get(key);
                String o = response.get();
                list.add(o);
            }
            return list;
        }finally {
            //關(guān)閉所有pipeline和jedis連接
            pipelineList.forEach(Pipeline::close);
            jedisList.forEach(Jedis::close);
        }

    }
3.2.2 處理異常case

上面的代碼還不足以覆蓋所有場景,我們還需要處理一些異常case

  • Redis Cluster擴縮容導致的數(shù)據(jù)遷移

數(shù)據(jù)遷移會造成兩種錯誤

1、MOVED錯誤

代表數(shù)據(jù)所在的槽位已經(jīng)遷移到另一個redis節(jié)點上了,服務(wù)端會告訴客戶端對應的槽的目標節(jié)點信息。此時我們需要做的是更新客戶端緩存的槽位信息,并嘗試重新獲取數(shù)據(jù)。

2、ASKING錯誤

代表槽位正在遷移中,且數(shù)據(jù)不在源節(jié)點中,我們需要先向目標Redis節(jié)點執(zhí)行ASKING命令,才能獲取遷移的槽位的數(shù)據(jù)。

List<String> list = new ArrayList<>();
for (String key : keys) {
    Response<String> response = resultMap.get(key);
    String o;
    try {
        o = response.get();
        list.add(o);
    } catch (JedisRedirectionException jre) {
        if (jre instanceof JedisMovedDataException) {
            //此槽位已經(jīng)遷移 更新客戶端的槽位信息
            this.connectionHandler.renewSlotCache(null);
        }
        boolean asking = false;
        if (jre instanceof JedisAskDataException) {
            //獲取槽位目標redis節(jié)點的連接 設(shè)置asking標識,以便在重試前執(zhí)行asking命令
            asking = true;
 askConnection.set(this.connectionHandler.getConnectionFromNode(jre.getTargetNode()));
        } else {
            throw new JedisClusterException(jre);
        }
        //重試獲取這個key的結(jié)果
        o = runWithRetries(this.maxAttempts, asking, true, key);
        list.add(o);
    }
}

數(shù)據(jù)遷移導致的兩種異常,會進行重試。重試會導致耗時增加,并且如果達到最大重試次數(shù),還沒有獲取到數(shù)據(jù),則拋出異常。

  • pipeline的某個命令執(zhí)行失敗

不捕獲執(zhí)行失敗的異常,拋出異常讓業(yè)務(wù)服務(wù)感知到異常發(fā)生。

4 效果展示

4.1 性能測試

在改造完客戶端之后,我們對客戶端的mget進行了性能測試,測試了下面三種類型的耗時

1、使用Jedis訪問Codis

2、使用改造的JedisCluster訪問Redis Cluster

3、使用Lettuce同步方式訪問Redis Cluster

4.1.1 mget 100key
Codis JedisCluster(改造) Lettuce
avg 0.411ms 0.224ms 0.61ms
tp99 0.528ms 0.35ms 1.53ms
tp999 0.745ms 1.58ms 3.87ms
4.1.2 mget 500key
Codis JedisCluster(改造) Lettuce
avg 0.96ms 0.511ms 2.14ms
tp99 1.15ms 0.723ms 3.99ms
tp999 1.81ms 1.86ms 6.88ms
4.1.3 mget 1000key
Codis JedisCluster(改造) Lettuce
avg 1.56ms 0.92ms 5.04ms
tp99 1.83ms 1.22ms 8.91ms
tp999 3.15ms 3.88ms 32ms

4.2 結(jié)論

  • 使用改造的客戶端訪問Redis Cluster,比使用Lettuce訪問Redis Cluster要快1倍以上;
  • 改造的客戶端比使用codis稍微快一點,tp999不如codis性能好。

但是改造的客戶端相對于Lettuce也有缺點,JedisCluster是基于復雜的連接池實現(xiàn),連接池的配置會影響客戶端的性能。而Lettuce是基于Netty的NIO框架實現(xiàn),對于大多數(shù)的Redis操作,只需要維持單一的連接即可高效支持并發(fā)請求,不需要業(yè)務(wù)考慮連接池的配置。

5 總結(jié)

Redis Cluster在架構(gòu)設(shè)計上對Multi-Key進行的限制,導致無法跨槽位執(zhí)行mget等命令。我們對客戶端JedisCluster的Multi-Key命令進行改造,通過分別對Redis節(jié)點執(zhí)行pipeline操作,提升了mget命令的性能。

關(guān)于作者

趙浩,轉(zhuǎn)轉(zhuǎn)架構(gòu)部后臺開發(fā)工程師

轉(zhuǎn)轉(zhuǎn)研發(fā)中心及業(yè)界小伙伴們的技術(shù)學習交流平臺,定期分享一線的實戰(zhàn)經(jīng)驗及業(yè)界前沿的技術(shù)話題。
關(guān)注公眾號「轉(zhuǎn)轉(zhuǎn)技術(shù)」(綜合性)、「大轉(zhuǎn)轉(zhuǎn)FE」(專注于FE)、「轉(zhuǎn)轉(zhuǎn)QA」(專注于QA),更多干貨實踐,歡迎交流分享~文章來源地址http://www.zghlxwxcb.cn/news/detail-827402.html

到了這里,關(guān)于Redis Cluster基于客戶端對mget的性能優(yōu)化的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • Redis客戶端介紹

    Redis客戶端介紹

    redis安裝完成后有自帶的命令行客戶端,即redis-cli,使用方式如下 options ——參數(shù) -h 127.0.0.1 :指定要連接的redis節(jié)點的ip地址,默認值127.0.0.1 -p 6379 :指定要連接的redis節(jié)點的端口,默認值6379 -a xxxxxx :指定redis的訪問密碼 … commonds ——redis的操作命令 ping :心跳測試,服務(wù)器

    2024年02月06日
    瀏覽(26)
  • redis 登錄客戶端命令

    Redis 命令用于在 redis 服務(wù)上執(zhí)行操作。 要在 redis 服務(wù)上執(zhí)行命令需要一個 redis 客戶端。Redis 客戶端在我們之前下載的的 redis 的安裝包中。 語法 Redis 客戶端的基本語法為: $ redis-cli 實例 以下實例講解了如何啟動 redis 客戶端: 啟動 redis 客戶端,打開終端并輸入命令 redis

    2023年04月08日
    瀏覽(84)
  • Redis客戶端 - RedisSerializer

    Redis客戶端 - RedisSerializer

    原文首更地址,閱讀效果更佳! Redis客戶端 - RedisSerializer | CoderMast編程桅桿 https://www.codermast.com/database/redis/redistemplate-redis-serializer.html 前景回顧 在上一篇中,我們實現(xiàn)了一個簡單的案例,操作一個 String 類型的數(shù)據(jù),插入了一個 name = codermast 的數(shù)據(jù)到Redis。 使用redis-cli客戶端

    2024年02月09日
    瀏覽(25)
  • Redis的Java客戶端-Java客戶端以及SpringDataRedis的介紹與使用

    Redis的Java客戶端-Java客戶端以及SpringDataRedis的介紹與使用

    Spring Data Redis底層支持同時兼容Jedis和Lettuce兩種不同的Redis客戶端,可以根據(jù)需要任意選擇使用其中的一種。這樣既可以保留現(xiàn)有代碼使用的Jedis方式,也可以通過使用基于Netty的高性能Lettuce客戶端,提升應用的性能和吞吐量。 Jedis是一個傳統(tǒng)的Java Redis客戶端,使用BIO進行So

    2024年02月08日
    瀏覽(22)
  • Redis的Java客戶端

    Redis的Java客戶端

    以下是redis.io官網(wǎng)所推薦使用前五的Java客戶端 Java客戶端 特點 Jedis 以Redis命令作為方法名稱,學習成本低,簡單實用,但是Jedis實例是線程不安全的,多線程環(huán)境下需要基于連接池來使用 lettuce Lettuce是基于Netty實現(xiàn)的,支持同步、異步和響應式編程方式,并且是線程安全的。

    2024年02月13日
    瀏覽(25)
  • Redis中的Java客戶端

    Redis中的Java客戶端

    Jedis是一個Java實現(xiàn)的Redis客戶端連接工具。 Jedis使用非常簡單,直接引入依賴?;谀J參數(shù)的Jedis連接池,初始化連接池類(使用默認連接池參數(shù))JedisPool,獲取一個Jedis連接Jedis jedis=jp.getResource()。 Jedis是線程不安全的,多線程使用同一個Jedis實例,會出現(xiàn)并發(fā)問題,原因是

    2024年01月17日
    瀏覽(22)
  • redis教程 二 redis客戶端Jedis使用

    redis教程 二 redis客戶端Jedis使用

    在Redis官網(wǎng)中提供了各種語言的客戶端,地址:https://redis.io/docs/clients/ 其中Java客戶端也包含很多但在開發(fā)中用的最多的還是Jedis,接下來就讓我們以Jedis開始我們的快速實戰(zhàn)。 入門案例詳細步驟 案例分析: 創(chuàng)建工程: 創(chuàng)建一個maven管理的java項目 引入依賴: 在pom.xml文件下添

    2024年02月05日
    瀏覽(22)
  • 【Redis入門篇】| Redis的Java客戶端

    【Redis入門篇】| Redis的Java客戶端

    目錄 一:?Redis的Java客戶端 1. Jedis快速入門 2. Jedis連接池 3. SpringDataRedis快速入門 4. RedisSerializer配置 5. StringRedisTemplate 圖書推薦 在Redis官網(wǎng)中提供了各種語言的客戶端,地址: https://redis.io/resources/clients/ Jedis: 以 Redis 命令作為方法名稱,學習成本低,簡單實用。但是 Jedis 實

    2024年02月03日
    瀏覽(22)
  • 使用C++操作Redis客戶端

    使用C++操作Redis客戶端

    \\\"Who can say where the path will go?\\\"? ? ? ? ? ?前面我們花了很大的篇幅,講解了redis中常見常使用的五種數(shù)據(jù)結(jié)構(gòu),以及五種數(shù)據(jù)結(jié)構(gòu)的操作和redis命令。不過在日常開發(fā)中,我們的這些操作都是在redis為我們提供的客戶端中的,就像使用mysql一樣,很多時候不是在mysql-cli去編寫s

    2024年02月10日
    瀏覽(21)
  • Redis系列之客戶端Redisson

    官方推薦的客戶端,支持Redis單實例、Redis哨兵、Redis Cluster、Redis master-slave等各種部署架構(gòu)。 GitHub, 功能: 分布式鎖 使用Redisson提供的分布式鎖的一個最常見場景,應用部署為多個節(jié)點,然后使用Spring提供的原生@Scheduled任務(wù)調(diào)度功能;而沒有使用xxl-job等輕量級分布式任務(wù)調(diào)

    2024年02月09日
    瀏覽(16)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包