什么是Redis?
- 非关系型的键值对数据库,可以根据键以O(1)的时间复杂度取出或插入关联值
- Redis的数据是存在内存中的
- 键值对中键的类型可以是字符串,整型,浮点型等,且键是唯一的
- 键值对中的值类型可以是sting, list, hash, set sorted set
- Redis内置了复制,持久化,LUA脚本,事务, SSL, ACLS, 客户端缓存,客户端代理等功能。
- 通过Redis哨兵和Redis Cluster模式提供高可用性。
存储海量数据结构
1. 链表, B+树,数组16个
数据库
object encoding some_int:可以查看redis底层数据结构。为int
raw string embstr(底层数据结构)
redis柔性策略设计原理
根据数据结构的长度来决定数据的范围。
String类型
sdshdr5
sdshdr8
sdshdr16
sdshdr32
sdshdr64
typedef char *sds
struct_attribute_((__package)) sdshdr8{
uint8_t len;
uint8_alloc;
unsigned char flags;
char buff[];
}
1 byte + 1byte + 1 byte + 1byte
static inline char sdsReqType(size_t string_size){
if(string_size < 32)
return SDS_TYPE_5
if(string_size<0xff) 2^8-1
return SDS_TYPE_8
if(string_size<0xfff) 2^16-1
return SDS_TYPE_16
if(string_size<0xffff) 2^32-1
return SDS_TYPE_64
}
CPU缓存行通常是64个byte
String应用场景
(1) 单值缓存
set key value
get key
(2)对象缓存
set user1: value(json格式数据)
mset user:1:name zhuge user:1:balance 1888
mget user1:name user:1:banlance
(3)分布式锁
setnx product:10001 true//返回1代表获取锁成功
setnx product: 10001 false//返回0代表获取锁失败
。。。执行业务操作
del product:10001 执行完业务释放锁
setnx product: 10001 true ex 10 nx//防止程序意外终止导致死锁
setnx key value
将key的值设为value,当且仅当key不存在
若给定的key以及存在,则setnx不做任何动作
(4) 计数器
incr article:readcount:{文章id}
get article:readcount:{文章id}
Web集群部署session共享
spring session + redis实现session共享
(5) 分布式系统全局序列号
incrby orderId 100(分库分表之后)redis批量生成序列号提升性能,以前生成100000条数据需要交互100000次,现在只需要交互1000次
Hash应用场景
双重map
对象缓存
id | name | balance |
1 | aa | 100 |
2 | bb | 200 |
HMSET user {userId}:name aa {userId}:balance 100
HMSET user 1:name aa 1:balance 100
HMSET user 2:name aa 2:balance 100
HMGET user 1:name 1:balance
在实际应用场景中,不可能把整张表的数据都丢入内存,一般都是缓存一些热点数据。redis是一个单线程操作
优点:
- 同类数据归类整合存储,方便数据管理。
- 相比string操作消耗内存与cpu更小。
- 相比string存储更节省空间。
缺点
- 过期功能不能是使用在field上,只能用在key上。
- Redis集群架构下不适合大规模使用。
List应用场景
LRANGE key start stop:拿取一个区间范围内的元素
正数索引 | 0 | 1 | 2 | 3 |
负数索引 | -4 | -3 | -2 | -1 |
常用数据结构
Stack(栈) LPUSH+LPOP
Queue(队列): L:PUSH + RPOP
Blocking MQ(阻塞队列) = LPUSH + BRPOP(相当于实现了一个监听,如果队列为空,则不取数据,只有里面存放了数据之后才能从里面获取数据)
微博和微信公众号消息流
如何实现超级大V?
Set应用场景
微信抽奖小程序
1) 点击参与抽奖加入集合
SADD key {userID}
2) 查看参与抽奖所有用户
SMEMBERS key
3)抽取COUNT名中奖者
SRANDMEMBER key [count] / SPOP key [count]
微信微博点赞,收藏,标签
1) 点赞
SADD like:{消息ID} {用户ID}
2)取消点赞
SREM like:{消息ID} {用户ID}
3) 检查用户是否点过赞
SISMEMBER like:{消息ID} {用户ID}
4) 获取点赞的用户列表
SMEMBERS like:{消息ID}
5) 获取点赞用户数
SCARD like:{消息ID}
集合操作实现微博微信关注模型
- SINTER set1 set2 set3:交集
- SUNION set1 set2 set3:并集
- SDIFF set1 set2 set3:以第一个集合为基准,再删除后面两个集合的并集中所包含的元素。
关注模型
微关系
redis底层设计原理之String类型
简单动态字符串
成倍的扩容,
原本len = 6
(len + addLen) * 2 = 18:
SDS:
free:9
len:9
char buf[] = "guojia123"
- 1. 二进制安全的数据结构
- 2. 内存预分配机制,避免了繁琐的内存分配
- 3. 兼容C语言函数库
不是一直都会成倍的扩容,而是如果超过了1M 就不会再成倍的库容了。下次再分配只会再多分配1M的空间。
sdshdr5
前3个比特用来表示类型,后5个bit用来表示buffer的长度。
- #define SDS_TYPE_5 0
- #define SDS_TYPE_8 1
- #define SDS_TYPE_16 2
- #define SDS_TYPE_32 3
- #define SDS_TYPE_64 4
sdshdr8
sdshdr16
redis kv底层设计原理
k-v:数据库: 海量数据的存储
数组 : O(1)
链表:O(N)
hash(key) ->自然数[大]
hash冲突采用的是头插法
字典的底层实现
数组成倍地扩容,不是直接从旧数组全部迁移到新数组,而是当旧的里面的key值访问过了以后才会迁移。每次事件轮询的时候都会搬几个key.
BitMap
周活跃用户: 按位或
连续登录: 按位与
BItMap的使用注意事项
如何扩容? (addLen + len) * 2表示的是成倍地扩容
如果userId非常大,如何设置offset?
List的数据结构
List是一个有序(按加入的时间排序)的数据结构,Redis采用quciklist(双端链表)和ziplist作为List的底层实现。
可以通过设置每个ziplist的最大容量,quciklist的数据压缩范围,提升数据存储效率
ziplist是个非常紧凑的数据结构。
之后如果有修改的话,改的都是非常小的ziplist
list-max-ziplist-size -2
list-compress-depth 0 :表示多少个不进行压缩。
Hash的数据结构
Hash数据结构底层实现为一个字典(dict),也是RedisDB用来存储K-V的数据结构,当数据量比较小,或者单个元素比较小时,底层用ziplist存储,数据大小和元素数量阈值可以通过如下参数设置。
Set的数据结构
Set为无序的,自动去重的集合数据结构,Set数据结构底层实现为一个value为null的字典(dict),当数据可以用整型表示时,Set集合将被被编码为intset数据结构。两个条件任意满足时,Set将用hashtabel存储结构1. 元素个数大于set-max-inset-entries 2.元素无法用整型表示。
redis跳表
每一层最多找两次
index: 1: n /2
index: 2: n/2^2
index: 3: n /2^3
index: K: n/2^k
n/2^k = 2
2^k = n / 2
k = log2 (n-1) 层数
k = 2 * log 2 n (每层最多查询两次) -> logN(和折半查找的时间复杂度是一样的)
优点:
- 1. 性能最大化,fork子进程来完成写操作,让主进程继续处理命令。使用单独子进程来进行持久化,保证了redis的高性能。
- 2. 当重启恢复数据库的时候,数据量比较大时,Redis直接解析RDB二进制文件,生成对应的数据存储在内存中,比AOF的启动效率更高。
利用到了调和平均数
根据最后连续为0的个数来估算n的值?