redis的数据结构
数据结构:string set zset hash list
几种新的redis数据存储方案
1.位图
场景呈现:比如的场景是签到记录,每个人都要有自己的签到记录,都是用 key value来记录那一天是否进行了签到的话会浪费很大的空间
redis提供的这种问题的解决,bitmap:每天是否签到,可以在bit位的0/1来表示,比如一年365天,50个字节就可以表述 400 位
位图不是特殊数据结构,就是byte数组,并且该数组支持自动扩展
基本操作:
API1:gitbit / setbit
位图支持零存整取比如 he 占两个字节 0110 1000 0110 0101
插入的时候使用 set s 1 1 /set s 2 1 /set s 4 1.....
取的时候使用get s
会得到 he
零存零取:setbit s 1 1 / getbit s 1 >>> 1
整存整取:
set s h >>>> 位图存入 0110 1000
getbit s 1 >>>> 1
API2:支持使用bitcount统计位图中1的数量
set w hello / bit count w >>> 21
API3:简单理解就是之前setbit gitbit只能一个个放一个个存,这个命令可以一次放多个一次取读个
示例:bitfield w get u3 4 从第五位取三位,u表示无符号
bitfield w get i4 0 从第一位取4位 , i表示有符号符
i(有符号符):第一个bit位表示符号,1为负号,0为正号
u(无符号付):所有取出的bit位都是值
2.HyperLogLog
使用场景:用于统计大数据量的重复数据 (set (sadd)也可以实现更精准)问题在于,如果访问数量特别大,但是老板不要求统计精准的情况下,hyploglog更使用
两个API:pfadd / pfcount
一个新增一个计数
pfadd computer 1
pfadd computer 2
pfadd computer 2
pfcount computer 2(具有去重作用)
注意:使用一个hyperloglog就会占用12k的空间,所以不能给系统的每一个用户一个hyperloglog 可以一起用
redis在3.2版本新增GeoHash、
这种存储方式是存在zset中的(不难理解有排序功能)
可以用于存储经纬度的信息
redis 提供的GeoHash算法用于计算两点经纬度之间的距离
提供的api
geoadd:geoadd company 11.111 12.222 baidu
geoadd company 22.222 33.333 alibaba
geodist(计算两点之间的距离):geodist compay(大集合) alibaba(元素名称) baidu(元素名称) km(距离单位)
geopos获取集合中任意两元素的坐标:geopos company aliabab baidu
最重要的一个是georadiusbymember(查看指定元素附近的其他元素)
georadiusbymember company ireader 20km count 3asc
(表示范围20km内的公司按照距离的正排序,不会剔除本身)
分布式锁的详解
自我理解:
使用setnx进行key值得存储,setnx的含义就是设置key值,如果存在不处理,不存在将值设置为新的,这样就很好理解,能设置成功表示我获取到锁,我设置失败表示该锁已经被占用了
问题一:设置锁,没有超时时间怎么处理?在设置完锁之后进行超时时间设置,事务处理完成后释放锁,
问题二:设置超时时间小于了事务处理的时间,在事务还没处理完,锁被释放了,单独起一个线程取监听超时时间,如果不是业务自动释放锁则不断将锁时间延长,这样业务挂了的话,由于在业务中会不断地给redis时间延长,所以,不会影响别的业务的使用,锁到时候会清除
官方解释
我说的问题一种没有解释一种问题:加超时时间失败了(还没加超时时间redis异常了),那redis会永远不释放锁,不可用redis的事务解决,事务是获取锁才能加超时时间,没获取到不能乱加;redis2.8 解决该问题:setnx 和 expire可以同时执行
set lock true ex 5 nx;在setnx里面加了ex保证原子性,redis自己实现
问题三:在sentail集群模式的情况下,如果client 向节点注册了一个key,主节点挂掉了,锁没有同步到从节点,这时候另一个客户端请求锁会立刻获取到出现问题
解决方案,使用redlock算法,该算法类似于分布式算法, redlok使用了大多数机制,加锁时多个节点set成功,则为成功,但是需要向多台节点写,所以性能会下降
REDIS的安全通信
跨机房读取数据;如果是用tcp链接,数据直接暴露在公网上不安全
redis不支持ssl安全链接,但是支持使用ssl代理
spiped 安全通道官方推荐使用的代理
左边的spiped进程负责接收redis client发送过来的数据加密后传输给右边的spiped进程,右边的spiped将接收的数据解密后传递到redis Server;反向流程一致
重中之重源码篇-底层实现
Redis的String
redis中的字符串是可以修改的字符串,在内存中他以字节数组的形式存在;redis的字符串又名:sds即:带长度信息的字节数组
String的长度不会超过512m
扩容策略:
在字符串长度小于1m的时候,扩容空间采用加倍策略,保留100%的冗余空间
字符串长度超过1m,避免加倍后的冗余空间大过大而导致浪费,每次扩容只会多分配1m的冗余空间
压缩列表 -- zset + hash
zset 和 hash 容器对象在元素个数较少的时候,采用压缩列表进行存储(ziplist)(连续的内存空间)
压缩列表支持双向遍历,所以才会有ztail_offset字段>>> 快速定位最后一个元素
新增元素
因为ziplinst是个紧凑的存储空间,没有冗余,插入一个新元素就要调用realloc来拓展内存,取决于内存分配器算法和当前的ziplist内存大小,realloc会分配新的内存空间,将之前的内筒一次性拷贝到新的地址,也有可能在原有的地址上扩展,
如果ziplist占据内存空间过大,重新分配内存会有很大的消耗,所以zipliset不适用于存储大型的字符串
快速列表--list
早期的redis存储list列表数据结构用的是ziplist和普通的双向链表linkedlist。当数据量少的时候用的ziplist多的时候用的linkedlist
linkedlist的结构(了解)
后期的redis使用quicklist来替代zipli1和linkedlist
quicklist 有前后指针, 指向压缩列表的指针, ziplist的字节总数 , ziplist中的元素数量, 存储形式(原生的字节数组还是LZF的压缩存储)
quicklist默认单个ziplist长度为8kb,超过该字节就会产生一个新的ziplist
压缩深度:
默认不压缩,可以根据配置list - compress -depth 决定,结构如下
跳跃列表重点看一下!
看zset是一个复合结构,一方面是需要一个hash结构存储value和score的对应关系,另一方面是需要提供按照score排序的功能,并且需要按照score的范围获取value
redis的跳表有64层,可以容纳2^64 个数据,每个kv对应的结构是zslnode
查找过程:
查找过程类似于二分查找,但是二分查找结构只能是有序数组。
过程:从最高层一直到最底层的每一层最后一个比我小的元素节点列表
插入
对于每一个新节点的插入,都要调用一个随机的算法将其分配一个合理的层数,每一层的晋升率只有25%,插入元素在顶层的概率位2^-64,先把紫色的搜索路径找出来,然后开始创建新的节点,新建的节点随机分配一个层数,再将搜索路径上的节点和这个新节点通过前向后向指针进行串联。
删除
删除过程和插入过程类似,先把搜索路径找出来,然后每一层相关的节点重排一下前向后向指针
更新
zset key value score
如果value不存在,那就是是插入过程,如果value就是更新score操作,redis的操作是先删除这个元素,再插入这个元素,需要经过两次路径搜索
紧凑列表
redis5.0引入的新的数据结构listpack(优化于ziplist)
LFU和LRU-redis 4.5引入
LRU:最近最少使用可以淘汰
LFU:根据最近访问的频率进行淘汰
一种场景:如果一个key只是偶然被访问一次,每隔一段时间,这样这个key在lru算法中不会删除
Redis 对象的热度
Redis 的所有对象头结构中都有一个 24bit 的字段,这个字段用来记录对象的热度。
Redis 的对象头结构
typedef struct redisObject {
unsigned type:4; //对象类型如 zset set hash
unsigned encoding:4; //对象编码如 ziplist intset skiplist
unsigned lru:24bit; //对象的热度
int refcount; //引用计数
void *ptr; /
LRU 和 LFU 都是根据lru字段进行比较
LRU模式下,该字段存储的是reids的server.lruclock,每次key被访问,都会触发该值的更改,根据该值来比对多久时间没访问
LFU模式下,lru该字段的24bit是用于存储两个值,分别是ldt 和 logc
logc用于存储范围频次,如果值过小会被回收掉,所以新建的key值会设置成一个默认的值 = 5
ldt:用于存储上一个logc的更新时间
与lru的区别在于,lRU模式下的lru字段,是在对象在访问的时候进行更新,而LFU是在数据删除的时候,会更新logc字段,进行衰减,根据控制衰减系数(lfu-decay-time)可以设置
衰减计算方式:(logc - 对象的空闲时间)/ 衰减系数(lfu_decay_time)
衰减系数为0则不衰减