Redis知识

常见数据结构

string:简单动态字符串

常用命令:set,get,strlen,exists,decr,incr,setex
应用场景: 一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量

list: 双向链表,即可以支持反向查找和遍历

常用命令: rpush,lpop,lpush,rpop,lrange,llen
例子:
rpush myList value1
lpop myList
lrange myList 0 1 // lrange myList 0 -1
应用场景: 发布与订阅或者说消息队列、慢查询。

hash:适合用于存储对象

常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals
应用场景: 系统中对象数据的存储
例子:
hmset userInfoKey name “guide” description “dev” age “24”
hget userInfoKey name 获取存储在哈希表中指定字段的值。
hgetall userInfoKey 获取在哈希表中指定 key 的所有字段和值
hkeys userInfoKey # 获取 key 列表
hvals userInfoKey # 获取 value 列表
hset userInfoKey name “GuideGeGe” # 修改某个字段对应的值

set:无序,不重复

常用命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion
应用场景: 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景。
例子:
sadd mySet value1 value2 # 添加元素进去
smembers mySet # 查看 set 中所有的元素
scard mySet # 查看 set 的长度
sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素
sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中

sorted set:增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表

常用命令: zadd,zcard,zscore,zrange,zrevrange,zrem
应用场景: 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息
例子:
zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
zcard myZset # 查看 sorted set 中的元素数量
zscore myZset value1 # 查看某个 value 的权重
zrange myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素

bitmap:存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身

常用命令: setbit 、getbit 、bitcount、bitop
应用场景: 适合需要保存状态信息(比如是否签到、是否登录…)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)
例子:
setbit mykey 7 1
getbit mykey 7
bitcount mykey # 通过 bitcount 统计被被设置为 1 的位的数量。

单线程模型

不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗
IO多路复用程序
1.监听客户端的大量连接
2.将事件(文件事件,时间事件)及类型注册到内核中并监听每个事件是否发生
3.关联不同的事件处理器。

过期

设置过期:expire key 60
查看数据还有多久过期:ttl key
判断过期:过期字典来保存数据过期的时间 (key:Redis 数据库中的某个键 ;value: long long 类型的过期时间)

删除策略

惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
定期删除 :每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

淘汰机制

问题:仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-lfu:从已设置过期时间的数据集中挑选最不经常使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key
allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
allkeys-random:从数据集中任意选择数据淘汰
no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错

Redis 事务

通过 MULTI,EXEC,DISCARD(取消一个事务,它会清空事务队列中保存的所有命令) 和 WATCH(用于监听指定的键,当调用 EXEC 命令执行事务时,如果一个被 WATCH 命令监视的键被修改的话,整个事务都不会执行,直接返回失败) 等命令来实现事务功能。

开始事务(MULTI)。
命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行)。
执行事务(EXEC)。
例子:
MULTI;SET USER “Guide哥”;EXEC。

使用 MULTI 命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了 EXEC 命令将执行所有命令。

持久化机制

快照:RDB

创建快照来获得存储在内存里面的数据在某个时间点上的副本.
1.命令:save (由redis主进程来执行RDB,会阻塞所有命令)
2.bgsave(开启子进程执行RDB,避免主进程受到影响)
fork主进程得到一个子进程,共享内存空间
子进程读取内存数据并写入新的RDB文件
用新RDB文件替换旧的RDB文件

fork采用的是copy-on-write技术:
当主进程执行读操作时,访问共享内存;
当主进程执行写操作时,则会拷贝一份数据,执行写操作。

在redis.conf文件中配置触发RDB的机制 save 900 1 (900s内,如果至少有1个key被修改,则执行bgsave,如果是save"" 则表示禁用RDB)

RDB的缺点
RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
fork子进程、压缩、写出RDB文件都比较耗时

只追加文件:AOF

每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 server.aof_buf 中,然后再根据 appendfsync 配置来决定何时将其同步到硬盘中的 AOF 文件。

三种不同的 AOF 持久化方式:
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显式地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步

AOF 重写:
AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
set num123;set name jack;set num 666;==>mset name jack num 666

混合持久化

AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头

集群

主从模式

原理
主从第一次同步是全量同步
slave节点执行replicaof命令,建立连接,发送请求数据同步给master。
master根据节点判断是否第一次同步,是第一次,返回master的数据版本信息。
slave节点保存版本信息。
master执行bfsave,生成RDM文件,并且记录RDB期间的所有命令,生成repl_baklog,发送RDB文件给slave节点。
slave节点清空本地文件,加载RDB文件。
master持续发送repl_baklog中的命令。
slave节点执行接收到的命令

slave节点请求增量同步
master节点判断replid,发现不一致,拒绝增量同步
master将完整内存数据生成RDB,发送RDB到slave
slave清空本地数据,加载master的RDB
master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
slave执行接收到的命令,保持与master之间的同步

Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。
因此slave做数据同步,必须向master声明自己的replication id 和offset,master才可以判断到底需要同步哪些数据

repl_baklog大小有上限,写满后会覆盖最早的数据。如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步。

简述全量同步和增量同步区别

全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

什么时候执行全量同步?
slave节点第一次连接master节点时
slave节点断开时间太久,repl_baklog中的offset已经被覆盖时
什么时候执行增量同步?
slave节点断开又恢复,并且在repl_baklog中能找到offset时

搭建主从模式

1.修改每个实例的声明IP,端口、工作目录
2.启动3个redis实例
3.开启主从关系,进入从节点,执行slaveof master的Ip master的端口
4.master节点查看集群状态info replication

哨兵模式

master宕机重选机制
哨兵(Sentinel)机制来实现主从集群的自动故障恢复。
监控:Sentinel 会不断检查您的master和slave是否按预期工作

Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令。
主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点,然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高最后是判断slave节点的运行id大小,越小优先级越高。

自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主节点
实现故障转移步骤:
sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点

通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端

集成Springboot使用

配置RedisTemplate的哨兵模式
在pom文件中引入redis的starter依赖:spring-boot-starter-data-redis
在配置文件application.yml中指定sentinel相关信息

spring:
	redis:
		sentinel:
			master: mymaster # 指定master名称
			nodes: # 指定redis-sentinel集群信息
				- 192.168.150.101:27001
				- 192.168.150.101:27002
				- 192.168.150.101:27003
	配置主从读写分离
	@Bean
	public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){
		return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
	}
	这里的ReadFrom是配置Redis的读取策略,是一个枚举,包括下面选择:
		MASTER:从主节点读取
		MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
		REPLICA:从slave(replica)节点读取
		REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master

搭建

1.创建三个sentinel实例
2.redis搭建主从模式。
3.修改sentinel的配置:sentinel.conf

port 27001 #是当前sentinel实例的端口
sentinel announce-ip 192.168.150.101 #哨兵自己的IP,手动设定也可自动发现,用于与其他哨兵通信
sentinel monitor mymaster 192.168.150.101 7001 2 # 指定主节点信息(mymaster:主节点名称,自定义,任意写;主节点的ip和端口;选举master时的quorum值)
sentinel down-after-milliseconds mymaster 5000 #
sentinel failover-timeout mymaster 60000 #
dir "/tmp/s1 #

分片模式

原理
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:海量数据存储问题,高并发写的问题
使用分片集群可以解决上述问题,分片集群特征:
集群中有多个master,每个master保存不同数据
每个master都可以有多个slave节点
master之间通过ping监测彼此健康状态
客户端请求可以访问集群任意节点,最终都会被转发到正确节点

散列插槽
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分
key中不包含“{}”,整个key都是有效部分
例如:key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。

Redis如何判断某个key应该在哪个实例
将16384个插槽分配到不同的实例
根据key的有效部分计算哈希值,对16384取余
余数作为插槽,寻找插槽所在实例即可
如何将同一类数据固定的保存在同一个Redis实例?
这一类数据使用相同的有效部分,例如key都以{typeId}为前缀

支持集群伸缩
添加一个节点到集群
故障转移:
当集群中有一个master宕机,该实例与其它实例失去连接,被疑似宕机,确定下线,自动提升一个slave为新的master

数据迁移:利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。

集成Springboot使用

RedisTemplate访问分片集群:
引入redis的starter依赖
配置分片集群地址
配置读写分离

spring:
		redis:
			cluster:
				nodes: # 指定分片集群的每一个节点信息
					- 192.168.150.101:7001
					- 192.168.150.101:7002
					- 192.168.150.101:7003
					- 192.168.150.101:8001
					- 192.168.150.101:8002
					- 192.168.150.101:8003

搭建

1.开启6个redis实例,包含3个master节点,每个master包含一个slave节点
2.每个实例配置文件

port 6379
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /tmp/6379/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /tmp/6379
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 192.168.150.101
# 保护模式
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /tmp/6379/run.log

3.创建集群

redis-cli --cluster create --cluster-replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003

参数:
redis-cli --cluster或者./redis-trib.rb:代表集群操作命令
create:代表是创建集群
–replicas 1或者–cluster-replicas 1 :指定集群中每个master的副本个数为1,此时节点总数 ÷ (replicas + 1)` 得到的就是master的数量。
因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master

4.查看集群状态
redis-cli -p 7001 cluster nodes

5.操作:
集群操作时,需要给redis-cli加上-c参数才可以

缓存问题

缓存穿透:大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层
解决方案:参数校验;缓存无效 key;布隆过滤器:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。

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

数据一致性

想要保证数据库和缓存一致性,推荐采用「先更新数据库,再删除缓存」方案,并配合「消息队列」或「订阅变更日志」的方式来做。
写方案:
全量数据刷到缓存中:1.数据库的数据,全量刷入缓存(不设置失效时间)2.写请求只更新数据库,不更新缓存3.启动一个定时任务,定时把数据库的数据,更新到缓存中
访问热点数据存缓存:1.读请求先读缓存,如果缓存不存在,则从数据库读取,并重建缓存。2.写入缓存中的数据,都设置失效时间
读方案:
1) 先更新缓存,后更新数据库:
2) 「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的。数据支持并发加锁
保证两步都成功:
异步重试机制:把重试请求写到「消息队列」中,然后由专门的消费者来重试,直到成功
订阅数据库变更日志,再操作缓存。canal

3种常用的缓存读写策略

旁路缓存模式:适合读请求比较多的场景
写 :先更新 DB 然后直接删除 cache 。
读 :从 cache 中读取数据,读取到就直接返回cache中读取不到的话,就从 DB 中读取数据返回再把数据放到 cache 中
读写穿透:
写:先查 cache,cache 中不存在,直接更新 DB。cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB
读:从 cache 中读取数据,读取到就直接返回 。读取不到的话,先从 DB 加载,写入到 cache 后返回响应。
异步缓存写入:
异步批量的方式来更新 DB

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值