Redis 客户端
客户端和服务器通过TCP 连接来进行数据交互, 服务器默认的端口号为6379 。
客户端和服务器发送的命令或数据一律以\r\n (CRLF 回车+换行)结尾。
Jedis
是我们最熟悉和最常用的客户端。轻量,简洁,便于集成和改造。
Jedis 多个线程使用一个连接的时候线程不安全。可以使用连接池,为每个请求创建
不同的连接,基于Apache common pool 实现。跟数据库一样,可以设置最大连接数
等参数。Jedis 中有多种连接池的子类。
- Jedis 有4 种工作模式:单节点、分片、哨兵、集群。
3 种请求模式:Client、Pipeline、事务。Client 模式就是客户端发送一个命令,阻
塞等待服务端执行,然后读取返回结果。Pipeline 模式是一次性发送多个命令,最后一
次取回所有的返回结果,这种模式通过减少网络的往返时间和io 读写次数,大幅度提高
通信性能。第三种是事务模式。Transaction 模式即开启Redis 的事务管理,事务模式开
启后,所有的命令(除了exec,discard,multi 和watch)到达服务端以后不会立即执
行,会进入一个等待队列。 - 分布式锁
jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
如果在释放锁的时候,这把锁已经不属于这个客户端(例如已经过期,并且被别的客户端获取锁成功了),那就会出现释放了其他客户端的锁的情况。所以我们把判断客户端是否相等和删除key 的操作放在Lua 脚本里面执行。
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
Lettuce
Lettuce 是一个可伸缩的线程安全的Redis 客户端,支持同步、异步和响应式模式(Reactive)。多个线程可以共享一个连接实例,而不必担心多线程并发问题。
Lettuce 是Spring Boot 2.x 默认的客户端,替换了Jedis。集成之后我们不需要单独使用它,直接调用Spring 的RedisTemplate 操作,连接和创建和关闭也不需要我们操心。
基于Netty 框架构建,支持Redis 的高级功能,如Pipeline、发布订阅,事务、Sentinel,集群,支持连接池。
Redisson
Redisson 本质是一个在Redis 的基础上实现的Java 内存数据网格(In-Memory Data Grid),提供了分布式和可扩展的Java 数据结构
基于Netty 实现,采用非阻塞IO,性能高
支持异步请求
支持连接池、pipeline、LUA Scripting、Redis Sentinel、Redis Cluster
不支持事务,官方建议以LUA Scripting 代替事务
主从、哨兵、集群都支持。Spring 也可以配置和注入RedissonClient。
- 分布式锁
在加锁的时候,在Redis 写入了一个HASH,key 是锁名称,field 是线程名称,value是1(表示锁的重入次数)。最终也是调用了一段Lua 脚本。里面有一个参数,两个参数的值;
Redisson 跟Jedis 定位不同,它不是一个单纯的Redis 客户端,而是基于Redis 实
现的分布式的服务,如果有需要用到一些分布式的数据结构,比如我们还可以基于
Redisson 的分布式队列实现分布式事务,就可以引入Redisson 的依赖实现。
数据一致性
Redis:删除还是更新?
这里我们先要补充一点,当存储的数据发生变化,Redis 的数据也要更新的时候,我们有两种方案,一种就是直接更新,调用set;还有一种是直接删除缓存,让应用在下次查询的时候重新写入。
如果得到数据比较复杂才能更新缓存,一般是建议直接删除
缓存预热的数据采用更新比较好
- 先更新数据库,再删除缓存
删除缓存失败的情况,数据不一致,采用异步更新缓存,监听binlog然后删除key,删除失败则重试 - 先删除缓存,再更新数据库
- 不一致场景
1)线程A 需要更新数据,首先删除了Redis 缓存
2)线程B 查询数据,发现缓存不存在,到数据库查询旧值,写入Redis,返回
3)线程A 更新了数据库 - 采用延时双删的策略,在写入数据之后,再删除一次缓存。
1)删除缓存
2)更新数据库
3)休眠500ms(这个时间,依据读取数据的耗时而定)
4)再次删除缓存
高并发问题
-
场景
在Redis 存储的所有数据中,有一部分是被频繁访问的。有两种情况可能会导致热
点问题的产生,一个是用户集中访问的数据,比如抢购的商品,明星结婚和明星出轨的
微博。还有一种就是在数据进行分片的情况下,负载不均衡,超过了单个服务器的承受
能力。热点问题可能引起缓存服务的不可用,最终造成压力堆积到数据库。出于存储和流量优化的角度,我们必须要找到这些热点数据。
1、客户端 代码入侵等局限性
2、代理层, 不一定使用了代理架构
3、服务端 Redis 有一个monitor 的命令,可以监控到所有Redis执行的命令。高并发场景影响性能,只能统计一个节点的热点key -
怎么去解决热点数据在高并发的场景下可能会出现的问题
-
缓存雪崩
缓存雪崩就是Redis 的大量热点数据同时过期(失效),因为设置了相同的过期时
间,刚好这个时候Redis 请求的并发量又很大,就会导致所有的请求落到数据库。 -
解决方案
1)加互斥锁或者使用队列,针对同一个key 只允许一个线程到数据库查询
2)缓存定时预先更新,避免同时失效
3)通过加随机数,使key 在不同的时间过期
4)缓存永不过期 -
缓存穿透
-
解决
(1)缓存空数据
(2)缓存足够多的值,利用布隆过滤器判断不存在
guaua有对布隆过滤器的实现
布隆过滤器把误判率默认设置为0.03,也可以在创建的时候指定。
redis 集群(采用虚拟槽方式,高可用)原 理(和哨兵模式原理类似,3.0 版本或以上 才有)
Redis集群采用P2P的Gossip(流言) 协议,Gossip协议工作原理就是节点彼此不断通信交换信息, 一段时间后所有的节点都会知道集群完整的信息;
常用的Gossip消息可分为: ping消息、 pong消息、 meet消息、 fail消息
- Redis 集群内节点通过 ping/pong 消息实现节点通信,消息不但可以传播节 点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是 通过消息传播机制实现的,主要环节包括:主观下线(pfail)和客观下线(fail)
- 主客观下线:
2.1 主观下线:集群中每个节点都会定期向其他节点发送 ping 消息,接收节点 回复 pong 消息作 为响应。如果通信一直失败,则发送节点会把接收节点标记为 主观下线(pfail)状态。
2.2 客观下线:超过半数 认为该节点pfail了,就对该主节点做客观下线 fail - 所有的主节点选举出某一从节点作为成为master node,来进行故障转移。
- 故障转移(选举从节点作为新主节点) ,主备切换