乐趣区

关于java:Redis复习总结

## Redis 内存模型
### Redis 内存调配
- 数据:Redis 存储的数据对象 字符串、哈希、列表、汇合、有序汇合 
- 过程自身所需内存:Redis 过程本人运行所须要的内存,比方代码,占用内存,常量池等
- 缓存内存:+ 客户端缓冲区:连贯客户端输入输出的缓存
    + 复制积压缓冲区:在主从同步时,非全量复制时,所须要的缓存区
    + 复制积压缓冲区:AOF 写入时的缓存
- 内存碎片:内存碎片是 Redis 在调配、回收物理内存过程中产生的

### Redis 内存分配器(jemalloc)+ jemalloc 将空间分为 小(Small)、大(Large)、微小(Huge)三种
![jemalloc](https://images2018.cnblogs.com/blog/1174710/201803/1174710-20180327001126509-2023165562.png)

### Redis 内存统计
- used_memory(Redis 内存分配器调配的内存)> 存储的数据的内存
- used_memory_rss(Redis 占操作系统的内存)> 包含存储的数据内存还有内存碎片以及 Redis 自身占用内存

### Redis 数据的存储过程
- RedisObject
![RedisObject](https://i.loli.net/2020/07/12/xQOXjFEvNzgoaf8.jpg)

- 数据类型
    + SDS
        ![SDS.jpg](https://i.loli.net/2020/07/12/hpUmPNkzaR9e4n1.jpg)
        - 空间预调配
            sdscat =》给字符串前面再拼接一个字符串
            当 sdscat 之后内存小于 1M,字符串长度 *2+1(’\0’)当 sdscat 之后内存大于 1M, 字符串长度 + 1M + 1(’\0’)- 空间懒调配
            如果 sdstrim(缩小字符串),则不急着回收空间,下次如果须要增加长度,间接应用多余的空间。+ List
        ![List.jpg](https://i.loli.net/2020/07/12/AcZDGyX5IJnYl6h.jpg)
    + Hash
        ![hash.jpg](https://i.loli.net/2020/07/12/Di1vVNXMo83Zfsb.jpg)
        在字典中存在 dictht 数组,表明是两个 hash 表
        ht[1] 的容量是 ht[0] 的两倍
        把 ht[0] 中的元素 rehash 复制到 ht[1] 中
    + set
    + zset

- 数据存储过程
> RedisObject -> 具体的数据类型

### Redis 内存回收策略
+ noeviction: 返回谬误当内存限度达到并且客户端尝试执行会让更多内存被应用的命令 (大部分的写入指令,但 DEL 和几个例外)
+ allkeys-lru: 尝试回收起码应用的键 (LRU),使得新增加的数据有空间寄存。+ volatile-lru: 尝试回收起码应用的键 (LRU),但仅限于在过期汇合的键, 使得新增加的数据有空间寄存。+ allkeys-random: 回收随机的键使得新增加的数据有空间寄存。+ volatile-random: 回收随机的键使得新增加的数据有空间寄存,但仅限于在过期汇合的键。+ volatile-ttl: 回收在过期汇合的键,并且优先回收存活工夫 (TTL) 较短的键,使得新增加的数据有空间寄存。## Redis 的原子性保障
+ 单指令原子性
> Redis 是单线程的,一个线程只能执行一个指令,因而具备原子性
+ Lua 原子性
> 官网解释来看,Lua 脚本和 Redis 的事务一样,被 exec/mutl 包裹,redis 保障每次只能执行一个 lua 脚本,别的 lua 脚本不会被执行,由此保障了原子性。## 分布式锁
### 次要命令 
+ setnx 不存在 key 才能够操作
+ set 与 set 相同
### 锁续期
> 利用 **Redission**,当胜利获取一个锁的时候,产生看门狗(watch dog)进行锁续期,一般来说是 10s 查看一次
> 外围在于 Redission 应用了 Lua 脚本
### 分布式锁的极其状况
> 当服务 A 从 Master 中获取锁,A 获取锁胜利后,还没来得及同步到从节点,master 挂了,从节点
从新成为 master,服务 B 过去后,发现该锁还未被获取,于是锁被反复获取

## Redis 的冷备和热备
+ 热备 - AOF
    - 数据文件比 RDB 更大
    - 每秒都去长久化,数据失落少
    - 存储的文件是每条的指令
    - 先执行命令,之后才存储到磁盘(因为 redis 不是齐全保护,只有执行当前才晓得后果,单纯的语法监测是无用的)- bgrewriteaof 能够对 aof 的日志文件进行瘦身,也就是 fork 一个子过程把原来的日志全副转化成 redis 命令存到一个新的日志中执行
+ 冷备 - RDB
    - 须要 fork 子过程,数据量大的话会导致几秒的提早,对于秒杀场景危险
    - 是段时间保留数据,一旦产生宕机,数据失落较多
    - RDB 复原的更快

## Redis 集群

### 哨兵监控
+ 主观下线
> 从节点无奈 ping 通 master,则主观认为 master 挂了
+ 主观下线(主节点的下线)> 多个从节点都无奈 ping 通 master,则从节点们主观认为 master 挂了,** 须要从新选举 **
+ 定时工作
+ 谬误转移
    - 过滤不衰弱的节点
    - 选举出新的节点
    - 让从节点成为主节点
    - 让原来的 master 成为从节点
+ 哨兵选举
> Raft : 谁先申请成为主节点,谁就是主节点

## 主从同步的过程
1. 从节点向 master 发送 slaveof 获取主节点的信息
    + 定时工作获取主节点信息
    + 从节点去 ping 主节点,主节点则返回 pang 和 runid 等信息
2. 从节点依据保留的 Master runid 判断是不是第一次同步复制
3. 如果是第一次 psync?-1,则进行全量复制
    + 全量复制址启用用 RDB 生成快照
        - 启动 RDB 会 fork 子过程,则子过程运行期间,新命令进入到缓存区
    + RDB 生成到磁盘,之后在读取到内存,再进行数据同步
        - 快照内容同步完当前,再将缓存的命令缓存到从节点
4. 如果不是第一次,则进行局部复制,从节点向 master 发送 Psync runid offset
5. Master 收到命令后会查看,runid 是否统一,之后查看偏移量 offset 是否超过复制积压缓存区
    + 如果偏移量超过复制积压缓存区,则 err,进行全量复制
    + 如果未超过,则 offset+ 偏移量 + 命令长度进行局部复制 

### 复制积压缓存区
> 在主从同步的期间,依然会有写命令在执行,这时命令在写入主节点的同时还会写入 ** 复制积压缓存区 **, 同时记录偏移量,如果这期间缓存的命令过多,则没必要再进行局部复制,间接进行全量复制即可

## 缓存的常见问题

1. 缓存穿透
    + 歹意拜访不存在的数据,导致打入数据库
    + 减少认证(接口拜访性能)2. 缓存击穿
    + 某热点数据忽然生效,打入数据库
    + 设置 null 值
3. 缓存雪崩
    + 大量数据同时生效
    + 设置随机工夫种子


## redis 中的  HyperLogLog 

### 场景剖析
> 拜访网站的独立访客 UV,例如每个访客拜访某网站,拜访屡次,其实只能算作一次。> 那么带来的间接问题是,如果每个用户都占用一个 key,那么就会产生数据量微小的问题
### 解决方案拆分
+ 借鉴数据库的 b + 树
> 解决了插入和查找的问题,然而解决不了数据量大,占用内存的问题
+ bitmap
> 如果是一亿个数据,那么 100_000_000(数据量)/ 8(字节)/ 1024(KB)/ 1024(MB)≈ 12 M

### 概率统计
> 能够看到 bitmap 曾经是属于极致的优化了,然而还是不够,不管怎么说,为了一个统计性能,单一个对象就是 12M, 然而再多一些还是会很多
> 则应用“预计的办法可能会好一些”,则应用 ** 概率统计 **

#### redis 的实现
HLL 中理论存储的是一个长度为 mm 的大数组 SS,将待统计的数据汇合划分成 mm 组,每组依据算法记录一个统计值存入数组中。数组的大小 mm 由算法实现方本人确定,redis 中这个数组的大小是 16834(2 的 14 次方),m 越大,基数统计的误差越小,但须要的内存空间也越大
![hll 的 redis 过程.jpg](https://i.loli.net/2020/07/27/Sctp8ygmNfFUJ5K.jpg)

#### hll 的实现原理 - 伯努利试验
+ 伯努利实现也就是掷硬币实现,那么咱们说假如存在第一次掷出侧面所用的最多的次数为 kmax,则能够提出假如
+ 假如:- 掷出 n 次侧面所用的次数肯定少于 n *kmax
    - 掷出 n 次侧面所用的次数肯定有那么一次是等于 kmax 的(其实正好是 1 减去下面的概率)![伯努利假如.jpg](https://i.loli.net/2020/07/23/1YSsrdghimzlXK8.jpg)

+ 推断:- 那么当 n 远大于 kmax 的时候,那么第一个不成立
    - 那么当 n 远小于 kmax 的时候,那么第二个不成立
+ 持续推断
    - 那么用 kmax 推断 n 的次数貌似是最好的状况
    则 n = 2^kmax

+ 持续思考
    - 如果很大的一段二进制,只用一个 kmax 误差比拟大
    - 那么把一段很大的离开预计就好了,因而有 ** 分桶原理 **,也就是 redis 过程中对应的数组 S 的 m

### 稠密存储和密集存储
1. 密集存储就是依照原来 16834 来存储
2. 稠密存储就是间断两个 0 作为统计接下来的 6bit 整数值加 1 
### hll 对象头 

struct hllhdr {

char magic[4];      /* 魔术字符串 "HYLL" */
uint8_t encoding;   /* 存储类型 HLL_DENSE or HLL_SPARSE. */
uint8_t notused[3]; /* 保留三个字节将来可能会应用 */
uint8_t card[8];    /* 总计数缓存 */
uint8_t registers[]; /* 所有桶的计数器 */

};



## 订阅与公布
### PubSub 

#### 毛病
+ 没有 ack 确认信息,不能保证数据间断
+ 不长久化音讯
#### PubSub
![redis_PubSub 订阅模式.jpg](https://i.loli.net/2020/07/24/6jHO7DIZMxAkvGL.jpg)

+ 准确匹配
> 订阅某个准确频道,则须要准确的晓得这个频道的 key 值,才能够订阅,比方 client 订阅 redis 频道
![redis_公布订阅_channel.jpg](https://i.loli.net/2020/07/24/MtX9ZLSxukBohrY.jpg)
+ 前缀多匹配
> 订阅某种类型的全副频道,也就是说晓得某频道类型的前缀,则能够订阅该类型下的全副频道,例如 **client 订阅 redis.* 的频道 **,
> 则会 ** 订阅 redis.log,redis.rdb 等一系列合乎 redis.* 的一系列前缀频道 **
![redis_patten_多匹配原理.jpg](https://i.loli.net/2020/07/24/CSMaz6mKxd3svFq.jpg)

#### steam 
![redis_steam.jpg](https://i.loli.net/2020/07/24/ZM5drvlSyHApVOF.jpg)

+ Consumer Group:消费者组
> 多个 consumer 须要某音讯的一个群组
+ last_delivered_id:音讯游标
> 群组须要哪个音讯,则指向这个音讯的 id, 音讯 ID 如果是由 XADD 命令返回主动创立的话,那么它的格局会像这样:timestampInMillis-sequence (毫秒工夫戳 - 序列号),例如 1527846880585-5,它示意以后的音讯是在毫秒工夫戳 1527846880585 时产生的,并且是该毫秒内产生的第 5 条音讯。+ pending_ids:状态数组
> 记录未确认 ack 的音讯的数组,如果客户端发送 ack 则数组中的内容缩小,如果不发送则会始终减少

退出移动版