Redis Jedis 开发示例及架构详解

由于原生 Java 并没有为 Redis 设计规范,所以不同于标准的 JDBC,操作 Redis 需要依赖第三方 API。Jedis 就是一个 Java 操作 Redis 的第三方 API 客户端。

1 Jedis 开发示例

通过 Jedis 实例,可以调用与 Redis Cli 命令同名的方法,发送相关命令。例如:

Cli 命令Jedis API
set key1 val1jedis.set("key1","val1")

1.1 引入依赖

1
2
3
4
5
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.5.2</version>
</dependency>

1.2 连接 Redis Server

如果有用户名和密码,可以使用 JedisShardInfo 配置验证信息:

1
2
3
4
5
6
JedisShardInfo jedisShardInfo = new JedisShardInfo("hostname", 1234);
jedisShardInfo.setUser("username");
jedisShardInfo.setPassword("password");
Jedis jedis = new Jedis(jedisShardInfo);
String pong = jedis.ping();
System.out.println("recv: " + pong);

打印结果如下:

1
recv: PONG

1.3 常用 API 使用示例

1.3.1 String 类型

这里我们演示通过 Jedis 执行 setgetmsetmget 命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 存储 String 类型数据
jedis.set("k1", "v1");
jedis.set("k2", "v2");
jedis.set("k3", "v3");
// 获取所有 key
Set<String> set = jedis.keys("*");
Iterator<String> iterator = set.iterator();
for (set.iterator(); iterator.hasNext(); ) {
    String k = iterator.next();
    System.out.println(k + "->" + jedis.get(k));
}
Boolean k2Exists = jedis.exists("k2"); // 查看 k2 是否存在
System.out.println("k2Exists = " + k2Exists);
System.out.println(jedis.ttl("k1"));// 查看 k1 的过期时间
// 通过 mset 同时设置 k4 k5
jedis.mset("k4", "v4", "k5", "v5");
// 获取所有刚刚设置的键值对
System.out.println(jedis.mget("k1", "k2", "k3", "k4", "k5"));

打印结果如下:

1
2
3
4
5
6
k3->v3
k1->v1
k2->v2
k2Exists = true
-1
[v1, v2, v3, v4, v5]

1.3.2 List 类型

这里我们演示通过 Jedis 执行 lpushlrange 命令:

1
2
3
4
5
jedis.lpush("list01", "l1", "l2", "l3", "l4", "l5");
List<String> list01 = jedis.lrange("list01", 0, -1);
for (String s : list01) {
    System.out.println(s);
}

打印结果如下:

1
2
3
4
5
l5
l4
l3
l2
l1

1.3.3 Set 类型

这里我们演示通过 Jedis 执行 saddsremsmembers 命令:

1
2
3
4
5
6
7
8
9
jedis.sadd("order", "jd001");
jedis.sadd("order", "jd002");
jedis.sadd("order", "jd003");
Set<String> order = jedis.smembers("order");
for (String s : order) {
    System.out.println(s);
}
jedis.srem("order", "jd002");
System.out.println(jedis.smembers("order").size());

打印结果如下:

1
2
3
4
jd001
jd002
jd003
2

1.3.4 Hash 类型

这里我们演示通过 Jedis 执行 hsethgethmsethmget 命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
jedis.hset("user1", "username", "james");
System.out.println(jedis.hget("user1", "username"));
HashMap<String, String> map = new HashMap<String, String>();
map.put("username", "tom");
map.put("gender", "boy");
map.put("address", "beijing");
map.put("phone", "18873332534");
jedis.hmset("user2", map);
List<String> list = jedis.hmget("user2", "username", "phone");
for (String s : list) {
    System.out.println(s);
}

打印结果如下:

1
2
3
james
tom
18873332534

1.3.5 Zset 类型

这里我们演示通过 Jedis 执行 zaddzrange 命令:

1
2
3
4
5
6
7
8
jedis.zadd("zset01", 60d, "zs1");
jedis.zadd("zset01", 70d, "zs2");
jedis.zadd("zset01", 80d, "zs3");
jedis.zadd("zset01", 90d, "zs4");
Set<String> zset01 = jedis.zrange("zset01", 0, -1);
for (String s : zset01) {
    System.out.println(s);
}

打印结果如下:

1
2
3
4
zs1
zs2
zs3
zs4

1.4 Jedis 事务操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 拿到 Jedis 实例
Jedis jedis = new Jedis(jedisShardInfo);

// 调用 multi 方法开启事务
Transaction multi = jedis.multi();
multi.set("k1", "v1");
// 提交事务
multi.exec();
multi.close(); // 关闭连接
System.out.println(jedis.get("k1"));

// 新开事务
multi = jedis.multi();
multi.set("k2", "v2");
// 回滚事务
multi.discard();
multi.close(); // 关闭连接
System.out.println(jedis.get("k2"));

打印结果如下:

1
2
v1
null

1.5 JedisPool 连接池

Jedis 内部封装了 Apache-Commons-Pool2 对资源池进行管理,同时提供了一个继承自 GenericObjectPoolConfig 的默认配置类 JedisPoolConfig,使用示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 配置连接池
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(50);
config.setMaxWaitMillis(3000);
config.setTestOnBorrow(true);
config.setTestOnReturn(true);

// 配置 Redis 实例
List<JedisShardInfo> list = new LinkedList<JedisShardInfo>();
JedisShardInfo jedisShardInfo1 = new JedisShardInfo("hostname", 1234);
jedisShardInfo1.setUser("username");
jedisShardInfo1.setPassword("password");
list.add(jedisShardInfo1);

// 将实例加入池
ShardedJedisPool pool = new ShardedJedisPool(config, list);

使用的时候,通过 pool 实例获取 Redis 连接就可使用了:

1
2
ShardedJedis resource = pool.getResource();
System.out.println(resource.set("k1", "v1"));

使用完后,需要调用 close 将连接归还给连接池:

1
2
if (jedis != null) 
    jedis.close();

2 Jedis 源码详解

2.1 系统架构

jedis 系统架构
  • JedisBinaryJedis 分别是字符串参数类型、二进制参数类型的 Redis 操作类。Jedis 继承自 BinaryJedis,是 Java 操作 Redis 的主要入口。
  • Client 类是 BinaryJedis 的内聚成员变量,Client 继承自 BinaryClient,并实现了字符串参数转换为二进制参数的方法。所以无论是传入什么类型的参数,最终都会执行 BinaryClient 中的方法。
  • BinaryClient 类是命令最终执行类,用于发送命令到 Redis 服务器。BinaryClient 类继承自 Connection 类,发送命令时,依赖 Connection 中的连接资源。
  • Connection 类抽象了与服务器的连接,是 Jedis 客户端与 Redis 服务器通讯的关键。

2.2 Jedis 类

1
2
3
4
5
6
public class Jedis 
      extends BinaryJedis 
      implements JedisCommands, MultiKeyCommands, AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands, ModuleCommands {
    protected JedisPoolAbstract dataSource = null;
    ...
}

Jedis 类通过字符串参数来操作 Redis,以 set 命令为例:

1
2
3
4
5
6
7
8
9
@Override  
public String set(final String key, final String value) {
    // 如果处于事务中,抛异常
	checkIsInMultiOrPipeline();
    // 通过客户端执行命令
	client.set(key, value);
    // 等待返回结果
	return client.getStatusCodeReply();
}

Jedis 继承的接口中,定义了需要重写的各种 String 参数类型的方法。然后 Jedis 中的方法再调用 Client 中的方法发送命令。clientBinaryJedis 中的 protected 对象。

2.3 BinaryJedis 类

1
2
3
4
5
6
7
8
public class BinaryJedis implements BasicCommands, BinaryJedisCommands, MultiKeyBinaryCommands,
      AdvancedBinaryJedisCommands, BinaryScriptingCommands, Closeable {
    protected Client client = null;  
    protected Transaction transaction = null;  
    protected Pipeline pipeline = null;  
    private final byte[][] dummyArray = new byte[0][];
    ...
}

BinaryJedis 类通过二进制参数来操作 Redis,还是以 set 命令为例:

1
2
3
4
5
6
@Override
public String set(final byte[] key, final byte[] value, final SetParams params) {
    checkIsInMultiOrPipeline();
    client.set(key, value, params);
    return client.getStatusCodeReply();
}

BinaryJedis 继承的接口也定义了需要重写的各种二进制参数类型的方法,然后再调用 Client 中的方法。

2.4 Client 类

1
2
3
4
5
6
7
8
public class Client extends BinaryClient implements Commands {
	//...
    @Override
    public void set(final String key, final String value) {
        set(SafeEncoder.encode(key), SafeEncoder.encode(value));
    }
	//...
}

Client 类继承自 BinaryClient,用于将 String 类型的参数转换为二进制,然后调用 BinaryClient 中相关的方法:

2.5 BinaryClient 类(核心)

1
2
3
4
5
6
7
public class BinaryClient extends Connection {
    //...
    public void set(final byte[] key, final byte[] value) {
        sendCommand(SET, key, value);
    }
    //...
}

BinaryClient 类是各项命令最终执行类,通过调用 sendCommand 方法发送指令:

1
2
3
4
5
6
public void sendCommand(final ProtocolCommand cmd, final byte[]... args) {
    try {
        connect();
        Protocol.sendCommand(outputStream, cmd, args);
    } catch (JedisConnectionException ex) {...}
}

该方法首先调用 connect() 获取 I/O 流,然后调用 Protocol.sendCommand 完成命令发送,其本质就是按照 Redis 协议的要求组合命令并写入输出流:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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);
    }
}

至此,Jedis 的基本内容就介绍完了。


欢迎关注我的公众号,第一时间获取文章更新:

微信公众号

相关内容