专栏目录
Redis
1. NoSql
1.1 对比SQL
SQL是关系型数据库 | NoSQL是非关系型数据库 | |
---|---|---|
数据结构 | 结构化 | 非结构化 |
数据关联 | 数据之间关联的 | 数据之间非关联的 |
查询方式 | SQL查询-语法固定 | 非SQL查询 |
事务特性 | 满足ACID特性 | BASE-无法全部满足ACID |
存储方式 | 磁盘 | 内存 |
扩展性 | 垂直 | 水平 |
使用场景 | 1)数据结构固定 2)相关业务对数据安全性、一致性要求高 | 1)数据结构不固定 2)对一致性、安全性要求不高 3)对性能要有高 |
1.2 优势
性能 | ⽀持⾼并发数据访问,性能较⾼ |
---|---|
性能 | 数据存储结构松散,能够灵活⽀持多种类型的数据格式 |
性能 | 数据库能够支持海量数据的存储,且易于横向扩展 |
可⽤性 | 数据库基于分布式数据存储,不存在单点故障和性能瓶颈,可⽤性⾼ |
1.3 劣势
其他 | 并未形成⼀定的标准,产品种类繁多,缺乏官⽅⽀持 |
---|---|
其他 | 数据库不提供对 SQL 的⽀持,学习和应⽤迁移成本较⾼ |
其他 | 数据库⽀持的特性不够丰富,现有产品提供的功能⽐较有限 |
2. Redis
Redis(Remote Dictionary Server远程词典服务器)是一个基于内存的键值型NoSql数据库
2.1 特征
- 键值型,value支持多种不同数据结构,功能丰富
- 单线程,每个命令具备原子性
- 低延迟、速度快(基于内存、IO多路复用、良好编码)
- 支持数据持久化
- 支持主从集群、分片集群
- 支持多语言客户端
2.2 对比Memcached
Memcached是⼀套分布式的⾼速缓存系统,与Redis相似。⼀般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态web应用速度、提高可扩展性。
为了提高性能,Memcached中保存的数据都存储在Memcached内置的内存存储空间中。
由于数据仅存在于内存中,因此重启 Memcached、重启操作系统会导致全部数据消失。
另外,内容容量达到指定值之后,就基于LRU算法自动删除不使用的缓存。
Memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。
Memcached | Redis | |
---|---|---|
数据类型 | key/value | key/value,list,map,set,hash,sorted |
持久性 | 不支持 | 支持 |
分布式存储 | 支持 | 多方式,主从、Sentinel、Cluster等 |
多线程 | 支持 | 6.0后支持请求多线程 |
内存 | 有 | 有 |
事务支持 | 不支持 | 有限支持 |
2.3 Redis基本数据结构
redis是一个键值数据库,key一般为String
类型,但value可以有多种数据类型
类型 | 说明 | 示例 |
---|---|---|
String | 字符串 | helloword |
Hash | 哈希 | {name:“Obama”,age:“22”} |
List | 列表 | [A -> B -> C] |
Set | 集合 | {A , B , C} |
SortedSet(Zset) | 有序集合 | {A:1 , B:2 , C:3} |
这五种为基本类型,除此之外还有多种数据类型,比如GEO、BitMap、HyperLog等。
2.3.1 String(字符串)
string是redis最基本的类型,一个key对应一个value。string是二进制安全的。
意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。
使用场景:
- 缓存功能:String字符串是最常用的数据类型,不仅仅是Redis,各个语言都是最基本类型,因此,利用Redis作为缓存,配合其它数据库作为存储层,利用Redis支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。
- 计数器:许多系统都会使用Redis作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。
- 共享用户Session:用户重新刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存Cookie,但是可以利用Redis将用户的Session集中管理,在这种模式只需要保证Redis的高可用,每次用户Session的更新和获取都可以快速完成。大大提高效率。
- 分布式锁
- 分布式系统全局序列号
2.3.2 Hash(哈希)
RedisHash是一个string类型的field和value的映射表,Hash特别适合用于存储对象。Redis中每个hash可以存储 2 32 − 1 2^{32}-1 232−1个键值对。
使用场景:
●适用于存储对象,比如把用户的信息存到hash里,以用户id为key,用户的详细信息为value。
●电商购物车,以用户ID为key,商品ID为field,商品数量为value。
2.3.3 List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。
一个列表最多可以包含 2 32 − 1 2^{32}-1 232−1个元素。
使用场景:
- Stack栈:LPUSH+LPOP
- Queue队列:LPUSH+RPOP
- BlockingMQ阻塞队列:LPUSH+BRPOP
- 最新列表,List类型的lpush命令和lrange命令能实现最新列表的功能,每次通过lpush命令往列表里插入新的元素,然后通过lrange命令读取最新的元素列表,如朋友圈的点赞列表、评论列表:
- A关注了B,C等大V
- B发微博了,消息ID为1001:LPUSHmsg:{A的ID}1001
- C发微博了,消息ID为1002:LPUSHmsg:{A的ID}1002
- A查看最新的5条微博消息:LRANGEmsg:{A的ID}05
- 按照时间排序的这些朋友圈信息等
2.3.4 Set(集合)
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis中集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是O(1)。
集合中最大的成员数为 2 32 − 1 2^{32}-1 232−1。
使用场景:
- 给用户添加标签,跟我们上面的例子一样。一个人对应多个不同的标签。
- 好友/关注/粉丝/感兴趣的人集合,可以使用上面的取交集、并集相关的命令。
- 随机展示,通过srandmember随机返回对应的内容,像一些首页获取动态内容可以这么玩。
- 黑名单/白名单,有业务出于安全性方面的考虑,需要设置用户黑名单、ip黑名单、设备黑名单等,set类型适合存储这些黑名单数据,sismember命令可用于判断用户、ip、设备是否处于黑名单之中。
示例:微信抽奖小程序
- 点击参与抽奖加入集合:SADDitem:1001{userID}
- 查看参与抽奖的所有用户:SMEMBERSitem:1001
- 抽取3名中奖者:SRANDMEMBER/SPOPitem:10013
SRANDMEMBER后,中奖的用户不会从集合中移除
SPOP后,中奖的用户会从集合中移除。
2.3.5 Zset(有序集合)
Redis有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但**分数(score)**却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 2 32 − 1 2^{32}-1 232−1。
使用场景:
- 标签:比如我们博客网站常常使用到的兴趣标签,把一个个有着相同爱好,关注类似内容的用户利用一个标签把他们进行归并。
- 共同好友功能,共同喜好,或者可以引申到二度好友之类的扩展应用。
- 统计网站的独立IP。利用set集合当中元素唯一性,可以快速实时统计访问网站的独立IP。
- 统计用户的点赞/取消点赞
- 排行榜功能,比如展示获取赞数最多的十个用户(这个是最大的作用)
ZSet本质就是Set结构上加了个排序的功能,除了添加数据value之外,还提供另一属性score,这一属性在添加修改元素时候可以指定,每次指定后,Zset会自动重新按新的值调整顺序。可以理解为有两列字段的数据表,一列存value,一列存顺序编号。
3. Redis数据持久化方案及对比
Redis数据是存储在内存中的,为了保证Redis数据不丢失,那就要把数据从内存存储到磁盘上,以便在服务器重启后还能够从磁盘中恢复原有数据,这就是Redis的数据持久化。Redis数据持久化有三种方式。
- AOF日志(AppendOnlyFile,文件追加方式):记录所有的操作命令,并以文本的形式追加到文件中。
- RDB快照(RedisDataBase):将某一个时刻的内存数据,以二进制的方式写入磁盘。
- 混合持久化方式:Redis4.0新增了混合持久化的方式,集成了RDB和AOF的优点。
3.1 AOF
AOF采用的是写后日志的方式,Redis先执行命令把数据写入内存,然后再记录日志到文件中。AOF日志记录的是操作命令,不是实际的数据,如果采用AOF方法做故障恢复时需要将全量日志都执行一遍。
3.2 RDB
RDB采用的是内存快照的方式,它记录的是某—时刻的数据,而不是操作,所以采用RDB方法做故障恢复时只需要直接把RDB文件读入内存即可,实现快速恢复。
要注意,Redis对RDB的执行频率非常重要,因为这会影响快照数据的完整性以及Redis的稳定性,所以在Redis4.0后,增加了AOF和RDB混合的数据持久化机制:把数据以RDB的方式写入文件,再将后续的操作命令以AOF的格式存入文件,既保证了Redis重启速度,又降低数据丢失风险。
4. Redis高可用方案及对比
Redis如何实现高可用。Redis实现高可用主要有三种方式:主从复制、哨兵模式,以及Redis集群。
4.1 主从复制
将从前的一台Redis服务器,同步数据到多台从Redis服务器上,即一主多从的模式,这个跟MySQL主从复制的原理一样。
4.2 哨兵模式
使用Redis主从服务的时候,会有一个问题,就是当Redis的主从服务器出现故障宕机时,需要手动进行恢复,为了解决这个问题,Redis增加了哨兵模式(因为哨兵模式做到了可以监控主从服务器,并且提供自动容灾恢复的功能)。
4.3 集群模式
RedisCluster是一种分布式去中心化的运行模式,是在Redis3.0版本中推出的Redis集群方案,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,从而提高Redis服务的读写性能。
4.4 哨兵模式vs集群模式
使用哨兵模式在数据上有副本数据做保证,在可用性上又有哨兵监控,一旦master宕机会选举salve节点为master节点,这种已经满足了我们的生产环境需要,那为什么还需要使用集群模式呢?
哨兵模式归根节点还是主从模式,在主从模式下我们可以通过增加salve节点来扩展读并发能力,但是没办法扩展写能力和存储能力,存储能力只能是master节点能够承载的上限。所以为了扩展写能力和存储能力,我们就需要引入集群模式。
4.5 节点选择算法
RedisCluster采用的是类一致性哈希算法实现节点选择的,至于什么是一致性哈希算法后面会提到。
RedisCluster将自己分成了16384个Slot(槽位),哈希槽类似于数据分区,每个键值对都会根据它的key,被映射到一个哈希槽中,具体执行过程分为两大步。
- 根据键值对的key,按照CRC16算法计算一个16bit的值。
- 再用16bit值对16384取模,得到0~16383范围内的模数,每个模数代表一个相应编号的哈希槽。
每个Redis节点负责处理一部分槽位,假如你有三个master节点ABC,每个节点负责的槽位如下:
节点 | 处理槽位 |
---|---|
A | 0-5000 |
B | 5001-10000 |
C | 10001-16383 |
这样就实现了cluster节点的选择。
5. Redis生产中会遇到的问题
5.1 缓存穿透原理以及解决方案
通缓存穿透说简单点就是大量请求的key是不合理的,根本不存在于缓存中,也不存在于数据库中。这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机。
举个例子某个黑客故意制造一些非法的key发起大量请求,导致大量请求落到数据库,结果数据库上也没有查到对应的数据。也就是说这些请求最终都落到了数据库上,对数据库造成了巨大的压力。
解决方案
-
最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库id不能小于0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
-
缓存无效key。如果缓存和数据库都查不到某个`key的数据就写一个到Redis中去并设置过期时间。
这种方式可以解决请求的key变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求key,会导致Redis中缓存大量无效的key。
很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的key的过期时间设置短一点比如1分钟。
-
布隆过滤器。布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们需要的就是判断key是否合法,有没有感觉布隆过滤器就是我们想要找的那个“⼈”。
把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
加入布隆过滤器之后的缓存处理流程图如下:
但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是:布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。这个可以看后面的布隆过滤器的解读。
5.2 缓存击穿原理以及解决方案
缓存击穿中,请求的key对应的是热点数据,该数据存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期)。这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
秒杀进行过程中,缓存中的某个秒杀商品的数据突然过期,这就导致瞬时大量对该商品的请求直接落到数据库上,对数据库造成了巨大的压力。
解决方案:
设置热点数据永不过期或者过期时间比较长。针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。请求数据库写数据到缓存之前,先获取互斥锁,保证只有一个请求会落到数据库上,减少数据库的压力。
5.3 缓存雪崩原理以及解决方案
实际上,缓存雪崩描述的就是这样一个简单的场景:缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。另外,缓存服务宕机也会导致缓存雪崩现象,导致所有的请求都落到了数据库上。
举个例子:数据库中的大量数据在同一时间过期,这个时候突然有大量的请求需要访问这些过期的数据。这就导致大量的请求直接落到数据库上,对数据库造成了巨大的压力。
解决方案:
针对Redis服务不可用的情况采用Redis集群,避免单机出现问题整个缓存服务都没办法使用。限流,避免同时处理大量的请求。针对热点缓存失效的情况:设置不同的失效时间比如随机设置缓存的失效时间。缓存永不失效(但是不太推荐,实用性太差)。设置二级缓存。
5.3 总结
三种问题最终都是使数据库的压力激增,甚至导致出现宕机现象。
问题 | 原理 |
---|---|
缓存穿透 | 大量请求的key是不合理的,根本不存在于缓存中,也不存在于数据库中 |
缓存击穿 | 该数据存在于数据库中,但由于不存在于缓存,导致瞬时大量的请求直接打到了数据库上 |
缓存雪崩 | 缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上 |