概念:
一款高性能的NoSql系列的非关系型数据库
优点:
- 速度快:纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value 数据库。
- 支持丰富数据类型:字符串(String)、字符串列表(List)、字符串集合(Set)、有序字符串集合(Sorted Set)、哈希(Hash)。
- 丰富的特性:订阅发布 Pub / Sub 功能、Key 过期策略、事务、支持多个 DB、计数。
- 持久化存储:Redis 提供 RDB 和 AOF 两种数据的持久化存储方案,解决内存数据库最担心的万一 Redis 挂掉,数据会消失掉。
缺点:
- 由于是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然redis本身有key过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。
- 无法做到太复杂的关系数据库模型
- 如果进行完整重同步,由于需要生成rdb文件,并进行传输,会占用主机的CPU,并会消耗现网的带宽。不过redis2.8版本,已经有部分重同步的功能,但是还是有可能有完整重同步的。比如,新上线的备机。
- 修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。在这个过程中,redis不能提供服务。
Redis持久化机制:
Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
持久化方式:
RDB:RDB 是 Redis 默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据。
优点:
- 适合大规模的数据恢复。
- 如果业务对数据完整性和一致性要求不高,RDB是很好的选择。
缺点:
- 数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了。
- 备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍),最后再将临时文件替换之前的备份文件。所以Redis 的持久化和数据的恢复要选择在夜深人静的时候执行是比较合理的。
AOF:Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
优点:
- 数据的完整性和一致性更高
缺点:
- 因为AOF记录的内容多,文件会越来越大,数据恢复也会越来越慢。
总结:
- Redis默认开启RDB持久化方式,在指定的时间间隔内,执行指定次数的写操作,则将内存中的数据写入到磁盘中。
- RDB持久化适合大规模的数据恢复但它的数据一致性和完整性较差。
- Redis需要手动开启AOF持久化方式,默认是每秒将写操作日志追加到AOF文件中。
- AOF的数据完整性比RDB高,但记录内容多了,会影响数据恢复的效率。
- Redis针对AOF文件大的问题,提供重写的瘦身机制。
- 若只打算用Redis做缓存,可以关闭持久化。
- 若打算使用Redis的持久化。建议RDB和AOF都开启。其实RDB更适合做数据的备份,留一后手。AOF出问题了,还有RDB。
常见问题:
缓存雪崩:
由于原有缓存失效,新缓存未到期间
eg:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期,所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
解决办法:大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。
缓存穿透:
是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
解决办法:
缓存预热:
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据。
解决办法:
1.直接写个缓存刷新页面,上线时手工操作下。
2.数据量不大,可以在项目启动的时候自动进行加载。
3.定时刷新缓存。
缓存更新:
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择,后面讲),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。
缓存降级:
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
冷数据和热点数据:
热点数据,缓存才有价值。
对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。
数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。
频繁修改的数据,看情况考虑使用缓存。
数据修改频率很高,但是又不得不考虑缓存的场景?比如:这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。
Redis的过期策略以及内存淘汰机制:
Redis采用的是定期删除 + 惰性删除策略。
为什么不用定时删除策略?
定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略。
定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。
在redis.conf中有一行配置:
maxmemory-policy volatile-lru
该配置就是配内存淘汰策略的
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据,新写入操作会报错
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。
多机redis 的部署如何保证数据一致:
主从复制,读写分离
一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
Redis 常用命令:
清空Redis所有key:
flush db # 清除当前数据库的所有keys
flush all # 清除所有数据库的所有keys
查询匹配key:
keys * # 查看所有keys
keys prefix_* # 查看前缀为"prefix_"的所有keys
key基本操作:
exists key # 确认一个key是否存在,存在返回1,不存在返回0
set key value # 设置key和value
get key # 获取key的value
del key # 删除一个key,返回结果为成功删除键的个数
type key # 返回值的类型
keys pattern # 返回满足给定pattern的所有key
random key # 随机返回key空间的一个
key rename oldname newname # 重命名key
db size # 返回当前数据库中key的数目
select index # 选择第0~15中的库
move key dbindex # 移动当前数据库中的key到dbindex数据库
设置key的生存时间:
expire key seconds #设置key的有效时间 单位为秒
ttl key #获取key的剩余有效时间,持久key返回-1,key不存在返回-2,具体时间返回秒数
persist key #设置有时效性的key为持久key
value的基本操作:
字符串(String)类型:
INCR key # 递增数字,仅仅对数字类型的键有用,相当于Java的i++运算
INCRBY key increment # key自增increment,increment可以为负数,表示减少
DECR key # 递减数字,仅仅对数字类型的键有用,相当于Java的i–-运算
DECRBY key decrement # key自减decrement,decrement可以为正数,表示增加
INCRBYFLOAT key increment # 增加指定浮点数,仅仅对数字类型的键有用
APPEND key value # 向尾部追加值,相当于append方法
STRLEN key # 获取字符串长度
MSET key1 value1 [key2 value2 ...] # 同时设置多个key的值
MGET key1 [key2 ...] # 同时获取多个key的值
字符串列表(List)类型:
rpush key value # 在名称为key的list尾添加一个值为value的元素
lpush key value # 在名称为key的list头添加一个值为value的 元素
llen key # 返回名称为key的list的长度
lrange key start end # 返回名称为key的list中start至end之间的元素
ltrim key start end # 截取名称为key的list
lindex key index # 返回名称为key的list中index位置的元素
lset key index value # 给名称为key的list中index位置的元素赋值
lrem key count value # 删除count个key的list中值为value的元素
lpop key # 返回并删除名称为key的list中的首元素
rpop key # 返回并删除名称为key的list中的尾元素
rpoplpush srckey dstkey # 返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部
字符串集合(Set)类型:
sadd key member # 向名称为key的set中添加元素member
srem key member # 删除名称为key的set中的元素member
spop key # 随机返回并删除名称为key的set中一个元素
smove srckey dstkey member # 移到集合元素
scard key # 返回名称为key的set的基数
sismember key member # member是否是名称为key的set的元素
sinter key1 key2 …key # 求交集
sinterstore dstkey keys # 求交集并将交集保存到dstkey的集合
sunion key1 keys # 求并集
sunionstore dstkey keys # 求并集并将并集保存到dstkey的集合
sdiff key1 keys # 求差集
sdiffstore dstkey keys # 求差集并将差集保存到dstkey的集合
smembers key # 返回名称为key的set的所有元素
srandmember key # 随机返回名称为key的set的一个元素
有序字符串集合(Sorted Set)类型:
ZADD key score1 value1 [score2 value2 score3 value3 ...] # 添加元素
ZSCORE key value # 获取元素的分数
ZRANGE key start stop [WITHSCORE] # 获取排名在某个范围的元素,按照元素从小到大的顺序排序,从0开始编号,包含start和stop对应的元素,WITHSCORE选项表示是否返回元素分数
ZREVRANGE key start stop [WITHSCORE] # 获取排名在某个范围的元素,和上一个命令用法一样,只是这个倒序排序的。
ZRANGEBYSCORE key min max # 获取指定分数范围内的元素,包含min和max,(min表示不包含min,(max表示不包含max,+inf表示无穷大
ZINCRBY key increment value # 增加某个元素的分数
ZCARD key # 获取集合中元素的个数
ZCOUNT key min max # 获取指定分数范围内的元素个数,min和max的用法和5中的一样
ZREM key value1 [value2 ...] # 删除一个或多个元素
ZREMRANGEBYRANK key start stop # 按照排名范围删除元素
ZREMRANGEBYSCORE key min max # 按照分数范围删除元素,min和max的用法和4中的一样
ZRANK key value # 获取正序排序的元素的排名
ZREVRANK key value # 获取逆序排序的元素的排名
哈希(Hash)类型:
HSET key field value # 赋值
HMSET key field1 value1 [field2 values] # 一次赋值多个字段
HSET key field # 取值
HMSET key field1 [field2] # 一次取多个字段的值
HGETALL key # 一次取所有字段的值
HEXISTS key field # 判断字段是否存在
HSETNX key field value # 当字段不存在时赋值
HDEL key field # 删除字段
HKEYS key # 获取所有字段名
HVALS key # 获取所有字段值
HLEN key # 获取字段数量
汇总: