在项目中缓存是如何使用的?

高性能

比如查询通讯录功能,各种连表查询,递归查询,半天查出来一个结构,耗时600ms。但是这个结果可能接下来几个小时都不会变了,或者变了也可以不用立即反馈给用户。

我们将折腾 600ms 查出来的结果组织架构,扔缓存里,下次再有人查,先查缓存,2ms搞定。性能提高300倍。

就是说对于一些需要复杂操作耗时查出来的结果,且确定后面不怎么变化,但是有很多读请求,那么直接将查询出来的结果放在缓存中,后面直接读缓存就好。

高并发

缓存是走内存的,内存天然就支持高并发。

mysql 这么重的数据库,压根儿设计不是支持高并发的。虽然也可以用,但是天然支持不好。mysql单机支持到 2000 QPS 也开始容易报警了。

所以要是系统,高分期一秒钟过来的请求有 1万,那一个mysql单机绝对会死掉。这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 key-value 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发。单机承载并发量是 mysql 单机的几十倍。

Redis 和 Memcached 有什么区别?

redis 有更加丰富的数据类型,可以适用各种复杂情况的场景。memcached只有简单的key,value。
redis 可以支持原生的集群,memached没有原生的集群模式。

Redis 的线程模型是什么?

IO多路复用,NIO模型

  1. 服务端创建 ServerSocketChannel,设置为非阻塞,获取Selector,将serverSocketChannel绑定到Selector当中,设置只对channel的通过连接事件感兴趣。
  2. 客户端创建SocketChannel,设置为非阻塞,获取Selector,将socketChannel绑定到Selector当中,设置只对channel的连接事件感兴趣。
  3. 每个 channel 对应一个 buffer 缓冲区,Buffer 和 channel 都可以即可以读也可以写。
  4. 无论是ServerSocketChannel还是SocketChannel,对 selector 来说都是已注册的客户端。
  5. 客户端或者服务端通过 channel 传输数据。如果有需要读写的操作,Channel又会使用Buffer来缓存数据。
  6. selector 会轮询监听已注册的客户端 channel发现有事件发生了才转交给后端线程处理,后端线程不需要做任何阻塞等待,直接处理客户端事件的数据即可,处理完马上结束,或返回线程池供其他客户端事件继续使用。

bio 首先accpet()会阻塞,同时线程处理io流会阻塞其他的连接io流操作。
nio 非阻塞的体现。当有事件发生,selector就会分发线程会立刻去执行,其他事件不会阻塞等待。

IO多路复用类型有:
select 上限1024 ,数组,线性遍历。 O(n)
poll 无上限,数组,线性遍历。O(n)
epoll 无上限,哈希表,事件通知,当有io事件,注册的在系统的回调函数就会被调用。O(1)

为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?

  • 纯内存操作
  • io多路复用模型
  • 单线程,反而避免了多线程上下文切换问题

    Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?

    string

    简单的 KV缓存

    hash

    一般就是可以将结构化的数据,比如一个对线给缓存在redis里,每次读写缓存的时候,可以操作hash里的某个字段。

    list

    有序列表,他存储的元素可以重复的
  1. 分页功能,通过lrange命令,实现分页查询。
  2. 简单的消息队列

    set

    无序集合,自动去重

  3. 在分布式系统下,可以将数据放到 set 中做全局去重。

  4. 多个列表,放入set做交集,并集,差集的操作。

    sorted set

    可排序的set,可以去重,可以排序

  5. 排行榜,写进去的时候给一个分数,自动根据分数排序。

    1. 获取前几排行榜
    2. 获取某个人的排名 ```shell zadd board 85 zhangsan zadd board 72 lisi zadd board 96 wangwu zadd board 63 zhaoliu

获取排名前三的用户(默认是升序,所以需要 rev 改为降序)

zrevrange board 0 3

获取某用户的排名

zrank board zhaoliu

  1. <a name="i0zbE"></a>
  2. ## Redis 的过期策略都有哪些?手写一下 LRU 代码实现?
  3. <a name="sz8v6"></a>
  4. ### 定期删除+惰性删除
  5. 定期删除:指的是 redis 默认是每个 100 ms 就**随机抽取一些**设置了过期时间的 key,检查其是否过期,如果过期就删除。<br />惰性删除:在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。
  6. <a name="1lxgu"></a>
  7. ### 内存淘汰机制
  8. 如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了就会触发 内存淘汰机制。
  9. - 拒绝新写入的数据
  10. - 移出最近最少(LRU)使用的key
  11. - 随机删除某个key
  12. - 在设置了过期时间,移出最近最少(LRU)使用的key
  13. - 在设置了过期时间,随机移出某个key
  14. - 在设置了过期时间,优先移除更早(快要)过期时间的key。
  15. <a name="COASo"></a>
  16. ### 手写
  17. ```java
  18. class LRUCache<K, V> extends LinkedHashMap<K, V> {
  19. private final int CACHE_SIZE;
  20. /**
  21. * 传递进来最多能缓存多少数据
  22. *
  23. * @param cacheSize 缓存大小
  24. */
  25. public LRUCache(int cacheSize) {
  26. // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
  27. super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
  28. CACHE_SIZE = cacheSize;
  29. }
  30. @Override
  31. protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
  32. // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
  33. return size() > CACHE_SIZE;
  34. }
  35. }

如何保证 Redis 高并发、高可用?

  • redis 主从架构(读写分离)
  • redis 基于哨兵实现高可用

    Redis 的主从复制原理能介绍一下么?

    核心机制

  • redis 采用异步方式复制数据到 slave 节点

  • 一个 master node 也可以连接其他的 slave node
  • slave node 做复制的时候,不会 block master node 的正常工作
  • slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
  • slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。

注意,如果采用了主从架构,那么建议必须开启 master node 的持久化,不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。

全量复制

  • master 执行 bgsave ,在本地生成一份 rdb 快照文件。
  • master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
  • master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
  • 如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。

client-output-buffer-limit slave 256MB 64MB 60

  • slave node 复制期间,基于旧的数据版本对外提供服务。
  • slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,这时候会暂停服务。
  • 如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。

    增量复制

  • 如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。

  • master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。
  • master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。

    过期 key 处理

    slave 不会过期 key,只会等待 master 过期 key。如果 master 过期了一个 key,或者通过 LRU 淘汰了一个 key,那么会模拟一条 del 命令发送给 slave。

    主从复制的断点续传

    从 redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。

master node 会在内存中维护一个 backlog,master 和 slave 都会保存一个 replica offset 还有一个 master run id,offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了,slave 会让 master 从上次 replica offset 开始继续复制,如果没有找到对应的 offset,那么就会执行一次 resynchronization

heartbeat

主从节点互相都会发送 heartbeat 信息。
master 默认每隔 10秒 发送一次 heartbeat,slave node 每隔 1秒 发送一个 heartbeat。

如果根据 host+ip 定位 master node,是不靠谱的,如果 master node 重启或者数据出现了变化,那么 slave node 应该根据不同的 run id 区分。

Redis 的哨兵原理能介绍一下么?

哨兵的作用

  • 集群监控:负责监控 redis master 和 slave进程是否正常工作。
  • 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
  • 故障转移:如果master node 挂掉了,会自动将 master 转移到 slave node上。
  • 配置中心:如果故障转移发生了,通知 client 客户端新的master地址。

哨兵用实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

哨兵核心知识

  • 哨兵至少需要3个实例,来保证自己的健壮性。
  • 哨兵+ redis 主从的部署架构,是不保证数据零丢失的,只能保证 reids 集群的高可用性。
  • 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。

    哨兵主备切换的数据丢失问题

  • 异步复制导致的数据丢失:因为 master -> slave 的复制时异步的,所以可能有部分数据还么有复制到slave,master宕机了,此时这部分数据就丢失了。

  • 脑裂导致的数据丢失:某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还是运行着。此时哨兵可能就会认为master宕机了,然后选举其他的slave切换成,master。集群会出现两个master,就是所谓的脑裂。

解决方案:

进行如下配置:
min-slaves-to-write 1
min-slaves-max-lag 10

表示,要求至少有 1 个 slave,数据复制和同步的延迟不能超过 10 秒。
如果说一旦所有的 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了。最多就丢失 10 秒的数据。

哨兵集群的自动发现机制

  • 哨兵之间发生,是通过 redis 的 生产消费系统实现的。每个哨兵向 哨兵channel发送一个消息,其他哨兵消费这个消息,会感知其他的哨兵的存在
  • 每个两秒,每个哨兵会往自己监控的 master +slaves 对应的 channel 里发送一个消息,内容是自己的host ip 和 runid 以及这个master 的监控配置。
  • 每个哨兵也会监听 master + slaves 对应的 channel,感知其他哨兵的存在。
  • 每个哨兵还会跟其他哨兵交换对应的master 的监控配置,相互进行控制配置的同步。

    sdown 和 odown 转换机制

  • sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机

  • odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机

sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 is-master-down-after-milliseconds 指定的毫秒数之后,就主观认为 master 宕机了;如果一个哨兵在指定时间内,收到了 quorum 数量的其它哨兵也认为那个 master 是 sdown 的,那么就认为是 odown 了。

slave->master 选举算法

如果一个 master 被认为 odown 了,而且 majority 数量的哨兵都允许主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个 slave 来,会考虑 slave 的一些信息:

  • 跟 master 断开连接的时长 (超过配置的时长,直接进行第二维度判断)
  • slave 优先级
  • 复制 offset
  • run id

    Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?

    RDB

    他是快照,原理是写时复制,redis会单独创建(fork)一个与当前进程一模一样的子进程来进行持久化,所有数据(变量。环境变量,程序程序计数器等)都和原进程一模一样,会先将数据写入到一个临时文件中,待持久化结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程不进行任何的io操作,这就确保了极高的性能

  • 手动触发:

    • save命令:他不会fork子进程,但是会阻塞主进程。
    • bgsave命令:他会在后台fork一个进程,异步进行快照操作
  • 自动触发:

    • redis.conf 配置 save m n 定时触发(用的bgsvae)
      • 10分钟 1次
      • 5分钟 10次
      • 1分钟 1万次
    • 主从复制时候,主节点自动触发
    • shutdown命令时,如果没有开启aof,会触发。
    • flushall命令,也会产生dump.rdb文件,但里面是空的,无意义

      AOF

      原理就是将Redis的操作日志追加的方式写入文件,读操作时不记录的
      触发机制(根据配置文件配置项)
  • no:表示等操作系统进行数据缓存同步到磁盘(快,持久化没保证)

  • always:同步持久化,每次发生数据变更时,立即记录到磁盘(慢,安全)
  • everysec:表示每秒同步一次(默认值,很快,但可能会丢失一秒以内的数据)

aof重写机制

重写条件:

  1. 超过原大小的百分比 100% auto-aof-rewrite-percentage 100
  2. 当aop文件大小超过配置项 auto-aof-rewrite-min-size 64mb

重写原理: 瘦身,直接存的是rdb格式数据到aof中。

  1. 主线程fork一个子进程
  2. 子进程把新的aof写到一个临时文件里
  3. 主进程持续将新的变动同时写到aof_rewrite_buf和旧的aof文件
  4. 等aof文件重写完成,替换掉旧的aof文件

    优缺点

  5. rdb(快照)

    1. 优点:存储的文件小,恢复速度快吗
    2. 缺点:数据不完整,需要fork子进程。
  6. aof(镜像)
    1. 优点:可读性高,可以确保数据完整性
    2. 缺点:文件比较大,文件的恢复比较慢

      RDB 和 AOF 到底该如何选择

  • 不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
  • 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
  • redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。

    Redis 集群模式的工作原理能说一下么?

    redis cluster 介绍

  • 自动将数据进行分片,每个 master 上放一部分数据

  • 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的

在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。

16379 端口号是用来进行节点间通信的,也就是一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间,用来进行故障检测、配置更新、故障转移授权。

redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。
任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。

redis cluster 的高可用的原理,几乎跟哨兵是类似的。redis cluster 的高可用的原理,几乎跟哨兵是类似的。
客观判断节点宕机
主备切换,从节点选举

在集群模式下,Redis 的 key 是如何寻址的?

redis cluster 有固定的 16384 个 hash slot,对每个 key 计算 CRC16 值,然后对 16384 取模,可以获取 key 对应的 hash slot。

分布式寻址都有哪些算法?

hash算法
一致性 hash 算法
hash slot 算法

了解一致性 hash 算法吗?

一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。
来了一个 key,首先计算 hash 值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,遇到的第一个 master 节点就是 key 所在位置。
在一致性哈希算法中,如果一个节点挂了,受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响。增加一个节点也同理。
燃鹅,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。

如何动态增加和删除一个节点?

hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。

了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?

缓存雪崩

现象:假设redis 每秒只能支持4000请求,高峰期5000个请求,但是缓存机器意外发生了全盘宕机。此时5000请求全部落到数据库,数据库必然扛不住。数据库直接挂了。

解决方案:

  • 事前:redis 高可用,主从+哨兵,redis cluster避免全盘崩溃。
  • 事中:本地 ehcache 缓存 + hystix 限流&降级,避免 MySQL 被打死。
  • 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

限流组件:可以设置每秒的请,有多少能通过组件,剩余的未通过的请求,走降级!可以返回一些默认的值,或者友情提示,或者空白的值。
好处:

  • 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
  • 只要数据库不死,就是说,对用户来说,一部分的请求是可以被处理的。
  • 一部分请求可以被处理,意味着系统没有事,对用户来说,可能点击几次刷不出来,但是多点几次,就可以刷出来了。

    缓存穿透

    现象:访问了数据库中不存的数据的。请求每次缓存查不到,去数据库也差不多。请求每次都“视缓存于无物”。

解决方案:请求只要数据库中没有查,就写一个空值到缓存离去。然后设置一个过期时间,这样的话,下次相同的key过来,在缓存失效之前,都可以直接从缓存中取数据。

缓存击穿

现象:某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障被击穿了一个洞。

解决方案:可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建万缓存之后,再释放锁,进而其他情趣才能通过该 key 访问数据。

如何保证缓存与数据库的双写一致性?

现象:先删除了缓存,还没有来得及更新数据库的时候。查询请求过来了,又将老数据写到了缓存,导致缓存和数据库不一致。

解决方案:

  • 延迟双删:先删除缓存,然后更新完数据库的时候,再将缓存删除。

  • 串行化:比如修改订单状态,根据id算hash,将更新事件到该hash对应的内存队列中。之后来的查询订单请求,都会按顺序入队。将修改和查询串行化,这样就可以读到更新之后的数据了。

这种场景,如果在高并发的场景下比较影响吞吐量。首先需要将查询请求放到更新请求后面,并且可以过滤只需一个查询请求,其他请求等待该请求即可。其次本来直接查询数据库更新到缓存,如果队列更新请求比较多,那么查询请求等待其他的更新操作完,才可以查询。解决方案就是,扩容机器,去异步处理队列。

Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?

现象:就是多个客户端同时并发写一个key,可能本来应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个key,修改值之后再写回去,只要顺序错了,数据就错了。

解决方案:
写入缓存的数据,都是从mysql里查出来的,都得写入mysql中,写入mysql中的时候必须保存一个时间戳,从mysql 查出来的时候,时间戳也查出来。
每次写之前,先判断一下当前个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。

生产环境中的 Redis 是怎么部署的?

  • redis cluster,10台机器,5台机器redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。
  • 机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。
  • 5 台机器对外提供读写,一共有 50g 内存。
  • 因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。
  • 你往内存里写的是什么数据?每条数据的大小是多少?销售订单,生产订单,生产工单,生产领料单等工作流数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。
  • 我们公司,有基础架构的 team 负责缓存集群的运维。

    你们是用哪个开源框架实现的Redis分布式锁?能说说其核心原理么?

Redis面试题 - 图1
Redisson框架
redisson.lock(“product_1_stock”)

  • key的业务语义,就是针对product_id = 1的商品的库存,也就就是苹果的库存,进行加锁
  • watchdog,redisson框架后台执行一段逻辑,每隔10s去检查一下这个锁是否还被当前客户端持有,如果是的话,重新刷新一下key的生存时间为30s
  • 其他客户端尝试加锁,这个时候发现“product_1_stock”这个key已经存在了,里面显示被别的客户端加锁了,此时他就会陷入一个无限循环,阻塞住自己,不能干任何事情,必须在这里等待
  • 第一个客户端加锁成功了,此时有两种情况,第一种情况,这个客户端操作完毕之后,主动释放锁;第二种情况,如果这个客户端宕机了,那么这个客户端的redisson框架之前启动的后台watchdog线程,就没了
  • 此时最多30s,key-value就消失了,自动释放了宕机客户端之前持有的锁

    如果Redis是集群部署的,那么集群故障时分布式锁还有效么?

    瞬时故障问题,比如一个客户端在 master加锁,还没有来得及同步到从机,此时从机会被转为主机,第二个客户端也会加锁。那么这个分布式锁就无效了。
    彻底解决这个问题,很难,除非你修改一些redis和redisson框架的源码,源码级的二次开发,加锁,必须是master和slave同时写成功,才算是加锁成功

    jedis和lettuce区别

    Jedis

    Jedis在实现上是直接连接的Redis Server,如果在多线程环境下是非线程安全的。
    每个线程都去拿自己的 Jedis 实例,当连接数量增多时,资源消耗阶梯式增大,连接成本就较高了。

    Lettuce

    Lettuce的连接是基于Netty的,Netty 是一个多线程、事件驱动的 I/O 框架。连接实例可以在多个线程间共享,当多线程使用同一连接实例时,是线程安全的。