Research Redis client implemented BIO hand (rejection Jedis)

  In Redis of course, most people are ready to use the client, such as Jedis, Redisson, Lettuce. Therefore, this paper with handwritten Redis client-side attempt BIO way, the problems encountered in exploration and summary.

1, call the class handwriting BIO

  Prepared using BIO way Redis client, first define a Connection, Connection class including the establishment of a remote connection BIO Host address, Port port, and a Socket input and output streams.

  Connection of such a method of construction, a Connection initialization method, and a request transmission method.

public class Connection {

    private String host;
    private int port;
    private Socket socket;
    private InputStream inputStream;
    private OutputStream outputStream;

    public Connection(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public boolean isConnection() {

        if (socket != null && !socket.isClosed() && !socket.isBound() && socket.isConnected()) {
            return true;
        }
        try {
            socket = new Socket(host, port);
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public String sendCommand(byte[] command) {
        if (isConnection()) {
            try {
                outputStream.write(command);
                int length = 0;
                byte[] response = new byte[1024];
                while ((length = inputStream.read(response)) > 0) {
                    return new String(response, 0, length);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

  With BIO after connecting category you may send a request, and then transmits a request to build Connection Test:

public class MainTest {
    public static void main(String[] args) {

        String command = "set ant 123456";
        Connection connection = new Connection("localhost", 6379);
        System.out.println(connection.sendCommand(command.getBytes()));
  }
}

  Findings in the following figure, the request did not return the call, but the main method is not over, through debug can know because InputStream.read (the Response) ) is a blocking call this a code, because the results have not been returned, thus blocking the main method, as follows Figure:

          

   In fact, the reason is because the requests are based on any protocol, after sending a request command = "set ant 123456", because the agreement does not follow any access Redis, so Redis not recognize the request and make a return. RESP Redis protocol employed, RESP protocol is introduced in Redis 1.2, but it becomes the standard method Redis 2.0 Redis communication with the server.

  RESP actually supports the following sequence of protocol data types: simple string, error, integers, strings and large capacity arrays.

  RESP Redis as the request - response protocol following manner:

  • The client sends commands to the server Redis RESP array as a large-capacity strings.
  • Server replies with one RESP according to the type of command to achieve.

  In RESP, the type of some data depends on the first byte of:

  • For a simple string , the first byte of the answer is "+"
  • For the error , the first byte of the reply is "-"
  • For integer , the first byte answer is ":"
  • For bulk strings , the first byte answer is "$"
  • For an array , the first byte of the reply is "  *"

  Further, RESP Bulk Strings may be used or a particular variant Array represented Null value, as specified later. Different parts of the RESP, the protocol always "\ r \ n" (CRLF ) terminates. For details, see https://redis.io/topics/protocol

2, handwritten RESP Protocol Class

  Defines a protocol class Protocol, the present example not fully achieved, the SET is simply achieved, and parses the contents of the GET request.

public  class Protocol { 

    public  static  Final String Doller = "$" ;
     public  static  Final String ALLERSTIC = "*" ;
     public  static  Final String CRLF = "\ R & lt \ n-" ; 
  
  // The SET request Ant 7777 SET 
  // *. 3 \ r \ n array of length 3
  // $ 3 \ r \ n first string of length 3
  // the SET \ r \ n first string of the SET
  // $ 3 \ r \ n second string length is. 3
  // Ant \ R & lt \ n-second string Ant 
  // $. 4 \ R & lt \ n-third length of the string. 4
  // 7777 \ R & lt \ n-third string 7777 
    public  static byte[] buildRespByte(Command command, byte[]... bytes){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(ALLERSTIC).append(bytes.length+1).append(CRLF);

     // 封装方法SET、GET        
     stringBuilder.append(DOLLER).append(command.name().length()).append(CRLF);
stringBuilder.append(command.name()).append(CRLF); // 封装参数 for(byte[] arg:bytes){ stringBuilder.append(DOLLER).append(arg.length).append(CRLF); stringBuilder.append(new String(arg) ).append(CRLF); } return stringBuilder.toString().getBytes(); } public enum Command{ SET,GET } }

  Then create a call Client calls package set and get methods:

public class SelfRedisClient {

    private Connection connection;

    public SelfRedisClient(String host, int ip) {
        connection = new Connection(host, ip);
    }

    public String set(String key, String value) {
        String result = connection.sendCommand(
                Protocol.buildRespByte(Protocol.Command.SET, key.getBytes(), value.getBytes()));
        return result;
    }

    public String get(String key) {
        String result = connection.sendCommand(
                Protocol.buildRespByte(Protocol.Command.GET, key.getBytes()));
        return result;
    }
}

  Then call the Main method:

public class MainTest {
    public static void main(String[] args) {
        SelfRedisClient selfRedisClient = new SelfRedisClient("localhost", 6379);
        System.out.println(selfRedisClient.set("ant", "123456"));
        System.out.println(selfRedisClient.get("ant"));
    }
}

  The results can be seen in a normal return, of course, we did not use the agreement to return the results of analysis:

      

 3, the use of multi-threading Redis request

  The above example is testing in single-threaded access to the case, in a multithreaded situation will happen then. Next, we construct a thread pool, use multiple threads attempt to Redis request to build a ClientRunnable as follows:

public class ClientRunnable implements Runnable {

    private SelfRedisClient selfRedisClient;
    private String value;

    public ClientRunnable(SelfRedisClient selfRedisClient, String value) {
        this.selfRedisClient = selfRedisClient;
        this.value = value;
    }
    @Override
    public void run() {
        selfRedisClient.set("ant", value);
    }
}

  The main method is as follows:

public class MainTest {
    public static void main(String[] args) {
        SelfRedisClient selfRedisClient = new SelfRedisClient("localhost", 6379);
        ExecutorService pool = Executors.newCachedThreadPool();
        for(int i=0;i<20;i++){
            pool.execute(new ClientRunnable(selfRedisClient,"value"+i));
        }
    }
}

  And increasing the output to the console in the set method:

public String set(String key, String value) {
    String result = connection.sendCommand(
            Protocol.buildRespByte(Protocol.Command.SET, key.getBytes(), value.getBytes()));
    System.out.println("Thread name: " + Thread.currentThread().getName() + "[result]: "
            + result.replace("\r\n", "") + " [value]: " + value);
    return result;
}

  View the results as follows:

        

   Found not only return results even more time there have been two Redis service its OK to return, but the main method of execution has not ended. Why the case, because in a multi-threaded under Socket is thread safe, when multiple threads access Socket, while sending the request, and then returns the requested results are accumulated, then fully acquired a thread, and the rest sent a request the thread will block waiting to return, but has been intercepted prior to thread the stream, so the program can not continue.

            

   So now we need a thread pool to manage Connection, each thread using a single Connection, Connection for thread did not get to wait in the queue blocked until the thread is completed calls, and Connection released back into the thread pool, blocked thread before continuing to make the call. As shown below:

          

4、实现Connection的线程池管理

  首先实现一个阻塞队列用于管理特定数量的Connection,当有Connection使用时就返回Connection,用完Connection后就进行归还。

public class RedisClientPool {

    private LinkedBlockingQueue<SelfRedisClient> linkedBlockingQueue;

    public RedisClientPool(String host,int port ,int connectionCount){
        this.linkedBlockingQueue = new LinkedBlockingQueue<SelfRedisClient>(connectionCount);
        for(int i=0;i<connectionCount;i++){
            SelfRedisClient selfRedisClient = new SelfRedisClient(host,port);
            linkedBlockingQueue.add(selfRedisClient);
        }
    }

    public SelfRedisClient getClient(){
        try{
            return linkedBlockingQueue.take();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return null;
    }

    public void returnClient(SelfRedisClient selfRedisClient) {
        if(selfRedisClient != null){
            linkedBlockingQueue.add(selfRedisClient);
        }
    }
}

  修改ClientRunnable方法,改为从线程池获取Connection进行请求调用:

public class ClientRunnable implements Runnable {

    private RedisClientPool redisClientPool;
    private String value;

    public ClientRunnable(RedisClientPool redisClientPool, String value) {
        this.redisClientPool = redisClientPool;
        this.value = value;
    }

    @Override
    public void run() {
        // 执行前先去管理Connection的阻塞队列中获取封装了Connection的SelfRedisClient
        SelfRedisClient selfRedisClient = redisClientPool.getClient();
        selfRedisClient.set("ant", value);
        // 使用完后进行归还client
        redisClientPool.returnClient(selfRedisClient);
    }
}

  使用Main方法进行请求调用:

public class MainTest {
    public static void main(String[] args) {
        RedisClientPool redisClientPool = new RedisClientPool("localhost",6379,5);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for(int i=0;i<10;i++){
            executorService.execute(new ClientRunnable(redisClientPool,"value"+i));
        }
    }
}

  查看执行结果:    

          

   可以知道成功返回了所有的请求调用,最后也是线程9成功将value值修改为value8。

  因此,可以发现使用一个阻塞队列对Connection资源进行管理不仅近能节省Connection的创建和回收时间,在本例中更核心的功能是实现了线程不安全资源的管理。  

Guess you like

Origin www.cnblogs.com/jing99/p/11854530.html