Redis 相关问题

167 阅读10分钟

Redis介绍

基于内存的高性能key-value的no-sql。操作均在内存中,定期通过异步操作将数据flush到硬盘上。每秒超过10w次的读写操作。支持保存多种数据结构(string,list,set,sorted set,hash),单个value的限制为1G。

优点

  • 速度快:因为数据存在内存中,类似于HashMap,查找和操作的时间复杂度都是O(1)
  • 丰富的数据类型:支持string,list,set,sorted set,hash
  • 支持事务:操作都是原子性
  • 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
  • 单线程,避免了不必要的上下文切换和竞争条件。
  • 使用多路复用IO模型,非阻塞IO。

常见性能问题和解决方案

  • Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
  • Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次
  • Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
  • Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内

如何保证redis中都是热点数据/缓存配置

在 redis 中,允许用户设置最大使用内存大小,server.maxmemory默认为0,没有指定最大缓存,如果有新的数据添加,超过最大内存,则会使redis崩溃,所以一定要设置。redis 内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。

redis提供6种数据淘汰策略:

1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
6. no-enviction(驱逐):禁止驱逐数据(永不回收)
7. Redis4.0加入了LFU(least frequency use)淘汰策略,包括volatile-lfu和allkeys-lfu,通过统计访问频率,将访问频率最少,即最不经常使用的KV淘汰。

策略的使用规则:

  • 如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
  • 如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random

redis的并发竞争问题如何解决(分布式锁)

Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Redis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:

  • 客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。需要应用程序处理资源的同步。
  • 服务器角度,利用setnx实现锁。setnx和expire可以组合使用。

redis持久化

  1. RDB(snapshots):全量持久化,适用于备份。eg:每N秒有超过M此更新就将数据写入磁盘。可以手工调用SAVE或BGSAVE。默认文件名为dump.rdb。redis主进程fork一个子进程将数据写入到临时文件中,写入完成后替换旧文件。copy on write。
  2. AOF:增量持久化,可以在配置文件中打开AOF模式,用定时sync,记录所有操作。每一个写命令都通过write函数追加到appendonly.aof中
appendfsync yes   
appendfsync always     #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec   #每秒钟同步一次,该策略为AOF的缺省策略。
  1. 虚拟内存。vm-max-threads这个参数,可以设置访问swap文件的线程数。 在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。

主从复制

主从配置结合哨兵模式能解决单点故障问题,提高redis可用性。从节点仅提供读操作,主节点提供写操作。对于读多写少的状况,可给主节点配置多个从节点,从而提高响应效率。

  • 从节点执行slaveof[masterIP][masterPort],保存主节点信息
  • 从节点中的定时任务发现主节点信息,建立和主节点的socket连接
  • 从节点发送Ping信号,主节点返回Pong,两边能互相通信 连接建立后,主节点将所有数据发送给从节点(数据同步)
  • 主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来, 主节点就会持续的把写命令发送给从节点,保证主从数据一致性。

同步机制

Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次快照,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

redis集群

Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。 Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

redis中的事务机制

MULTI/EXEC/DISCARD/WATCH这四个命令是实现事务的基础。

  • 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行。
  • 和关系型数据库中的事务相比,在Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。
  • 可以通过MULTI命令开启一个事务,可以将其理解为"BEGIN TRANSACTION"。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。可以将其理解为关系型数据库中的COMMIT/ROLLBACK命令。
  • 在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行。
  • 当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。

Redis服务器会在重新启动时执行一系列必要的一致性检测,Redis工具包中提供的redis-check-aof工具可以定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后可以再次重新启动Redis服务器了。

WATCH命令和基于CAS的乐观锁

在Redis的事务中,WATCH命令可用于提供CAS(check-and-set)功能。假设通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Null multi-bulk应答以通知调用者事务执行失败。

WATCH mykey
  val = GET mykey
  val = val + 1
MULTI
  SET mykey $val
EXEC

主键失效机制

在Redis当中,有生存期的key被称为volatile。在创建缓存时,要为给定的key设置生存期,当key过期的时候(生存期为0),可能会被删除。

  1. 影响生存时间的操作:生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SET 和 GETSET 命令覆盖原来的数据,也就是说,修改key对应的value和使用另外相同的key和value来覆盖以后,当前数据的生存时间不同。
  2. 更新生存时间:可以对一个已经带有生存时间的 key执行EXPIRE命令,新指定的生存时间会取代旧的生存时间。EXPIRE和TTL命令搭配使用,TTL可以查看key的当前生存时间。设置成功返回 1;当 key 不存在或者不能为key 设置生存时间时,返回0。

redis的适用场景

Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢?

如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点:

Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。 Redis支持数据的备份,即master-slave模式的数据备份。 Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

  1. 会话缓存(Session Cache)
  2. 全页缓存(FPC)
  3. 队列(list set)
  4. 排行榜/计数器(set)
  5. 发布/订阅

redis雪崩

缓存同一时间大面积过期,导致请求落到数据库上。存数据时实现时间加上随机值。如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效。或者设置热点数据永不过期,有更新操作就更新缓存就好了

缓存穿透和击穿

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。 击穿是指某一个key在失效时处理大量请求,失效后请求落到数据库上,在这个key的点上击穿缓存。增加校验。 使用布隆过滤器,利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在。设置热点数据永不过期。