Redis

9/1/2022 Redis

# 为什么那么快

# 基于内存实现

# 高效的数据结构

# 丰富而合理的编码

# 写时拷贝

(CopyOnWrite)

内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟空间结构,但是不为这些段分配物理内存

# 零拷贝技术

直接从 PageCache 中把数据复制到 Socket 缓冲区中,不用把数据复制到用户内存空间,DMA 控制器可以直接完成数据复制,不需要 CPU 参与,速度更快

# 客户端管道批量命令

# 单线程

# 合适的线程模型

I/O 多路复用

# Redis6.0后引入多线程提速

# 数据结构

# string

适用于简单key-value存储、setnx key value实现分布式锁、计数器(原子性)、分布式全局唯一ID

底层:用char[]数组表示,源码中用SDS(simple dynamic string)封装char[],这是是Redis存储的最小单元,一个SDS最大可以存储512M信息,

Redis对SDS再次封装生成了RedisObject,type = REDIS_STRING,Redis会创建键的RedisObject 和 值的RedisOjbect

自动存储int类型,非int类型用raw编码

OBJ_ENCODING_EMBSTR

Redis针对短字符串的优化

内存申请和释放都只需要调用一次内存操作函数

redisObject、sdshdr结构保存在一块连续的内存中,减少了内存碎片

OBJ_ENCODING_RAW

长度大于OBJ_ENCODING_EMBSTR_SIZE_LIMIT的字符串,在该编码中,redisObject、sds结构存放在两个不连续的内存块中

OBJ_ENCODING_INT

将数值型字符串转换为整型,可以大幅降低数据占用的内存空间

# sds

获取字符串长度复杂度为O(1),因为前期已经将字符串的长度写在len这个变量里面了

SDS在修改的时候,Redis的API会主动将SDS的大小扩展到执行修改所需要的大小,然后才执行实际的修改操作

C 字符串 只能保存文本数据,比如“\0”

通过空间预分配和惰性空间释放两种方法,搭配len和free两个变量,来实现对内存分配策略的优化,减少修改字符串时带来的内存重分配次数

1.C字符串中,更改字符串长度,内存就会重新分配,而SDS不需要每次都进行重新分配

2.空间预分配,当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间

小于1MB,*2

于等于1MB,=1m

# list

lpush + lpop = stack 先进后出的栈

lpush + rpop = queue 先进先出的队列

lpush + ltrim = capped collection 有限集合

lpush + brpop = message queue 消息队列

字符串长度且元素个数小于一定范围使用 ziplist 编码,否则转化为 linkedlist 编码

# hash

# 渐进式rehash

rehashidx != -1则表示扩容到数组中的第几个了

load_factor = ht[0].used / ht[0].size

# <0.1收缩
# >=1
# >=5

服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令

K是字符串,V是多种

# set

保存元素为整数及元素个数小于一定范围使用 intset 编码,任意条件不满足,则使用 hashtable 编码

# zset

底层:跳表,查找数据时,是从上往下,从左往右进行查找。

保存的元素个数小于定值且成员长度小于定值使用 ziplist 编码,任意条件不满足,则使用 skiplist 编码

# 应用

积分排行榜

时间排序新闻

延时队列

# Redis Geo

GEO利用 GeoHash 将二维的经纬度转换成字符串,来实现位置的划分跟指定距离的查询

# HyperLogLog

# bitmap

# 应用

用户签到

统计活跃用户

# Bloom Filter

不存在的一定不存在,存在的不一定存在

# 持久化

# 快照-RDB

(snapshotting)

# 只追加文件-AOF

(append-only file)

# 高可用

# 单机

架构简单,部署方便

机器故障、容量瓶颈、QPS瓶颈

# 主从复制(6379)

高可靠性,读写分离

故障恢复复杂,主库的写跟存受单机限制

# 一主一从

slaveof/slaveof no one

从节点只读 slave-read-only = yes

网络延迟 repl-disable-tcp-nodelay no 当开启时,主节点会合并较小的 TCP 数据包从而节省带宽

# 一主多从

replicaof/info replication

# 树状主从结构

# 哨兵模式

-Redis Sentinel (26379)

优点:

集群部署简单,HA

缺点:

原理繁琐,slave存在资源浪费,不能解决读写分离问题

当主节点出现故障时,Redis Sentinel 能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用

redis-server sentinel.conf --sentinel

监控

通知

主节点故障转移

配置提供者

# 脑裂问题

因为网络原因,导致master节点、slave节点 和 sentinel集群处于不用的网络分区,因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点

# 高并发

# 集群

数据动态存储solt,可扩展,高可用

客户端动态感知后端变更,批量操作支持查

redis-cli --cluster create 192.168.153.128:6379 192.168.153.131:6379 192.168.153.132:6379

redis-cli --cluster add-node 192.168.153.129:6379 192.168.153.128:6379 --cluster-slave --cluster-master-id a7e948208badf171d19dbfe2d444ea7295bdbf60

redis-cli --cluster check 192.168.153.128:6379 --cluster-search-multiple-owners

redis-cli --cluster info 192.168.153.128:6379

为什么RedisCluster设计成16384个槽

如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。 如上所述,在消息头中,最占空间的是myslots[CLUSTER_SLOTS/8]。 当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb

redis的集群主节点数量基本不可能超过1000个

槽位越小,节点少的情况下,压缩率高

# 常见问题

# 缓存雪崩

Redis中大批量key在同一时间同时失效导致所有请求都打到了MySQL

# 解决

缓存数据的过期时间加上个随机值

如果缓存数据库是分布式部署,将热点数据均匀分布在不同得缓存数据库中

设置热点数据永远不过期

# 缓存穿透

指缓存和数据库中都没有的数据

# 解决

后端接口层增加 用户鉴权校验,参数做校验等

单个IP每秒访问次数超过阈值直接拉黑IP

从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null 失效时间可以为15秒防止恶意攻击

用Redis提供的 Bloom Filter 特性也OK

# 缓存击穿

大并发集中对这一个热点key进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库

# 解决

设置热点数据永远不过期

加上互斥锁

# 双写一致性

先更新数据库,再更新缓存

先删缓存,再更新数据库

# Cache Aside Pattern

更新 DB,然后直接删除 cache

# 事务

# 过期策略和内存淘汰策略

# 过期策略

定时过期

惰性过期

定期过期

# 内存淘汰策略

在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据

1)全局的键空间选择性移除

allkeys-lru:在键空间中,移除最近最少使用的key。(这个是最常用的)

allkeys-random:在键空间中,随机移除某个key。

no-eviction:当内存不足以容纳新写入数据时,新写入操作会报错。

2)设置过期时间的键空间选择性移除

volatile-lru:在设置了过期时间的键空间中,移除最近最少使用的key。

volatile-random:在设置了过期时间的键空间中,随机移除某个key。

volatile-ttl:在设置了过期时间的键空间中,有更早过期时间的key优先移除。

总结

Redis的内存淘汰策略的选取并不会影响过期的key的处理。

内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;

过期策略用于处理过期的缓存数据。

# 消息通知

# 管道

Last Updated: 4/15/2023, 1:36:11 AM