Redis详解

基础

Redis数据类型

  • string
  • hash
  • list
  • set
  • sorted set

Redis过期策略

两种过期策略:定期删除+惰性删除

  1. 定期删除

    redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查是否过期,如果过期就删除。有两个要点:

    • 定期删除指每隔一段时间对数据库做一次检查,删除过期key
    • 由于不可能对所有key做轮询删除,所有redis每次随机抽取一些key做检查和删除
    1. 为什么要随机?

      假如redis存了几十万个key,每隔100ms遍历所有设置过期时间的key的话,就会给CPU带来很大的负载。

    2. 为什么不用单个key的到期删除策略?

      这种需要用一个定时器来负责监视key,过期则删除。虽然内存及时释放,但过多的定时器十分消耗CPU资源,得不偿失。

  2. 惰性删除

    惰性删除指我们查询key的时候才对key进行检查,如果过期则删除。显然,它有一个缺点是如果过期的key没有被访问,那么它就一直无法被删除,一直占用内存。

定期删除+惰性删除存在的问题:

如果某个key过期后,定期删除没有删除成功,也没有再次访问key,这时,如果大量这种key堆积在内存中,redis的内存会越来越高,导致redis内存耗尽。此时就应该采用内存淘汰机制

在这里插入图片描述

  • noeviction:不进行淘汰数据。一旦缓存被写满,再有写请求进来,Redis就不再提供服务,而是直
    接返回错误。Redis 用作缓存时,实际的数据集通常都是大于缓存容量的,总会有新的数据要写入缓
    存,这个策略本身不淘汰数据,也就不会腾出新的缓存空间,我们不把它用在 Redis 缓存中。
  • volatile-lru:在设置了过期时间的键值对中,移除最近最少使用(最近最久未使用)的键值对。
  • volatile-random:在设置了过期时间的键值对中,随机移除某个键值对。
  • volatile-ttl:在设置了过期时间的键值对中,移除即将过期的键值对。
  • volatile-lfu:在设置了过期时间的键值对中,移除最近最不频繁使用的键值对, 或者移除 最不经常
    使用的键值对。
  • allkeys-lru:在所有的键值对中,移除最近最少使用(最近最久未使用)的键值对。
  • allkeys-random:在所有键值对中,随机移除某些key。
  • allkeys-lfu:在所有的键值对中,移除最近最不频繁使用的键值对, 或者移除 最不经常使用的键值对.。

通常情况下推荐优先使用allkeys-lru策列。

Redis为什么快

Redis的速度非常快,单机就可以支撑每秒十几万的并发,相对MySQL来说,性能是MySQL的几十倍。速度快主要有以下几点:

  • 完全基于内存操作
  • 使用单线程,避免线程切换和竞态产生的消耗
  • 基于非阻塞的IO多路复用机制
  • C语言实现,优化过的数据结构,基于几种基础的数据结构,大量的优化,性能极高

Redis中只有网络请求模块和数据操作模块是单线程,而其他的如持久化存储模块、集群支撑模块等是多线程的。

Redis的持久化

Redis持久化机制有两种:RDB和AOF

  • RDB

    RDB、AOF作为redis持久化机制,用于crash后,redis的数据恢复。这里对这两种机制的原理介绍不做讲解,因为很基础,而且网上对它们的介绍文章数不胜数。这里讲讲它们的优势、劣势和生产中推荐的配置。

    优势:

    • 只有一个文件dump.rdb,方便持久化

    • RDB相当于内存的一个快照,数据恢复快

    劣势:

    • bgsave时间长,丢失数据多
  • AOF

    优势:

    • AOF可以配置appendfsync属性为always,每进行一次操作旧记录到AOF文件中一次,数据最多丢失一次
    • rewrite机制,缩小文件体积

    劣势:

    • AOF文件大,通常比RDB文件大很多
    • 比RDB持久化启动效率低,数据集大时较为明显
    • AOF文件体积可能迅速变大,需要定期执行重写操作来降低文件体积
  • RDB和AOF如何选择

    为了达到数据安全性,应该同时使用两种持久化功能

    save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
    save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
    save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
    
    appendonly yes #开启aof持久化
    appendfsync always #每次有数据修改发生时都会写入AOF文件。
    appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
    appendfsync no #从不同步。高效但是数据不会被持久化。
    

高级特性

Redis主从复制

Redis可以使用主从同步,从从同步。

第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制目标节点接受完成后将RDB镜像加载到内存。

加载完成后,再通知主节点将期间修改的操作记录同步到复制节点,进行重载就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。所以主从复制需要RDB和AOF两种持久化机制。

  • 主从复制实现原理

    总的来说主从复制的步骤分为6个步骤:

    1. 设置主节点的地址和端口
    2. 建立套接字连接
    3. 发送PING命令
    4. 权限验证
    5. 同步数据
    6. 命令传播

    为了测试,本地机开启两个Redis节点,一主一从,分别监听6379端口(主)和6380端口(从)

    主redis可以先启动起来,加入从节点不影响主节点的运行。

    1. 设置主服务器的地址和端口

      在从服务器的配置文件中配置需要同步的主服务器信息,包括IP、端口。

      • 配置文件

        在从服务器配置文件中加入: 低版本slaveof masterip masterport,高版本replicaof masterip masterport

      • 启动命令:

        redis-server启动命令后加入: 低版本--slaveof masterip masterport,高版本--replicaof masterip masterport

      • 客户端命令

        redis从服务器启动后,直接通过客户端执行命令:低版本slaveof masterip masterport,高版本replicaof masterip masterport

      上述3种方式是等效的,执行其一即可。具体执行语句是什么,可以参考官网。

    2. 建立套接字连接

      执行完slaveof masterip masterport,在6380从服务器上执行info replication命令,查看6380服务器的角色:slave

在这里插入图片描述

 6379服务器角色:master

在这里插入图片描述

  1. 发送PING命令

    建立socket连接之后,redis从节点发送ping命令,检查socket连接是否可用。

  2. 身份验证

    主节点配置了masterauth选项后,从节点需要向主节点进行身份验证。具体做法是,在主节点的配置文件中配置了masterauth 密码,从节点的配置文件相应添加masterauth 密码即可。

  3. 同步

    同步就是将从节点的数据库状态更新成主节点当前的数据库状态。从节点向主节点发送psync命令(Redis2.8以前是sync命令),开始同步。 数据同步阶段是主从复制最核心的阶段,根据主从节点当前状态的不同,可以分为全量复制和部分复制。

  4. 命令传播

    经过上面同步操作,此时主从的数据库状态其实已经一致了,但这种一致的状态的并不是一成不变的。 在完成同步之后,也许主服务器马上就接受到了新的写命令,执行完该命令后,主从的数据库状态又不一致。

    数据同步阶段完成后,主从节点进入命令传播阶段;在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。

    另外命令转播我们需要关注两个点: 延迟与不一致 和 心跳机制

主从复制完成,redis主从复制的特点是master负责写操作,从节点负责读。因为从节点配置文件默认配置了replica-read-only yes导致从节点执行写操作返回错误,读写分离的模式能很大程度提高redis的并发能力。

在这里插入图片描述

在这里插入图片描述

Redis哨兵模式

Redis主从复制有什么缺陷呢?很明显,当redis master宕机后,整个redis将不可用,需要人工干预来重新启动redis主从模式。在生产上是万万不能真么操作,不符合高可用(HA)特性。而Redis哨兵模式正好可以解决这个问题。

在这里插入图片描述

当主节点挂了的时候,sentinel集群通过选举机制在剩下的被监视存活的节点中重新选举master,以保证redis服务对外的高可用。

Redis集群

那么Redis哨兵模式又有什么缺陷呢?试想,当redis保存的数据有几十G上百G时,一台服务器完全放不下时怎么办,很显然,哨兵模式是没办法解决这个问题,哨兵模式下的每台redis服务器都是保存的全量数据,不足以支撑大量数据,而集群分片却能很好解决这一问题。

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

在这里插入图片描述

前面说到了主从存在高可用和高扩展的问题,哨兵解决了高可用的问题,而集群就是终极方案,一举解决高可用和高扩展问题。

  • 数据分区:

    数据分区 (或称数据分片) 是集群最核心的功能。集群将数据分散到多个节点,一方面 突破了 Redis 单机内存大小的限制,存储容量大大增加;另一方面每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。数据分片是高扩展的基础。

  • 高可用:

    集群支持主从复制和主节点的 自动故障转移 (与哨兵类似),当任一节点发生故障时,集群仍然可以对外提供服务。

这有一篇redis集群非常详细的讲解认识Redis集群——Redis Cluster - JJian - 博客园 (cnblogs.com)

场景应用

redis分布式锁

setnx+expire

Bitmap

一个经典面试场景,1000W级用户场景的签到优化:

优化1:利用Bitmap实现用户签到存储优化

假如有1000万用户,平均每人每年签到次数为10次,一年下来,则这张表数据量为多少呢?

数据比较吓人: 1亿条

每签到一次需要使用(8 + 8 + 1 + 1 + 3 + 1)共22 字节的内存,一个月则最多需要600多字节

我们如何能够简化一点呢?

其实可以考虑小时候一个挺常见的方案,就是小时候,咱们准备一张小小的卡片,你只要签到就打上一
个勾,我最后判断你是否签到,其实只需要到小卡片上看一看就知道了。我们可以采用类似这样的方案来实现我们的签到需求。我们按月来统计用户签到信息,签到记录为1,未签到则记录为0。把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路就称为位图(BitMap)。这样我们就用极小的空间,来实现了大量数据的表示。

在这里插入图片描述

那么,一个月的签到数据,可以使用一个无符号整数来存储。之前一个月要 600个字节,现在保存一个人一个月的签到数据, 只要 24个字节 ,一下就提升了 25倍。

优化2:利用Redis Bitmap实现用户签到的性能优化:

刚好redis 有Bitmap结构。可以利用Redis Bitmap实现用户签到的性能优化,将当前用户当天签到信息保存到Redis中。
在这里插入图片描述

redis的吞吐量为 2Wqps以上, 完全可以满足 1000W用户签到的需求。但是 SpringCloud 微服务够呛,怎么办呢?

优化3:利用Nginx +lus 实现更进一步的用户签到的性能优化:

SpringCloud 微服务够呛,但是nginx可以,nignx的吞吐量为 2Wqps以上, 完全可以满足 1000W用户签到的需求。
在这里插入图片描述

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

  • 缓存雪崩

    某⼀时刻发⽣⼤规模的缓存失效的情况,例如缓存服务宕机、大量key在同一时间过期,这样的后果就是⼤量的请求进来直接打到DB上,db无响应,最后可能导致整个系统的崩溃,称为雪崩。

    对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了:

    • 缓存全盘宕机,缓存挂了
    • 大量key在同一时间过期

    此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后db无响应,最后导致整个系统的崩溃。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。

    解决方案

    • 提高缓存可用性:
      1. 集群部署:通过集群来提升缓存的可用性,可以利用Redis本身的Redis Cluster或者第三方集群方案如Codis等。
      2. 多级缓存:设置多级缓存,设置一级缓存本地 guava 缓存,第一级缓存失效的基础上再访问二级缓存 redis,每一级缓存的失效时间都不同。
    • 过期时间:
      1. 均匀过期:为了避免大量的缓存在同一时间过期,可以把不同的 key 过期时间随机生成,避免过期时间太过集中。
      2. 热点数据永不过期。
    • 熔断降级:
      1. 服务熔断:当缓存服务器宕机或超时响应时,为了防止整个系统出现雪崩,可以使用hystrix 类似的熔断,暂时停止业务服务访问db, 或者其他被依赖的服务,避免 MySQL 被打死。
      2. 服务降级:当出现大量缓存失效,而且处在高并发高负荷的情况下,在业务系统内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的 fallback(退路)错误处理信息。
  • 缓存击穿

    一个并发访问量比较大的key在某个时间过期,导致所有的请求直接打在DB上。具体来是,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

    解决方案:

    • 预先设置热门数据:在redis高峰访问时期,提前设置热门数据到缓存中,或适当延长缓存中key过期时间。
    • 实时调整:实时监控哪些数据热门,实时调整key过期时间。
    • 对于热点key设置永不过期。
    • 加锁更新:⽐如请求查询A,发现缓存中没有,对A这个key加锁,同时去数据库查询数据,写⼊缓存,再返回给⽤户,这样后⾯的请求就可以从缓存中拿到数据了。
  • 缓存穿透

    缓存穿透指的查询缓存和数据库中都不存在的数据,这样每次请求直接打到数据库,就好像缓存不存在一样。对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。缓存穿透可能有两种原因:

    • 自身业务代码问题
    • 恶意攻击,爬虫造成空命中

    解决办法:

    • 对空值缓存:如果一个查询数据为空(不管数据是否存在),都对该空结果进行缓存,其过期时间会设置非常短。
    • 采用布隆过滤器:布隆过滤器可以判断元素是否存在集合中,他的优点是空间效率和查询时间都比一般算法快,缺点是有一定的误识别率和删除困难。
    • 设置可以访问名单:使用bitmaps类型定义一个可以访问名单,名单id作为bitmaps的偏移量,每次访问时与bitmaps中的id进行比较,如果访问id不在bitmaps中,则进行拦截,不给其访问。
    • 进行实时监控:对于redis缓存中命中率急速下降时,迅速排查访问对象和访问数据,将其设置为黑名单。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EzrealYi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值