<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Redis異步客戶端選型及落地實(shí)踐

          共 6868字,需瀏覽 14分鐘

           ·

          2023-02-18 05:22

          為了支持更加廣泛的業(yè)務(wù)場(chǎng)景,可視化編排系統(tǒng)近期需要支持對(duì)緩存的操作功能,為保證編排系統(tǒng)的性能,服務(wù)的執(zhí)行過(guò)程采用了異步的方式,因此我們考慮使用Redis的異步客戶端來(lái)完成對(duì)緩存的操作。


          一、Redis客戶端


          Jedis/Lettuce
          Redis官方推薦的Redis客戶端有Jedis、Lettuce等等,其中Jedis 是老牌的 Redis 的 Java 實(shí)現(xiàn)客戶端,提供了比較全面的 Redis 命令的支持,在spring-boot 1.x 默認(rèn)使用Jedis。
          但是Jedis使用阻塞的 IO,且其方法調(diào)用都是同步的,程序流需要等到 sockets 處理完 IO 才能執(zhí)行,不支持異步,在并發(fā)場(chǎng)景下,使用Jedis客戶端會(huì)耗費(fèi)較多的資源。
          此外,Jedis 客戶端實(shí)例不是線程安全的,要想保證線程安全,必須要使用連接池,每個(gè)線程需要時(shí)從連接池取出連接實(shí)例,完成操作后或者遇到異常歸還實(shí)例。當(dāng)連接數(shù)隨著業(yè)務(wù)不斷上升時(shí),對(duì)物理連接的消耗也會(huì)成為性能和穩(wěn)定性的潛在風(fēng)險(xiǎn)點(diǎn)。因此在spring-boot 2.x中,redis客戶端默認(rèn)改用了Lettuce。
          我們可以看下 Spring Data Redis 幫助文檔給出的對(duì)比表格,里面詳細(xì)地記錄了兩個(gè)主流Redis客戶端之間的差異。
          異步客戶端Lettuce
          Spring Boot自2.0版本開(kāi)始默認(rèn)使用Lettuce作為Redis的客戶端。Lettuce客戶端基于Netty的NIO框架實(shí)現(xiàn),對(duì)于大多數(shù)的Redis操作,只需要維持單一的連接即可高效支持業(yè)務(wù)端的并發(fā)請(qǐng)求 —— 這點(diǎn)與Jedis的連接池模式有很大不同。同時(shí),Lettuce支持的特性更加全面,且其性能表現(xiàn)并不遜于,甚至優(yōu)于Jedis。
          Netty是由JBOSS提供的一個(gè)java開(kāi)源框架,現(xiàn)為 Github上的獨(dú)立項(xiàng)目。Netty提供異步的、事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架和工具,用以快速開(kāi)發(fā)高性能、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序。
          也就是說(shuō),Netty 是一個(gè)基于NIO的客戶、服務(wù)器端的編程框架,使用Netty 可以確保你快速和簡(jiǎn)單的開(kāi)發(fā)出一個(gè)網(wǎng)絡(luò)應(yīng)用,例如實(shí)現(xiàn)了某種協(xié)議的客戶、服務(wù)端應(yīng)用。Netty相當(dāng)于簡(jiǎn)化和流線化了網(wǎng)絡(luò)應(yīng)用的編程開(kāi)發(fā)過(guò)程,例如:基于TCP和UDP的socket服務(wù)開(kāi)發(fā)。
          上圖展示了Netty NIO的核心邏輯。NIO通常被理解為non-blocking I/O的縮寫(xiě),表示非阻塞I/O操作。圖中Channel表示一個(gè)連接通道,用于承載連接管理及讀寫(xiě)操作;EventLoop則是事件處理的核心抽象。一個(gè)EventLoop可以服務(wù)于多個(gè)Channel,但它只會(huì)與單一線程綁定。EventLoop中所有I/O事件和用戶任務(wù)的處理都在該線程上進(jìn)行;其中除了選擇器Selector的事件監(jiān)聽(tīng)動(dòng)作外,對(duì)連接通道的讀寫(xiě)操作均以非阻塞的方式進(jìn)行 —— 這是NIO與BIO(blocking I/O,即阻塞式I/O)的重要區(qū)別,也是NIO模式性能優(yōu)異的原因。
          Lettuce憑借單一連接就可以支持業(yè)務(wù)端的大部分并發(fā)需求,這依賴于以下幾個(gè)因素的共同作用:
          1. Netty的單個(gè)EventLoop僅與單一線程綁定,業(yè)務(wù)端的并發(fā)請(qǐng)求均會(huì)被放入EventLoop的任務(wù)隊(duì)列中,最終被該線程順序處理。同時(shí),Lettuce自身也會(huì)維護(hù)一個(gè)隊(duì)列,當(dāng)其通過(guò)EventLoop向Redis發(fā)送指令時(shí),成功發(fā)送的指令會(huì)被放入該隊(duì)列;當(dāng)收到服務(wù)端的響應(yīng)時(shí),Lettuce又會(huì)以FIFO的方式從隊(duì)列的頭部取出對(duì)應(yīng)的指令,進(jìn)行后續(xù)處理。
          2. Redis服務(wù)端本身也是基于NIO模型,使用單一線程處理客戶端請(qǐng)求。雖然Redis能同時(shí)維持成百上千個(gè)客戶端連接,但是在某一時(shí)刻,某個(gè)客戶端連接的請(qǐng)求均是被順序處理及響應(yīng)的。
          3. Redis客戶端與服務(wù)端通過(guò)TCP協(xié)議連接,而TCP協(xié)議本身會(huì)保證數(shù)據(jù)傳輸?shù)捻樞蛐浴?/span>
          如此,Lettuce在保證請(qǐng)求處理順序的基礎(chǔ)上,天然地使用了**管道模式**(pipelining)與Redis交互 —— 在多個(gè)業(yè)務(wù)線程并發(fā)請(qǐng)求的情況下,客戶端不必等待服務(wù)端對(duì)當(dāng)前請(qǐng)求的響應(yīng),即可在同一個(gè)連接上發(fā)出下一個(gè)請(qǐng)求。這在加速了Redis請(qǐng)求處理的同時(shí),也高效地利用了TCP連接的全雙工特性(full-duplex)。而與之相對(duì)的,在沒(méi)有顯式指定使用管道模式的情況下,Jedis只能在處理完某個(gè)Redis連接上當(dāng)前請(qǐng)求的響應(yīng)后,才能繼續(xù)使用該連接發(fā)起下一個(gè)請(qǐng)求。
          在并發(fā)場(chǎng)景下,業(yè)務(wù)系統(tǒng)短時(shí)間內(nèi)可能會(huì)發(fā)出大量請(qǐng)求,在管道模式中,這些請(qǐng)求被統(tǒng)一發(fā)送至Redis服務(wù)端,待處理完成后統(tǒng)一返回,能夠大大提升業(yè)務(wù)系統(tǒng)的運(yùn)行效率,突破性能瓶頸。R2M采用了Redis Cluster模式,在通過(guò)Lettuce連接R2M之前,應(yīng)該先對(duì)Redis Cluster模式有一定的了解。


          二、Redis Cluster模式
          在redis3.0之前,如果想搭建一個(gè)集群架構(gòu)還是挺復(fù)雜的,就算是基于一些第三方的中間件搭建的集群總感覺(jué)有那么點(diǎn)差強(qiáng)人意,或者基于sentinel哨兵搭建的主從架構(gòu)在高可用上表現(xiàn)又不是很好,尤其是當(dāng)數(shù)據(jù)量越來(lái)越大,單純主從結(jié)構(gòu)無(wú)法滿足對(duì)性能的需求時(shí),矛盾便產(chǎn)生了。
          隨著redis cluster的推出,這種海量數(shù)據(jù)+高并發(fā)+高可用的場(chǎng)景真正從根本上得到了有效的支持。
          cluster 模式是redis官方提供的集群模式,使用了Sharding 技術(shù),不僅實(shí)現(xiàn)了高可用、讀寫(xiě)分離、也實(shí)現(xiàn)了真正的分布式存儲(chǔ)。
          集群內(nèi)部通信
          在redis cluster集群內(nèi)部通過(guò)gossip協(xié)議進(jìn)行通信,集群元數(shù)據(jù)分散的存在于各個(gè)節(jié)點(diǎn),通過(guò)gossip進(jìn)行元數(shù)據(jù)的交換。
          不同于zookeeper分布式協(xié)調(diào)中間件,采用集中式的集群元數(shù)據(jù)存儲(chǔ)。redis cluster采用分布式的元數(shù)據(jù)管理,優(yōu)缺點(diǎn)還是比較明顯的。在redis中集中式的元數(shù)據(jù)管理類似sentinel主從架構(gòu)模式。集中式有點(diǎn)在于元數(shù)據(jù)更新實(shí)效性更高,但容錯(cuò)性不如分布式管理。gossip協(xié)議優(yōu)點(diǎn)在于大大增強(qiáng)集群容錯(cuò)性。
          redis cluster集群中單節(jié)點(diǎn)一般配置兩個(gè)端口,一個(gè)端口如6379對(duì)外提供api,另一個(gè)一般是加1w,比如16379進(jìn)行節(jié)點(diǎn)間的元數(shù)據(jù)交換即用于gossip協(xié)議通訊。
          gossip協(xié)議包含多種消息,如ping pong,meet,fail等。
          1. meet:集群中節(jié)點(diǎn)通過(guò)向新加入節(jié)點(diǎn)發(fā)送meet消息,將新節(jié)點(diǎn)加入集群中。
          2. ping:節(jié)點(diǎn)間通過(guò)ping命令交換元數(shù)據(jù)。
          3. pong:響應(yīng)ping。
          4. fail:某個(gè)節(jié)點(diǎn)主觀認(rèn)為某個(gè)節(jié)點(diǎn)宕機(jī),會(huì)向其他節(jié)點(diǎn)發(fā)送fail消息,進(jìn)行客觀宕機(jī)判定。
          分片和尋址算法
          hash slot即hash槽。redis cluster采用的正式這種hash槽算法實(shí)現(xiàn)的尋址。在redis cluster中固定的存在16384個(gè)hash slot。
          上圖所示,如果我們有三個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)都是一主一從的主從結(jié)構(gòu)。redis cluster初始化時(shí)會(huì)自動(dòng)均分給每個(gè)節(jié)點(diǎn)16384個(gè)slot。當(dāng)增加一個(gè)節(jié)點(diǎn)4,只需要將原來(lái)node1~node3節(jié)點(diǎn)部分slot上的數(shù)據(jù)遷移到節(jié)點(diǎn)4即可。在redis cluster中數(shù)據(jù)遷移并不會(huì)阻塞主進(jìn)程。對(duì)性能影響是十分有限的。總結(jié)一句話就是hash slot算法有效的減少了當(dāng)節(jié)點(diǎn)發(fā)生變化導(dǎo)致的數(shù)據(jù)漂移帶來(lái)的性能開(kāi)銷。
          集群高可用和主備切換
          主觀宕機(jī)和客觀宕機(jī):
          某個(gè)節(jié)點(diǎn)會(huì)周期性的向其他節(jié)點(diǎn)發(fā)送ping消息,當(dāng)在一定時(shí)間內(nèi)未收到pong消息會(huì)主觀認(rèn)為該節(jié)點(diǎn)宕機(jī),即主觀宕機(jī)。然后該節(jié)點(diǎn)向其他節(jié)點(diǎn)發(fā)送fail消息,其他超過(guò)半數(shù)節(jié)點(diǎn)也確認(rèn)該節(jié)點(diǎn)宕機(jī),即客觀宕機(jī)。十分類似sentinel的sdown和odown。
          客觀宕機(jī)確認(rèn)后進(jìn)入主備切換階段及從節(jié)點(diǎn)選舉。
          節(jié)點(diǎn)選舉:
          檢查每個(gè) slave node 與 master node 斷開(kāi)連接的時(shí)間,如果超過(guò)了 cluster-node-timeout * cluster-slave-validity-factor,那么就沒(méi)有資格切換成 master。
          每個(gè)從節(jié)點(diǎn),都根據(jù)自己對(duì) master 復(fù)制數(shù)據(jù)的 offset,來(lái)設(shè)置一個(gè)選舉時(shí)間,offset 越大(復(fù)制數(shù)據(jù)越多)的從節(jié)點(diǎn),選舉時(shí)間越靠前,優(yōu)先進(jìn)行選舉。
          所有的 master node 開(kāi)始 slave 選舉投票,給要進(jìn)行選舉的 slave 進(jìn)行投票,如果大部分 master node(N/2 + 1)都投票給了某個(gè)從節(jié)點(diǎn),那么選舉通過(guò),那個(gè)從節(jié)點(diǎn)可以切換成 master。
          從節(jié)點(diǎn)執(zhí)行主備切換,從節(jié)點(diǎn)切換為主節(jié)點(diǎn)。


          三、Lettuce的使用


          建立連接
          使用Lettuce大致分為以下三步:
          1. 基于Redis連接信息創(chuàng)建RedisClient
          2. 基于RedisClient創(chuàng)建StatefulRedisConnection
          3. 從Connection中獲取Command,基于Command執(zhí)行Redis命令操作。
          由于Lettuce客戶端提供了響應(yīng)式、同步和異步三種命令,從Connection中獲取Command時(shí)可以指定命令類型進(jìn)行獲取。
          在本地創(chuàng)建Redis Cluster集群,設(shè)置主從關(guān)系如下:
          7003(M) --> 7001(S)
          7004(M) --> 7002(S)
          7005(M) --> 7000(S)
          List<RedisURI> servers = new ArrayList<>();servers.add(RedisURI.create("127.0.0.1", 7000));servers.add(RedisURI.create("127.0.0.1", 7001));servers.add(RedisURI.create("127.0.0.1", 7002));servers.add(RedisURI.create("127.0.0.1", 7003));servers.add(RedisURI.create("127.0.0.1", 7004));servers.add(RedisURI.create("127.0.0.1", 7005));//創(chuàng)建客戶端RedisClusterClient client = RedisClusterClient.create(servers);//創(chuàng)建連接StatefulRedisClusterConnection<String, String> connection = client.connect();//獲取異步命令RedisAdvancedClusterAsyncCommands<String, String> commands = connection.async();//執(zhí)行GET命令RedisFuture<String> future = commands.get("test-lettuce-key");try {    String result = future.get();    log.info("Get命令返回:{}", result);} catch (Exception e) {    log.error("Get命令執(zhí)行異常", e);}
          可以看到成功地獲取到了值,由日志可以看出該請(qǐng)求發(fā)送到了7004所在的節(jié)點(diǎn)上,順利拿到了對(duì)應(yīng)的值并進(jìn)行返回。
          作為一個(gè)需要長(zhǎng)時(shí)間保持的客戶端,保持其與集群之間連接的穩(wěn)定性是至關(guān)重要的,那么集群在運(yùn)行過(guò)程中會(huì)發(fā)生哪些特殊情況呢?作為客戶端又應(yīng)該如何應(yīng)對(duì)呢?這就要引出智能客戶端(smart client)這個(gè)概念了。
          智能客戶端
          在Redis Cluster運(yùn)行過(guò)程中,所有的數(shù)據(jù)不是永遠(yuǎn)固定地保存在某一個(gè)節(jié)點(diǎn)上的,比如遇到cluster擴(kuò)容、節(jié)點(diǎn)宕機(jī)、數(shù)據(jù)遷移等情況時(shí),都會(huì)導(dǎo)致集群的拓?fù)浣Y(jié)構(gòu)發(fā)生變化,此時(shí)作為客戶端需要對(duì)這一類情況作出應(yīng)對(duì),來(lái)保證連接的穩(wěn)定性以及服務(wù)的可用性。隨著以上問(wèn)題的出現(xiàn),smart client這個(gè)概念逐漸走到了人們的視野中,智能客戶端會(huì)在內(nèi)部維護(hù)hash槽與節(jié)點(diǎn)的映射關(guān)系,大家耳熟能詳?shù)腏edis和Lettuce都屬于smart client。客戶端在發(fā)送請(qǐng)求時(shí),會(huì)先根據(jù)CRC16(key)%16384計(jì)算key對(duì)應(yīng)的hash槽,通過(guò)映射關(guān)系,本地就可實(shí)現(xiàn)鍵到節(jié)點(diǎn)的查找,從而保證IO效率的最大化。
          但如果出現(xiàn)故障轉(zhuǎn)移或者h(yuǎn)ash槽遷移時(shí),這個(gè)映射關(guān)系是如何維護(hù)的呢?
          客戶端重定向
          MOVED
          當(dāng)Redis集群發(fā)生數(shù)據(jù)遷移時(shí),當(dāng)對(duì)應(yīng)的hash槽已經(jīng)遷移到變的節(jié)點(diǎn)時(shí),服務(wù)端會(huì)返回一個(gè)MOVED重定向錯(cuò)誤,此時(shí)并告訴客戶端這個(gè)hash槽遷移后的節(jié)點(diǎn)IP和端口是多少;客戶端在接收到MOVED錯(cuò)誤時(shí),會(huì)更新本地的映射關(guān)系,并重新向新節(jié)點(diǎn)發(fā)送請(qǐng)求命令。
          ASK
          Redis集群支持在線遷移槽(slot)和數(shù)據(jù)來(lái)完成水平伸縮,當(dāng)slot對(duì)應(yīng)的數(shù)據(jù)從源節(jié)點(diǎn)到目標(biāo)節(jié)點(diǎn)遷移過(guò)程中,客戶端需要做到智能識(shí)別,保證鍵命令可正常執(zhí)行。例如當(dāng)一個(gè)slot數(shù)據(jù)從源節(jié)點(diǎn)遷移到目標(biāo)節(jié)點(diǎn)時(shí),期間可能出現(xiàn)一部分?jǐn)?shù)據(jù)在源節(jié)點(diǎn),而另一部分在目標(biāo)節(jié)點(diǎn),如下圖所示
          當(dāng)出現(xiàn)上述情況時(shí),客戶端鍵命令執(zhí)行流程將發(fā)生變化,如下所示:
          1)客戶端根據(jù)本地slots緩存發(fā)送命令到源節(jié)點(diǎn),如果存在鍵對(duì)象則直 接執(zhí)行并返回結(jié)果給客戶端
          2)如果鍵對(duì)象不存在,則可能存在于目標(biāo)節(jié)點(diǎn),這時(shí)源節(jié)點(diǎn)會(huì)回復(fù) ASK重定向異常。
          3)客戶端從ASK重定向異常提取出目標(biāo)節(jié)點(diǎn)信息,發(fā)送asking命令到目標(biāo)節(jié)點(diǎn)打開(kāi)客戶端連接標(biāo)識(shí),再執(zhí)行鍵命令。如果存在則執(zhí)行,不存在則返回不存在信息。
          在客戶端收到ASK錯(cuò)誤時(shí),不會(huì)更新本地的映射關(guān)系
          節(jié)點(diǎn)宕機(jī)觸發(fā)主備切換
          上文提到,如果redis集群在運(yùn)行過(guò)程中,某個(gè)主節(jié)點(diǎn)由于某種原因宕機(jī)了,此時(shí)就會(huì)觸發(fā)集群的節(jié)點(diǎn)選舉機(jī)制,選舉其中一個(gè)從節(jié)點(diǎn)作為新的主節(jié)點(diǎn),進(jìn)入主備切換,在主備切換期間,新的節(jié)點(diǎn)沒(méi)有被選舉出來(lái)之前,打到該節(jié)點(diǎn)上的請(qǐng)求理論上是無(wú)法得到執(zhí)行的,可能會(huì)產(chǎn)生超時(shí)錯(cuò)誤。在主備切換完成之后,集群拓?fù)涓峦瓿桑藭r(shí)客戶端應(yīng)該向集群請(qǐng)求新的拓?fù)浣Y(jié)構(gòu),并更新至本地的映射表中,以保證后續(xù)命令的正確執(zhí)行。
          有意思的是,Jedis在集群主備切換完成之后,是會(huì)主動(dòng)拉取最新的拓?fù)浣Y(jié)構(gòu)并進(jìn)行更新的,但是在使用Lettuce時(shí),發(fā)現(xiàn)在集群主備切換完成之后,連接并沒(méi)有恢復(fù),打到該節(jié)點(diǎn)上的命令依舊會(huì)執(zhí)行失敗導(dǎo)致超時(shí),必須要重啟業(yè)務(wù)程序才能恢復(fù)連接。
          在使用Lettuce時(shí),如果不進(jìn)行設(shè)置,默認(rèn)是不會(huì)觸發(fā)拓?fù)渌⑿碌模虼嗽谥鱾淝袚Q完成后,Lettuce依舊使用本地的映射表,將請(qǐng)求打到已經(jīng)掛掉的節(jié)點(diǎn)上,就會(huì)導(dǎo)致持續(xù)的命令執(zhí)行失敗的情況。
          可以通過(guò)以下代碼來(lái)設(shè)置Lettuce的拓?fù)渌⑿虏呗裕_(kāi)啟基于事件的自適應(yīng)拓?fù)渌⑿拢渲邪薓OVED、 ASK、PERSISTENT_RECONNECTS等觸發(fā)器,當(dāng)客戶端觸發(fā)這些事件,并且持續(xù)時(shí)間超過(guò)設(shè)定閾值后,觸發(fā)拓?fù)渌⑿拢部梢酝ㄟ^(guò)enablePeriodicRefresh()設(shè)置定時(shí)刷新,不過(guò)建議這個(gè)時(shí)間不要太短。
          // 設(shè)置基于事件的自適應(yīng)刷新策略ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()        //開(kāi)啟自適應(yīng)拓?fù)渌⑿?/span>        .enableAllAdaptiveRefreshTriggers()        //自適應(yīng)拓?fù)渌⑿率录瑫r(shí)時(shí)間,超時(shí)后進(jìn)行刷新        .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30))        .build();
          redisClusterClient.setOptions(ClusterClientOptions.builder() .topologyRefreshOptions(topologyRefreshOptions) // redis命令超時(shí)時(shí)間 .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(30))) .build());
          進(jìn)行以上設(shè)置并進(jìn)行驗(yàn)證,集群在主備切換完成后,客戶端在段時(shí)間內(nèi)恢復(fù)了連接,能夠正常存取數(shù)據(jù)了。


          四、總結(jié)


          對(duì)于緩存的操作,客戶端與集群之間連接的穩(wěn)定性是保證數(shù)據(jù)不丟失的關(guān)鍵,Lettuce作為熱門(mén)的異步客戶端,對(duì)于集群中產(chǎn)生的一些突發(fā)狀況是具備處理能力的,只不過(guò)在使用的時(shí)候需要進(jìn)行設(shè)置。本文目的在于將在開(kāi)發(fā)緩存操作功能時(shí)遇到的問(wèn)題,以及將一些涉及到的底層知識(shí)做一下總結(jié),也希望能給大家一些幫助。
          -end-

          瀏覽 57
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲涩图91 | 激情片91 | 日B免费视频 | 欧美毛片在线 | 国产Av特级片 |