Redis合集

113 阅读16分钟

1. 介绍

Redis是key-value的数据,因此每一个数据都是一个键值对
Redis会周期性的把更新的数据写入磁盘(RDB)或者把修改操作写入追加的记录文件(AOF) \

  • 键的类型是字符串
  • 值的类型分为五种: 
    • string 字符串
    • hash hash字典
    • list 列表
    • set 集合
    • zset 有序集合

2. 命令

通用命令

  • 设置键值set key value
  • 获取键值get key
  • 设置键值及过时时间,以秒为单位SETEX key seconds value
  • 设置多个键值MSET key value [key value ...]
  • 获取多个键值MGET key [key ...]
  • 将值加1INCR key
  • 将key对应的value加整数INCRBY key increment
  • 将key对应的value减1DECR key
  • 将key对应的value减整数DECRBY key decrement
  • 追加值APPEND key value
  • 获取值长度STRLEN key

键命令

  • 查找键,参数支持正则KEYS pattern 好比KEYS *
  • 判断键是否存在,若是存在返回1,不存在返回0EXISTS key [key ...]
  • 查看键对应的value的类型TYPE key
  • 删除键及对应的值DEL key [key ...]
  • 设置过时时间,以秒为单位EXPIRE key seconds
  • 查看有效时间,以秒为单位TTL key

列表命令

  • 列表的元素类型为string
  • 按照插入顺序排序
  • 在列表的头部或者尾部添加元素
  • 在头部插入数据LPUSH key value [value ...]
  • 在尾部插入数据RPUSH key value [value ...]
  • 在一个元素的前|后插入新元素LINSERT key BEFORE|AFTER pivot value
  • 设置指定索引的元素值,索引是基于0的下标
  • 索引能够是负数,表示偏移量是从list尾部开始计数,如-1表示列表的最后一个元素LSET key index value
  • 移除而且返回 key 对应的 list 的第一个元素LPOP key
  • 移除并返回存于 key 的 list 的最后一个元素RPOP key
  • 返回存储在 key 的列表里指定范围内的元素,start 和 end 偏移量都是基于0的下标,偏移量也能够是负数,表示偏移量是从list尾部开始计数,如-1表示列表的最后一个元素LRANGE key start stop
  • 裁剪列表,改成原集合的一个子集,start 和 end 偏移量都是基于0的下标,偏移量也能够是负数,表示偏移量是从list尾部开始计数,如-1表示列表的最后一个元素LTRIM key start stop
  • 返回存储在 key 里的list的长度LLEN key
  • 返回列表里索引对应的元素LINDEX key index

hash命令

  • hash用于存储对象,对象的格式为键值对
  • 设置单个属性HSET key field value
  • 设置多个属性HMSET key field value [field value ...]
  • 获取一个属性的值HGET key field
  • 获取多个属性的值HMGET key field [field ...]
  • 获取全部属性的值HGETALL key
  • 获取全部的属性HKEYS key
  • 返回包含属性的个数HLEN key
  • 获取全部值HVALS key
  • 判断属性是否存在HEXISTS key field
  • 删除属性及值HDEL key field [field ...]
  • 返回值的字符串长度HSTRLEN key field

集合命令

  • 元素为string类型
  • 元素具备惟一性,不重复
  • 添加元素SADD key member [member ...]
  • 返回key集合全部的元素SMEMBERS key
  • 返回集合元素个数SCARD key
  • 求多个集合的交集SINTER key [key ...]
  • 求某集合与其它集合的差集SDIFF key [key ...]
  • 求多个集合的合集SUNION key [key ...]
  • 判断元素是否在集合中SISMEMBER key member

有序集合命令

  • 元素为string类型
  • 元素具备惟一性,不重复
  • 每一个元素都会关联一个double类型的score,表示权重,经过权重将元素从小到大排序
  • 元素的score能够相同
  • 添加ZADD key score member [score member ...]
  • 返回指定范围内的元素ZRANGE key start stop
  • 返回元素个数ZCARD key
  • 返回有序集key中,score值在min和max之间的成员ZCOUNT key min max
  • 返回有序集key中,成员member的score值ZSCORE key member

3. 缓存模式

针对热门且非绝对一致性的数据:请求,数据备份提高容错性,中间件和集群提高性能

  • cache aside:可以用于读写使用
    • 写操作:先更新数据库 然后删除缓存 db驱动更新
    • 读操作:查询时先判断缓存,没有的话再读取数据库,并将数据库的数据写入缓存。
    • 场景: 特别常用
  • Read-Through
    • 写操作:写业务先更新缓存再更新数据库,没有缓存直接写数据库
    • 读操作:查询时先判断缓存,没有的话再读取数据库,并将数据库的数据写入缓存。
  • Write-Through
    • 所有的写操作都经过缓存,每次我们向缓存中写数据的时候,缓存会把数据持久化到对应的数据库中去,且这两个操作都在一个事务中完成。因此,只有两次都写成功了才是最终写成功了。这的确带来了一些写延迟但是它保证了数据一致性
    • 场景:经常与Read-Through一起使用,需要频繁读取相同数据,不能忍受数据丢失
  • write behind caching
    • 写操作:只更新缓存,批量异步更新db
    • 读操作:没有缓存就读取db再回写,有缓存就直接读取

4. 持久化

RDB(快照):默认持久化方式

RDB(Redis DataBase)是将某一个时刻的内存快照(Snapshot),以二进制的方式写入磁盘的过程。内存快照就是我们上面说的,它指内存中的数据在某一个时刻的状态记录,类似于拍照片,当你给朋友拍照时,一张照片就能把朋友一瞬间的形象全部记录下来。
触发 RDB 的方式也分为两种:一类是手动触发,另一类是自动触发。

  • 手动触发 手动触发持久化的操作有两个: save 和 bgsave ,它们主要区别体现在:是否阻塞 Redis 主线程的执行。
  • 自动触发 save m n 是指在 m 秒内,如果有 n 个键发生改变,则自动触发持久化。 参数 m 和 n 可以在 Redis 的配置文件中找到,例如,save 60 1 则表明在 60 秒内,只要有一个键发生改变,就会触发 RDB 持久化。 自动触发持久化,本质是 Redis 通过判断,如果满足设置的触发条件,自动执行一次 bgsave 命令。 注意:当设置多个 save m n 命令时,满足任意一个条件都会触发持久化。

image.png

  • 优点:

    • RDB 的内容为二进制的数据,占用内存更小,更紧凑,更适合做为备份文件
    • RDB 对灾难恢复非常有用,它是一个紧凑的文件,可以更快的传输到远程服务器进行 Redis 服务恢复
    • RDB 可以更大程度的提高 Redis 的运行速度,因为每次持久化时 Redis 主进程都会 fork 一个子进程,进行数据持久化到磁盘,Redis 主进程并不会执行磁盘 I/O 等操作
    • 与 AOF 格式的文件相比,RDB 文件可以更快的重启
  • 缺点:

    • 因为 RDB 只能保存某个时间间隔的数据,如果中途 Redis 服务被意外终止了,则会丢失一段时间内的 Redis 数据
    • RDB 需要经常 fork 才能使用子进程将其持久化在磁盘上。如果数据集很大,fork 可能很耗时,并且如果数据集很大且 CPU 性能不佳,则可能导致在几毫秒甚至一秒钟内 Redis 无法为客户端服务

AOF: 以日志的方式记录非查询命令

AOF(Append Only File)指的是追加到文件,所以 AOF 我们可以猜测它应该类似于日志文件一样,事实上也确实如此。但它和我们熟悉的数据库的预写式日志(Write Ahead Log,WAL)不同,WAL 是先把修改的数据记到日志文件中,然后执行相关操作;而 AOF 正好相反,Redis 是先执行命令、将数据写入内存,然后才记录日志。

  • 例如: set name hasher

image.png

    • 其中 *3 表示当前命令中有 3 个部分,每个部分上面都会有一个 $数字,表示该部分占了多少字节,比如 $4 表示 name 这个 key 占了 4 个字节。但是为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查,因此要是先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。而 AOF 这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到 AOF 日志文件中,否则系统就会直接向客户端报错,所以 Redis 使用 AOF 这一方式的一大好处就是可以避免出现记录错误命令的情况。它是在命令执行后才记录日志,所以不会阻塞当前的写操作。
  • 查询是否开启:config get appendonly

  • 触发持久化->自动触发:有两种情况可以自动触发 AOF 持久化,分为是:"满足 AOF 设置的持久化策略触发" 和 "满足 AOF 重写触发"。其中,AOF 重写触发会在本文的后半部分详细介绍,这里重点来说 AOF 持久化策略都有哪些。 AOF 持久化策略,分为以下三种:

      • always:每条 Redis 操作命令都会写入磁盘,最多丢失一条数据;
      • everysec:每秒钟写入一次磁盘,最多丢失一秒的数据;
      • no:不设置写入磁盘的规则,根据当前操作系统来决定何时写入磁盘,Linux 默认 30s 写入一次数据至磁盘。
  • 触发持久化-> 手动触发:在客户端执行 bgrewriteaof 命令就可以手动触发 AOF 持久化。

    • AOF 持久化会生成 appendonly.aof 文件
    • AOF 会自动满足文件大小或者文件内容而进行重写(去除相同命令)
  • -文件位置 正常情况下,只要开启了 AOF 持久化,并且提供了正常的 appendonly.aof 文件,在 Redis 启动时就会自定加载 AOF 文件并启动。

  • 优点:

    • AOF 持久化保存的数据更加完整,AOF 提供了三种保存策略:每次操作保存、每秒钟保存一次、跟随系统的持久化策略保存,其中每秒保存一次,从数据的安全性和性能两方面考虑是一个不错的选择,也是 AOF 默认的策略,即使发生了意外情况,最多只会丢失 1s 钟的数据
    • AOF 采用的是命令追加的写入方式,所以不会出现文件损坏的问题,即使由于某些意外原因,导致了最后操作的持久化数据写入了一半,也可以通过 redis-check-aof 工具轻松的修复
    • AOF 持久化文件,非常容易理解和解析,它是把所有 Redis 键值操作命令,以文件的方式存入了磁盘。即使不小心使用 flushall 命令删除了所有键值信息,只要使用 AOF 文件,重启 Redis 即可恢复之前误删的数据,但前提是把 AOF 文件中最后的 flushall 命令删除之后再恢复,否则恢复完之后就又 flushall 了
  • 缺点:

    • 对于相同的数据集来说,AOF 文件要大于 RDB 文件
    • 在 Redis 负载比较高的情况下,RDB 比 AOF 性能更好
    • RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF 更健壮

持久化文件加载规则

  • 如果只开启了 AOF 持久化,Redis 启动时只会加载 AOF 文件(appendonly.aof),进行数据恢复
  • 如果只开启了 RDB 持久化,Redis 启动时只会加载 RDB 文件(dump.rdb),进行数据恢复
  • 如果同时开启了 RDB 和 AOF 持久化,Redis 启动时一样只会加载 AOF 文件(appendonly.aof),进行数据恢复

选择持久化策略建议

  • 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;
  • 如果允许分钟级别的数据丢失,可以只使用 RDB;
  • 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。

文章摘抄于: www.cnblogs.com/traditional… 感谢作者!!!

5. redis快的原因(单线程or多线程)

这里我们强调的单线程,指的是网络请求模块使用一个线程来处理,即一个线程处理所有网络请求,其他模块仍用了多个线程

  • 那为什么使用单线程呢?官方答案是:因为CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

  • 但是,我们使用单线程的方式是无法发挥多核CPU 性能,不过我们可以通过在单机开多个Redis 实例来解决这个问题

  • Redis6.0 之前为什么一直不使用多线程?

    • Redis使用单线程的可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。
  • Redis6.0 为什么要引入多线程呢?

    • 因为Redis的瓶颈不在内存,而是在网络I/O模块带来CPU的耗时,所以Redis6.0的多线程是用来处理网络I/O这部分,充分利用CPU资源,减少网络I/O阻塞带来的性能损耗。
  • Redis6.0 如何开启多线程? 默认情况下Redis是关闭多线程的,可以在conf文件进行配置开启:

io-threads-do-reads yes
io-threads 线程数
官方建议的线程数设置:4核的机器建议设置为23个线程,8核的建议设置为6个线程,
线程数一定要小于机器核数,尽量不超过8个。
    • 在redis的多线程模式下,获取、解析命令,以及输出结果着两个过程,可以配置成多线程执行的,因为它毕竟是我们定位到的主要耗时点,但是命令的执行,也就是内存操作,依然是单线程运行的。所以,Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行,也就不存在并发安全问题。

抖音笔记

  • 单线程:Redis对外提供的键值存储服务的主要流程是单线程,也就是网络IO和数据读写是由单个线程来完成的,但是 持久化、异步删除、集群数据同步是由额外的线程来执行的。
  • 好处:防止同步代码占用主线程,导致阻塞,从而影响后续的代码执行。
  • 结论:redis 确实是单线程,但是并不是全面单线程。
  • 官网描述:由于redis是基于内存的操作,查找和操作的时间复杂度都是O(1),因此CPU并不是redis的瓶颈,Redis的瓶颈很可能是机器内存或者网络带宽的大小。既然单线程易于实现并且CPU不会成为瓶颈,那采用单线程解决方案是合乎逻辑的。
  • 为何效率这么高?
    • 完全基于内存的操作
    • 降低了CPU的消耗(单线程)
    • 采用更高效的非阻塞I/O -> epoll

6. 缓存雪崩、击穿、穿透

  • 缓存雪崩:大量缓存数据同时过期,redis故障
    • 大量缓存数据同时过期:使用过期时间+随机时间,不让缓存在同一时间过期;双key策略,一个key设置过期时间,一个key不设置过期时间,让它们的值一样,取不到设置了过期时间的,就取没有过期时间的,需要做好双key的数据同步。
    • redis服务宕机:构建redis集群,主从架构,主服务器宕机了,由哨兵进行检测,使从库变主库。
  • 缓存击穿:热点数据缓存过期,此时大量请求就会访问数据库。
    • 使用互斥锁:保证只有一个线程来更新缓存,没有获取到锁的请求,要么直接返回,要么返回空值或者默认值。使用set_nx 可以写入的话 就允许访问数据库,不然直接返回或者等待
  • 缓存穿透:数据不存在缓存,也不在数据库
    • 业务误操作: 缓存和数据库中的数据都被删除了,导致两个库都没有数据。
    • 黑客恶意攻击: 故意大量访问某些读取不存在数据的业务。
    • 应对一:非法请求的限制
      • 当有大量恶意请求访问不存在的数据的时候,会发生缓存穿透,因此在API入口处我们要判断请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
    • 应对二:缓存空值或默认值
      • 当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。
    • 应对三:使用布隆过滤器快速判断数据是否存在,避免通过查询数据库进行判断
      • 我们可以在写入数据库数据时,使用布隆过滤器做个(0,1)标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。
      • 通过位图,hash。 查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据
  • 总结:

image.png