Redis基本使用

1.redis几大应用场景

1.1.1 缓存

缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。

1.1.2 排行榜

很多网站都有排行榜应用的,如月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。

1.1.3 计数器

什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。

1.1.4 分布式会话

集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。

1.1.5 分布式锁

在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。

1.1.6 最新列表

Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。

1.1.7 消息系统

消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。

1.2.1 实际案例

1.秒杀(以前的经历)

  • 1.第一步是我们用户进入商品秒杀页面,点击秒杀按钮,向服务端发送秒杀请求。
  • 2.服务端在接受到用户秒杀请求,根据请求的商品id参数,去查询数据库中该商品id的库存量。
  • 3.当查询到该商品库存量后,进行判断。如果库存量不足,则返回给用户,商品库存不足的信息。
  • 4.当查询到该商品的库存足够时,则生成订单数据并减少商品库存。接着将成功信息返回给用户。
  • 5.用户接受到抢购成功消息后,才可进入下单页面。此时按照正常逻辑,进行下单支付。

1.2.2 卖超问题:

假如此时我们查询到的商品库存为 1,这时候就会走 4 中上面的部分 (插入抢购信息并减少库存),由于并发量大的情况下,下一个请求在上一个还未执行减库操作就去查询了商品库存,这时候查询出来的库存量依然是 1。同样的,会走到 4 上面的步骤中去。然后上一个请求执行了减库操作,此时库存为 0,第二个请求再去减库时,就会把库存量设置为 - 1,这样就出现了超卖情况。

1.2.3 Redis解决卖超问题的实现 1.在秒杀前将商品的库存信息加入到 Redis 缓存list中。

 $numberArr = range(1,100); 
 $redis->>lPush('goods:queue:5',...$numberArr); 

2.当秒杀开始时,用户发送请求,每次去检测一下商品的队列是否为空,当非空时,则使用 lpop 减少一个长度,也就是减少一个库存量。这时候将秒杀的信息写入到缓存中去,给缓存信息配一个唯一的键,将该键返回给用户。(由于 lpop 是原子性的,即是大量并发来了,也是要在 Redis 内部进行排队执行的,假如在判断是否为空时,检测到是非空,进行 lpop 操作,由于队列是空,这时候去执行出队列也是返回错误的)。

  1. 利用秒杀成功队列中的键值,将秒杀中的缓存信息写入数据库并生成对应的订单。

2.redis持久化、主从复制和哨兵

2.1 Redis持久化基本概述

持久化的功能:Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将Redis中的数据以某种形式从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。

2.1.1 RDB持久化

RDB持久化是将当前进程中的数据生成快照保存到硬盘,保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。

触发条件: 手动触发 bgsave命令 save: save 执行一个同步操作,以RDB文件的方式保存所有数据的快照。 bgsave: 执行一个异步操作,redis使用Linux系统的fock()生成一个子进程来将DB数据保存到磁盘,主进程继续提供服务以供客户端调用。如果操作成功,可以通过客户端命令LASTSAVE来检查操作结果。

自动 save m n的实现原理: m 秒内有至少有 n个键被改动,就会触发。Redis的save m n,是通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。 

2.1.2 AOF持久化

AOF持久化,则是将Redis执行的每次写命令记录到单独的日志文件中。 开启: appendonly yes AOF持久化的三种策略:配置文件配置 Redis 多久才将数据记录到磁盘一次

  • always:每次有新命令追加到 AOF 文件时就执行一次 fsync 。
  • everysec:每秒 fsync 一次,故障时只会丢失 1 秒钟的数据
  • no:从不同步
AOF重写:

因为 AOF 的运作方式是不断地将命令追加到文件的末尾, 所以随着写入命令的不断增加, AOF文件的体积也会变得越来越大。(100次INCR ,实际一个SET就可以搞定)Redis对AOF文件进行重建(rebuild)。执行 bgrewriteaof 命令, Redis 将生成一个新的AOF文件, 这个文件包含重建当前数据集所需的最少命令。

作用:

  • 1.减少磁盘占用量
  • 2.加速数据恢复

auto-aof-rewrite-min-size 64M 触发AOF文件执行重写的最小尺寸 auto-aof-rewrite-percentage 100 触发AOF文件执行重写的增长率(当前写入日志文件的大小超过上 一次rewrite之后的文件大小的百分之100时就是2倍时触发rewrite)

RDB和AOF的对比

2.2.1 Redis主从复制

为了使得集群在一部分节点下线或者无法与集群的大多数节点进行通讯的情况下, 仍然可以正常运作,Redis 集群对节点使用了主从复制功能: 集群中的每个节点都有 1 个至 N 个复制品, 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。简单的来说就是一个主节点master可以拥有一个甚至多个从节点的slave。 原理:从服务器都将向主服务器发送一个SYNC命令。接到 SYNC 命令的主服务器将开始执行 BGSAVE ,并在保存操作执行期间,将所有新执行的写入命令都保存到一个缓冲区里面。当 BGSAVE 执行完毕后,主服务器将执行保存操作所得的 .rdb 文件发送给从服务器,从服务器接收这个 .rdb 文件,并将文件中的数据载入到内存中。 其中主节点以写为主,从节点只能读不可写入。其中主节点写入的数据会同步到salve上,这样如果主节点出现故障,数据丢失,则可以通过salve进行恢复。

优点:主从模式很好的解决了数据备份问题,并且由于主从服务数据几乎是一致的,因而可以将写入数据的命令发送给主机执行,而读取数据的命令发送给不同的从机执行,从而达到读写分离的目的。 缺点:

  1. 每个客户端连接redis实例的时候都是指定了ip和端口号的,如果所连接的redis实例因为故障下线了,而主从模式也没有提供一定的手段通知客户端另外可连接的客户端地址,因而需要手动更改客户端配置重新连接
  2. 主从模式下,如果主节点由于故障下线了,那么从节点因为没有主节点而同步中断,因而需要人工进行故障转移工作
  3. 无法实现动态扩容,内存消耗问题。
本地Windows下测试步骤:
1.准备window for Redis分别命名为redis-6379,redis-6380
2.修改redis-6380下的redis.windows.conf
port 6380
# slaveof <masterip> <masterport>
slaveof 127.0.0.1 6379
启动server服务
port 6380 redis-server.exe  redis.windows.conf
在port6379服务上set数据,在从服务上也能get到数据
* 主节点能读写,从节点只能读取不能写入
* 如果master设置了密码。需要配置:masterauth

2.2.2 哨兵模式

为了解决Redis的主从复制的不支持高可用性能,Redis实现了哨兵机制解决方案。由一个或多个哨兵去监听任意多个主服务以及主服务器下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线的主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已经下线的从服务器,并且哨兵可以互相监视。

概念:
每个 Sentinel 以每秒钟一次的频率向它所知的主服务器、从服务器以及其他 Sentinel 实例发送一个 PING 命令。

主观下线:指的是单个 Sentinel 实例对服务器做出的下线判断。
客观下线:指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断

优点

  1. Master 状态监测
  2. 如果Master 异常,则会进行Master-slave 转换,将其中一个Slave作为Master,将之前的Master作为Slave 
  3. Master-Slave切换后,master_redis.conf、slave_redis.conf和sentinel.conf的内容都会发生改变,即master_redis.conf中会多一行slaveof的配置,sentinel.conf的监控目标会随之调换(之前slave_redis.conf端口换成新的master端口) 缺点:
  4. 如果是从节点下线了,sentinel是不会对其进行故障转移的,连接从节点的客户端也无法获取到新的可用从节点
  5. 无法实现动态扩容

3.内存淘汰机制

假如你 redis 只能存 5G 数据,可是你写了 10G,那会删 5G 的数据。怎么删的,这 个问题思考过么?还有,你的数据已经设置了过期时间,但是时间到了,内存占 用率还是比较高,有思考过原因么?

Redis 采用的是定期删除 + 惰性删除策略。

定时删除,用一个定时器来负责监视 key, 过期则自动删除。虽然内存及时释放, 但是十分消耗 CPU 资源。在大并发请求下,CPU 要将时间应用在处理请求,而不是删除 key, 因此没有采用这一策略。

  1. 定期删除,redis 默认每个100ms检查,是否有过期的 key, 有过期key则删除。需要说明的是,redis 不是每个100ms将所有的key检查一次,而是随机抽取进行检查 (如果每隔100ms, 全部key进行检查,redis 岂不是卡死)。因此,如果只采用 定期删除策略,会导致很多key到时间没有删除。

  2. 于是,惰性删除派上用场。也就是说在你获取某个 key 的时候,redis会检查一 下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

    如果定期删除没删除 key。然后你也没即时去请求 key,也就是说惰性删除也没生效。这样,redis 的内存会越来越高。那么就应该采用内存淘汰机制。 在 redis.conf 中有一行配置 # maxmemory-policy volatile-lru 该配置就是配内存淘汰策略的noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。

  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除近少使用的 key。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除近少使用的 key。这种情况一般是把 redis 既当缓存,又做持久化存储的时候才用。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。

4.缓存预热、穿透、雪崩、更新

缓存雪崩: 由于原有缓存失效,新缓存未到期间 (例如:我们设置缓 存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库 CPU 和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。 解决方案: 1.考虑用加锁或者队列或者限流的方式的方式保证来保证不会有大量的线程对数据库一次性进行读写。 2.随机增加失效时间,比如 1-5 分钟,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

缓存穿透: 缓存穿透是指用户查询数据,在数据库没有,在缓存中没有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次 无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

  1. 对于每一个缓存key都有一定的规范约束,这样在程序中对不符合parttern的key 的请求可以拒绝。
  2. 布隆过滤器:将可能出现的缓存key的组合方式的所有数值以hash形式存储在一个很大的bitmap中,后端每次新增一个可能的组合就同步一次。
  3. 对空结果缓存:如果对应在数据库中的数据都不存在,我们将此key对应的value设置为一个默认的值,比如“NULL”,并设置一个较短的缓存的失效时间(常用)

** 缓存预热:** 缓存 预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求 的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据。

缓存更新:

  1. 定时去清理过期的缓存;
  2. 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系 统得到新数据并更新缓存。

打 赏