jedis学习笔记【1】

redis 指令:

redis.io/commands

源码地址:

https://github.com/xetorthio/jedis

maven依赖:

http://mvnrepository.com/artifact/redis.clients/jedis

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

  • 原理:

jedis

jedis底层主要有两个类:
redis.clients.jedis.Protocol
redis.clients.jedis.Connection
Connection负责client与server之间通信,Protocol是client与server之间通信协议。


  • 主要方法:
public class Connection implements Closeable {
private static final byte[][] EMPTY_ARGS = new byte[0][];
    private String host = "localhost"; //redis服务器地址(默认"localhost")
    private int port = 6379;//port:服务端号(默认6379)
    private Socket socket;
    private RedisOutputStream outputStream;//redis-client发送给redis-server的内容
    private RedisInputStream inputStream;//redis-server返回给redis-client的内容
    private int pipelinedCommands = 0;//管道命令数
    private int connectionTimeout = 2000;//连接超时时间(默认2000ms)
    private int soTimeout = 2000;//响应超时时间(默认2000ms)
    private boolean broken = false;

...

/**主要方法*/

//连接
 public void connect() {
        if(!this.isConnected()) {
            try {
                this.socket = new Socket();
                this.socket.setReuseAddress(true);
                this.socket.setKeepAlive(true);
                this.socket.setTcpNoDelay(true);
                this.socket.setSoLinger(true, 0);
                this.socket.connect(new InetSocketAddress(this.host, this.port), this.connectionTimeout);
                this.socket.setSoTimeout(this.soTimeout);
                this.outputStream = new RedisOutputStream(this.socket.getOutputStream());
                this.inputStream = new RedisInputStream(this.socket.getInputStream());
            } catch (IOException var2) {
                this.broken = true;
                throw new JedisConnectionException(var2);
            }
        }

    }

//发送命令内容
protected Connection sendCommand(Command cmd, byte[]... args) {
        try {
            this.connect();
            Protocol.sendCommand(this.outputStream, cmd, args);
            ++this.pipelinedCommands;
            return this;
        } catch (JedisConnectionException var6) {
            JedisConnectionException ex = var6;

            try {
                String errorMessage = Protocol.readErrorLineIfPossible(this.inputStream);
                if(errorMessage != null && errorMessage.length() > 0) {
                    ex = new JedisConnectionException(errorMessage, ex.getCause());
                }
            } catch (Exception var5) {
                ;
            }

            this.broken = true;
            throw ex;
        }
    }
}    
//协议
public final class Protocol {

//命令的发送都是通过redis.clients.jedis.Protocol的sendCommand来完成的,就是对RedisOutputStream写入字节流 
/**
*[*号][消息元素个数]\r\n ( 消息元素个数 = 参数个数 + 1个命令)
*[$号][命令字节个数]\r\n
*[命令内容]\r\n
*[$号][参数字节个数]\r\n
*[参数内容]\r\n
*[$号][参数字节个数]\r\n
*[参数内容]\r\n 
*/
private static void sendCommand(RedisOutputStream os, byte[] command, byte[]... args) {
        try {
            os.write((byte)42);
            os.writeIntCrLf(args.length + 1);
            os.write((byte)36);
            os.writeIntCrLf(command.length);
            os.write(command);
            os.writeCrLf();
            byte[][] e = args;
            int var4 = args.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                byte[] arg = e[var5];
                os.write((byte)36);
                os.writeIntCrLf(arg.length);
                os.write(arg);
                os.writeCrLf();
            }

        } catch (IOException var7) {
            throw new JedisConnectionException(var7);
        }
    }
}

//返回的数据是通过读取RedisInputStream  进行解析处理后得到的
  /** 
   * public static final byte PLUS_BYTE = 43;
   * public static final byte DOLLAR_BYTE = 36;
   * public static final byte ASTERISK_BYTE = 42;
   * public static final byte COLON_BYTE = 58;

   * "+": 状态回复(status reply)         PLUS_BYTE 
   *    * <pre> 
   * 状态回复通常由那些不需要返回数据的命令返回,这种回复不能包含新行。 
   * eg: 
   *    cli: set name zhangsan 
   *    server: +OK 
   * </pre> 
   * 
   * "$": 批量回复(bulk reply)           DOLLAR_BYTE 
   *   服务器使用批量回复来返回二进制安全的字符串,字符串的最大长度为 512 MB。 
   *  eg: 
   *      cli: get name 
   *      server: $8\r\nzhangsan\r\n 
   *  空批量回复: 
   *  如果被请求的值不存在, 那么批量回复会将特殊值 -1 用作回复的长度值。当请求对象不存在时,客户端应该返回空对象,而不是空字符串。 
   * 
   * "*": 多条批量回复(multi bulk reply)  ASTERISK_BYTE  
   *    * 多条批量回复是由多个回复组成的数组, 数组中的每个元素都可以是任意类型的回复, 包括多条批量回复本身。 
   * eg: 
   *    cli: lrange mylist 0 3 
   *    server: *4\r\n 
   *            :1\r\n 
   *            :2\r\n 
   *            :3\r\n 
   *            $3\r\n 
   *            foo\r\n 
   * 多条批量回复也可以是空白的, 
   * eg: 
   *    cli: lrange mylist 7 8 
   *    server: *0\r\n 
   * 无内容的多条批量回复(null multi bulk reply)也是存在的, 比如当 BLPOP 命令的阻塞时间超过最大时限时, 它就返回一个无内容的多条批量回复, 这个回复的计数值为 -1 : 
   * eg: 
   *    cli: blpop key 1 
   *    server: *-1\r\n 
   * 多条批量回复中的元素可以将自身的长度设置为 -1 , 从而表示该元素不存在, 并且也不是一个空白字符串(empty string)。 
   * 
   * ":": 整数回复(integer reply)        COLON_BYTE
   *    *  整数回复就是一个以 ":" 开头, CRLF 结尾的字符串表示的整数。 
   *    eg: 
   *    cli: exists name 
   *    server: :1 
   * 
   * "-": 错误回复(error reply)          MINUS_BYTE
   */ 
private static Object process(RedisInputStream is) {
        byte b = is.readByte();
        if(b == 43) {
            return processStatusCodeReply(is);
        } else if(b == 36) {
            return processBulkReply(is);
        } else if(b == 42) {
            return processMultiBulkReply(is);
        } else if(b == 58) {
            return processInteger(is);
        } else if(b == 45) {
            processError(is);
            return null;
        } else {
            throw new JedisConnectionException("Unknown reply: " + (char)b);
        }
    }

}

  • 示例:

以Jedis的get方法为例:

public String get(String key) {
        this.checkIsInMultiOrPipeline();
        this.client.sendCommand(Command.GET, new String[]{key});
        return this.client.getBulkReply();
    }

1:this.checkIsInMultiOrPipeline();
进行无事务检查 Jedis不能进行有事务的操作 带事务的连接要用redis.clients.jedis.Transaction类。
2:this.client.sendCommand(Command.GET, new String[]{key});
2.1:redis.clients.jedis.Connection connect()方法建立连接
2.2:public final class Protocol sendCommand()方法向RedisOutputStream写入命令
2.3:在命令写入成功之后,会将Connection的piplinedCommands 属性自增一,表示在管道中已经有一个命令了
3:return this.client.getBulkReply();
get方法使用getBulkReply()获取返回结果,其他见上文redis.clients.jedis.Protocol process()方法

  • pipeline

redis是一个cs模式的tcp server,使用和http类似的请求响应协议。一个client可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client。
所以在多条命令需要处理时,使用pipeline效率会快得多。
通过pipeline方式当有大批量的操作时候。我们可以节省很多原来浪费在网络延迟的时间。pipeline方式将client端命令一起发出,redis server会处理完多条命令后,将结果一起打包返回client,从而节省大量的网络延迟开销。需要注意到是用 pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并是不是打包的命令越多越好。具体多少合适需要根据具体情况测试。

  • pipeline 示例
 Pipeline pipeline = jedis.pipelined();
        pipeline.set("a","a");
        pipeline.get("a");
        pipeline.set("b","b");
        pipeline.get("b");
        pipeline.del("a");
        pipeline.get("a");
        List<Object> list = pipeline.syncAndReturnAll();
        for (Object o : list){
            System.out.println(o);
        }

结果如下:

OK
a
OK
b
1
null

  • 参考链接

Redis Java Client Jedis 源码分析

Jedis 最简单的例子分析

深入Jedis

Jedis - Redis通信协议

Redis学习笔记7–Redis管道(pipeline)

猜你喜欢

转载自blog.csdn.net/yx1214442120/article/details/51942065