Redis 学习笔记

redis学习笔记之数据结构
redis有五种数据结构,分别是String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)

redis的集合和列表都可以存储多个字符串,它们之间的不同是:
列表可以存储多个相同的字符串;
集合通过使用散列表保证自己存储的每个字符串是各不相同的;
散列可以存储多个键值对之间的映射,它可以存储字符串或数字;
有序集合存储键值对,键被称为成员,每个成员不同。

分布式锁实现(redis场景)还有缓存
在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。

string:常用命令: set(mset批量设置),get,strlen,exists,dect,incr,setex 等等。
应用场景 :一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。

list(双向链表):常用命令: rpush,lpop,lpush,rpop,lrange、llen 等。
应用场景: 发布与订阅或者说消息队列、慢查询。

hash :特别适合用于存储对象,一个 string 类型的 field 和 value 的映射表
常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。
应用场景: 系统中对象数据的存储。

set:set 类似于 Java 中的 HashSet 。Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择
常用命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion 等
应用场景: 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景

sorted set:有序排列(zset)
数据结构:跳表实现,链表加多级索引的结构,就是跳表
对链表建立一级 索引,每两个节点(或多个)提取一个结点到上一级,被抽出来的这级叫做 索引 或 索引层。
至于提取的极限,是同一层只有两个节点
跳表插入的时间复杂度是O(logN),空间复杂度是O(N)


常用命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。
应用场景: 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。

Redis是如何判断数据是否过期的呢?
Redis 通过一个叫做过期字典(可以看作是hash表)来保存数据过期的时间。过期字典的键指向Redis数据库中的某个key(键),过期字典的值是一个long long类型的整数,这个整数保存了key所指向的数据库键的过期时间
设置过期时间是因为内存是有限的
过期时间除了有助于缓解内存的消耗,还有什么其他用么?
很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在1分钟内有效,用户登录的 token 可能只在 1 天内有效。

过期的数据的删除策略了解么?
惰性删除 :只会在取出key的时候才对数据进行过期检查。这样对CPU最友好,但是可能会造成太多过期 key 没有被删除。
定期删除 : 每隔一段时间抽取一批 key 执行删除过期key操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
Redis 采用的是 定期删除+惰性/懒汉式删除 定期删除对内存更加友好,惰性删除对CPU更加友好
EXPIRE和PERSIST命令,Redis key的过期时间和永久有效设置

仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就Out of memory了。

怎么解决这个问题呢?答案就是: Redis 内存淘汰机制。
Redis 提供 6 种数据淘汰策略:
volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
4.0 版本后增加以下两种:

volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
快照(snapshotting)持久化(RDB)
Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期


快照持久化是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置:

save 900 1           #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 300 10          #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 60 10000        #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
1
2
3
4
5
AOF(append-only file)持久化(只追加文件)
与快照持久化相比**,AOF 持久化 的实时性更好**,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:(appendonly yes)
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的 AOF 文件,在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no        #让操作系统决定何时进行同步
1
2
3
优缺点是什么?
优点:
AOF文件比RDB更新频率高,优先使用AOF还原数据。
AOF比RDB更安全 (aof同步策略保证每次发生的数据变化都会被立即记录到磁
盘中)
RDB性能比AOF好
rdb容灾性好,方便备份
如果两个都配了优先加载AOF
缺点:
AOF 文件比 RDB 文件大,且恢复速度慢

集群方案
哨兵模式

哨兵的介绍
sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能:

集群监控:负责监控 redis master 和 slave 进程是否正常工作。
消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
哨兵的核心知识

哨兵至少需要 3 个实例,来保证自己的健壮性。

如果master宕机,sentinel 1 和 sentinel 2 只要有一个认为master宕机就会进行切换,同时sentinel 1 和sentinel 2 就会选出一个sentinel来进行故障转移,这个时候就需要用到majority,即大多数哨兵都是运行的,2个哨兵的majority是2(3个的majority=2,4个的majority=2,5个的majority=3),也就是说现在这两个哨兵节点都是正常运行的就可以进行故障转移。

但是,现在master和sentinel1运行的整个机器如果宕机了的话,那么哨兵就只有一个了,此时就无法来通过majority来进行故障转移了,所以,我们至少需要三台哨兵实例。

哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
数据丢失的问题,那我们该怎么去解决呢?
redis哨兵主备切换的数据丢失问题。
1.主从异步复制导致的数据丢失
2.脑裂导致的数据丢失(脑裂其实就是网络分区导致的现象)
核心思想就是,一旦所有的slave节点,在数据复制和同步时延迟了超过10秒的话,那么master它就不会再接客户端的请求了,这样就会有效减少大量数据丢失的发生。
对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
redis 哨兵底层核心原理深度解析
1.sdown和odown转换机制
sdown,即主观宕机,如果一个哨兵它自己觉得master宕机了,就是主观宕机
odown,即客观宕机,如果quorum数量的哨兵都认为一个master宕机了,则为客观宕机
哨兵在ping一个master的时候,如果超过了is-master-down-after-milliseconds指定的毫秒数之后,就是达到了sdown,就主观认为master宕机了。

如果一个哨兵在指定时间内,收到了quorum指定数量的其他哨兵也认为那个master是sdown了,那么就认为是odown了,客观认为master宕机,就完成了sdown到odown的转换。
2、哨兵集群的自动发现机制
哨兵互相之间的发现,是通过redis的pub/sub系统实现的,每个哨兵都会往__sentinel__:hello这个channel里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在
3.slave配置的自动纠正
哨兵会负责自动纠正slave的一些配置,比如slave如果要成为潜在的master候选人,哨兵会确保slave在复制现有master的数据; 如果slave连接到了一个错误的master上,比如故障转移之后,那么哨兵会确保它们连接到正确的master上
4.slave->master选举算法
考虑slave的一些信息
(1)跟master断开连接的时长 (2)slave优先级 (3)复制offset (4)run id
5.quorum和majority
如果quorum < majority,比如5个哨兵,majority就是3,quorum设置为2,那么就3个哨兵授权就可以执行切换
但是如果quorum >= majority,那么必须quorum数量的哨兵都授权,比如5个哨兵,quorum是5,那么必须5个哨兵都同意授权,才能执行切换

redis 集群模式的工作原理
Redis Cluster是一种服务端Sharding(分片)技术,3.0版本开始正式提供。Redis Cluster并没有使用一致性hash,而是采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行

Redis实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁
当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。返回值:设置成功,返回 1 。设置失败,返回 0 。
为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
释放锁,使用DEL命令将锁数据删除

为什么要用 Redis/为什么要用缓存?
高性能 :操作内存,速度相当快
高并发:一般像 MySQL 这类的数据库的 QPS ((Query Per Second):服务器每秒可以执行的查询次数;)大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的情况,redis 集群的话会更高)。

既然是单线程,那怎么监听大量的客户端连接呢?
Redis 通过IO 多路复用程序 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
I/O是指网络I/O、多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。
即:同一个线程内同时处理多个TCP连接。 最大优势是减少系统开销小,不必创建/维护过多的线程。
这样的好处非常明显: I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗
Redis6.0 之前 为什么不使用多线程?
单线程编程容易并且更容易维护;
Redis 的性能瓶颈不再 CPU ,主要在内存和网络;
多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能
Redis6.0 之后为何引入了多线程?
Redis6.0 引入多线程主要是为了提高网络 IO 读写性能

如何保证缓存和数据库数据的一致性?

增加cache更新重试机制(常用): 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将 缓存中对应的 key 删除即可。

缓存常用的3种读写策略
Cache Aside Pattern(旁路缓存模式)
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存。
比较适合读请求比较多的场景。
写 :
先更新 DB
然后直接删除 cache 。
读 :
从 cache 中读取数据,读取到就直接返回
cache中读取不到的话,就从 DB 中读取数据返回
再把数据放到 cache 中。

redis主从复制原理

当启动一个slave node的时候,它会发送一个PSYNC命令给master node
如果这是slave node重新连接master node,那么master node仅仅会复制给slave部分缺少的数据; 否则如果是slave node第一次连接master node,那么会触发一次full resynchronization(完全重新同步)
开始full resynchronization的时候,master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。RDB文件生成完毕之后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。
slave node如果跟master node有网络故障,断开了连接,会自动重连。master如果发现有多个slave node都来重新连接,仅仅会启动一个rdb save操作,用一份数据服务所有slave node。


redis 主从复制的核心机制
(1)redis采用异步方式复制数据到slave节点,不过redis 2.8开始,slave node会周期性地确认自己每次复制的数据量 (2)一个master node是可以配置多个slave node的 (3)slave node也可以连接其他的slave node (4)slave node做复制的时候,是不会block master node的正常工作的 (5)slave node在做复制的时候,也不会block对自己的查询操作,它会用旧的数据集来提供服务; 但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了 (6)slave node主要用来进行横向扩容,做读写分离,扩容的slave node可以提高读的吞吐量

假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用keys指令可以扫出指定模式的key列表
如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率

总结
redis高并发:主从架构,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万QPS,多从用来查询数据,多个从实例可以提供每秒10万的QPS。
redis高并发的同时,还需要容纳大量的数据:一主多从,每个实例都容纳了完整的数据,比如redis主就10G的内存量,其实你最多只能容纳10g的数据量。如果你的缓存要容纳的数据量很大,达到了几十g,甚至几百g,或者是几t,那你就需要redis集群,而且用redis集群之后,可以提供可能每秒几十万的读写并发。
redis高可用:如果你做主从架构部署,其实就是加上哨兵就可以了,就可以实现,任何一个实例宕机,自动会进行主备切换。

缓存与数据库双写不一致性问题


一般做法是线程1:解决办法,延迟双删,sleep()一会,再次删除缓存(弊端:让所有写操作的时间延长)

其他解决方法:利用分布式锁,将操作原子化。(弊端,性能问题)
读写锁;将一个锁对象拆分为读锁和写锁(适用于读多写少的场景,读读是不互斥的)读写互斥是因为都会设置相同的key,setnx遇到相同key就不会执行。读读不互斥在于一个mode(模式)

**缓存穿透:**大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。

解决办法:
一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等

缓存雪崩:缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求

解决办法:
针对 Redis 服务不可用的情况:
采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
限流,避免同时处理大量的请求。
针对热点缓存失效的情况:
设置不同的失效时间比如随机设置缓存的失效时间。
缓存永不失效。
————————————————
版权声明:本文为CSDN博主「大人的博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42037864/article/details/106327977

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值