jedis 高級特性

jedis 高級特性

jedis是redis的一個性能良好的客戶端,里面包含了所有的redis的特性,用起來相當爽。redis以存儲方式多樣,存儲類型多樣,性能穩定著稱。目前為止,支持的數據類型有List,Set,Map,SortedSet等。本文著重講述jedis中的高級特性。本文實例是在2.9.0版本上測試通過。自己測試時可以引入下面的內容,下面開始。

pom

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

一. Publish/Subscribe(發布訂閱)

? 觀察者模式的具體體現。常用來解耦合,有很多版本都實現了此類的功能,比如說Rxjava,EventBus,Zk等。jedis也有自己的實現,首先說一下原理:首先客戶端與服務端建立連接,客戶端注冊感興趣的事件,同時定義一個當感興趣的事件過來時,需要如何處理的回調(繼承JedisPubSub類,覆寫里面的方法,例如onMessage,是消息接受的方法)。客戶端或設置與服務端的socket鏈接超時時間為0(不超時),然后阻塞在RedisInputStream中的read方法,等待消息的到來(也就是說RedisInputStream中有數據)。當有的客戶端發布了感興趣的信息,那么redis 的 server端就會查詢哪些client,關注了這個消息,然后,將結果放入該客戶端的RedisInputStream中,客戶端就接收了感興趣的消息,然后處理自己的邏輯。下面是測試代碼實例:

publish and subscribe

static class MyListener extends JedisPubSub {
 
    @Override
    public void onMessage(String channel, String message) {
        System.out.println(channel + "============" + message);
        super.onMessage(channel, message);
    }
}
 
@Test
public void testSubscribe() {
    jedis.subscribe(new MyListener(), "channel1");
}
 
@Test
public void testPublish() {
    jedis.publish("channel1", "message1");
}

? 現在跟著代碼看一下

redis.clients.jedis.JedisPubSub#proceed

public void proceed(Client client, String... channels) {
  this.client = client;
  client.subscribe(channels);   //訂閱
  client.flush();               //數據flush,發送給server端
  process(client);              //do while 獲取數據
}

redis.clients.jedis.JedisPubSub#process

private void process(Client client) {
 
  do {
    List<Object> reply = client.getRawObjectMultiBulkReply();    //阻塞獲取server端傳遞的數據,根據數據,觸發相應的回調
    final Object firstObj = reply.get(0);
    if (!(firstObj instanceof byte[])) {
      throw new JedisException("Unknown message type: " + firstObj);
    }
    final byte[] resp = (byte[]) firstObj;
    if (Arrays.equals(SUBSCRIBE.raw, resp)) {
      subscribedChannels = ((Long) reply.get(2)).intValue();
      final byte[] bchannel = (byte[]) reply.get(1);
      final String strchannel = (bchannel == null) ? null : SafeEncoder.encode(bchannel);
      onSubscribe(strchannel, subscribedChannels);
    } else if (Arrays.equals(UNSUBSCRIBE.raw, resp)) {
      subscribedChannels = ((Long) reply.get(2)).intValue();
      final byte[] bchannel = (byte[]) reply.get(1);
      final String strchannel = (bchannel == null) ? null : SafeEncoder.encode(bchannel);
      onUnsubscribe(strchannel, subscribedChannels);
    } else if (Arrays.equals(MESSAGE.raw, resp)) {
      final byte[] bchannel = (byte[]) reply.get(1);
      final byte[] bmesg = (byte[]) reply.get(2);
      final String strchannel = (bchannel == null) ? null : SafeEncoder.encode(bchannel);
      final String strmesg = (bmesg == null) ? null : SafeEncoder.encode(bmesg);
      onMessage(strchannel, strmesg);
    } else if (Arrays.equals(PMESSAGE.raw, resp)) {
      final byte[] bpattern = (byte[]) reply.get(1);
      final byte[] bchannel = (byte[]) reply.get(2);
      final byte[] bmesg = (byte[]) reply.get(3);
      final String strpattern = (bpattern == null) ? null : SafeEncoder.encode(bpattern);
      final String strchannel = (bchannel == null) ? null : SafeEncoder.encode(bchannel);
      final String strmesg = (bmesg == null) ? null : SafeEncoder.encode(bmesg);
      onPMessage(strpattern, strchannel, strmesg);
    } else if (Arrays.equals(PSUBSCRIBE.raw, resp)) {
      subscribedChannels = ((Long) reply.get(2)).intValue();
      final byte[] bpattern = (byte[]) reply.get(1);
      final String strpattern = (bpattern == null) ? null : SafeEncoder.encode(bpattern);
      onPSubscribe(strpattern, subscribedChannels);
    } else if (Arrays.equals(PUNSUBSCRIBE.raw, resp)) {
      subscribedChannels = ((Long) reply.get(2)).intValue();
      final byte[] bpattern = (byte[]) reply.get(1);
      final String strpattern = (bpattern == null) ? null : SafeEncoder.encode(bpattern);
      onPUnsubscribe(strpattern, subscribedChannels);
    } else if (Arrays.equals(PONG.raw, resp)) {
      final byte[] bpattern = (byte[]) reply.get(1);
      final String strpattern = (bpattern == null) ? null : SafeEncoder.encode(bpattern);
      onPong(strpattern);
    } else {
      throw new JedisException("Unknown message type: " + firstObj);
    }
  } while (isSubscribed());
 
  /* Invalidate instance since this thread is no longer listening */
  this.client = null;
 
  /*
   * Reset pipeline count because subscribe() calls would have increased it but nothing
   * decremented it.
   */
  client.resetPipelinedCount();
}

redis.clients.jedis.Protocol#process

private static Object process(final RedisInputStream is) {
 
  final byte b = is.readByte();   //阻塞獲取server端傳遞的數據
  if (b == PLUS_BYTE) {
    return processStatusCodeReply(is);
  } else if (b == DOLLAR_BYTE) {
    return processBulkReply(is);
  } else if (b == ASTERISK_BYTE) {
    return processMultiBulkReply(is);
  } else if (b == COLON_BYTE) {
    return processInteger(is);
  } else if (b == MINUS_BYTE) {
    processError(is);
    return null;
  } else {
    throw new JedisConnectionException("Unknown reply: " + (char) b);
  }
}

二. pipeline(管道)

? 傳統的交互方式是request->response request->response request->response,而pipeline是 request->request->request-> response->response->response,將多次請求的報文放到一個請求體中,服務端也是一樣,將多次的響應信息合并成一次返回,所以 ,能不快嗎?!這也就是pipeline的基本原理。下面是測試代碼實例:

pipeline

@Test
public void test36() {
    Pipeline pipelined = jedis.pipelined();
    pipelined.set("test1", "22");
    pipelined.set("test2", "33");
 
    pipelined.sync();   //無返回值
 
    pipelined.get("test1");
    pipelined.get("test2");
 
    List<Object> objects = pipelined.syncAndReturnAll();  //有返回值
    for (Object o : objects) {
        System.out.print(o + " ");
    }
}
 
result: 22 33

跟著代碼看一下:

redis.clients.jedis.PipelineBase#set(java.lang.String, java.lang.String)

public Response<String> set(byte[] key, byte[] value) {
  getClient(key).set(key, value);     //執行set命令
  return getResponse(BuilderFactory.STRING);   //注意設置響應信息的順序
}

redis.clients.jedis.Connection#sendCommand(redis.clients.jedis.Protocol.Command, byte[]...)

protected Connection sendCommand(final Command cmd, final byte[]... args) {
  try {
    connect(); //與server端形成鏈接,實例化輸入輸出流
    Protocol.sendCommand(outputStream, cmd, args);   //往輸出流中按照協議寫入key與value,但不發送,調用sync()或syncAndReturnAll()時發送
    pipelinedCommands++;
    return this;
  } catch (JedisConnectionException ex) {
    /*
     * When client send request which formed by invalid protocol, Redis send back error message
     * before close connection. We try to read it to provide reason of failure.
     */
    try {
      String errorMessage = Protocol.readErrorLineIfPossible(inputStream);
      if (errorMessage != null && errorMessage.length() > 0) {
        ex = new JedisConnectionException(errorMessage, ex.getCause());
      }
    } catch (Exception e) {
      /*
       * Catch any IOException or JedisConnectionException occurred from InputStream#read and just
       * ignore. This approach is safe because reading error message is optional and connection
       * will eventually be closed.
       */
    }
    // Any other exceptions related to connection?
    broken = true;
    throw ex;
  }
}

redis.clients.jedis.Queable#getResponse

protected <T> Response<T> getResponse(Builder<T> builder) {
  Response<T> lr = new Response<T>(builder);  //初始化一個Response
  pipelinedResponses.add(lr);    //放入列表中,保證順序
  return lr;
}

當調用syncAndReturnAll時

redis.clients.jedis.Pipeline#syncAndReturnAll

public List<Object> syncAndReturnAll() {
  if (getPipelinedResponseLength() > 0) {    //判斷Response列表中的數量
    List<Object> unformatted = client.getAll();    //獲取所有的結果,阻塞獲取
    List<Object> formatted = new ArrayList<Object>();
 
    for (Object o : unformatted) {
      try {
        formatted.add(generateResponse(o).get());    //解析每一個得到的信息,因為原來都是byte[],需要解析成String
      } catch (JedisDataException e) {
        formatted.add(e);
      }
    }
    return formatted;
  } else {
    return java.util.Collections.<Object> emptyList();
  }
}

redis.clients.jedis.Connection#getAll(int)

public List<Object> getAll(int except) {
  List<Object> all = new ArrayList<Object>();
  flush();
  while (pipelinedCommands > except) {
    try {
      all.add(readProtocolWithCheckingBroken());  //根據pipelinedCommands值判斷all里面需要多少跳數據
    } catch (JedisDataException e) {
      all.add(e);
    }
    pipelinedCommands--;
  }
  return all;
}
readProtocolWithCheckingBroken還是阻塞獲取

redis.clients.jedis.Protocol#process

private static Object process(final RedisInputStream is) {
 
  final byte b = is.readByte();  //阻塞獲取值,判斷第一個字節的類型,從而進入不同的處理
  if (b == PLUS_BYTE) {
    return processStatusCodeReply(is);
  } else if (b == DOLLAR_BYTE) {
    return processBulkReply(is);
  } else if (b == ASTERISK_BYTE) {
    return processMultiBulkReply(is);
  } else if (b == COLON_BYTE) {
    return processInteger(is);
  } else if (b == MINUS_BYTE) {
    processError(is);
    return null;
  } else {
    throw new JedisConnectionException("Unknown reply: " + (char) b);
  }
}

三. Transaction(事務)

? 1. 事務為提交之前出現異常,那么事務內的操作都不執行。這個很好理解,操作同pipeline,事務提交的時候才會flush數據同服務器交互。

? 2. 結合watch。事務開始前觀察某個值,在事務期間如果其他的線程改了所觀察的值,事務不執行!

transaction

@Test
public void testTransaction() {
    //要在事務執行之前進行監視
    jedis.watch("key100", "key101");
    Transaction multi = jedis.multi();
 
    multi.set("key100", "key100");
    multi.set("key101", "key101");
 
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
    }
 
     multi.exec();
 
    String key6 = jedis.get("key100");
    System.out.println(key6);
    String key7 = jedis.get("key101");
    System.out.println(key7);
 
}
 
@Test
public void test() {
    jedis.set("key100", "value3");
    String key10 = jedis.get("key100");
    System.out.println(key10);
}

先執行testTransaction,再執行test,test的結果

test

    
value3

10s后testTransaction的結果

testTransaction

testTransaction

\3. redis的事務不支持回滾。因為這種復雜的功能和redis追求的簡單高效的設計主旨不符合,并且他認為,redis事務的執行時錯誤通常都是編程錯誤造成的,這種錯誤通常只會出現在開發環境中,而很少會在實際的生產環境中出現,所以他認為沒有必要為redis開發事務回滾功能。

參考資料: https://github.com/xetorthio/jedis/wiki/AdvancedUsage

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內容