Redis
-
NoSql概述
-
为什么用NoSql
- Mysql扩展性瓶颈
- MySQL数据库也经常存储一些大文本的字段,导致数据库表庞大,在做数据库恢复会非常的慢,不容易快速恢复数据库。MySQL的扩展性较差,大数据下IO压力大,表结构更改困难。
-
好处
- 大规模数据处理:NoSQL数据库适合处理大规模数据集,可以水平扩展应对高并发和大数据量的需求
- 高性能和低延迟:NoSQL数据库采用键值对,文档,列族或图形等非关系型数据模型,可以更高效的存储和检索数据
- 灵活的数据模型
- NoSQL提供了灵活的数据模型,存储和处理不同的类型和结构的数据,无需事先定义固定的表结构
- 高可用和容错性
- NoSQL通常具有分布式架构和自动数据复制机制,当一个节点故障时会切换到其他可用节点,保证数据的可访问性和持久性
- Mysql扩展性瓶颈
-
什么是NoSQL
- NoSQL(Not Only SQL)是一种非关系型数据库的范畴,它与传统的关系型数据库(如MySQL、Oracle)相对应。
-
-
什么是Redis
- 简介
- redis是一种开源的数据结构存储
-
优点
- 读写速度快,支持多种数据类型,支持不同业务场景,对数据类型的操作都是原子性的,不存在并发竞争问题
- 支持事务,持久化,Lua脚本,主从复制模式,哨兵模式,内存淘汰机制,过期删除机制等
-
Redis与Memcached区别
- 共同点
- 基于内存的数据库,一般都用来当作缓存使用
- 都有过期策略
- 性能高
- 不同点
- Redis数据类型丰富,Memcached只支持最简单的k-v
- redis支持数据持久化,可以将内存的数据保持在磁盘上
- redis原生支持集群模式
- redis支持订阅模式,lua脚本,事务等功能
- 共同点
-
为什么用redis作为mysql的缓存
- 高性能,高并发
- 高性能:redis将用户的数据缓存在redis中,下次在访问直接从缓存中获取,操作内存。mysql是从硬盘读取
- 高并发:单机可以达到10wQPS(每秒处理完请求的次数)
- 高性能,高并发
-
Redis数据类型
-
String
- 结构存储的值:字符串,整数浮点数
- 读写能力:可以对字符串进行部分或全部操作;对数字进行自增自减
- 应用场景:缓存对象,常规计数:如关注和粉丝
- 结构存储的值:字符串,整数浮点数
-
Hash
- k-v无序散列表
- 添加,删除,获取单个元素
- 应用场景:购物车,缓存对象
- k-v无序散列表
-
List
- 链表;链表上每个节点都包含一个字符串
- 两端可以进行push和pop操作,读取单个或多个元素;根据值查找或删除元素
- 应用场景:消息队列
- 链表;链表上每个节点都包含一个字符串
-
Set
- 字符串的无序集合
- 添加,删除,获取元素;计算交集并集差集
- 应用场景:聚合计算,热门和排行榜
- 字符串的无序集合
-
Zset
- 和散列一样,存储键值对
- 字符串成员与浮点数之间的有序映射;排列由分数大小决定;添加,获取,删除单个元素以及根据分值范围或成员来获取元素
- 应用场景:电话,姓名排序
- 和散列一样,存储键值对
-
-
- 简介
-
-
Redis线程模式
- Redis单线程指的是:(接收客户端请求-->解析请求->进行数据读写操作->发送数据给客户端 )是由一个线程来完成的。但Redis程序不是单线程在启动时会启动后台线程(处理关闭文件,AOF刷盘,lazyfree线程(异步释放redis内存))。
-
-
-
- 支持epoll(I/O多路复用,Linux独占),kqueue(是在UNIX上比较高效的IO复用技术)等高性能网络模型
-
Redis采用单线程那么快的原因
- 大部分操作都在内存完成
- 避免多线程竞争
- I/O多路复用处理大量Socket请求
-
-
Redis持久化
-
AOF持久化
- 以日志的追加方式记录每个写请求的指令
- 可以配置磁盘刷新的时间间隔,比如每秒都刷盘
- 当进程奔溃或重启,执行日志文件中的指令进行恢复
- 日志文件过大会启动日志重写。fork一个子进程进行命令压缩
- FORK
- 父进程在子进程fork出的一瞬间完全一样,当父进程进来一个新的子进程k,更新之前用的内存空间和内存地址是同一个更新后会分开,父进程重新生成一个新的内存把这个内存空间对应的数据拷贝到父进程在做修改,修改后子进程和父进程就是两份独立的内存空间。这样父进程修改不会影响子进程的数据。
-
RDB持久化
- 当Redis需要做持久化时会fork一个子进程(子进程会继承父进程,保存父进程的内容)
- 子进程会将数据写入磁盘上一个RDB文件上
- 子进程完成后,会把原来的RDB文件换掉
- 应用到的特性时c-o-w(copy-on-write)机制
- 为什么混合持久化
- RDB数据恢复速度快,但频率不好把握;频率低丢失数据多频率高会影响性能
- AOF丢失数据少,但恢复慢
- 优点:以RDB开头启动快,AOF后半部减少数据丢失风险
- 缺点:AOF中添加了RDB使得AOF文件的可读性变差,低版本不兼容
-
-
Redis集群
-
主从同步(读写分离,高可用,安全性备份,异步进行)
- 防止出现单点故障
- 缓解主节点的读压力,可水平扩展
- 数据冗余,保证数据库安全性
- 无法实现强一致性保证,数据可能会不一致
-
- 支持epoll(I/O多路复用,Linux独占),kqueue(是在UNIX上比较高效的IO复用技术)等高性能网络模型
-
-
-
哨兵模式
- 为了解决在使用Redis主从服务,当主服务器宕机时,需要手动恢复的问题
- 监控主从服务器,提供主从节点故障转移的功能
-
-
-
-
-
切片集群模式
- 当缓存数据量大到服务器无法缓存时,使用Redis切片集群降低系统对单主节点依赖,提高Redis服务的读写性能
- 切片集群采用哈希槽来处理数据和节点的映射关系,一个切片集群有16384个哈希槽,类似数据分区根据键值对的key映射到一个哈希槽中
- 执行过程
- 1.根据键值对的key按照CRC16算法计算一个16bit的值
- 2.再用16bit值对16384取模,每个模数代表一个相应编号的哈希槽
- 哈希槽映射
- 平均分配
- 手动分配
- 需要把16384个槽都分配完毕,否则无法正常运行
- 执行过程
-
-
-
-
集群脑裂
- 集群脑裂导致数据丢失
- 由于网络问题,集群节点之间失去联系。主从数据不同步;重新平衡选举,产生两个主服务。等网络恢复,旧主节点会降级为从节点,再与新主节点进行同步复制的时候,由于会从节点会清空自己的缓冲区,所以导致之前客户端写入的数据丢失了
-
解决方案
- min-slaves-to-write x,主节点必须要有至少 x 个从节点连接,如果小于这个数,主节点会禁止写数据
- min-slaves-max-lag x,主从数据复制和同步的延迟不能超过 x 秒,如果超过,主节点会禁止写数据
- 主库连接的从库至少有n个和主库进行数据复制的ACK消息延迟不能超过T秒,否则主库就不会在接受客户端的写请求了。
- 即使原主库是假故障,也无法响应哨兵,不能同步,自然也就无法和从库进行ACK确认。然后,min-slaves-to-write 和 min-slaves-max-lag 的要求无法满足,原主库就会被限制接受客户端的写请求,客户端也不能在原主库写入新数据了。等到新主库上线,只有新竹库可以接受处理客户端请求,新写的数据会被直接写入新主库;原主库降低为从库,即使数据被清空,也不会有新数据丢失
- 集群脑裂导致数据丢失
-
Redis过期删除和内存淘汰
-
过期删除
-
Redis的过期删除策略
- 对key设置过期时间进行对过期键值删除策略
- Redis会把key带上过期时间存储到一个过期字典
- 检查key是否存在去过期字典中
- 不存在。正常读取键值
- 存在,获取key的过期时间,与系统时间进行比对,然后删除
- 检查key是否存在去过期字典中
-
Redis的过期删除策略是 惰性删除+定期删除搭配使用
-
惰性删除
- 不主动删除过期键,每次从数据库访问key时,都检测key是否过期,如果过期则删除该key
-
-
-
-
-
-
-
-
-
- 优点:每次访问才会检查key,只会占用很少的系统资源对cpu时间最友好
- 缺点:一直没有被访问,它所占用的内存就不会释放,造成内存空间浪费,对内存不友好
-
定期删除
- 每个一段时间随机从数据库中取出一定数量的key进行检查,并删除过去key
-
-
-
-
-
优点:通过限制删除执行频率减少删除操作对cpu的影响,也减少内存占用内存淘汰
-
淘汰策略
- 不进行数据淘汰策略
- 当到达阈值不淘汰任何数据,直接返回错误
- 数据淘汰策略
-
过期时间中淘汰
- volatile-random随机淘汰设置了过期时间的任意键值
- volatile-ttl优先淘汰更早过期的键值
- volatile-lru逃过过期时间中的键值,最久未使用的键值
- volatile-lfu淘汰过期时间中的键值,使用最少的
-
所有数据范围
- allkeys-random:随机淘汰任意键值
- allkeys-lru:淘汰整个键值中最久未使用的键值
- allkeys-lfu:淘汰整个键值中最少使用的键值
-
- 当运行内存达到某个阈值会触发淘汰机制,通过配置项目为maxmemory
- 缺点:如果执行频率多对CPU不友好,执行少内存不会及时释放
-
Redis中RDB和AOF对过期键处理
-
RDB
- 文件生成:过期的键不会被保存到新的RDB文件中
- 加载阶段
- 主服务器:在载入RDB文件时,程序会对文件中保存的键进行检查,过期的键不会载入到数据库
- 从服务器:在载入RDB时,无论是否过期都会载入到数据库中;但是在进行主从同步时,从数据库的数据会被清空,过期键也不会对从服务器造成影响
-
AOF
- 写入阶段
- 数据库中过期键没被删除,AOF文件会保留,当过期键被删除后,Redis会向AOF文件追加一条DEL命令来显式地删除该键值
- 重写阶段:重写进行检查过期键不会重写入AOF文件中
- 写入阶段
-
-
Redis主从模式对过期键如何处理
- 从库不会进行过期扫描,如有客户端访问从库中过期的key依旧会和未过期的键值对一样返回;当主库在key到期时,会在AOF文件中增加一条DEL指令,同步到从库来同步删除过期的key
-
Redis缓存设计
-
缓存雪崩
- 大量缓存数在同一时间过期(失效)时,如果有大量的用户请求,都无法在Redis中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重会造成数据宕机,从而形成一系列连锁反应,造成整个系统崩溃。
- 将缓存失效时间随机打散:减少缓存时间重复降低失效概率
- 设置缓存不过期:通过后台服务来更新缓存数据,减少缓存并发和缓存失效
-
-
-
-
-
缓存击穿
- 缓存中某个热点数据过期,由于大量的请求导致无法从缓存中读取,直接访问数据库,被高并发请求冲垮,这就是缓存击穿
- 互斥锁方案:Redis使用setNX方法设置一个状态为,标识一种锁定状态,保证同一时间只有一个业务线程请求缓存,未获得锁等待锁释放;当请求过多时返回过少可能会产生平均式加锁,这时候可以再加一个自旋锁
- 不给热点数据设置过期时间:有后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及更新设置过期时间
-
-
-
-
-
缓存穿透
- 当发生雪崩胡总和击穿时,数据库中还是保存了要访问的数据,一旦恢复相应的数据就可以减轻数据库的压力;但是当用户访问的数据,即不在缓存也不再数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,数据库也没要访问的数据,也没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤这就是缓存穿透问题。
- 非法请求限制:当大量恶意请求访问不存在的数据时,也会发生缓存穿透,因此API入口处要判断请求参数是否合理,是否存在,是否有非法值,判断出恶意请求直接返回错误。
- 设置空置或者默认值:当线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置空置或者默认值,这样在有请求时就会返回给应用,而不是继续查询数据库。
- 使用布隆过滤器快速判断是否存在,避免通过查询数据据库来判断数据是否存在。在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在,即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库
-
-
-
-
缓存策略动态缓存热点数据
- 由于数据存储受限,只是将一部分热点数据缓存起来,所以要设计一个热点数据动态缓存的策略
- 思路:通过数据最新访问时间来做排名,并过滤掉补偿访问的数据,只留下经常访问的数据
- 举例:以电商为例访问Top1000商品
- 1.先通过缓存系统做一个排序队列,系统会根据商品的访问时间,更新队列信息,越是最近访问越靠前
- 2.系统会定期过滤队列排名最后200个商品,然后在根据ID从另一个缓存数据结构中读取实际的商品信息,并返回
- 3.当请求每次到达的时候,会优先从队列获取商品ID,命中就根据ID在从另一个缓存数据结构中读取实际的商品信息,并返回
- 在Redis中用zadd方法和zrange放回来完成排序队列和获取200个商品的操作
-
常见的缓存更新策略
-
Cache Aisde 旁路缓存策略
- 适合读多写少的场景:因为当写入比较频繁时,缓存中的数据会被频繁地清理,这样会对缓存的命中率有一些影响
-
- 由于数据存储受限,只是将一部分热点数据缓存起来,所以要设计一个热点数据动态缓存的策略
-
- 优化:1.更新数据时也更新缓存,在更新缓存前先加一个分布式锁,只允许一个线程更新缓存,就不会产生并发问题,但会对性能造成一些影响。
- 在写策略的步骤不能倒过来:不能删除缓存在更新数据库,因为在读+写并发时候,会出现缓存和数据库的数据不一致问题。
- 读策略:1.如果读取的数据命中缓存直接返回;2.如果没有命中从数据库中读取数据,然后再写入到缓存中,并且返回给用户
- 写策略:先更新数据库的数据,在删除缓存中的数据
- 但是也可能产生数据不一致问题,但是概率不高,因为缓存的写入通常要快于数据库的写入
- 2.更新数据时更新缓存给缓存加一个较短的过期时间,这样即使出现缓存不一致的情况,缓存的数据也会很快过期,对业务的影响也是可以接受
- 在写策略的步骤不能倒过来:不能删除缓存在更新数据库,因为在读+写并发时候,会出现缓存和数据库的数据不一致问题。
- Read Through读穿/Write Through写穿
- Read Through/Write Through 策略的特点是由缓存节点而非应用程序来和数据库打交道,无论是 Memcached 还是 Redis 都不提供写入数据库和自动加载数据库中的数据的功能。而我们在使用本地缓存的时候可以考虑使用这种策略。
- Read/Write Through(读穿 / 写穿)策略原则是应用程序只和缓存交互,不再和数据库交互,而是由缓存和数据库交互,相当于更新数据库的操作由缓存自己代理了。
- Write Back写回
- 写回适合写多的场景
- Write Back(写回)策略在更新数据的时候,只更新缓存,同时将缓存数据设置为脏的,然后立马返回,并不会更新数据库。对于数据库的更新,会通过批量异步更新的方式进行。
- 实际上,Write Back(写回)策略也不能应用到我们常用的数据库和缓存的场景中,因为 Redis 并没有异步更新数据库的功能。
- 写回适合写多的场景
-
Redis如何实现延迟队列
- 可以使用ZSet的方式来实现延迟消息队列,ZSet有一个Score属性来存储延迟执行的时间;使用zadd score1 value1 命令就可以一直往内存中生产消息再利用zrangebyscore查询符合条件的待处理任务,通过循环队列任务即可
-
-
Redis的大key如何处理
-
什么是大key:key对应的value很大
- String类型的值大于10kb
- hash list set zse类型元素个数超过5000个
-
大key带来的影响
- 客户端超时阻塞:由于Redis执行命令时单线程处理,在操作大key时比较耗时,就会阻塞Redis
- 引发网络阻塞:如果一个key大小1MB,那么每秒访问一千次就接近一个G的流量,这对于千兆网卡服务器是灾难性的
- 阻塞工作线程:如果使用del删除大key时,会阻塞工作线程导致无法处理后续的命令
- 内存分布不均:集群模型在slot分片均匀下,会出现数据和查询倾斜情况,部分大key的Redis节点占用内存多,QPS也会较大
-
查找大key
- redis-cli-bigkeys最好在从节点执行;因为会阻塞如果没有节点,在业务低峰进行扫描查询,或者-i参数控制扫描间隔
- 不足:1.只返回每种类型中最大的key无法获得排列较前的bigkey
- 2.只统计元素个数的多少,而不是实际占用的内存量。
- 3.一个集合中的元素个数多,并不一定占用的内存就多。因为,有可能每个元素占用的内存很小,这样的话,即使元素个数有很多,总内存开销也不大
- 使用SCAN查找大key
- 用type命令获取返回的每一个key类型
- 使用第三方工具RdbTools查找
- redis-cli-bigkeys最好在从节点执行;因为会阻塞如果没有节点,在业务低峰进行扫描查询,或者-i参数控制扫描间隔
-
删除大key
- 1.分批次删除
- 2.异步删除
-
-
Redis管道
- 用于一次处理多个 Redis 命令,使用管道技术(pipeline)可以解决多个命令执行时的网络等待,它是把多个命令整合到一起发送给服务器端处理之后统一返回给客户端,有效地提高了程序的执行效率。
- 避免发送命令过大,或者管道内的数据太多导致网络阻塞;管道技术是客服端提供的功能,而非Redis服务端的功能。
-
Redis支持事务回滚吗
- 并不提供回滚机制;DISCARD命令只是主动放弃事务执行,把暂存的命令队列清空,起不到回滚的效果。
- 事务执行过程中,如果命令入队时没报错,而事务提交后,实际执行时报错了,正确的命令依然可以正常执行,所以这可以看出 Redis 并不一定保证原子性
- 为什么不支持回滚:1.作者认为错误通常是编译错误造成且只会出现在开发环境中,没必要;2.与Redis简单高效设计主旨不符合
- 这里不支持事务回滚,指的是不支持事务运行时错误的事务回滚。
-
如何用Redis实现分布式锁
- 分布式锁适用于分布式环境下并发控制的一种机制,用于空置某个资源在同一时刻只能被一个应用所使用。
- Redis 可以被多个客户端共享访问,是一个共享存储系统,可用来保存分布式锁,并且Redis 的读写性能高,可以应对高并发的锁操作场景
-
-
加锁满足三个条件
- 解锁 就是 del lock_key键删除
- 判断锁的 unique_value 是否为加锁客户端,是,将 lock_key 键删除
- 解锁是两个操作
- 需要使用Lua脚本来保证锁的原子性
-
实现命令
- SER lock_key unique_value NX PX 10000
- lock_key就是key键
- unique_value时客户端生成的唯一标识,区分不同客户端的锁操作
- NX代表lock_key不存在时,才进行操作
- PX 10000代表过期时间为10s,避免客户端发生异常而无法释放锁
- SER lock_key unique_value NX PX 10000
- 1.以原子操作的方式完成,所以,我们使用 SET 命令带上 NX 选项来实现加锁
- 2.锁变量设置过期时间,以免客户端拿到锁后发生异常,锁无法释放;在 SET 命令执行时加上PX 选项,设置其过期时间。
- 3.锁变量的值要区分不同客户端的加锁操作,以免释放时出现误释放操作,用SET命令设置值时,每个客户端设置的值是一个唯一值,用来表示不同的客户端。
- 解锁 就是 del lock_key键删除
-
优点: 性能高;实现方便;避免单点故障
-
缺点:
- 超时时间不好设置
- 锁的超时时间过长会影响性能;超时时间过短保护不到共享资源
- 如何合理设置超时时间:给锁设置一个超时时间,然后启动守护线程,在一段时间后,重新设置这个锁的超时时间,当锁快失效时在进行续约加锁,当主线程执行完成后,销毁即可,但是实现复杂。
- 主从复制模式的数据是异步数据,导致分布式锁的不可靠性
- 在主节点获取到锁后,在没有同步其他节点时,宕机了此时新的主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁
- 解决方法:通过分布式锁算法Redlock(红锁)。基本思路:让客户端和多个独立的 Redis 节点依次请求申请加锁,如果客户端能够和半数以上的节点成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。推荐至少部署5个孤立的主节点,
- 超时时间不好设置
-