引用

https://juejin.cn/post/6844903951502934030
https://blog.csdn.net/snakorse/article/details/78154402(些许过时)
https://blog.liexing.me/2019/12/28/from-ziplist-linkedlist-to-quicklist/
https://www.cnblogs.com/yangmingxianshen/p/8054094.html
https://blog.csdn.net/weixin_43935927/article/details/109498737

redis

  • Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。
  • 数据库使用磁盘,读写速度慢,redis使用内存,读写速度快
  • redis是一种非关系行数据库(NoSQL),不用维护索引,在高IO环境下有天然优势
  • 使用多路I/O复用模型,非阻塞IO;
  • Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

redis编码方式

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
\#define OBJ_ENCODING_RAW 0     /* Raw representation */
\#define OBJ_ENCODING_INT 1     /* Encoded as integer */
\#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
\#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */ // 已废弃
\#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
\#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
\#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
\#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
\#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
\#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
  • embstr(embedded string) 嵌入式字符串:保存长度小于44字节的字符串(字符串与其对应的 redisObject 对象分配在 同一块连续的内存空间)
  • raw 字符串:保存长度大于44字节的字符串(字符串与其redisObject 的 内存不再连续)
  • int 整数:保存long 型的64位有符号整数 ( Redis 启动时会预先建立 10000 个分别存储 0~9999 的 redisObject 变量作为共享对象,set字符串的键值在 0~10000 之间的话,则可以直接指向共享对象)
  • ziplist 压缩列表:链表(List),哈希(Hash),有序集合(Sorted Set)在成员较少,成员值较小的时候都会采用压缩列表(ZIPLIST)编码方式进行存储,Ziplist使用连续的内存数据块,其内存利用率很高,但增删改查效率较低
  • ht(hashtable)哈希表:ht是Redis中存在最广泛的一种数据结构,集合对象()和有序结合对象()中都有使用,而且Redis所有的Key,Value都是存在db->dict这张字典中的。
  • linkedlist 双链表:linkedlist在插入、删除元素时,元素数量、单个元素的长度对耗时影响小(耗时分布比较集中),但是空间利用率比较差,特别是数据规模较小时,空间利用率非常差
  • quicklist 快速双链表:quicklist 每个节点的值都是使用ziplist 进行存储,quicklist结合了二者的优点,首先时间消耗上,数据规模对其影响小,其次是空间利用率,因为底层使用了ziplist,所以在小规模数据上空间表现也良好
  • intset 整数集:当一个集合只包含整数值元素, 并且这个集合的元素数量不多时, Redis 就会使用整数集合作为集合键的底层实现
  • skiplist 跳跃表:跳跃表以有序的方式在层次化的链表中保存元素,在一般情况下情况下效率和平衡树媲美, 比起平衡树来说,跳跃表的实现要简单直观得多

Redis数据类型

Redis内部使用一个redisObject对象来表示所有的key和value
image.png

String:字符串

  1. String 类型的值最大能存储 512MB
  2. String 编码可以是 int,raw 或者 embstr
SET key value
GET key
value

String使用环境

  1. 对象缓存(保存Json字符串等)
  2. 简单分布式锁
  3. 计数器
  4. Web集群Session共享

Hash:哈希

  1. Hash 可以存储 2^32 -1 个键值对(40多亿)
  2. Hash 编码可以是ziplist(512以下),hashtable
HSET myhash field1 "Hello" field2 "World"
HGET myhash field1
"hello"

Hash使用环境

  1. 对象缓存(较String 占用更少空间)
  2. 模拟购物车

List:列表

  1. List 可存储 2^32 - 1 个元素 (40多亿)
  2. List 编码可以是ziplist(512以下),linkedlist/quicklist
  3. 3.2后使用quicklist
右插入
rpush key value1 value2
返回 2 当前列表长度
lrange list 0 - 1
value1 
value2

List 使用环境

  1. 实现栈,队列,阻塞队列
  2. 订阅(消息队列)

Set:集合

  1. Set 可存储 2^32 - 1 个元素 (40多亿)
  2. Set 编码可以是intset(512以下整数),hashtable
sadd key value
1(成功1,失败0)
sadd key value
0(重复)
smembers key
"value"

Set使用环境

  1. 抽奖(随机出队)
  2. 点赞(不可重复)
  3. 互相关注(多个set取交集)
  4. 多店商品列表(多个set取并集)

ZSet:有序集合

  1. ZSet 可存储 2^32 - 1 个元素 (40多亿)
  2. ZSet 编码可以是ziplist(128以下),skiplist
zadd key 1 a 3 b 2 c
3 (长度)
zrange key 0 10 withscores
a
1
c
2
b
3

ZSet使用环境

  1. 热搜(多天亦可)
  2. 排行榜(更新)

Redis 高级数据结构

HyperLogLog

  • Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
  • Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

HyperLogLog使用

//添加
pfadd key value1
1 (成功)
pfadd key value2
1
pfadd key value2
0 (重复)
// 返回估算值
pfcount key
2
// 合并两个HyperLogLog 
pfadd key1 value1
1
pfadd key1 value3
1
pfmerge newkey key key1 
OK
pfcount newkey
3
pfcount key
2

HyperLogLog原理

对于一个输入的字符串,首先得到64位的hash值,用前14位来定位桶的位置(共有 2^14 ,即16384个桶)。后面50位即为伯努利过程,每个桶有6bit,记录第一次出现1的位置count,如果count>oldcount,就用count替换oldcount。

Geo(3.2增加)

geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。

Geo 使用

//添加
geoadd key  13.12 38.11 sh
1
geoadd key 15.08 37.50 ca
1
//获取
geopos key sh
13.12
38.11
//两点距离(单位:m,km,ft(英尺),mi(英里))
geodist key sh ca m
1666274.15
//获取范围内坐标
georadius key 13 38 2 km
georadiusbymember key sh 2 km
//获取位置hash
geohash key sh

pub/sub 发布订阅

发送者 (pub) 发送消息,订阅者 (sub) 接收消息

使用

//订阅
subscribe key
//发布
publish key value
1
//接收方
1) "message"
2) "key"
3) "value"

Redis 常见问题

缓存雪崩

原因

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决

随机过期时间

缓存击穿

原因

一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。

解决

  1. 设置永不过期
  2. 添加互斥锁,一个线程获取时,其他线程等待一段时间

缓存穿透

原因

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

解决

  1. 使用布隆过滤器,请求拦截
  2. 找不到值时,缓存空结果