Redis的笔记拾遗

665 阅读3分钟

docker 安装一个redis

# 拉取 redis 镜像
> docker pull redis
# 运行 redis 容器
> docker run --name myredis -d -p6379:6379 redis
# 执行容器中的 redis-cli,可以直接使用命令行操作 redis
> docker exec -it myredis redis-cli

redis.io/commands 所有命令检索

string

常用命令 set get setex expire exist del mset mget incr incrby

小于1M 扩容加倍现有空间

大于1M 扩容+1M

capacity < 512M

list

常用命令 rpush lpush rpop lpop llen

可做队列 右进左出

可做栈 右进右出

慢操作 lindex ltrim

ltrim 和lrange 的区别

数据较少时zipList 数据较多改为quickList

hash

常用命令 hset hlen hget hmset hmget hincrby hincr hgetall

渐进式rehash

关于信息用hash或string存储的对象的优劣 hash可以查询对象的某些字典 这样可以节省流量 避免整查的浪费 但结构消耗比string大 具体问题具体分析

set and zset

一个是无序集合 一个是有序集合

常用命令 zrange books 0 -1 查询 范围的正序排列

zrevrange books 0 -1 查询 范围的逆序排列

zcard count()

zscore "xxx" 查询score

zrank "xxx" 查询排名

zrangebyscore 0 9.0 根据score值来查询范围中的元素

zrangebyscore books -inf 8.91 withscores ps:-inf = -∞ withscores 分数也查询出来

zrem 删除

有序集合通过 指定 score的方式来对存储的set元素进行排序

zset通过跳表的数据结构实现有序性

通用的规则 set/hash/list/zset

1、如果没有创建 create if not exists

2、如果集合没有元素 集合删除 drop if no elements

过期事件 expire ttl

分布式锁 redis

原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch 线程切换。 这个时候就要使用到分布式锁来限制程序的并发执行

方案1

setnx 加锁 del 解锁

问题:如果程序在setnx 和 del之间 出现故障 del无法执行 程序死锁

方案2

setnx + expire 加锁并设置过期时间 del解锁

问题: 如果程序在setnx 和expire之间 出现故障 del无法执行 且无法通过过期机制解锁 程序死锁

方案3 set xxx ex 5 nx 加锁设置过期时间 del解锁

由于加锁设置过期时间被redis作者用一条命令 作为原子操作进行

超时问题

问题1 如果程序逻辑执行时间太长 超过expire时间 这时锁被解开 有可能造成数据错乱(需要人工介入)

问题2 前一个操作的del解锁了 后一个操作刚抢到的锁 ps:解决方法 被每个锁加stamp 加锁生成一个stamp 解锁用stamp验证 保证解的是正确的锁 Redis 也没有提供类似于delifequals这样的指令 故而需要用lua脚本来处理多条执行的原子操作

集群环境下的分布锁

Redis Sentinel 集群下 如果主节点获得到锁 在同步过程中 主节点挂了 这时候从节点没有锁信息 从节点被提升为主节点 锁被无形中释放了

解决方案 Redlock 算法 加锁时,它会向过半节点发送 set(key, value, nx=True, ex=xxx) 指令,只要过半节点 set 成功,那就认为加锁成功。释放锁时,需要向所有节点发送 del 指令。不过 Redlock 算法还需要考虑出错重试、时钟漂移等很多细节问题,同时因为 Redlock 需要向多个节点进行读写,意味着相比单实例 Redis 性能会下降一些。

Redisson Watchdog "看门狗"机制

当应用任务在执行时 加锁会额外申请一个看门狗的线程每隔一段时间(必须比锁过期时间短)去redis续期锁信息的过期时间 解决任务执行未完成 锁过期的情况 并因为任务执行完或意外取消消失 看门狗也没了 故而过期机制依然保证 不会造成死锁

锁冲突处理

分布式锁,客户端在处理请求时加锁没加成功怎么办。一般有 3 种策略来处理加锁失败:

1 直接抛出异常,通知用户稍后重试;

2 sleep 一会再重试;

3 将请求转移至延时队列,过一会再试

延时队列

lpush/rpop

blpop/brpop 空闲连接自动断开 闲置过久,服务器一般会主动断开连接,减少闲置资源占用。这个时候blpop/brpop会抛出异常来

延时队列的实现

位图

位图实际是string的小单位bitmap

用户一年的签到记录,签了是 1,没签是 0,要记录 365 天

setbit 设置位图的位置的值

bitcount 统计指定位置范围内 1 的个数 (统计用户一共签到了多少天)

bitpos 查找指定范围内出现的第一个 0 或 1 (查找用户从哪一天开始第一次签到)

bitfield 有三个子指令,分别是 get/set/incrby,它们都可以对指定位片段进行读写,但是最多只能处理 64 个连续的位,如果超过 64 位,就得使用多个子指令,bitfield 可以一次执行多个子指令

www.cnblogs.com/liujiduo/p/… 用户签到场景redis封装

HyperLogLog

HyperLogLog 提供了两个指令 pfadd 和 pfcount pfmerge

在计数比较小时,它的存储空间采用稀疏矩阵存储,空间占用很小,仅仅在计数慢慢变大,稀疏矩阵占用空间渐渐超过了阈值时才会一次性转变成稠密矩阵,才会占用 12k 的空间

提供不精确的统计计数 省空间

布隆过滤器

判重场景 guava实现

关于限流

系统要限定用户的某个行为在指定的时间里只能允许发生 N 次

固定窗口 滑动窗口 漏桶 令牌桶

blog.csdn.net/weixin_4184…

1、滑动窗口方案

# 指定用户 user_id 的某个行为 action_key 在特定的时间内 period 只允许发生一定的次数 max_count
def is_action_allowed(user_id, action_key, period, max_count):
    return True
# 调用这个接口 , 一分钟内只允许最多回复 5 个帖子
can_reply = is_action_allowed("laoqian", "reply", 60, 5)
if can_reply:
    do_reply()
else:
    raise ActionThresholdOverflow()

2、漏斗算法

关于geo

Scan

1 使用高位进位加法来遍历 考虑到字典的扩容和缩容时避免槽位的遍历重复和遗漏 2 渐进式rehash

美团近期修复的Scan的一个bug mp.weixin.qq.com/s/ufoLJiXE0…

Redis的IO线程模型

事件轮训(多路复用) 监听事件 每个客户端有一个指令队列保证每个客户端的命令顺序 同时响应队列也是一样的 队列为空时应该取消Selector上的读写事件操作符 避免事件轮训获取到channel发现没有数据可读可写 CPU飙高

Redis的持久化方案

RDB快照 全量 二进制存储当前的数据 bgsave

AOF日志 增量 保存历史修改命令的日志 BGREWRITEAOF

RBD过程 fork一个子进程 进行数据序列化写盘 dump.rdb 由于父进程需要并行操作数据,需要父进程利用COW机制对数据页进行复制操作

AOF过程 AOF 日志记录了自 Redis 实例创建以来所有的修改性指令序列。 先执行指令再写日志 这是因为Redis定位是缓存 日志只是附加功能 先写日志再执行指令会影响性能

由于AOF会一直膨胀 Redis 提供了bgrewriteaof指令为AOF瘦身 开辟一个子进程对内存进行遍历转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中 再合并操作期间的Redis指令

AOF记录指令 需要先写入内核缓存 然后再写入文件 fsync 是一个Linux下强制进行刷盘的指令 Redis 通过定时器 1per/s 来刷盘 在性能和可用性之间取得了一个平衡

Redis 4.0 混合持久化 由于RDB快照恢复数据会造成比较多的数据丢失。AOF的"重放"又会使得Redis的恢复十分缓慢

AOF文件不再是从启动开始记录了,改为从上一次RDB文件生成起记录,这就使得AOF日志文件很小 恢复也充分利用了两者的优点

Redis pipline

pipline是客户端提供的一种操作,用于多个操作连续执行,减少数据包的数量 ps 命令之间必须无依赖关系 管道的本质是无需等待返回数据再进行命令发送 并行处理所以返回时间是最慢的那个命令处理时间决定的

Redis 事务

事务指令

multi/exec/discard。multi 指示事务的开始,exec 指示事务的执行,discard 指示事务的丢弃

Redis事务无法保证原子性 只能保证隔离性 无回滚

Redis的乐观锁机制Watch

PubSub

一种队列的多播方案

CAP

CAP 原理就好比分布式领域的牛顿定律,它是分布式存储的理论基石。自打 CAP 的论文发表之后,分布式存储中间件犹如雨后春笋般一个一个涌现出来。理解这个原理其实很简单,本节我们首先对这个原理进行一些简单的讲解。

C - Consistent ,一致性 A - Availability ,可用性 P - Partition tolerance ,分区容忍性 分布式系统的节点往往都是分布在不同的机器上进行网络隔离开的,这意味着必然会有网络断开的风险,这个网络断开的场景的专业词汇叫着「网络分区」。

在网络分区发生时,两个分布式节点之间无法进行通信,我们对一个节点进行的修改操作将无法同步到另外一个节点,所以数据的「一致性」将无法满足,因为两个分布式节点的数据不再保持一致。除非我们牺牲「可用性」,也就是暂停分布式节点服务,在网络分区发生时,不再提供修改数据的功能,直到网络状况完全恢复正常再继续对外提供服务。

redis的架构

主从同步

AP方案

过程:Redis 同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了 (偏移量)。

因为内存的 buffer 是有限的,所以 Redis 主库不能将所有的指令都记录在内存 buffer 中。Redis 的复制内存 buffer 是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容

断网很长时间会使得主节点的buffer满了.被后来的指令覆盖,导致丢失一部分需要同步的指令,这时需要快照同步。即在主从之间传递RBD文件同步主节点当前数据然后恢复到增量同步方案

Wait命令 从节点同步

redis.conf配置解析

INCLUDES 可以通过include 把一些定义化的少量配置同步到大量配置文件中 如果想覆盖放在最后

MODULES 启动加载插件模块

NETWORK bind 0.0.0.0 开放给外网 protected-mode yes 限制只能统一个机器访问

GENERAL daemonize no 是否后台运行

SNAPSHOTTING save

REPLICATION

SECURITY

sentinel

Redis Sentinel(哨兵)是一个我们能自动化切换主从保证可用性的方案。

关于sentinel主从集群

关于redis-cluster

关于Haproxy redis主从集群

关于Codis集群方案

关于过期

Redis建立了一个设置了过期key的集合,定时遍历过期的key值进行删除。除此之外还是用了惰性删除的思想,当用户访问过期的key时,执行删除。一种是集中处理一种是零散处理。

定时扫描的策略。

1、从过期字典中随机 20 个 key; 2、删除这 20 个 key 中已经过期的 key; 3、如果过期的 key 比率超过 1/4,那就重复步骤 1;

同时为了不使应用服务器卡顿,设置了回收的上限时间,默认25ms。这里和思想和垃圾收集器G1很相似

通过给过期时间+随机时间来规避大量key同一时间失效的缓存穿透问题

从库数据靠主库AOF指令来同步删除

LRU

Redis通过设置maxmemory限定最大的内存占用,超过则启动淘汰key机制。

6种淘汰机制:

noeviction 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。

volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。

volatile-ttl 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。

volatile-random 跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。

allkeys-lru 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。

allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。

LRU 字典加链表 按照put的先后顺序(也可以是其他顺序)排列链表,访问使用时把元素放在末尾。等空间满了,淘汰头部元素。

Redis使用的是一种近似LRU算法。Redis 为实现近似 LRU 算法,它给每个 key 增加了一个额外的小字段,这个字段的长度是 24 个 bit,也就是最后一次被访问的时间戳

安全

禁用Redis某些命令 比如:rename-command flushall ""

加密码 requirepass xxxxx 主从复制 masterauth xxxxx

使用spiped来做Redis的SSL的代理

lua脚本扩展指令集

例如:

del_if_equals.lua

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

SCRIPT LOAD 和 EVALSHA 指令解决长lua脚本问题。在Redis服务器缓存放回对应的sha1 id。再通过EVALSHA调用

有用的命令行工具

扫描大key

redis-cli --bigkeys -i 0.01

远程备份RDB文件

redis-cli --rdb ./user.rdb