从海量数据中查询某一些固定前缀的key

keys命令可以实现,但是redis里key要是特别多,keys命令效率会很低
推荐使用 scan 命令
scan cursor [MATCH pattern] [COUNT count]

  1. # 游标从0开始查找十个k1开头的key
  2. scan 0 match k1* count 10

返回的第一个便是游标
image.png
可以将此游标用来继续查询
image.png
查询到的key可能会重复,可以在代码层做去重处理

删除策略

设置有效期,到期之后不会立即回收,占据着内存

如果不设有效期,一直存在,命中率很低的资源,占据内存,内存的资源没有被合理利用

如何删除:

  1. 定时删除:10次/S,每隔一段时间就会查看设置了过期时间的key,100ms的间隔中默认查看3个key,key太多的时候就会导致有些key没有删除一直存在内存中
  2. 惰性删除:当查询一个已经过时的key,刚好key过期,这个时候才删除key并返回一个空值
  3. 回收算法
    TTL:根据过期时间
    LRU(最近最少使用):根据缓存命中率,推荐!
    不回收(默认行为):满了就只读,不写

淘汰机制

因为redis的删除策略,会导致添加新的数据的时候,内存不够用

内存不足时,会执行淘汰机制,淘汰机制有八种

  1. volatile-lru:在设置了生存时间的key中干掉一个最近最少使用的key
  2. allkeys-lru:在全部key中干掉最近最少使用的key
  3. volatile-lfu:在设置了生存时间的key中干掉一个最近时间段内最少频次使用的key
  4. allkeys-lfu:在全部key中干掉最近最近时间段内最少频次使用的key
  5. volatile-random:在设置了生存时间的key中随机干掉一个
  6. allkeys-random:在全部key中随机干掉
  7. volatile-ttl:在设置了生存时间的key中干掉一个剩余时间最少的key
  8. noeviction:内存不足时直接报错,默认配置

配置文件中配置淘汰机制

指定淘汰机制,配置文件中配置

maxmemory-policy noeviction

设置redis最大内存,单位为字节

maxmemory <bytes>

持久化策略

意义:

当redis服务器发送故障,通过重启快速恢复数据,从而保护数据库,否则,重启将是空壳。

恢复数据时是快照的方式恢复

RDB

产出二进制文件,默认开启方式,在服务启动的时候恢复数据

  • save指令,save指令会马上执行,会阻塞当前redis服务器,直到当前RDB过程完成,线上环境不建议使用该指令
  • bgsave指令,针对save指令阻塞进行优化,后台执行,开启子进程来创建rdb文件,会牺牲一点性能,返回的信息在日志中可看,建议采用image.png
  • 上面两种都是指令的形式,有自动化方案,配置文件添加 save 900 60 ,表示900s内key变化量达到60次,就进行持久化,只要达到了60次没到900s也会持久化;如果900s内没达到60次,就重新计时,通常根据业务需求来设置,普遍前面大后面小
  • RDB相关配置
    save 900 60
    # 文件存储的地址
    dir /redis-7000/data
    # 本地文件名 通常设置为dump-{端口号}.rdb
    dbfilename dump-7000.rdb
    # 存储至本地是否压缩数据,采用LZF压缩 默认开启 关闭可节省CPU运行时间
    rdbcompression yes
    # 是否进行RDB文件格式校验 默认开启 关闭可节约读写10%的时间
    rdbchecksum yes
    # 后台存储中如果出现错误,停止保存操作 默认开启
    stop-writes-on-bgsave-error yes
    

优点:紧凑压缩的二进制文件,效率较高,在某个时间点的数据快照,非常适合用于数据备份,全量复制

缺点:无法做到实时持久化,可能丢失数据

AOF

产出日志文件,默认不开启,配置文件中 appendonly yes 开启,实际上如果开启了aof,redis在启动时只会读取aof文件,而不会读取rdb文件

  • 写数据过程,将指令写入缓存区,将命令同步到AOF文件image.png
  • 触发机制三种策略:
    • everysec 每秒,准确性较高,性能较好,突然宕机会丢失一秒内的数据,建议使用
    • always 每次操作:数据0误差,io性能较差,不建议使用
    • no,由操作系统控制每次同步到AOF文件的周期,过程不可控
  • 重写,比如对一个key进行多次覆盖的操作,aof文件体量越来越大,重写就是再做一份替换掉大体量的,不是在原有的文件上修改
    • 超时的数据不再写入
    • 忽略无效指令,只保留最终数据的写入指令
    • 同一数据的多条写命令合并为一条
    • 手动重写指令bgrewriteaof
    • 如果体量很大,子进程就会很耗时
# 开启aof
appendonly yes
# 持久化文件名
appendfilename "appendonly.aof"
# 触发机制策略
appendfsync everysec
# 自动重写触发条件设置 两种可以都配置也可以只配置一种 不配置就是非重写
# 自动重写的最小尺寸,默认值比较大 和aof_current_size当前缓冲区的大小
# aof_current_size>auto-aof-rewrite-min-size触发自动重写
auto-aof-rewrite-min-size 64mb
# 自动重写的百分比
# 和aof_base_size(基础尺寸)对比 
# (aof_current_size-aof_base_size)/aof_base_size  >= auto-aof-rewrite-percentage 触发
auto-aof-rewrite-percentage 100
# 这里配置的意思就是体量超过64mb开始重写,且比上次重写后的体量增加了100%时触发重写

存储体积较大时,恢复较慢,一个个运行指令的恢复

选择之惑

  • 对数据非常敏感,不允许出错,使用AOF
  • 数据呈阶段有效性,可以承受数分钟内的数据丢失,使用RDB,灾难恢复选用RDB

image.png

应用场景

  • 给数据库提供缓存,不使用
  • 购物车数据存储设计,不使用
  • 抢购,限购类、限量发放,使用
  • 操作先后顺序的数据控制,可以使用
  • 最新消息展示,可以使用
  • 黑白名单
  • 排名功能,使用

RDB-AOF混合持久化

redis4.0新功能,redis5中默认打开
开启后,AOF在重写时会直接读取RDB中的内容,重写后的新 AOF 文件前半段是 RDB 格式的全量数据,后半段是 AOF 格式的增量数据。
在配置文件中修改

aof-use-rdb-preamble yes|no

事务

开启事务multi

然后进行操作

提交事务exec,才会顺序执行操作命令

如果开启事务的过程中发现有错误

discard 取消事务

事务执行流程

image.png

监听

使用起来很简单

开启监听watch keyname ,关闭监听 unwatch

事务开启前最好是开启监听,这样多线程场景下其他线程在事务提交更新了key value那么当前事务提交必然失败

有点类似乐观锁

就需要关闭然后再次开启

高可用架构

主从,哨兵,集群是三种东西
image.png

主从复制架构

从节点要去找主节点

从节点的配置文件添加,指定主节点的ip和端口

我的版本中replicaof取代了原先的slaveof

replicaof <masterip> <masterport>
# 如果主节点配置了密码 这里就要配置
masterauth <masterpassword>

哨兵机制

主节点挂掉,从节点找不到主节点,整个主从架构就失效,所以需要用到哨兵

在每个redis节点中都要启动哨兵,哨兵会在主节点挂掉的时候在从节点中重新选举出新的主节点
image.png

配置哨兵要修改sentinel.conf配置文件,每一个节点都要配置

# 哨兵服务端口号
port 26379
# 哨兵工作信息目录
dir /tmp
# 启动守护进程
daemonize yes
# 指定master节点ip和端口 
# sentinel monitor <给master起的名字> <masterip> <端口> <几个从节点>
sentinel monitor master 192.168.101.50 6379 2
# 哨兵每隔多久监听一次redis架构
sentinel down-after-milliseconds master 30000
# 主节点的名称和密码 master和slave需要密码相同
sentinel auth-pass master 123

在每个节点使用命令启动哨兵,指定哨兵配置文件启动

redis-sentinel sentinel.conf

集群

RedisCluster的理解

  1. 解决哨兵+主从的基本功能之外解决存储数据量的问题
  2. 没有中间层,客户端可以直接跟RedisCluster的任一节点连接
  3. ping-pang机制,每个节点之间相互通信,如果没有返回信息就被认为是挂掉了
  4. 投票机制,集群内投票认为哪个节点挂掉
  5. 每个主节点都支持读写操作,分担写的压力,后续可以继续增加新的主机,做hash迁移,新主机就可以分担压力
  6. 每个节点负责的区域不同,进行set的时候根据key来做crc16算法,从而得到一个数值,数值%16384=(0~18383),从而确定存储到哪个节点
  7. 每个节点都可以做主从,保证高可用
  8. 后期支持新增主节点和从节点,需要做数据迁移,从之前的主节点迁移部分数据到新增主节点
  9. 每一个集群节点,至少要跟着一个从节点
  10. 超过半数节点宕机,redis集群瘫痪

image.png

配置文件

# 开启集群
cluster-enabled yes
# 节点信息文件名
cluster-config-file nodes.conf
cluster-node-timeout 5000
# 集群对外ip地址
cluster-announce-ip 111.229.225.62
# 节点端口
cluster-announce-port 7000
# 节点间通信的端口
cluster-announce-bus-port 17000

集群具体搭建看https://www.yuque.com/kelisidiyanuodi/dttdng/mqa1so#b96b8066

Redis是多线程还是单线程?

Redis 确实是单线程模型,指的是执行 Redis 命令的核心模块是单线程的,而不是整个 Redis 实例就一个线程,Redis 其他模块还有各自模块的线程的。

单线程+多路IO复用

Redis的网络请求模块是单线程+多路IO复用——非阻塞IO

一个线程处理所有网络请求,其他模块仍用了多个线程。

阻塞IO:发送请求,没有回复就一直阻塞不能进行其他操作直到回复

非阻塞IO:发送请求,没有回复就一直发送

IO多路复用:多个客户端发起请求,这些IO操作会被暂时挂起,入内存队列,服务端可以选择什么时候读取处理这些IO,所以服务端可以同时hold多个IO,多路复用指使用一个线程来检测多个文件描述符(socket)的就绪状态,如果有一个文件描述符就绪,则返回,否则阻塞直到超时,得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动一个线程执行

多路复用又包括select,poll,epoll

select:调用select函数后,进程会阻塞,直到描述符就绪或者超时,函数返回;但是有文件描述符数量的限制,一般是1024

poll:与 select() 类似,但是 poll() 没有最大文件描述符数量的限制,基于链表来存储

epoll:给监视的请求设置标识符,标识符显示就绪状态,就不需要阻塞,在传递内核与用户空间的消息时使用了内存共享,而不是内存拷贝,在描述符读写就绪时,通过回掉函数将自己加入就绪队列中,描述符数目也不受限制

redis使用的就是epoll

缓存问题

缓存预热

现象:服务器启动后迅速宕机

问题:

  1. 请求数量较高
  2. 主从之间数据吞吐量较大,数据同步操作频度较高

解决方案:

  • 前置准备工作
    • 统计数据访问记录,统计访问频度较高的热点数据
    • 利用LRU数据删除策略,构建数据留存队列,例如storm+kafka
  • 准备工作
    • 根据级别,让redis优先加载级别较高的热点数据
    • 利用分布式多服务器同时进行数据读取
  • 实施
    • 使用脚本程序固定触发数据预热过程
    • 使用CDN

缓存预热就是系统启动前,提前将相关缓存数据直接加载到缓存系统,避免在用户请求的时候先查询数据库

缓存雪崩

忽然数据库连接激增,应用服务器无法及时处理请求,大量408,500错误页面

数据库崩溃 -> 应用服务器崩溃 ->重启应用服务器无效 ->redis服务器崩溃 ->redis集群崩溃->重启数据库再次被瞬间流量放倒

问题:

  1. 大量的key在同一时间过期
  2. redis未命中,向数据库获取数据,导致数据库访问量激增,数据库忙不过来

解决方案:

  • 不让大量缓存在同一时间失效,比如定时任务刷新缓存,随机设置有效时间,或者不设置有效时间(永久有效)
  • 页面静态化处理
  • Nginx缓存+redis缓存+ehcache缓存
  • 对数据库排除,例如超时查询、耗时较高事务
  • 灾难预警机制
  • 限流、降级,待业务低俗运转后再初步放开
  • LRU与LFU切换
  • 根据业务有效期进行分类错峰,A类90分钟、B类80分钟、C类70分钟
  • 定期维护
  • 超热数据永久key
  • 加锁,谨慎使用

缓存击穿

并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力、数据库崩溃

问题:

  1. 缓存中没有该数据,但数据库中有数据
  2. 某个key过期,但该key访问量巨大,请求都打到数据库上了
  3. 多个请求压到redis后,均未命中
  4. 短时间大量对数据库同一数据访问

解决方案:

  • 加大key过期时间或永久key,但是永久key不太好
  • 设置不同失效时间
  • 后台启动定时任务,定时更新,刷新数据有效期
  • 访问数据库的时候用上分布式锁

缓存穿透

redis命中率下降,数据库没有该数据,而用户不断请求,导致数据库崩溃

问题:

  1. 这时的用户很可能是攻击者、黑客,攻击,疯狂访问id不存在的数据,导致数据库压力过大
  2. 缓存和数据库中都没有数据
  3. redis大面积未命中
  4. 大量非正常URL访问
  5. redis未持久化

解决方案:

  • 布隆过滤器
  • 后端接口增加校验,如用户鉴权,请求校验,拦截器,拦截不合法的参数请求
  • 缓存null,对查询结果为空的数据缓存处理,设定短过期时间
  • 实时监控redis命中率与null数据对比

性能指标监控

命令:

  • redis-benchmark [-h] [-p] [-c] [-n ] [-k],直接在linux里用,不需要连接redis-cli客户端
    • 举例1:redis-benchmark 50个连接,10000此请求对应的性能
    • 举例2:redis-benchmark -c 100 -n 5000 100个连接,5000次请求性能
  • redis-cli,连接redis-cli客户端后使用的指令
    • monitor
    • showlog [operator]
      • get:获取慢查询日志
      • len:获取慢查询日志条目数
      • reset:重置慢查询日志