关于redis- 学习笔记

Redis能干啥?

缓存热点数据=缓存标准代码 String

每日签到功能 bitmap 二值法

实质String
一个bit8位=1个字节 底层String类型
get  sgin:nicky:202204 得到结果是二进制的ascii编码对应
最大偏移量 为2的32次方 大约42.9亿数据
月key
setbit sgin:nicky:202204 0 1  
setbit sgin:nicky:202204 1 1 
setbit sgin:nicky:202204 2 1 
setbit sgin:nicky:202204 3 1 
setbit sgin:nicky:202204 30 1 
年key
setbit sgin:nicky:2022 0 1  
setbit sgin:nicky:2022 1 1 
setbit sgin:nicky:2022 2 1 
setbit sgin:nicky:2022 3 1 
setbit sgin:nicky:2022 365 1 

getbit sgin:nicky:202204 14代表4月14号签到了吗?
月活统计
bitCount sgin:nicky:202204 0 6 统计nicky 4月份第一周签到了几天
bitCount sgin:nicky:202204 统计nicky 4月份签到了几天
年统计
bitCount sgin:nicky:2022  统计nicky2020年签到了几天
365天/8位 = 46个字节 1000W的用户一年也只需要44MB数据存储
假如是亿级系统 1亿位bitmap约占12MB 10天的bitmap内存开销为120MB
对bitmap设置过期时间,让redis自动删除不再需要的签到记录
STRLEN sgin:nicky:202204   得到的是ascii码转出来对应的长度8位=1字节
按照字节来统计长度
统计连续打卡的人
//将用户id和bit位的映射关系-----Map<userId,bit>
setbit 20220415 0(代表用户编号) 1;//4月15号 0号用户签到过了
setbit 20220416 0(代表用户编号) 1;//4月16号 0号用户签到过了
setbit 20220416 1(代表用户编号) 1;//4月16号 1号用户签到过了
//查看4月15号和4月16号都签到的用户
连续性操作用bitop来操作
//将key=20220416和key20220415的值取出来进行位运算
10000000 ----key = 20220415
11000000-----key = 20220416
结果----
10000000----destkey
说明某个bit位都是为1 代表15号和16号都签到了
bitop and destkey 20220416 20220415 判断4月15号和4月16号都是1的
bit位(用户编号)
bitcount destkey //统计为1的个数--就是2个key都为1的用户数量
gitbit destkey 0 //判断0号用户是否都签到了 
gitbit destkey 1 //判断1号用户是否都签到了 

去重复统计 HyoerLogLog–UV统计

实质String
亿级系统统计专用 存的进 取得出  多统计
键花费12kb内存 可以计算2的64次方不同元素的基数
去重复的内容 
hashset数据量过大 直接内存溢出
bitmap  UV独立访客数 根据访问登录接口
拦截IP去重来进行统计 进行ip和位数的转换然后
setbit uvbit 1 1 一个对象样本 1亿个用户 大约12M
如果10000个样本 例如10000个视频统计将近120G 不适合

需要使用	HyoerLogLog去重统计误差率0.81%
PFADD taobao:uv 111 112 113 添加元素
PFCOUNT taobao:uv  计算key的总值
PFMERGE result key1 key2 合并结果集
取8位的前6位
16384个槽/6位/8 = 12kb
12kb存储接近2的64次方个不同元素的基数

分布式锁

分布式 CAP理论 P分区容错性全都满足 C强一致性 A高可用性
ZK CP理论当所有节点完成同步后才提供服务
Eureka和redis都是AP高可用性
由于服务是分布式的,jvm的锁只能锁住本实例,分布式多机部署的情况下就需要用
到分布式锁,分布式锁实现 有zk redis等等
分布式锁条件 :
1.独占
2.高可用
3.防死锁  杜绝死锁必须有超时机制或撤销操作
4.不乱抢 自己加锁只能自己释放
5.重入性 同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁
zk实现分布式锁原理
	zk中有且只有1个znode节点 CP保证强一致性
redis分布式锁原理:	lua脚本 原子性 和 key是否过期 看门狗
工作中用redisson框架实现 lock
lock加锁:3段lua脚本保证操作原子性不可分割
	1.锁不存在给当前线程加锁
	2.锁存在并且锁是当前线程获取 锁的次数加1 加锁成功--重入
	3.加锁成功开启watchdog后台线程隔过期时间的1/3检查是否持有锁
	有则续命 将过期时间恢复
unlock方法:3段lua脚本
	如果释放锁的线程和持有锁的线程不是同一个线程 返回null
    通过hincrby递减1,先释放一次锁,
    若剩余次数还大于0,则证明当前所是重入锁,刷新过期时间
    若剩余次数小于0,删除key并发布锁释放的消息,解锁成功 	
 redis多机部署会产生重复获取锁的问题   
红锁,对多个不同的(主节点)进行设置key,如果半数以上的主节点设置成功那么返回
加锁成功.   

发红包功能

微信红包架构
发 用list  不加锁
1:N
抢
list.pop
记录已经抢过红包的人
Hash
红包均值算法--二倍均值法
每次抢到的金额 = 随机区间 (0(剩余红包金额M ÷ 剩余人数N ) X 2

地图功能

GEO类型实现  底层Zset
经纬度
GEOADD city 116.403963 39.915119 "天安门" 
116.403414 39.924091 "故宫" 
116.024067 40.362639 "长城"
GEOPOS  city 天安门 获取经纬度
GEOHASH city 天安门 获取唯一哈希编码
------返回2个位置的距离
GEODIST city 天安门 故宫  m(单位)
GEORADIUS---工作中使用
返回指定经纬度10 km 范围内 
withdist 返回元素位置与中心距离 
withcoord返回位置元素的经纬度
count 指定返回记录数量
GEORADIUS city 116.418017 39.914402 10 km withdist 
withcoord count 10 with hash des
GEORADIUSBYMEMBER  city 名字

排序功能-热搜前十 SortedSet

面试题:

  1. redis为什么是16384个槽
redis集群没有使用一致性hash而是引入哈希槽
CRC16算法产生的hash值有16bit可以产生65536个值
redis节点不建议超过1000个
槽位如果是65535 集群发送心跳包过于庞大 8kb
16384个槽够用了

2.布隆过滤器
用途:解决缓存穿透的问题

很长的二进制数组+一系列随机hash算法映射函数
将key通过n个哈希算法映射到n个槽位
查询的时候根据相同n个哈希算法将key映射到n个槽位 如果都是1那么这个key
可能存在 如果有1个不为1 那么这个key必定不存在
用来解决缓存穿透的恶意攻击

3.缓存雪崩穿透击穿预热

雪崩:redis主机挂了 比如缓存大量key过期或者主机挂了
解决:多级缓存ehcache本地缓存 熔断限流策略

缓存穿透:恶意攻击,并发访问不存的key-导致大量的sql直接查询数据库都不存在
增加数据库压力
解决:缓存null,布隆过滤器Guava,redis的布隆过滤器

击穿:单个热点key失效 请求全部打到数据库 
解决:热点key永不失效 加锁  
差异失效时间 
 A缓存失效时间短于B缓存失效时间
 先更新B缓存,再更新A缓存
 查询先查A,A没有的情况下再查缓存B

缓存问题解决方案
4.redis缓存淘汰策略了解吗?LRU?

8中淘汰策略
volatile和allkey
volatile对设置了过期时间的key
allkey针对所有key
ttl删除快要过期的key

默认noeviction 出厂配置  需要配置LRU淘汰策略
推荐allkeys-lru: 对所有key使用LRU算法进行删除
LRU 最近很少使用算法
LFU 最近频率很少

5.redis底层数据结构了解吗?
redisObjecct结构

dictEntry = <String , redisObject>
redis的key一般为字符串
value是redisObject对象 是五大类型的父类

1.String
SDS

sds结构 获取字符串长度len直接拿到 o(1)时间复杂度
内部buf字段存储真正元素
好处:
1.空间预分配 长度小于1M会额外分配与len相同长度未使用空间大于1M则分配1M
额外空间 
2.惰性扩容:释放空间会有free保存可用空间
3.二进制安全 C语言\0代表结束标志 sds根据长度来结束
三种结构 ---redisObject对象
int ptr中存储
embstr(跟redisObject拼接存储) 
raw-redisObject的ptr指针指向位置存储

2.Hash

压缩链表

压缩链表的构成
压缩链表的Entry对象构成
3.List

quicklist  = ziplist和linkedlist的结合体

QuickList结构
4.Set

ziplist压缩链表 或者skiplist跳表

5.Zset

skiplist跳表 是一种以空间换取时间的结构 
跳表 = 链表 + 多级索引
给链表加索引 两两取首
    时间复杂度O(logN)
    空间复杂度O(N)

跳表结构
几种数据类型的时间复杂度
6.Redis和mysql的双写一致性如何保证?

canal组件 利用mysql的bin.log日志文件 跟mysql的主从复制原理类似
bin.log修改同步更新redis

mysql主从复制原理:
1.当 master 主服务器上的数据发生改变时,则将其改变写入二进制事件日志文
件中
2.salve 从服务器会在一定时间间隔内对 master 主服务器上的二进制日志进
行探测,如果修改过启动 IO线程请求binlog
3.同时master 主服务器为每个IO Thread 启动一个dumpThread向其发送二进
制事件日志
4.slave 从服务器将接收到的二进制事件日志保存至自己本地的中继日志文件中
5.salve 从服务器将启动 SQL Thread 从中继日志中读取二进制日志,在本地
重放,使得其数据和主服务器保持一致
6.最后 I/O Thread 和 SQL Thread 将进入睡眠状态,等待下一次被唤醒;

canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave 向MySQL 
master发送dump 协议 MySQL master 收到 dump 请求,开始推送binarylog
给slave(即canal)
canal 解析 binary log 对象(原始为 byte)  

结论:
分布式系统只有最终一致性,很难做到强一致性
SHOW VARIABLES LIKE 'log_bin'; 查看日志文件必须开启
 开启 MySQL的binlog写入功能  my.ini文件
    log-bin=mysql-bin #开启 binlog
    binlog-format=ROW #选择 ROW 模式

7.缓存双写一致性

同步直写 缓存和数据库中的数据⼀致
异步缓写 
对于读写缓存来说 采⽤同步直写策略
给缓存设置过期时间 是保证最终一致性的解决方案
更新策略:
	1.更新数据库再更新缓存--不可用 redis会读到脏数据
	2.先删除缓存在更新数据库
		存在问题:1.低并发会写会旧值 2.高并发缓存击穿
	A线程删除redis更新mysql此时B线程发现redis无,则读mysql写回缓
	存,写回旧值,高并发情况下mysql请求剧增 缓存击穿了。
解决方案:延时双删策略
	当A线程执行完mysql操作写入数据,隔一段时间再删除redis,A的sleep
	时间要大于B线程的读取数据再写入redis缓存的时间
	3先更新数据库,再删除缓存-----工作中常用 
	 假如缓存删除失败或者来不及,导致请求再次访问redis时缓存命中,
     读取到的是缓存旧值。	
解决方案:
     canal监听binlog日志删除redis缓存,删除失败放入消息队列重试
     canal加mq   
 如果重试超过的一定次数后还是没有成功	

8.Redis为什么快?

底层IO复用 实现:select-poll-epoll
同步、异步的讨论对象是被调用者(服务提供者)重点在于获得调用结果的消息
通知方式上

阻塞、非阻塞的讨论对象是调用者(服务请求者),重点在于等消息时候的行为,
调用者是否能干其它事
BIO
	accept函数阻塞
	read函数阻塞
NIO
   accept()非阻塞 没有客户端就返回error
   read()非阻塞读不到数据就返回error,读到数据阻塞read()方法读数据时间
   当一个客户端与服务端进行连接,这个socket就会加入到一个数组中,隔一段
时间遍历一次,看这个socket的 read()方法能否读到数据,这样一个线程就能处
理多个客户端的连接和读取了 
NIO特点是用户进程需要不断的主动询问内核数据准备好了吗

问题:socket连接多了 需要循环遍历大部分可能都是没数据的,询问内核
这将占用大量的 CPU 时间
IO多路复用应运而生,也即将上述工作直接放进Linux内核
1.内核态不会有阻塞
2.处理完成直接给用户最终结果 不涉及两态切换
select:O(n)
阻塞直到有数据发送到socket 会把&rset相应的位置置位
但是不会返回哪个socket有数据
用户态只需要遍历&rset看哪一位被置位了
缺点:
	1.bitmap最大1024位,一个进程最多只能处理1024个客户端
	2&rset不可重用,每次socket有数据就相应的位会被置位
	3.fd文件描述符数组拷贝到了内核态
	4.没有通知哪一个socket有数据 仍然需要遍历
poll:O(n)
	去掉1024个客户端的限制
	&rset可复用
epoll:O(1)
	1.epoll_create 创建一个 epoll 句柄
    2.epoll_ctl 向内核添加、修改或删除要监控的文件描述符
    3.epoll_wait 类似发起了select() 调用
1、当有网卡上有数据到达了,首先会放到DMA(内存中的一个buffer,网卡可以
直接访问这个数据区域)中
2、网卡向cpu发起中断,让cpu先处理网卡的事
3、中断号在内存中会绑定一个回调,哪个socket中有数据,回调函数就把哪个
socket放入就绪链表中  
快原因:
1.变成了一次系统调用 + 内核层遍历这些文件描述符  
2.这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程 
3.一个socket的生命周期中只有一次从用户态拷贝到内核态的过程,开销小
4.使用event事件通知机制,每次socket中有数据会主动通知内核,并加入到
就绪链表中,不需要遍历所有的socket

9.redis持久化和主从备份处理如何实现?

RDB:在不同的时间点将 redis 的数据生成的快照同步到磁盘 
AOF:将redis 所执行过的所有指令都记录下来,在下次redis重启时,只需要
执行指令就可以了 写日志。
bgsave 做镜像全量持久化,aof 做增量持久化。
Redis会定期做 aof 重写,压缩 aof 文件日志大小  

bgsave 的原理,fork 和 cow, ork是指redis 通过创建子进程来进行bgsave
操作,cow 指的是 copy onwrite,子进程创建后,父子进程共享数据段,父进
程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。


主从复制实现:主节点将自己内存中的数据做一份快照,将快照发给从节点
从节点将数据恢复到内存中。之后再每次增加新数据的时候,主节点以类似于
mysql 的二进制日志方式将语句发送给从节点,从节点拿到主节点发送过来的
语句进行重放先RDB恢复,然后AOF恢复执行
集群分片
分片:Cluster 中有一个 16384 长度的虚拟槽,每个Master节点都会负责一部
分的槽,当有某个 key 被映射到某个Master 负责的槽,那么这个Master负责
为这个key提供服务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值