缓存的使用场景:
1. DB缓存,减轻服务器压力 指优先访问缓存, 没有命中找DB
2. 提高系统响应 解决频繁IO而无法响应
3. 做Session分离, 多个服务器共享Session信息
4. 做分布式锁, 控制多个进程并发下产生的问题,以及控制时序性,使用Redis实现的setNX
5. 做乐观锁,Redis可以实现乐观锁 watch + incr
缓存的读写模式:
1. Cache Aside Pattern(常用)
Cache Aside Pattern(旁路缓存),是最经典的缓存+数据库读写模式。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存。
为什么是删除缓存,而不是更新缓存呢?
a. 缓存的值是一个结构:
hash
、
list
,更新数据需要遍历
b. 懒加载,使用的时候才更新缓存. 也可以采用异步的方式填充缓存
高并发脏读的三种情况
1、先更新数据库,再更新缓存
2、先删除缓存,再更新数据库
3、先更新数据库,再删除缓存(推荐)
2. Read/Write Through Pattern
3. Write Behind Caching Pattern
缓存架构的设计思路:
1. 多层次, 分布式缓存宕机,本地缓存还可以使用
2. 数据类型, Value是字符串或整数, Value
的值比较大(大于
100K)只进行setter, gtter(采用Memcached)
3.
复杂数据类型 Value是
hash
、
set
、
list
、
zset 需要存储关系,聚合,计算 可采用Redis
4. 分布式缓存集群方案(
Redis) 哨兵
+主从 RedisCluster
5. 缓存的数据结构设计: 缓存的数据是经常访问的; 需要存储关系,聚合,计算等;比如某个用户的帖子、用户的评论。
key
:
UID+
时间戳
(
精确到天
)
评论一般以天为计算单位
value
:
Redis
的
Hash
类型。
fifield
为
id
和
content
expire
:设置为一天
Redis数据类型:
String:
应用场景:
1
、
key
和命令是字符串
2
、普通的赋值
3
、
incr
用于乐观锁
incr
:递增数字,可用于实现乐观锁
watch(
事务
)
4
、
setnx
用于分布式锁
当
value
不存在时采用赋值,可用于实现分布式锁
eg: setnx name zhangsan 当再一次复制时失败
set age 18 NX PX 1000 NX 原子操作 PX 过期时间
list列表类型:
list列表类型可以存储有序、可重复的元素
获取头部或尾部附近的记录是极快的
list的元素个数最多为
2^32-1
个(
40
亿)
应用场景:
1
、作为栈或队列使用
列表有序可以作为栈和队列使用
2
、可用于各种列表,比如用户列表、商品列表、评论列表等。
set集合类型:
命令:
无序, 唯一性
应用场景: 适用于不能重复的且不需要顺序的数据结构 比如:关注的用户,还可以通过spop进行随机抽奖
sortedset有序集合类型:
元素本身是无序不重复的, 每个元素关联一个分数(score), 可按分数排序,分数可重复
zadd key score1 member1 score2 member2 ...
zrem key mem1 mem2 .... 删除成员
zcount key min max 返回区间的个数
zcard key 获得元素数量
zincrby key increment member 在集合的member上加increment
zscore key member 获得集合中的分数
zrank key member
获得集合中
member
的排名(按分值从
小到大)
zrevrank key member 按分值从大到小排序
zrange key start end 指定区间, 按分数递增排序
zrevrange key start end 指定区间, 按分数递减排序
应用场景:
由于可以按照分值排序,所以适用于各种排行榜。比如:点击排行榜、销量排行榜、关注排行榜等。
hash类型(散列表)
Redis hash
是一个
string
类型的
fifield
和
value
的映射表,它提供了字段和字段值的映射。
hset key fifield value 赋值 不区分新增 | 添加
hmset fifield1 value1 fifield2 value2 批量赋值
hsetnx key fifield value 赋值 存在不操作
hexists key fifiled 查看是否存在
hget key fifield 获取某个字段
hmget key fifield1 fifield2 ... 获取多个字段
hincrby key fifield increment 该字段自增
应用场景:
对象的存储 ,表数据的映射
stream数据流类型
RedisObject结构:
typedef struct redisObject {
unsigned type:4;//类型 五种对象类型
unsigned encoding:4;//编码
void *ptr;//指向底层实现数据结构的指针 //...
int refcount;//引用计数 //...
unsigned lru:LRU_BITS; //
LRU_BITS为24bit 记录最后一次被命令程序访问的时间
//...
}robj;
type: 指存储当前值是什么类型
REDIS_STRING(
字符串
)
、
REDIS_LIST (
列表
)
、
REDIS_HASH(
哈希
)
、
REDIS_SET(
集合
)
REDIS_ZSET(
有 序集合)
。 命令: type key
4
位
encoding:
指每个对象都有着不同的编码格式, 命令: object encoding key
Redis
通过
encoding
属性为对象设置不同的编码
对于少的和小的数据,
Redis
采用小的和压缩的存储方式,体现
Redis
的灵活性
大大提高了
Redis
的存储量和执行效率
比如
Set
对象:
intset
: 元素是
64
位以内的整数 hashtable:元素是
64
位以外的整数
24
位
LRU : 记录着最后一次访问的时间, 高16为记录时间戳, 低8为记录访问计数
refcount
refcount 记录的是该对象被引用的次数,类型为整型。
refcount 的作用,主要在于对象的引用计数和内存回收。
当对象的refcount>1
时,称为共享对象
Redis 为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原的对象。
ptr: 指着具体的数据 ,比如:
set hello world
,
ptr
指向包含字符串
world
的
SDS
。
SDS的优势:
1
、
SDS
在
C
字符串的基础上加入了
free
和
len
字段,获取字符串长度:
SDS
是
O(1)
,
C
字符串是
O(n)
。
buf
数组的长度
=free+len+1
2
、
SDS
由于记录了长度,在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。
3
、可以存取二进制数据,以字符串长度
len
来作为结束标识
使用场景:
SDS
的主要应用在:存储字符串和整型数据、存储
key
、
AOF
缓冲区和用户输入缓冲。
跳跃表是有序集合(
sorted-set
)的底层实现,效率高,实现简单。
将有序链表中的部分节点分层,每一层都是一个有序链表。
字典dict
又称散列表(
hash
),是用来存储键值对的一种数据结构。
Redis
整个数据库是用字典来存储的。(
K-V
结构)
对
Redis
进行
CURD
操作其实就是对字典中的数据进行
CURD
操作。
数组:用来存储数据的容器,采用头指针
+
偏移量的方式能够以
O(1)
的时间复杂度定位到数据所在的内 存地址。
快速列表
快速列表(
quicklist
)是
Redis
底层重要的数据结构。是列表的底层实现。(在
Redis3.2
之前,
Redis
采用双向链表(adlist
)和压缩列表(
ziplist
)实现。)在
Redis3.2
以后结合
adlist
和
ziplist
的优势
Redis
设 计出了quicklist
。
双向链表优势:
1.
双向:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为
O(1)
。
2.
普通链表(单链表):节点类保留下一节点的引用。链表类只保留头节点的引用,只能从头节点插
入删除
3.
无环:表头节点的
prev
指针和表尾节点的
next
指针都指向
NULL,
对链表的访问都是以
NULL
结
束。
4.
带链表长度计数器:通过
len
属性获取链表长度的时间复杂度为
O(1)
。
5.
多态:链表节点使用
void*
指针来保存节点值,可以保存各种不同类型的值。
数据压缩
quicklist
每个节点的实际数据存储结构为
ziplist
,这种结构的优势在于节省存储空间。为了进一步降低
ziplist
的存储空间,还可以对
ziplist
进行压缩。
Redis
采用的压缩算法是
LZF
。其基本思想是:数据与前
面重复的记录重复位置及长度,不重复的记录原始数据。
缓存过期和淘汰策略
长期使用,
key
会不断增加,
Redis
作为缓存使用,物理内存也会满
内存与硬盘交换(
swap
) 虚拟内存 ,频繁
IO
性能急剧下降
maxmemory
不设置最大内存的场景:
key是固定的, 不会增加,
Redis
作为
DB
使用,保证数据的完整性,不能淘汰 , 可以做集群,横向扩展
淘汰策略: 禁止驱除
设置的场景:
key在不断增加
key在不断增加
默认为0时, 不限制, 直到超过物理内存, 崩溃
设置
maxmemory
后,当趋近
maxmemory
时,通过缓存淘汰策略,从内存中删除对象
expire数据结构
命令:
expire key ttl(单位秒)
数据结构中: dict存储key-value,
expires则维护了Redis了设置失效时间的key
当我们使用
expire
命令设置一个
key
的失效时间时,
Redis
首先到
dict
这个字典表中查找要设置的
key
是 否存在,如果存在就将这个key
和失效时间添加到
expires
这个字典表。
当我们使用
setex
命令向系统插入数据时,
Redis
首先将
Key
和
Value
添加到
dict
这个字典表中,然后 将 Key
和失效时间添加到
expires
这个字典表中。
删除策略
Redis
的数据删除有定时删除、惰性删除和主动删除三种方式。
Redis
目前采用惰性删除
+
主动删除的方式。
定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除 操作。
需要创建定时器,而且消耗
CPU
,一般不推荐使用。
惰性删除
在
key
被访问时如果发现它已经失效,那么就删除它。
调用
expireIfNeeded
函数,该函数的意义是:读取数据之前先检查一下它有没有失效,如果失效了就删 除它。
主动删除
在
redis.conf
文件中可以配置主动删除策略
,
默认是
no-enviction
(不删除)
maxmemory-policy allkeys-lru
LRU
LRU (Least recently used)
最近最少使用,算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“
如果数据最近被访问过,那么将来被访问的几率也更高
”
。
通过LinkedHashMap来维护缓存数据
1. 新数据插到表头
2. 每当有数据命中, 提到对头
3. 当链表满时, 删除队尾数据
Redis
的
LRU
数据淘汰机制
在服务器配置中保存了
lru
计数器
server.lrulock
,会定时(
redis
定时程序
serverCorn()
)更新, server.lrulock 的值是根据
server.unixtime
计算出来的。
另外在RedisObject中有lru的属性, 则每次访问数据, 都会更新
LRU
数据淘汰机制是这样的:在数据集中随机挑选几个键值对,取出其中
lru
最大的键值对淘汰。
volatile-lru
从已设置过期时间的数据集(
server.db[i].expires
)中挑选最近最少使用的数据淘汰
allkeys-lru
从数据集(
server.db[i].dict
)中挑选最近最少使用的数据淘汰
LFU
LFU (Least frequently used)
最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将 来一段时间内被使用的可能性也很小
volatile-random
从已设置过期时间的数据集(
server.db[i].expires
)中任意选择数据淘汰
allkeys-random
从数据集(
server.db[i].dict
)中任意选择数据淘汰
ttl
volatile-ttl
从已设置过期时间的数据集(
server.db[i].expires
)中挑选将要过期的数据淘汰
redis
数据集数据结构中保存了键值对过期时间的表,即
redisDb.expires
。
TTL
数据淘汰机制:从过期时间的表中随机挑选几个键值对,取出其中
ttl
最小的键值对淘汰。
noenviction
禁止驱逐数据,不删除 默认
缓存淘汰策略的选择
allkeys-lru
: 在不确定时一般采用策略。
volatile-lru
: 比
allkeys-lru
性能差
存
:
过期时间
allkeys-random
: 希望请求符合平均分布
(
每个元素以相同的概率被访问
)
自己控制:
volatile-ttl
缓存穿透