Redis 是什么?
Redis 是 C 语言开发的一个开源的(遵从 BSD 协议)高性能非关系型(NoSQL) 的(key-value)键值对数据库。可以用作数据库、缓存、消息中间件等。
Redis 的存储结构有哪些?
- String,字符串,是 redis 的最基本的类型,一个 key 对应一个 value。是二进制安全的,最大能存储 512MB。
- Hash,散列,是一个键值(key=>value)对集合。string 类型的 field 和 value 的映射表,特别适合用于存储对象。每个 hash 可以存储 232 -1 键值对(40 多亿)
- List,列表,是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列 边或者尾部(右边)。最多可存储 232 - 1 元素(4294967295, 每个列表可存储 40 亿)
- Set,集合, 是 string 类型的无序集合,最大的成员数为 232 -1(4294967295, 每个集合可存储 40 多亿个成员)。
- Sorted set,有序集合,和 set 一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复。
Redis 的优点
- 因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value 数据库。Redis 支持事务 、持久化
- 单线程操作,避免了频繁的上下文切换。
- 采用了非阻塞 I/O 多路复用机制。I/O 多路复用就是只有单个线程,通过跟踪每个 I/O 流的状态,来管理多个 I/O 流。
为什么要用 Redis?
-
高性能
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
-
高并发
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
redis 的持久化
Redis 提供了两种持久化的方式,分别是 **RDB(Redis DataBase)**和 AOF(Append Only File)。
- RDB,简而言之,就是在不同的时间点,将 redis 存储的数据生成快照并存储到磁盘等介质上。
- AOF,则是换了一个角度来实现持久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。
注意: RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。
Redis 的缺点
- 缓存和数据库双写一致性问题
一致性的问题很常见,因为加入了缓存之后,请求是先从 redis 中查询,如果 redis 中存在数据就不会走数据库了,如果不能保证缓存跟数据库的一致性就会导致请求获取到的数据不是最新的数据。
解决方案:
- 编写删除缓存的接口,在更新数据库的同时,调用删除缓存的接口删除缓存中的数据。这么做会有耦合高以及调用接口失败的情况。
- 消息队列:ActiveMQ,消息通知。
- 缓存的并发竞争问题
并发竞争,指的是同时有多个子系统去 set 同一个 key 值。
解决方案:
- 最简单的方式就是准备一个分布式锁,大家去抢锁,抢到 锁就做 set 操作即可
- 缓存雪崩问题
缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。
解决方案:
- 给缓存的失效时间,加上一个随机值,避免集体失效。
- 使用互斥锁,但是该方案吞吐量明显下降了。
- 搭建 redis 集群。
- 缓存击穿问题
缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
解决方案:
- 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了, 再去请求数据库。没得到锁,则休眠一段时间重试
- 采用异步更新策略,无论 key 是否取到值,都直接返回,value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。
Redis 集 群
- 主从复制
原理:
- 从服务器连接主服务器,发送 SYNC 命令。主服务器接收到 SYNC 命名后,开始执行BGSAVE 命令生成 RDB 文件并使用缓冲区记录此后执行的所有写命令。
- 主服务器BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令。
- 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照。主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令。
- 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令(从服务器初始化完成)。
- 主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令(从服务器初始化完成后的操作)。
优点:
支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。为了分载 Master 的读操作压力,Slave 服务器可以为客户端提供只读操作的服务,写服务仍然必须由 Master 来完成,Slave 同样可以接受其它 Slaves 的连接和同步请求,这样可以有效的分载 Master 的同步压力 Master Server 是以非阻塞的方式为 Slaves 提供服务。所以在 Master-Slave 同步期间,客户端仍然可以提交查询或修改请求。Slave Server 同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis 则返回同步之前的数据。
缺点:
Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败, 需要等待机器重启或者手动切换前端的 IP 才能恢复。主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP 后还会引入数据不一致的问题,降低了系统的可用性。Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
- 哨兵模式:
当主服务器中断服务后,可以将一个从服务器升级为主服务器,以便继续提供服务,但是这个过程需要人工手动来操作。为此,Redis2.8 中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。哨兵的作用就是监控 Redis 系统的运行状况,它的功能包括以下两个。
- 监控主服务器和从服务器是否正常运行。
- 主服务器出现故障时自动将从服务器转换为主服务器。
工作方式:
- 每个 Sentinel (哨兵)进程以每秒钟一次的频率向整个集群中的 Master 主服务器,Slave 从务器以及其他 Sentinel(哨兵)进程发送一个 PING 命令。
- 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)。
- 如果一个 Master 主服务器被标记为主观下线(SDOWN),则正在监视这个 Master 主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认 Master 主服务器的确进入了主观下线状态。
- 当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值) 在指定的时间范围内确认 Master 主服务器进入了主观下线状态(SDOWN),则 Master 主服务器会被标记为客观下线(ODOWN)。
- 在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一 169 / 196 次的频率向集群中的所有 Master 主服务器,Slave 从服务器发送 INFO 命令。
- 当 Master 主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master 主服务器的所有 Slave 从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
- 若没有足够数量的 Sentinel(哨兵)进程同意 Master 主服务器下线, Master 主服务器的客观下线状态就会被移除。
- 若 Master 主服务器重新向 Sentinel (哨兵)进程发送 PING 命令返回有效回复,Master 主服务器的主观下线状态就会被移除。
优点:
哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。主从可以自动切换,系 统更健壮,可用性更高。
缺点:
Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
- Redis-Cluster 集 群
redis 的哨兵模式基本已经可以实现高可用,读写分离,但是在这种模式下每台 redis 服务器都存储相同的数据,很浪费内存,所以在 redis3.0 上加入了 cluster 模式,实现的redis 的分布式存储,也就是说每台 redis 节点上存储不同的内容。Redis-Cluster 采用无中心结构,它的特点如下:
- 所有的 redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输速度和带宽。节点的 fail 是通过集群中超过半数的节点检测失效时才生效。客户端与 redis 节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
工作方式:
- 在 redis 的每一个节点上,都有这么两个东西,一个是插槽(slot),它的取值范围是: 0-16383。还有一个就是 cluster,可以理解为是一个集群管理的插件。
- 当我们的存取的 key到达的时候,redis 会根据 crc16 的算法得出一个结果,然后把结果对 16384 求余数, 这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽
- 通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。
- 为了保证高可用, redis-cluster 集群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与 A 通信超时,那么认为主节点 A 宕机了。如果主节点 A 和它的从节点 A1 都宕机了, 那么该集群就无法再提供服务了。
- Redis 的分布式锁
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
- 安全特性:互斥访问,即永远只有一个 client 能拿到锁
- 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的client crash 了或者出现了网络分区
- 容错性:只要大部分 Redis 节点存活就可以正常提供服务
Redis 实现分布式锁:
- Redis 为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis 的连接并不存在竞争关系 Redis 中可以使用 SETNX 命令实现分布式锁。
- 当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。返回值:设置成功,返回 1 。设置失败,返回 0 。
使用 SETNX 完成同步锁的流程及事项如下:
- 使用 SETNX 命令获取锁,若返回 0(key 已存在,锁已存在)则获取失败,反之获取成功
- 为了防止获取锁后程序出现异常,导致其他线程/进程调用 SETNX 命令总是返回 0 而进入死锁状态,需要为该 key 设置一个“合理”的过期时间释放锁,使用 DEL 命令将锁数据删除。
欢迎java热爱者了解文章,作者将会持续更新中,期待各位友友的关注和收藏。。。