国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Jedis 源代碼分析:客戶端設(shè)計(jì)與實(shí)現(xiàn)的套路

paulquei / 2588人閱讀

摘要:前言是應(yīng)用訪問(wèn)服務(wù)的首選客戶端,本文通過(guò)分析客戶端源代碼,扒一扒客戶端設(shè)計(jì)與實(shí)現(xiàn)的常用套路連接要訪問(wèn)服務(wù),首先需要與服務(wù)建立連接,因此客戶端庫(kù)首先需要對(duì)連接進(jìn)行抽象和封裝,使用類來(lái)封裝與服務(wù)器的一個(gè)連接通常類還會(huì)對(duì)的讀寫進(jìn)行一層簡(jiǎn)單的封裝,

前言

Jedis 是 java 應(yīng)用訪問(wèn) Redis 服務(wù)的首選客戶端,本文通過(guò)分析 jedis 客戶端源代碼,扒一扒客戶端設(shè)計(jì)與實(shí)現(xiàn)的常用套路

連接(Connection)

要訪問(wèn)(Redis)服務(wù),首先需要與服務(wù)建立連接,因此客戶端庫(kù)首先需要對(duì)連接進(jìn)行抽象和封裝,Jedis 使用 Connection 類來(lái)封裝與服務(wù)器的一個(gè) socket 連接:

public class Connection implements Closable {
    private Socket socket;
    private connectionTimeout = Protocol.DEFAULT_TIMEOUT;
    private int soTimeout = Protocol.DEFAULT_TIMEOUT;
    ...
    public Connection() {}
    public Connection(final String host) {
        this.host = host;
    }
    public Connection(final String host, final int port) {
        this.host = host;
        this.port = port;
    }
}

通常 Connection 類還會(huì)對(duì) socket 的讀寫進(jìn)行一層簡(jiǎn)單的封裝,對(duì)于 Redis 客戶端就是發(fā)送命令以及獲取結(jié)果,這里的 Command 類是一個(gè)命令的枚舉類型,args 是可變長(zhǎng)參數(shù),表示命令參數(shù)

protected Connection sendCommand(final Command cmd, final byte[]... args) {
    // 見(jiàn)下文
}

由于網(wǎng)絡(luò)通信的不穩(wěn)定,客戶端與服務(wù)器的通信通常都需要 捕獲 各種異常并進(jìn)行恢復(fù)(重試,重連)

// sendCommand 實(shí)現(xiàn)
    try {
      connect();
      Protocol.sendCommand(outputStream, cmd, args);
      pipelinedCommands++;
      return this;
    } catch (JedisConnectionException ex) {
      try {
        String errorMessage = Protocol.readErrorLineIfPossible(inputStream);
        if (errorMessage != null && errorMessage.length() > 0) {
          ex = new JedisConnectionException(errorMessage, ex.getCause());
        }
      } catch (Exception e) {
      }
      // Any other exceptions related to connection?
      broken = true;
      throw ex;
    }

connect 方法建立連接,如果已經(jīng)建立連接當(dāng)然就不需要,這里又涉及到 socket 的一些常用參數(shù)

reuse address

keep alive

tcp no delay

so linger

so timeout

  public void connect() {
    if (!isConnected()) {
      try {
        socket = new Socket();
        // ->@wjw_add
        socket.setReuseAddress(true);
        socket.setKeepAlive(true); // Will monitor the TCP connection is
        // valid
        socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
        // ensure timely delivery of data
        socket.setSoLinger(true, 0); // Control calls close () method,
        // the underlying socket is closed
        // immediately
        // <-@wjw_add

        socket.connect(new InetSocketAddress(host, port), connectionTimeout);
        socket.setSoTimeout(soTimeout);
        outputStream = new RedisOutputStream(socket.getOutputStream());
        inputStream = new RedisInputStream(socket.getInputStream());
      } catch (IOException ex) {
        broken = true;
        throw new JedisConnectionException(ex);
      }
    }
  }
輸入輸出流(Input, output stream)

通??蛻舳硕紩?huì)自定義輸入輸出流來(lái)封裝協(xié)議格式以及對(duì)傳輸內(nèi)容進(jìn)行緩存(預(yù)讀)來(lái)提高效率,Jedis 使用 RedisInputStream 和 RedisOutputStream 類類封裝輸入輸出流

RedisInputStream

RedisInputStream 類中定義了一個(gè) byte 類型的字節(jié)數(shù)組 buf 來(lái)保存從 socket inputstream 讀到的數(shù)據(jù),limit 字段表示實(shí)際讀到的數(shù)據(jù)大小,count 字段表示當(dāng)前已經(jīng)讀取的數(shù)據(jù)(偏移量),

public class RedisInputStream extends FilterInputStream {
    protected final byte[] buf;
    protected int count, limit;
    public RedisInputStream(InputStream  in, int size) {
        super(int);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
}

我們來(lái)看看 readLine 方法,它用于從 socket input stream 中讀取一行

  public String readLine() {
    final StringBuilder sb = new StringBuilder();
    while (true) {
      // 預(yù)讀
      ensureFill();
      byte b = buf[count++];
      if (b == "
") {
        // 按照協(xié)議 
 必定連續(xù)出現(xiàn),所以讀到 
 后再預(yù)讀一次確保數(shù)據(jù)完整性
        ensureFill(); // Must be one more byte
        byte c = buf[count++];
        if (c == "
") {
          break;
        }
        sb.append((char) b);
        sb.append((char) c);
      } else {
        sb.append((char) b);
      }
    }
    final String reply = sb.toString();
    if (reply.length() == 0) {
      throw new JedisConnectionException("It seems like server has closed the
          connection.");
    }
    return reply;
  }
RedisOutputStream 協(xié)議(Protocol)

Socket 連接在客戶端和服務(wù)器之間建立了一個(gè)通信通道,協(xié)議(Protocol)規(guī)定了數(shù)據(jù)傳輸格式,對(duì)于 Redis 這種使用 socket 長(zhǎng)連接的服務(wù),一般都會(huì)自定義 協(xié)議,所以接下來(lái)要對(duì) 協(xié)議 進(jìn)行抽象和封裝.

通常有兩種做法:

使用面向?qū)ο蟮姆治雠c設(shè)計(jì),將每個(gè)協(xié)議單元抽象成一個(gè)類

將所有與協(xié)議相關(guān)的字段和方法封裝到一個(gè)工具類里頭

Jedis 使用了后者,可能是因?yàn)?Redis 命令本身比較簡(jiǎn)單,沒(méi)必要過(guò)度設(shè)計(jì).

Protocol 類包含了 Jedis 和 Redis 服務(wù)通信協(xié)議相關(guān)的代碼,比如上文提到的 Protocol.sendCommand 方法

  private static void sendCommand(final RedisOutputStream os, final byte[] command,
      final byte[]... args) {
    try {
      os.write(ASTERISK_BYTE);
      os.writeIntCrLf(args.length + 1);
      os.write(DOLLAR_BYTE);
      os.writeIntCrLf(command.length);
      os.write(command);
      os.writeCrLf();

      for (final byte[] arg : args) {
        os.write(DOLLAR_BYTE);
        os.writeIntCrLf(arg.length);
        os.write(arg);
        os.writeCrLf();
      }
    } catch (IOException e) {
      throw new JedisConnectionException(e);
    }
  }

從 sendCommand 方法可以看出 Redis 發(fā)送命令協(xié)議,完整的協(xié)議可以參考 Redis 官網(wǎng)文檔,或者使用 tcpdump 這樣的抓包工具來(lái)觀察 Redis 協(xié)議

Facade 設(shè)計(jì)模式

使用 Connection, Command, Protocol 等類就可以直接和 Redis 服務(wù)通信,但是客戶端庫(kù)通常會(huì)再做一層封裝供調(diào)用者使用,類似設(shè)計(jì)模式中的 Facade 模式,這也就是為什么在很多客戶端中會(huì)有各種各樣的 XXXClient, XXXManager 等

在 Jedis 中這個(gè) Facade(門面)就是 Jedis 和 Client

Jedis 類層次結(jié)構(gòu):

BinaryJedis ---> Client
    Jedis

BinaryJedis 使用 Client 類和 Redis 服務(wù)通信

Client 類層次結(jié)構(gòu):

Connection
    BinaryClient
        Client

以 Redis set 命令的實(shí)現(xiàn)為例:

  public String set(final byte[] key, final byte[] value) {
    checkIsInMultiOrPipeline();
    // 發(fā)送命令
    client.set(key, value);
    // 讀取響應(yīng)
    return client.getStatusCodeReply();
  }
性能優(yōu)化 連接池

如果只有一條路,車比較多的時(shí)候就會(huì)造成阻塞,因此直觀的方案是多修幾條(道)路,所以客戶端和服務(wù)器之間的連接普遍都會(huì)用到 連接池,除了上述效率的考慮外,使用連接池還可以增加容錯(cuò)能力,比如一個(gè)連接掛了,系統(tǒng)可以從連接池中選取其它的連接進(jìn)行服務(wù)

Jedis 通過(guò) JedisPool 來(lái)抽象和封裝 連接池,向用戶隱藏了實(shí)現(xiàn)細(xì)節(jié):實(shí)例化 JedisPool 的一個(gè)實(shí)例并調(diào)用 getResource 方法就可以獲取一個(gè) Jedis 實(shí)例,使用完成后調(diào)用 Jedis.close 方法,就這么簡(jiǎn)單

public JedisPool(String host, int port) {
    ...
}

@Override
public Jedis getResource() {
    Jedis jedis = super.getResource();
    jedis.setDataSource(this);
    return jedis;
}
總結(jié)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/66845.html

相關(guān)文章

  • 一文理清21種設(shè)計(jì)模式:用實(shí)例分析和對(duì)比

    摘要:設(shè)計(jì)模式無(wú)論是對(duì)于最底層的的編碼實(shí)現(xiàn)還是較高層的架構(gòu)設(shè)計(jì)都有著重要的指導(dǎo)作用。所謂光說(shuō)不練假把式,今天我就把項(xiàng)目中常見(jiàn)的應(yīng)用場(chǎng)景涉及到的主要設(shè)計(jì)模式及其相關(guān)設(shè)計(jì)模式總結(jié)一下,用實(shí)例分析和對(duì)比的方式在一片文章中就把最常見(jiàn)的種設(shè)計(jì)模式梳理清楚。 設(shè)計(jì)模式無(wú)論是對(duì)于最底層的的編碼實(shí)現(xiàn)還是較高層的架構(gòu)設(shè)計(jì)都有著重要的指導(dǎo)作用。所謂光說(shuō)不練假把式,今天我就把項(xiàng)目中常見(jiàn)的應(yīng)用場(chǎng)景涉及到的主要設(shè)計(jì)模...

    PrototypeZ 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<