Redis Pipeline原來是這么用的

拋出,問題

最近項目碰到這么一個技術(shù)上的需求:

前端通過長輪詢的機制(http long polling)绞灼,獲取服務(wù)端的消息數(shù)據(jù)呈野。而服務(wù)端是需要訂閱所有業(yè)務(wù)方的業(yè)務(wù)消息,再通知到給前端商佛。

長輪詢姆打,其實簡單來說,就是前端發(fā)起一個http請求幔戏,服務(wù)端把當(dāng)前的請求 hang 住税课,直到超時或者有需要返回的內(nèi)容,才return韩玩。 Apollo 配置中心就是使用這個機制實現(xiàn)配置的更新通知。

但有這么一種情況合愈,假如服務(wù)端消費到消息击狮,但此時前端與服務(wù)端的連接剛好斷開了,那這個消息就沒法通知到前端彪蓬。

所以,我們得需要把服務(wù)端消費的消息保存下來膘茎,保證前端的每次發(fā)起長輪詢的時候,都能拿到消息數(shù)據(jù)辽狈。

如果說,前端能直接訂閱業(yè)務(wù)方的消息隊列的話驮配,那其實就沒服務(wù)端什么關(guān)系了着茸。當(dāng)然,這是不允許的涮阔,前端不能連接咱們的消息中間件,并且業(yè)務(wù)方的消息數(shù)據(jù)也需要清洗處理后才能給到前端掰邢。

思考伟阔,方案

Apollo 的實現(xiàn)機制是,所有的配置都會寫入數(shù)據(jù)庫皱炉。每次請求過來,會去數(shù)據(jù)庫獲取是否有數(shù)據(jù)變更合搅。

考慮到我們實際的業(yè)務(wù)場景,我們的業(yè)務(wù)消息其實時候時效性的康铭,也就是說消息如果過期了赌髓,那其實也沒用了。

要考慮輕量春弥,我第一想到的就是 Redis Lists,利用其可以實現(xiàn)隊列的特性扫责,所以綜合考慮最終采用 Redis 作為消息的存儲模型逃呼。

優(yōu)化者娱,代碼

Redis 的 Lists 數(shù)據(jù)結(jié)構(gòu)苏揣,是簡單的字符串鏈表,按插入順序排序框沟≡鎏浚可以在頭部(Left)或者尾部(Right)添元素。

利用這個特性隙姿,我們可以實現(xiàn)隊列(先進先出)的數(shù)據(jù)結(jié)構(gòu),搭配命令 rpush + lpop 或者 lpush + rpop 队丝。

但是不管是 lpop 或是 rpop 欲鹏,列表都只會彈出一個數(shù)據(jù),沒法達到我們的需求一次獲取多個貌虾。

網(wǎng)上搜索了一下相關(guān)的解決方案裙犹,再結(jié)合官網(wǎng)的命令文檔。

得出一種比較可行的方案: lrange + ltrim + pipeline

lrange 和 ltrim 是Redis Lists 的指令

lrange mylist 0 5

表示從隊列頭部(Left)開始取袄膏,下標(biāo)為0掺冠,到下標(biāo)為5的元素。如果是負(fù)數(shù)的話德崭,表示隊列尾部(Right)開始取。

ltrim mylist 0 5

ltrim 的含義是只保留指定范圍的元素锌奴,上面的意思是憾股,只保留列表下標(biāo)為 0 - 5 的元素箕慧,其他的都會被刪掉茴恰。

那如果要刪掉前6條數(shù)據(jù)颠焦,就可能需要這么寫:

ltrim mylist 5 -1

負(fù)數(shù)表示隊列尾部(Right)開始取往枣。

按實現(xiàn)來說, lrange + ltrim 就能實現(xiàn)從 List 獲取多個元素的效果似忧,為什么還需要用上 pipeline 呢丈秩?

其實這里有一個很顯然的并發(fā)問題,這兩個命令對于redis來說饺著, 并不是原子操作 肠牲,假如有其他線程在執(zhí)行這兩個命令的線程之間,刪除了隊列的一部分?jǐn)?shù)據(jù)缀雳,那么第二個命令執(zhí)行的時候,其實是list里面的數(shù)據(jù)已經(jīng)是不對了肥印。

而 Redis 的 pipeline 功能,可以解決我們上面說的這個問題腹鹉。

大部分人可能對pipeline 比較陌生敷硅,因為平時業(yè)務(wù)上也很少用到。

先來看看 Redis Pipelining 的定義

A Request/Response server can be implemented so that it is able to process new requests even if the client hasn't already read the old responses. This way it is possible to send multiple commands to the server without waiting for the replies at all, and finally read the replies in a single step.

This is called pipelining, and is a technique widely in use for many decades. For instance many POP3 protocol implementations already support this feature, dramatically speeding up the process of downloading new emails from the server.

簡單來說力奋,它能夠支持支持客戶端一次發(fā)送多個命令幽七,服務(wù)端接收到這些命令后,會統(tǒng)一按順序處理,并且是在一個原子事務(wù)里藕届。

顯然亭饵,Pipelining 最明顯的優(yōu)勢在于提高 client 與 server 的交互響應(yīng)時間。將幾個命令放在同一個請求中辜羊,和每個命令作為一個請求相比,效率是肯定提高的碱妆。

PS:這種方式昔驱,跟我為什么要獲取多個list 數(shù)據(jù)的思路類似的。前端長輪詢獲取數(shù)據(jù)結(jié)果時骤肛,要返回多個數(shù)據(jù)。

話不多說腋颠,直接貼上示例代碼:

@Autowired
RedisTemplate<String, Object> redisTemplate;

public void run() throws Exception {
    System.out.println("start......");
    String key = "key:list";
    Integer number = 5;
    // 每次獲取5個
    List<Object> objects = redisTemplate.executePipelined(new RedisCallback<Object>() {

        @Override
        public Object doInRedis(RedisConnection connection) throws DataAccessException {
            connection.lRange(key.getBytes(StandardCharsets.UTF_8), 0, number - 1);
            connection.lTrim(key.getBytes(StandardCharsets.UTF_8), number, -1);
            return null;
        }
    });
    System.out.println(Arrays.toString(objects.toArray()));
    System.out.println("end......");
}

redisTemplate 提供了兩種類型的方法:

  • executePipelined(RedisCallback action)
  • executePipelined(SessionCallback session)

這兩者實現(xiàn)的功能都差不多,只不過 SessionCallback 比 RedisCallck 封裝的更友好一下巾腕,后者的話更加底層絮蒿,用法更加貼近與原生命令。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毁嗦,一起剝皮案震驚了整個濱河市回铛,隨后出現(xiàn)的幾起案子克锣,更是在濱河造成了極大的恐慌,老刑警劉巖袭祟,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巾乳,死亡現(xiàn)場離奇詭異鸟召,居然都是意外死亡氨鹏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門跟继,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镣丑,“玉大人舔糖,你說我怎么就攤上這事莺匠。” “怎么了辽聊?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵期贫,是天一觀的道長。 經(jīng)常有香客問我玛臂,道長封孙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任泡徙,我火速辦了婚禮膜蠢,結(jié)果婚禮上堪藐,老公的妹妹穿的比我還像新娘挑围。我一直安慰自己,他們只是感情好模捂,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著综看,像睡著了一般岖食。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上县耽,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音唾琼,去河邊找鬼澎剥。 笑死,一個胖子當(dāng)著我的面吹牛哑姚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倡蝙,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼绞佩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胆建?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤笆载,失蹤者是張志新(化名)和其女友劉穎涯呻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沿侈,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡市栗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年填帽,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛛淋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篡腌。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡嘹悼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杨伙,到底是詐尸還是另有隱情,我是刑警寧澤抖苦,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布米死,位于F島的核電站,受9級特大地震影響峦筒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜物喷,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一脯丝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宠进,春花似錦、人聲如沸实幕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闸溃。三九已至拱撵,卻和暖如春表蝙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背府蛇。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留务荆,地道東北人穷遂。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像塞颁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子酷窥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

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