redis总结

概述

redis是基于内存的数据库,对数据的读写操作都是在内存中完成。因此读写速度非常快。常用于缓存、消息队列、分布式锁等场景。他提供多种数据类型支持不同场景。如字符串、散列、列表、集合、有序集合(Zset)、基数统计、地理信息、流、位图

除此之外redis还支持事务、持久化、lua脚本,多种集群方案(主从复制、哨兵、cluster)提供高可用性、发布/订阅模式,内存淘汰机制、过期删除机制。

redis和memcached区别

  • redis支持更丰富的数据类型。memcache只支持最简单的key-value数据类型
  • redis支持数据持久化
  • redis支持集群
  • redis支持发布、订阅模式、lua脚本、事务

为什么选择redis作为mysql的缓存

  • redis具备【高性能】、【高可用】的特性
    • 单台redis的QPS(每秒处理完请求的次数)是MySQL的10倍。redis单机qps能破10W。
  • 数据结构
    • string
      • 缓存对象
      • 限流
      • 技术
      • 分布式锁
      • 共享session
    • List
      • 简单的消息队列
        • 生产者需要自行实现全局唯一id
        • 不能以消费组形式进行消费
        • 消息会丢失
    • hash
      • 缓存对象,对象属性可直接存取
      • 购物车
    • set
      • 好友关系
      • 抽奖活动
    • Zset
      • 排行榜
      • 排序场景
    • BitMap
      • 签到、用户登录状态、连续签到用户总数
    • hyperLogLog

数据结构内部实现

  • String
    • SDS,简单动态字符串
      • 不仅保存文本数据,还保存二进制数据
      • 可以获取字符串长度,sds记录了字符串长度
      • api安全,不会造成缓冲区溢出
  • List
    • 双向链表和压缩列表
      • 列表元素个数小于512且每个元素的值小于64字节,redis使用压缩列表
      • 否则使用双向链表
      • 但是redis3.2版本后,list数据类型底层数据结构只由quickList(快速列表)实现
  • hash类型内部实现
    • 哈希类型元素 个数小于512且每个元素小于64字节,redis会使用压缩列表
    • 否则使用哈希表
    • 7.0版本 使用listpack (紧凑列表)
  • set类型
    • 哈希表和整数集合实现
    • 集合中的元素都是整数且元素个数小于512,使用整数集合
    • 否则使用hash
  • zset
    • 有序集合元素个数小于128个,且每个元素的值小于64字节,使用压缩列表
    • 否则使用跳表
    • 7.0版本使用listpack数据结构
>    原理
>- 内部使用HashMap和跳跃表来保证数据的存储和有序。
>- hashMap里放的是成员到score的映射
  • 跳跃表存的则是所有成员
  • 排序依据是hashMap里存的score
  • 使用跳跃表可以获得比较高的查找效率,且实现比较简单

redis单线程模型

指的是[接收客户端请求->解析请求->进行数据读写等操作->发送数据给客户端] 这个过程是由一个线程完成的。redis6.0引入多线程

多线程处理

  • 处理关闭文件线程
  • AOF刷盘
  • 异步释放redis内存,unlink key等删除命令交给后台线程执行。
    • 好处:不会导致redis主线程卡顿,使用unlink异步删除大key

关闭文件、AOF刷盘、释放内存这三个任务都有各自的任务队列

文件事件处理器

redis内部是由文件事件处理器实现的

  • 套接字
  • IO对路复用程序
  • 事件分派器
  • 事件处理器
    • 连接处理器
    • 请求处理器
    • 回复处理器
    • 复制处理器

IO对路复用程序监听套接字,放到队列,事件分派器从队列中一个一个取出套接字,交给不同处理器进行处理

为什么单线程那么快

  • redis的大部分操作都在内存中完成,并采用高效数据结构。因此redis瓶颈可能是机器的内存或者网络带宽,并非CPU。
  • redis采用单线程,避免上下文切换,锁竞争
  • redis采用IO多路复用机制处理大量用户端的socket。IO多路复用指的是一个线程处理多个IO流

redis6.0引入多线程处理网络请求,默认情况下IO多线程只针对发送响应数据

持久化

redis共有3种数据持久化

  • aof
    • redis执行完写操作,把命令追加到aof缓冲区;
    • 通过write()系统调用,将缓冲区数据写入到aof文件。这个时候还没写到磁盘,而是拷贝到内核缓冲区
    • 回盘策略(内核缓冲区数据什么时候写入磁盘)
      • always:每次写操作命令执行就写回硬盘
      • every sec:每秒,每次写命令执行完,先将命令写入到AOF文件的内核缓冲区,每隔1秒将缓冲区里的内容写回磁盘
      • no:由操作系统决定何时缓冲区内容写回硬盘
    • AOF日志过大,触发重写机制
  • rdb
    • 快照,记录某一个瞬间的内存数据
    • redis提供了两个命令生成RDB文件
      • save
      • bgsave
        • 创建子进程生成rdb,避免主线程阻塞
      • 区别是是否在主线程里执行
    • 每隔一段时间自动执行一次bgsave :配置文件配置
      • 900秒内,对数据库进行至少1次修改
      • 300秒内,对数据库进行至少10次修改
      • 60秒内,对数据库进行至少1W次修改
    • fork子进程生成RDB
      • 子进程和父进程共享同一片内存数据
      • 因为创建子进程时,会复制父进程的页表,但页表指向的物理内存还是一个
      • 如果主线程执行写操作,被修改的数据会复制一份副本,bgsave将该副本数据写入rdb文件
  • 混合持久化

aof重写过程

  • 主进程创建重写AOF的子进程,此时父子进程共享物理内存,重写子进程只会对这个内存进行只读。

  • 重写工作:子进程读取当前数据库的所有键值对,将每个键值对用一条命令记录到『新的aof文件』

  • 重写期间,写命令会记录到重写缓冲区、AOF缓存区

  • 子进程完成AOF重写工作,向主进程发送一条信号,主进程接收到该信号,调用信号处理函数

    • 将AOF重写缓冲区中所有内容追加到新的AOF文件,使得新旧两个AOF所保存的数据库状态一致
    • 新AOF文件进行改名,覆盖现有AOF文件

    重写过程是由后台子进程bgrewriteaof来完成的

    • 好处
      • 子进程进行AOF重写期间,主进程可继续处理命令请求,避免阻塞
      • 子进程带有主进程数据副本。如果是线程,会共享内存。使用子进程,父子进程共享内存数据,但只能以只读方式,父子进程任一方修改共享内存,会发生写时复制,父子进程就有独立的数据副本,不用加锁保证数据安全

redis集群

想要设计一个高可用的redis服务,需要从redis的多服务节点来考虑。如主从复制、哨兵模式、切片集群

主从复制

是redis高可用服务的最基础保证。主从复制实现读写分离。

主从服务器之间的命令复制是异步的。

哨兵

主从复制宕机后,无法主动转移故障。哨兵模式提供了主从节点故障转移的功能。

多个哨兵实例定时ping主从节点,指定的哨兵实例数认为主节点挂了,就会进行故障转移

  • 选举新的主节点
  • 其他从节点从新的主节点同步数据
  • 通知客户端切换主节点
  • 将旧的主节点作为新主节点的子节点

cluster

redis缓存数据量大到一台的服务器无法缓存时,需要使用redis切片集群。将数据分布在不同服务器上。

redis cluster方案采用哈希槽,来处理数据和节点之间的关系。redis cluster中,一个切片集群共有16384个槽。

redis 的key根据计算映射到一个hash槽。哈希槽可平均、手动分配到节点上。

节点通信协议

gossip

脑裂产生的数据丢失

产生原因

由于网络问题,集群节点之间失去联系,主从数据不同步。重新平衡选举,产生两个主服务。网络恢复,旧主节点恢复,降级为新主节点的从节点,旧主节点与新主节点进行同步复制,旧主节点清空自己的数据,就会导致之前客户端写入的数据丢失

解决

主节点发现从节点下线或通信超时的总数量小于阈值,就拒绝客户端的写入。

至少有N个从库,在和主库进行数据复制时ACK延迟不超过T秒。新主库上线,就只有新主库能接收和处理客户端请求。

过期删除策略

  • 定期删除:隔一段时间随机从数据库取出一定量的key进行检查,删除其中过期的key
  • 惰性删除:访问到的key判断是否过期,过期就删除

redis持久化时,过期键如何处理

RDB

  • 文件生成阶段
    • 生成rdb的时候,会对key进行过期检查,过期的键不会被保存到新的rdb文件中
  • 文件加载阶段
    • 主服务器,载入rdb文件时,程序会对文件中保存的键进行检查,过期键不会载入到数据库
    • 从服务器,不论是否过期都会被载入到数据库,由于主从服务器进行同步,从服务的数据会被清空,一般来说,过期键载入RDB文件的从服务器也不会造成影响

AOF

  • 文件写入阶段
    • 持久化时,数据库某个键未过期,AOF保留此过期键
    • 当此过期键被删除,redis会向AOF文件追加一条del命令显示删除该键值
  • 文件重写阶段
    • 执行AOF重写,会对Redis键进行检查,已过期的键不会被保存到重写后的文件中

主从模式,如何处理过期键

从库对过期key处理是被动的,即使从库的key过期。

从库的过期键处理依靠主服务器控制,主库在key到期时,会在AOF文件增加一条del指令,同步到从库,从库通过执行del指令删除过期key。

内存淘汰策略

redis内存满了就会触发内存淘汰机制。

不进行数据淘汰

运行内存超过最大设置内存,不淘汰任何内存,不再提供服务,直接返回错误

进行数据淘汰

  • 设置了过期的数据中淘汰
    • volatile-random :随机淘汰设置了过期时间的任意键值
    • volatile-ttl:优先淘汰更早过期的键值
    • volatile-lru:淘汰设置过期时间的键值中,最久未使用的键值
    • volatile-lfu:淘汰设置过期时间的键值中,最少使用的键值
  • 在所有数据范围内淘汰
    • allkeys-random:随机淘汰任意键值;
    • allkeys-lru:淘汰整个键值中最久未使用的键值;
    • allkeys-lfu:淘汰整个键值中最少使用的键值

如何设计一个缓存策略,可以动态缓存热点数据?

内存有限,只能将其中一部分热点数据缓存起来,所以我们要设计一个热点数据动态缓存的策略

思路

通过数据最新访问时间做排名,并过滤掉不常访问的数据,只留下经常访问的数据。

如电商平台,只缓存用户经常访问的TOP 1000的商品

  • 通过缓存系统做一个排序队列,比如存放1000个商品。系统给根据商品访问时间,更新队列信息,越是最近访问的商品排名越靠前
  • 同时系统定期过滤队列中排名最后的200个商品,然后再从数据库随机读取出200个商品加入
  • 这样每次请求到达时,会先从队列获取商品id,如果命中,根据id再从另一个缓存数据结构中读取实际的商品信息,并返回
  • 在redis中可以用zadd方法和zrange方法完成排序队列和获取200个商品的操作

redis缓存策略

  • Cache Aside (旁路缓存) — 常用
  • Read/Write Through(读穿/写穿)策略
  • Write back(写回策略)

Cache Aside

应用程序直接与【数据库、缓存】交互,并负责对缓存的维护

  • 读策略
    • 命中缓存,返回
    • 没命中,从数据库读取。并更新缓存
  • 写策略
    • 先更新数据库,再删除缓存

cache Aside适合读多写少的场景,不适合写多场景

如果业务对缓存命中率有严格要求,可以考虑:

1、更新数据时,更新缓存。更新缓存前加分布式锁,这样在同一时间只允许1个线程更新缓存。

2、更新数据时,更新缓存。缓存加较短的过期时间。即使数据不一致,但缓存数据也很快过期

redis实现延迟队列

使用有序集合(ZSet)。ZSet有一个score属性用于存储延迟执行的时间

使用zadd score1 value1 命令往内存中生产消息,再利用zrangebyscore查询符合条件的所有待处理的任务,然后循环执行队列任务即可。

redis大key

什么是大key

1.String 类型的值大于10KB;

2.集合类型的元素个数超过5000个,或者hash所有元素的总大小大于100M

大key的影响

  • 客户端超时阻塞
    • 主要是持久化,大key会使得fork子进程造成阻塞
  • 网络阻塞
    • 大key查询产生的网络流量大
  • 阻塞工作线程
    • 删除大key会阻塞工作线程。建议使用unlink命令,异步删除
  • 内存分布不均
    • 集群模式在slot分布均匀情况下,会出现数据和查询倾斜,部分有大key的节点占用内存多,qps会比较大

如何查找大key

1.使用redis-cli --bigkeys查找

  • 注意事项
    • 最好在从节点执行,因为该命令会阻塞
    • 如果没有从节点,选择在业务低峰期执行
  • 不足
    • 只能返回每种类型中最大的big key。无法得到大小排在前N位的big key
    • 对于集合类型,这个方法只统计集合元素个数的多少。而非实际占用内存量。但个数多,不一样占用内存就多

2.使用rdbTools工具查找

解析rdb文件,找到大key

redis实现分布式锁

redis的set命令有个NX参数实现分布式锁。它表示key不存在才插入。

  • 如果key不存在,显示插入成功,用于表示加锁成功
  • 如果key存在,显示插入失败,用于表示加锁失败

基于redis节点实现分布式锁时,对于加锁操作,需要满足3个条件

1.set命令带上nx选项。加锁包括读取锁变量,检查锁变量值和设置锁变量3个操作,需要以原子操作的方式完成,所以使用set命令带上nx选项实现加锁

2.set命令带上ex选项。锁变量需要设置过期时间,以免客户端拿到锁发生异常,导致锁无法释放。

3.使用set命令设置锁变量值时, 每个客户端设置的值是唯一值。锁变量的值需要能区分来自不同客户端的加锁操作。以免在锁释放时,出现误释放操作。

SET lock_key unique_value NX PX 10000

  • lock_key是key键
  • unique_value是客户端生成的唯一标识,用于区分不同客户
  • NX表示只在lock_key不存在时,才对lock_key进行设置操作

redis分布式锁优缺点

优点

  • 性能高
  • 实现方便
  • 避免单点故障

缺点

  • 超时时间不好设置
    • 如何合理设置超时时间
    • 基于续约的方式设置超时时间。先给锁设置一个超时时间,启动守护线程,让守护线程在一段时间后,重新设置这个锁的超时时间
    • 实现方式:写一个守护线程,判断锁的情况。锁快失效时,进行续约加锁。主线程执行完成后,销毁续约锁即可(实现复杂)
  • reids主从复制模式的数据是异步复制的,导致分布式锁不可靠
    • 客户端在redis主节点获取到锁后,在没有同步到其他节点前,宕机了。客户端能够在新主节点获取到锁

redis如何解决集群情况下分布式锁的可靠性

红锁

你可能感兴趣的:(redis,java,数据库)