Redis管道

Redis之管道的使用

原文地址: https://blog.piaoruiqing.com/blog/2019/06/24/redis管道

關(guān)鍵詞

Redis Pipelining: 客戶端可以向服務(wù)器發(fā)送多個(gè)請(qǐng)求而無需等待回復(fù), 最后只需一步即可讀取回復(fù).

RTT(Round Trip Time): 往返時(shí)間.

為什么要用管道

Redis是使用client-server模型和Request/Response協(xié)議的TCP服務(wù)器. 這意味著通常通過以下步驟完成請(qǐng)求:

  • 客戶端向服務(wù)器發(fā)送查詢, 并通常以阻塞方式從套接字讀取服務(wù)器響應(yīng).
  • 服務(wù)器處理該命令并將響應(yīng)發(fā)送回客戶端.

應(yīng)用程序與Redis通過網(wǎng)絡(luò)進(jìn)行連接, 可能非呈硬快(本地回環(huán)), 也可能很慢. 但無論網(wǎng)絡(luò)延遲是多少, 數(shù)據(jù)包都需要時(shí)間從客戶端傳輸?shù)椒?wù)器, 然后從服務(wù)器返回到客戶端以進(jìn)行回復(fù)(此時(shí)間稱為RTT). 當(dāng)客戶端需要連續(xù)執(zhí)行許多請(qǐng)求時(shí)(例如, 將多個(gè)元素添加到同一列表或使用多個(gè)鍵填充數(shù)據(jù)庫), 很容易發(fā)現(xiàn)這種頻繁操作很影響性能. 使用管道將多次操作通過一次IO發(fā)送給Redis服務(wù)器, 然后一次性獲取每一條指令的結(jié)果, 以減少網(wǎng)絡(luò)上的開銷.

頻繁操作但未使用管道的情形如下圖:


直接操作

使用管道后如下圖:


使用管道

如何使用

Jedis

/** jedis pool */
private final Logger LOGGER = LoggerFactory.getLogger(getClass());
private static final JedisPool POOL =
    new JedisPool(new JedisPoolConfig(), "test-redis-server", 6379);
/**
 * test pipelining with Jedis
 */
@Test
public void testPipelining() {

    try (Jedis jedis = POOL.getResource()) {

        Pipeline pipelined = jedis.pipelined(); // (一)
        Response<String> response1 = pipelined.set("mykey1", "myvalue1");
        Response<String> response2 = pipelined.set("mykey2", "myvalue2");
        Response<String> response3 = pipelined.set("mykey3", "myvalue3");

        pipelined.sync();   // (二)

        LOGGER.info("cmd: SET mykey1 myvalue1, result: {}", response1.get());   // (三)
        LOGGER.info("cmd: SET mykey2 myvalue2, result: {}", response2.get());
        LOGGER.info("cmd: SET mykey3 myvalue3, result: {}", response3.get());
    }
}
  • (一): jedis.pipelined(): 獲取一個(gè)Pipeline用以批量執(zhí)行指令.
  • (二): pipelined.sync(): 同步執(zhí)行, 通過讀取全部Response來同步管道, 這個(gè)操作會(huì)關(guān)閉管道.
  • (三): response1.get(): 獲取執(zhí)行結(jié)果. 注意: 在執(zhí)行pipelined.sync()之前, get是無法獲取到結(jié)果的.

Lettuce

 private final Logger LOGGER = LoggerFactory.getLogger(getClass());

/** redis client */
private static final RedisClient CLIENT
        = RedisClient.create("redis://@test-redis-server:6379/0");
/**
 * test pipelining with Lettuce
 */
@Test
public void testPipelining() throws ExecutionException, InterruptedException {

    try (StatefulRedisConnection<String, String> connection = CLIENT.connect()) {

        RedisAsyncCommands<String, String> async = connection.async();
        async.setAutoFlushCommands(false);
        RedisFuture<String> future1 = async.set("mykey1", "myvalue1");
        RedisFuture<String> future2 = async.set("mykey2", "myvalue2");
        RedisFuture<String> future3 = async.set("mykey3", "myvalue3");

        async.flushCommands();

        LOGGER.info("cmd: SET mykey1 myvalue1, result: {}", future1.get());
        LOGGER.info("cmd: SET mykey2 myvalue2, result: {}", future1.get());
        LOGGER.info("cmd: SET mykey3 myvalue3, result: {}", future1.get());
    }
}

RedisTemplate

private final Logger LOGGER = LoggerFactory.getLogger(getClass());

@Resource
private StringRedisTemplate stringRedisTemplate;

/**
 * test pipelining with RedisTemplate
 */
@Test
public void testPipelining() {

    List<Object> objects 
        = stringRedisTemplate.executePipelined((RedisCallback<Object>)connection -> {

        connection.set("mykey1".getBytes(), "myvalue1".getBytes());
        connection.set("mykey2".getBytes(), "myvalue2".getBytes());
        connection.set("mykey3".getBytes(), "myvalue3".getBytes());
        return null;    // (一)
    });

    LOGGER.info("cmd: SET mykey myvalue, result: {}", objects);
}
  • (一): 此處必須返回null

簡單對(duì)比測(cè)試

redis服務(wù)器運(yùn)行在同一個(gè)路由器下的樹莓派上.

/**
 * pipeline vs direct
 */
@Test
public void compared() {

    try (Jedis jedis = POOL.getResource()) {   // warm up
        jedis.set("mykey", "myvalue");
    }

    try (Jedis jedis = POOL.getResource()) {
        long start = System.nanoTime();
        Pipeline pipelined = jedis.pipelined();
        for (int index = 0; index < 500; index++) {
            pipelined.set("mykey" + index, "myvalue" + index);
        }
        pipelined.sync();
        long end = System.nanoTime();
        LOGGER.info("pipeline cost: {} ns", end - start);
    }

    try (Jedis jedis = POOL.getResource()) {
        long start = System.nanoTime();
        for (int index = 0; index < 500; index++) {
            jedis.set("mykey" + index, "myvalue" + index);
        }
        long end = System.nanoTime();
        LOGGER.info("direct cost: {} ns", end - start);
    }
}

使用Jedis執(zhí)行500條set, 執(zhí)行結(jié)果如下:

22:16:00.523 [main] INFO - pipeline cost:   73681257 ns     // 管道
22:16:03.040 [main] INFO - direct cost  : 2511915103 ns     // 直接執(zhí)行

500次set執(zhí)行時(shí)間總和已經(jīng)和管道執(zhí)行一次的所消耗的時(shí)間不在一個(gè)量級(jí)上了.

擴(kuò)展

摘自redis官方文檔

使用管道不僅僅是為了降低RTT以減少延遲成本, 實(shí)際上使用管道也能大大提高Redis服務(wù)器中每秒可執(zhí)行的總操作量. 這是因?yàn)? 在不使用管道的情況下, 盡管操作單個(gè)命令開起來十分簡單, 但實(shí)際上這種頻繁的I/O操作造成的消耗是巨大的, 這涉及到系統(tǒng)讀寫的調(diào)用, 這意味著從用戶域到內(nèi)核域.上下文切換會(huì)對(duì)速度產(chǎn)生極大的損耗.

使用管道操作時(shí), 通常使用單個(gè)read() 系統(tǒng)調(diào)用讀取許多命令,并通過單個(gè)write()系統(tǒng)調(diào)用傳遞多個(gè)回復(fù). 因此, 每秒執(zhí)行的總查詢數(shù)最初會(huì)隨著較長的管道線性增加, 并最終達(dá)到不使用管道技術(shù)獲的10倍, 如下圖所示:

image

參考文獻(xiàn)

歡迎關(guān)注公眾號(hào): 代碼如詩


代碼如詩

[版權(quán)聲明]
本文發(fā)布于樸瑞卿的博客, 非商業(yè)用途允許轉(zhuǎn)載, 但轉(zhuǎn)載必須保留原作者樸瑞卿 及鏈接:blog.piaoruiqing.com. 如有授權(quán)方面的協(xié)商或合作, 請(qǐng)聯(lián)系郵箱: piaoruiqing@gmail.com.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恨锚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子榨婆,更是在濱河造成了極大的恐慌顺少,老刑警劉巖枢赔,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桅咆,死亡現(xiàn)場(chǎng)離奇詭異括授,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)岩饼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門荚虚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人籍茧,你說我怎么就攤上這事版述。” “怎么了寞冯?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵渴析,是天一觀的道長晚伙。 經(jīng)常有香客問我,道長俭茧,這世上最難降的妖魔是什么撬腾? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮恢恼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胰默。我一直安慰自己场斑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布牵署。 她就那樣靜靜地躺著漏隐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奴迅。 梳的紋絲不亂的頭發(fā)上青责,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音取具,去河邊找鬼脖隶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛暇检,可吹牛的內(nèi)容都是我干的产阱。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼块仆,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼构蹬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悔据,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤庄敛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后科汗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體藻烤,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年头滔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隐绵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拙毫,死狀恐怖依许,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缀蹄,我是刑警寧澤峭跳,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布膘婶,位于F島的核電站,受9級(jí)特大地震影響蛀醉,放射性物質(zhì)發(fā)生泄漏悬襟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一拯刁、第九天 我趴在偏房一處隱蔽的房頂上張望脊岳。 院中可真熱鬧,春花似錦垛玻、人聲如沸割捅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亿驾。三九已至,卻和暖如春账嚎,著一層夾襖步出監(jiān)牢的瞬間莫瞬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工郭蕉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疼邀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓召锈,卻偏偏與公主長得像檩小,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子烟勋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

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

  • Redis是一種基于客戶端-服務(wù)端模型以及請(qǐng)求/響應(yīng)協(xié)議的TCP服務(wù)规求。這意味著通常情況下一個(gè)請(qǐng)求會(huì)遵循以下步驟:客...
    奇點(diǎn)一氪閱讀 302評(píng)論 0 0
  • 目錄 Redis 管道技術(shù) SpringDataRedis 使用管道 Redis 管道的性能測(cè)試 使用管道技術(shù)的注...
    西召閱讀 716評(píng)論 0 1
  • Redis是一種基于客戶端-服務(wù)端模型以及請(qǐng)求/響應(yīng)協(xié)議的TCP服務(wù)。這意味著通常情況下一個(gè)請(qǐng)求會(huì)遵循以下步驟: ...
    誰在烽煙彼岸閱讀 319評(píng)論 1 2
  • 睡懶覺的大龍貓閱讀 135評(píng)論 0 1
  • 今天第一次看這個(gè)繪本丛塌,草草很喜歡。 看到這個(gè)暗頁畜疾,她竟然猜出這個(gè)是企鵝赴邻! 棒! 小企鵝白肚皮啡捶,扭搭扭搭在南極姥敛! 這...
    星星的孩子7閱讀 547評(píng)論 0 1