Jedis详解

因为工作的需要,底层同事对Redis进行了部分改造,增加了几个命令,对应着也就需要对Jedis进行部分修改,于是就把Jedis相关的代码读了一遍,发现其设计还是非常简单但又巧妙使用。

通常而言,我们对于Redis集群的操作通常来讲不会真正对应多个节点,而是由底层单独分片处理,换句话说我们应用程序对应的节点是一个,因此我们目前主要用的是JedisPool的方式,而很少会采用JedisCluster的方式。

下面的解释也主要是对上述的方式进行说明。

Jedis操作主要涉及到的类:JedisPool、Jedis、Client、BinaryClient、Connection等,下面是对其处理过程的简要记录。

1、Connection类是使用原生的Socket进行连接,下面是连接的代码:

从代码中可以很清晰的看到,使用的是原生Socket进行处理(换句话说,这种没有使用netty的方式不见得多稳定),另外我们也可以看到每个Connection对应了一个输入流和输出流,结合Redis是单线程的处理机制,我们会发现Jedis的处理很巧妙。

2、下面是JedisPool的定义:

我们发现它其实用的是Pool,Pool是啥?Pool其实是apache提供的一种池子的管理工具,像数据库连接池也有很多用这个的。

我们在使用JedisPool的时候通常是用getResource()方法获取到一个可操作的连接:

其实我们获取到的是一个Jedis对象,那么肯定有一个疑问,那就是Pool里面到底存储的是什么,它是怎么存储的?首先可以确认,肯定是存储的Jedis,从Pool<Jedis>这种泛型的使用方式也是可以看出的。那么在Pool对象里面是怎么存储Jedis对象的呢?

通过代码查看我们可以知道:其实最终存储在Pool里面的是:

SoftReference<T>这个类熟悉的童鞋可能会了解,它是JDK自带的一个类,说的比较玄乎,是一个软连接的类,其实就是说这个对象引用不是强引用,比较容易被GC掉。

SoftReference<T>这个类继承自Reference<T>,我们跟踪一下会发现最终存储的是一个Queue:

我们再回来深入看一下getResource的代码,也就是看一下super.getResource(),可以看出最终其实是由apache的pool实现的:

同时,我们发现其实还有一个和它并列的方法:

这样我们就可以猜测,其实是一个队列的获取操作,那么里面的参数(borrowMaxWaitMillis)也就是借用时间了,换句话说就是多长时间可以从队列中获取到对象。下面是这部分的主要实现代码:

代码太多,就不全部截取了,其中p只是一个临时变量,实际的队列其实是idleObjects,它是一个链表实现的双向阻塞队列:

3、获取到Jedis对象之后,剩下的其实就是Jedis的操作,以set方法为例:

我们会发现它主要是两个操作:一个是set,一个是getStatusCodeReply。这就是我们所说的Jedis设计巧妙之处,根据我们一般的理解,都是发送一个命令,然后等待应答,换句话说就是阻塞的处理方式。我们从Jedis的处理中发现,其实不是,它的发送和它的接收是分开处理的,当然这种分开处理指的是代码,其实本质上并没有分开。

首先看一下set命令的处理:

Jedis中set命令其实是Client执行的过程,而Client的处理其实是调用的BinaryClient的处理,BinaryClient都是使用byte[]与Redis进行交互,因此更具有普遍性。下面是BinaryClient的处理过程:

它调用的是sendCommand方法,该方法是由最开始我们建立的那个Connection实现的,如下:

从代码中我们可以看出,其实最终调用的是Protocol.sendCommand命令,如下:

RedisOutputStream其实也是一种流,它继承自FilterOutputStream,主要是改动就是为了适应Redis的协议,增加了一些变量的处理:

同时,为了保证安全期间,它被定义为一个final类,不允许被继承。

接着上述的Protocol.sendCommand命令,这个命令的执行其实没有flush,也就是说可能会出现写入不完整的问题,作者肯定不会犯这种低级的错误,所以在后面的那个getStatusCodeReply方法中,它是这样实现的:

它首先flush了一下,以保证其全部写入,然后是进行read,readProtocolWithCheckingBroken()方法如下:

最终还是调用的Protocol,也就是对输入流的一种读取操作。

最终从InputStream中读出的数据就是应答的结果。Jedis在实现的时候区分了多种应答情况,有的应答是状态(OK)、(NIL)、(ERROR)等,有的应答是数字,也有的应答是结果(Value),对应着不同的方法。

 

上面就是Jedis的基本处理过程,其实很简单,相信大家看了之后也可以自己写一个了,下面附上几张Jedis中的UML图,其中带ExtensionXXX的是我因为需求加的,源码中肯定没有。

Jedis:

Client:

BinaryClient

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RedisTemplate是Spring Data Redis提供的一个用于操作Redis的模板类,它封装了Redis的连接、数据序列化、异常处理等操作,使得我们可以更加方便地使用Redis。下面是RedisTemplate的使用详解: 1. 配置RedisTemplate 在Spring Boot中,我们可以通过配置文件来配置RedisTemplate,如下所示: ``` spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password= spring.redis.database=0 ``` 2. 注入RedisTemplate 在需要使用Redis的地方,我们可以通过注入RedisTemplate来获取Redis连接,如下所示: ``` @Autowired private RedisTemplate<String, Object> redisTemplate; ``` 3. RedisTemplate常用操作 RedisTemplate提供了多种操作Redis的方法,下面是一些常用的操作: - 字符串操作 ``` redisTemplate.opsForValue().set("key", "value"); redisTemplate.opsForValue().get("key"); ``` - 列表操作 ``` redisTemplate.opsForList().leftPush("list", "value"); redisTemplate.opsForList().rightPop("list"); ``` - 集合操作 ``` redisTemplate.opsForSet().add("set", "value"); redisTemplate.opsForSet().members("set"); ``` - 哈希操作 ``` redisTemplate.opsForHash().put("hash", "key", "value"); redisTemplate.opsForHash().get("hash", "key"); ``` - 事务操作 ``` redisTemplate.execute(new SessionCallback<Object>() { @Override public Object execute(RedisOperations operations) throws DataAccessException { operations.multi(); operations.opsForValue().set("key1", "value1"); operations.opsForValue().set("key2", "value2"); operations.exec(); return null; } }); ``` 4. Lua脚本操作 Redis支持使用Lua脚本进行操作,可以提高Redis的性能和安全性。下面是一个使用Lua脚本进行加锁的例子: ``` String lockKey = "lock"; String requestId = UUID.randomUUID().toString(); String script = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end"; Boolean result = redisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { Object nativeConnection = connection.getNativeConnection(); if (nativeConnection instanceof Jedis) { return (Boolean) ((Jedis) nativeConnection).eval(script, Collections.singletonList(lockKey), Arrays.asList(requestId, "60")); } return false; } }); ``` 这个例子中,我们使用Lua脚本实现了一个分布式锁,可以避免多个线程同时访问同一个资源的问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值