Redis初学笔记

前言:

自己学习Redis路上的一点小笔记📒

记录下来,自己也方便翻看

课程链接:https://coding.imooc.com/class/151.html

书籍推荐: 《Redis开发与运维》

文章首发: https://sleepymonster.cn

这里只是一个引子或者说一个查找~

针对某个方面具体的,建议多看看别人的文章或者书

从而Redis的理解会更深入,当然上面的课程推荐!

获取Redis

安装

$ brew install redis

$ redis-server  // 开启

可执行文件说明

$ redis-server  // Redis服务器

$ redis-cli  // Redis命令行客户端

$ redis-benchmark // Reids性能测试
 
$ redis-check-aof // Redis AOF文件修复工具

$ redis-check-dump // RDB文件检查工具

$ redis-sentinel // Sentinel服务器

三种启动方法

$ redis-server // 默认配置启动

$ redis-server --port 6380 // 动态参数启动

$ redis-server configPath // 配置文件启动

// 验证启动
$ ps -ef | grep redis
$ netstat -antpl | grep redis
$ redis-cli -h ip -p port ping

// 注意⚠️
// 单机多实例配置文件可以用端口区分开

Redis客户端的连接

$ redis-cli -h ip -p port
> set hello world
> Ok
> get hello
> "world"

Redis常用配置

  • daemonize 是否是守护进程(no|yes)
  • port
  • logfile Redis系统日志
  • dir Redis工作目录

配置文件启动

# 基础设置
# 开启持久化
daemonize yes
# 端口
port 6382 
# 工作目录
dir "/usr/local/Cellar/redis/6382-data"
 # 日志
logfile "/usr/local/Cellar/redis/6382.log"
$ redis-server ./6382-redis.conf

$ ps -ef | grep redis-server | grep 6382

$ cat ./6382.log //查看日志

合理使用API

通用命令

$ keys //获取所有的键
> keys*
> keys he* // 找出he开头的
> keys he[h-l]*
> keys ph?
// keys 命令一般不在生产环境使用

$ dbsize //数据库的大小
> dbsize

$ exists key // 判断是否存在
> exists a  //返回1 0

$ del key [key...]  //删除key
> del a

$ expire key seconds // 设置过期时间
> expire a 30
> ttl key // 查看过期时间  // -2 不存在了 -1 不存在过期时间
> persist key // 去掉过期时间

$ type key // 查看类型
// string hash list set zset(有序集合) none

注意事项

  • 一次只运行一条明路
  • 拒绝长命令
  • 其实不是单线程

字符串

场景: 缓存 计数器 分布式锁…

$ get key 
$ mget key1 key2 key3  // 批量获取
$ getset key newvalue // 设置新的并同时返回老的
$ append key value // 将value追加到旧的value
$ strlen key // 返回字符串的长度
$ getrange key start end // 获取字符串指定下标所有值
$ setrange key index value // 设置指定下标所有对应的值

$ set key value
$ setnx key value  // key不存在才能设置
$ set key value xx  // key存在才能设置
$ mset key1 value1 key2 value2 // 批量设置

$ del key

$ incr key // key自增1,如果key不存在,自增后get(key)=1

$ decr key 

$ incrby key k // key自增k,如果key不存在,自增后get(key)=k
$ incrbyfloat key 3.5 // 增加浮点数

$ decrby key k

哈希

$ hget key field
$ hmget key field1 field2
$ hgetall key
$ hvals key // 返回hash key 对应所有的field的value
$ hkeys key

$ hset key field value
$ hmset key field1 value1 field2 value2
$ hsetnx key field value  // 如果存在则设置失败

$ hdel key field

$ hexists key field

$ hlen key

$ hincrby key field intCounter // hash的key的field中的value自增intCounter
$ hincrbyfloat key field floatCounter

列表

// 增
$ rpush key v1 v2 v3 // 从右边插入

$ lpush key v1 v2 v3 // 从左边插入

$ linsert key before|after value newValue // 在key某个具体的值左右插入新的值

// 删
$ lpop key

$ rpop key // 右边弹出

$ lrem key count value // 删除与value相等的值 当 >0 从左开始 当 <0 从右开始 当 =0 全部

$ ltrim key start end // 按照索引范围修剪列表

$ blpop key timeout  // timeout为阻塞超时时间
$ brpop key timeout  // timeout为阻塞超时时间

// 查
$ lrange key start end (包括end) // 获取指定索引范围的item

// 改
$ lset key index newValue  // 设置

集合

// 单个操作
$ sadd key element // 如果存在则添加失败

$ srem key element

$ scard key // 计算集合大小

$ sismember key value // 判断value是否存在于集合中

$ srandmember key count // 从集合中随机挑选count个元素

$ spop key count // 随机弹出count个元素

$ smember key // 返回所有

// 多个之间的差集,交集,并集

$ sdiff key1 key2 // 差集

$ sinter key1 key2 // 交集

$ sunion key1 key2 // 并集

$ sdiff|sinter|sunion + store key // 保存起来

有序集合

$ zadd key score element // score可以重复但是element不行
 
$ zrem key element // 删除element

$ zscore key element // 获取分数

$ zincrby key increScore element // 对唯一的element操控分数

$ zcard key // 返回元素个数

$ zrank key element // 获取排名

$ zrange key 0 -1 withscores // 获取第一个到最后一个并且打印出分数

$ zrangebyscore key min max [withscores] // 通过分数来获取

$ zcount key min max

$ zremrangebyrank key min max // 通过排名开始删除

$ zinterstore | zunionstore

瑞士军刀

生命周期: 发送命令 -> 排队 -> 执行命令 -> 返回结果

慢查询

慢查询发生在第三阶段

客户端超时不一定是慢查询问题,但是个因素

  • 先进先出
  • 固定长度
// 默认值 10000
$ slowlog-max-len // 慢查询的时间(ms)
$ config set slowlog-max-len 1000 // 采用动态配置

// 默认值 128
$ slow-log-slower-than //慢查询的阈值

// 基本API
$ slowlog get [n] //获取慢查询队列
$ slowlog len // 获取慢查询队列长度
$ slowlog reset // 清空慢查询

经验:

  • slowlog-max-len通常设置为1ms
  • slow-log-slower-than一般设置为1000
  • 定期持久化慢查询

pipeline(流水线)

生命周期:传输命令-> 计算->返回结果

流水线就是一次性打包,传输命令之后,执行n次再反回来。

如何使用? 查询对应文档 看怎么把命令打包到pipeline 再发送。

使用建议:

  • 注意携带数量
  • pipeline只能作用在一个Redis上
  • M操作与pipeline区别

发布订阅

与消息队列模式不同

消息队列是抢一个 发布订阅是广播📢

角色: 发布者 订阅者 频道

模型: 发布者 -> 频道 <- 订阅者 (可以订阅多个频道) (不能消息堆积功能)

$ publish channel message // 发布命令 返回的是订阅者数量

$ subscribe [channel] // 订阅 可以一个或者多个

$ unsubscribe [channel] // 取消订阅

$ psubscribe [pattern] // 订阅模式

$ punsubscribe [pattern] // 退订指定模式

$ punsub channels // 列出至少有一个订阅者的频道

$ punsub numsub [channel] // 列出给定频道订阅者数量

$ punsub numpat // 列出被订阅模式的数量

Bitmap(位图)

实际上可以操作位的(二进制)

$ setbit key offset value // 给位图指定索引设置值 // 返回之前对应的值

$ getbit key offset // 获取位图指定索引值

$ bitcount key [start end] // 获取位图某个范围的1的个数

$ bitop op destkey key [key...] // 做多个Bitmap的and or not xor操作并将结果存在destkey中

$ bitpos key targetbit [start] [end] // 计算位图指定范围第一个偏移量对应的值等于destkey的位置

使用建议:

  • type=string 最大512M
  • 注意偏移量
  • 位图不是绝对好

HyperLogLog

极小的空间完成独立数量的统计

$ pfadd key element [element...] // 添加元素

$ pfcount key [key...] // 计算独立总数

$ pfmerge destkey sourcekey [sourcekey...] // 合并多个

局限性:

  • 错误率 0.81%
  • 没法取出来单条数据

GEO

存储类似经纬度 用于计算两地的距离

$ geo key longitude latitude member // 添加地理位置

$ geopos key member [member...] // 获取地理位置

$ geodist key member1 member2 [unit] // 获取两地之间的距离

$ georadius // 复杂 查文档https://cloud.tencent.com/developer/section/1374022

说明:

  • since 3.2+
  • type geoKey = zset
  • 删除直接使用zsetAPI

Redis持久化的取舍

内存中的数据将会异步保存在磁盘当中

  • 快照方式 Redis RDB
  • 写日志方式 Redis AOF

RDB

创建一个RDB二进制文件快照存储在硬盘,重启后重新载入

触发三种方式

  • save (同步)

save因为是同步的,可能会造成阻塞从而不能响应客户端

新的文件会替换老的文件。

  • bgsave (异步)

创建一个子进程(这里也会阻塞) fork(),会正常响应客户端,但会消耗内存

  • 自动

规则为: 保存为: dump.rdb

SecondsChanges
9001
30010
6010000
// 最佳配置

关闭自动
dbfilename dump-${port}.rdb
dir /oneBigDiskPath
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
  • 不容忽略的方式
    • 全量复制
    • debug reload
    • shutdown

AOF

实时写入AOF文件

  • 三种策略
策略描述缺点
always写入缓冲区,刷新到磁盘当中,每条命令都会写入。IO开销比较大
everysec每一秒刷新到磁盘中。丢失1秒数据
no操作系统来决定不可控
  • AOF重写

解决越写越大等问题

把过期的,重复的,没有用的优化化简。

// bgrewriteaof

// AOF重写配置
// auto-aof-rewrite-min-size AOF文件重写需要的尺寸
// auto-aof-rewrite-percentage AOF文件增长率
// aof_current-size AOF当前的尺寸
// aof_base_size AOF上次启动和重写的尺寸
  • 配置
appendonly yes
appendfilename "appendonly-${port}.aof"
appendfsync evertsec
dir /bigdiskpath
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

最佳策略

  • RDB

    • RDB “关”

    • 集中管理

    • 主从,从开

  • AOF

    • 开:缓存和存储
    • 重写集中管理
  • 最佳

    • 小分片
    • 缓存和存储
    • 监控

开发运维常见问题

  • fork
    • 同步操作
    • 需要的内存越大,时间越大
    • info: latest_fork_usec 查询上一次fork时间
    • 改善: 控制Redis实例最大可用内存maxmemory ; 合理配置linux内存分配策略vm.overcommit_memory=1;降低fork频率
  • 子进程开销与优化
    • 主要在文件生成的时候进行CPU密集写入
    • 改善:不做CPU绑定,不和COU密集型部署
    • 内存开销,会共享父进程的内存
    • 硬盘消耗 iostat /iotop去分析
    • 改善: 不要和高硬盘负载服务部署一起;no-appendfsync-on-rewrite=yes
  • AOF阻塞

​ 查看日志会发现:…xxxxxtaking too long.xxxx

​ aof_delayed_fsync 历史累计AOF阻塞数量

Redis 复制

主从复制

单机是有瓶颈的,发生机器故障,则客户端没法链接

且同样存在容量瓶颈 ; QPS瓶颈期

可以实现一主多从 一个slave只能有一个master 数据是单向的

可以到的 数据副本, 拓展读性能等功能

默认是全量复制

// 复制的配置
// 命令不需要重启
// 配置统一配置 需要重启

// slaveof 命令
$ slaveof masterIP
$ slaveof no one // 断掉成为别人的从节点

// 配置
slaveof ip port
slave-read-only yes

$ info replication // 查看分片(存在角色)

$ redis-cli -p 6379 info server | grep run # 查看runID

全量复制

image-20211219220549661

开销: bgsave时间;RDB文件网络传输时间;从节点清空RDB时间;从节点加载RDB;AOF重写时间

部分复制

image-20211219221028865

故障处理

自动故障转移: slave故障, master故障

slave故障 : 迁移到另外一个slave

master故障: 找一个slave成为master

开发运维中的问题

  • 读写分离
    • 读流量分摊到从节点
    • 复制数据延迟
    • 读到过期数据
    • 从节点故障
  • 主从配置不一致
    • maxmemory不一致:丢失数据
    • 数据结构优化参数(例如:hash-max-ziplist-entires): 内存不一致
  • 规避全量复制
    • 第一次全量复制
    • 节点运行ID不匹配
    • 复制积压缓冲区不足
  • 规避复制风暴
    • 单主节点挂掉,多个从节点开始全量复制
    • 一个机器上全是master,机器挂掉了

Redis Sentinel

基本架构

主从复制高可用问题: 主节点出现问题,通常是手动故障转移;写能力与存储能力受限。

基本架构:对故障判断,转移,通知。

客户端从Sentinel获取redis信息。(可以监控多套)

自动完成故障转移… ( 端口默认: 26379

image-20211226135132309

安装和配置

配置开启主从节点

配置开启Sentinel监控主节点

// 主节点
redis-server redis-7000.conf
port 7000
daemonzie yes
pidfile ${dir}-7000.pid
logfile 7000.log
dir ${dir}

// 从节点
redis-server redis-7001.conf
port 7001
daemonzie yes
pidfile ${dir}-7001.pid
logfile 7001.log
dir ${dir}
slaveof ip port

// Sentinel配置
port ${port}
dir ${dir}
pidfile ${port}.pid
sentinel monitor mymaster ip 7000 2 // 2的意思:2个sentinel认为有问题 则开始故障转移
sentinel down-after-milliseconds mymaster 30000  // 类似 ping
sentinel parallel-syncs mymaster 1 
sentinel failover-timeout mymaster 180000

客户端

需要知道Sentinel节点集合和masterName开始遍历…

内部是用发布订阅来实现的。(不是代理模式)

from redis.sentinel import Sentinel
sentinel = Sentinel(['localhost', 26379], ['localhost', 26380], ['localhost', 26381], socket_timeout=0.1)
sentinel.discover_master('mymaster')
sentinel.discover_slaves('mymaster')

定时任务原理

  1. 每10秒每个sentinel对master和slave执行info
    • 发现slave节点
    • 确认主从关系
  2. 每2秒每个sentinel通过master节点的channel交换信息
    • 通过频道交互
    • 交互对节点的看法和自身信息
  3. 每1秒钟每个sentinel对其他sentinel和redis执行ping

主观/客观下线->领导者选举->完成故障转移

  • 主观/客观下线
sentinel monitor mymaster ip 7000 <quorum>

sentinel down-after-milliseconds <masterName> <timeout>

主观下线: 即每个sentinel个体节点下线的判断

客观下线:超过了quorum统一,即多个认为下线了

  • 领导者选举
sentinel is-master-down-by-addr // 不仅可以判断master下线了吗 还可以申请作为领导

sentinel没有同意过其他人,就会同意你。

票数超过一半且大于quorum则为领导者,同时多个则重新选举。

  • 完成故障转移
  1. 选出来之后呢,会被执行slaveof no one使其成为master节点。
  2. 向剩余的slave节点发送命令,让他们成为新master的slave。
  3. 对原来的master设置为slave并且保持关注。
  • 注意点
  1. 选择slave优先级更高的(slave-priority)
  2. 会选择偏移量更大的节点(复制的完整性更高)
  3. 选择runID更小的节点

常见开发运维问题

sentinel failover <masterName> // 手动下线(手动触发故障转移)
  1. 从节点下线的时候要判断时候是暂时的,例如后续的清理工作等
  2. 只会对slave进行下线而对应的读的客户端没法自己转移。=> 客户端连接slave节点资源池
  3. Sentinel数量最好>=3。

Redis Cluster

为什么需要集群?

需要更高的并发量,数据量

数据分布

  • 顺序分区
    • 分散度易倾斜
    • 键值分布业务有关
  • 哈希分区
    • 数字分散度高
    • 键值分布业务无关
  1. 节点取余分区(数据迁移量范围太大了)
    1. 节点伸缩即扩容等操作会导致数据迁移
    2. 对于迁移的性能,推荐翻倍扩容
  2. 一致性哈希分区
    1. 优化了取余分区
    2. 只影响邻近节点,但是仍然会数据迁移
    3. 保证最小的数据迁移和负载均衡
  3. 虚拟槽分区(Redis Cluster使用)
    1. 每个槽映射一个数据子集,实现了分区
    2. 用CRC16函数直接去找对用的区域

搭建集群

image-20211226134841547

去访问,如果该数据不在对应的槽中,则会通知你去访问对应的槽。

每个节点负责数据的一部分,需要一个总的客户端来管理。

每个节点都是负责读写的,以及每个节点之间都是通信的。

  • 节点
cluster-enabled yes // 集群模式启动
  • meet

相互之间都会通信

  • 指派槽

指派每个槽的大小

  • 复制

每个主节点都有个副节点。

  • 安装

    • 原生命令的安装
    // 配置节点
    port ${port}
    daemonzie yes
    pidfile "${dir}-${port}.pid"
    logfile "${port}.log"
    dir "${dir}"
    dbfilename "dump-${port}.rdb"
    
    cluster-enabled yes // 集群模式启动
    cluster-config-file "nodes-${port}.conf" // 添加对应的配置
    cluster-node-timeout 15000 // 故障转移/节点下线...时间
    cluster-require-full-coverage no // 如果有一个不行了不会停止服务
    
    // 开启节点(这里为 3主 x 3)
    $ redis-server redis-7000.conf
    $ redis-server redis-7001.conf
    ...
    
    // meet实现通信
    $ redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7001 // 双方建立联系
    
    // 指派槽
    $ redis-cli -h 127.0.0.1 -p 7000 cluster addslots {0...5461} // 16384平均
    $ redis-cli -h 127.0.0.1 -p 7001 cluster addslots {5461...10922} // 16384平均
    $ redis-cli -h 127.0.0.1 -p 7002 cluster addslots {10923...16383} // 16384平均
    
    // 主从关系的分配实现故障转移
    $ redis-cli -h 127.0.0.1 -p 7003 cluster replicate ${node-id-7000}
    $ redis-cli -h 127.0.0.1 -p 7004 cluster replicate ${node-id-7001}
    $ redis-cli -h 127.0.0.1 -p 7005 cluster replicate ${node-id-7002}
    
    // 查询状态
    $ redis-cli -p 7000 cluster nodes
    $ redis-cli -p 7000 cluster info
    $ redis-cli -p 7000 cluster slots
    
    • 官方工具的安装
      • Ruby环境准备 下载 编译 安装
      • Rubygem redis客户端的安装
      • 安装redis-trib.rb
      • 参考链接: https://codeantenna.com/a/g07I0RgJZW
    cp ${REDIS_HOME}/src/redis-trib.rb /usr/loacl/bin
    
    // 配置开启redis但是集群还没有上线
    
    // 一键开启
    // 每个主节点后有一个从节点  前三个是主节点 后三个是从节点
    $ ./redis-trib.rb create --replicas 1 127.0.0.1:8000 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005
    

集群伸缩

原理:slot键值的迁移(理解 槽 节点 key 之间的关系)

  • 扩容

    • 准备新的节点 -> 同样的配置

    • 加入集群meet-> cluster meet

      • $ redis-trib.rb add-node 127.0.0.1:xxxx 127.0.0.1:xxxx(已经存在的)  // 官方也有
        
      • 作用1: 为迁移槽和数据做扩容

      • 作为从节点负责故障转移

    • 迁移槽和数据

      • 槽迁移计划

      • 迁移数据

      $ cluster setslot {slot} importing {sourceNodeId} // 对目标节点发送让目标节点准备导入槽的数据
      $ cluster setslot {slot} migrating {targetNodeId} // 让源节点准备迁出槽的数据
      $ cluster getkeysinslot {slot} {conut} // 源节点循环执行 获取count个属于槽的键
      $ migrate {targetIp} {targetPort} key 0 {timeout} // 源节点执行把指定key迁移
      $ cluster setslot {slot} node {targetNodeId} // 通知其他节点 我现在的槽到新的上面去了
      // 上面的太麻烦了
      
      $ redis-trib.rb reshard 127.0.0.1:7000 // 跟着提示走
      $ redis-cli -p 7000 cluster slots // 查看结果
      
      • 添加从节点
  • 收缩

    • 下线迁移槽

      • $ redis-trib.rb reshard --from {Fromid} --to {Toid} --slots 1366 127.0.0.1:7006
        
    • 忘记节点

      • $ cluster forget {downNodeID}  // 需要对其他所有节点忘记
        
        // 要先下从节点 再下主节点 否则会触发自动转移
        $ redis-trib.rb del-node 127.0.0.1:7000 {DownnodeId}  // 自动完成
        
    • 关闭节点

客户端路由

  • moved重定向(槽不命中)
    • -c 命名会自动重定向 不使用会返回MOVED (cluster模式)
  • ask重定向(客户端记录的是源节点 但是已经迁移到了目标节点)
  • smart客户端
    • 集群中选一个可以运行的节点,使用cluster slots初始化槽和节点映射
    • 将cluster slots的结果映射到本地,为每一个创建jedisPool
    • 准备执行命令
image-20211226134909521

故障转移

  • 故障发现:

    • 通过 ping / pong 来发现

    • 主观发现:

      image-20211225210027671
    • 客观下线:

      当半数以上持有槽的主节点都标记某节点主观下线

      image-20211225210041716
  • 故障恢复

    • 资格检查
    • 准备选举时间
    • 选举投票

开发运维常见问题

  • 批量操作 mget mset必须在一个槽上
    • 串行mget (for循环一个个来)
    • 串行IO (客户端先内聚分组再去pipline)
    • 并行IO (客户端先内聚分组再去pipline时候是多线程)
    • hash_tag (用hash使所有key都落到了一个节点)
  • 避免使用大集群,大业务可以使用多集群
  • cluster-node-timeout:带宽和故障转移速度的均衡
  • 发布订阅 建议单独一套集群 因为会在每个节点广播,加重带宽
  • 内存配置不一致: hash-max-ziplist-value ;set-max-intset-entries等
  • 对于热点key不要使用hash_tag;或者使用本地缓存
  • 集群下的从节点不能写也不能读,会跳转到主节点
  • 数据迁移不推荐官方的,推荐:redis-migrate-tool ; redis-port
集群vs单机
key的批量操作必须在一个槽
key事务和Lua支持的key在一个节点上
key是数据分区最小粒度
不支持多个数据库
复制只能复制一层

缓存

得与失

受益成本使用场景
加速读写数据不一致 更新策略有关降低高消耗的SQL
降低后端负载代码维护成本加速响应时间
运维成本大量写合并为批量写

缓存更新策略

策略一致性维护成本
LRU/LFU/FIFO算法剔除:例如 maxmemory-policy最差
超时剔除:例如expire较差
主动更新:开发控制生命周期
  1. 低一致性:最大内存和淘汰策略
  2. 高一致性:超时剔除与主动更新结合,最大内存和淘汰兜底

缓存粒度控制

  1. 通用性:全量属性更好
  2. 占用空间:部分属性更好
  3. 代码维护:表面上全量属性更好

缓存穿透问题

缓存的key不存在,大量流量打到了存储层

  1. 业务代码自身问题
  2. 恶意攻击、爬虫

发现:业务时间,业务逻辑,相关指标

解决:

  1. 缓存空对象:如果缓存不存在,将cache中的key为null
  2. 布隆过滤器拦截

缓存雪崩

缓存崩了,大量流量打到了存储层,设计的时候存储层是小流量,造成级联故障

  1. 保证混存高可用性
  2. 依赖隔离组件为后端限流
  3. 提前演练:压力测试

无底洞问题优化

“加机器”性能没提升反而下降,节点多了,IO(node)越来越高

  1. 命名本身优化
  2. 减少网络通信次数
  3. 降低接入成本
image-20211227115203464

热点key重建优化

热点key + 较长的重建时间,并发的时候大量线程在重建时间的时候都不会命中导致都在重建。

  1. 减少重缓存次数
  2. 数据尽可能一致
  3. 减少潜在风险
  • 互斥锁
  • 永不过期,逻辑去解决。
image-20211227115935924

Redis云平台CacheCloud

开源地址: https://github.com/sohutv/cachecloud

  1. 一键开启Redis
  2. 监控和报警
  3. 客户端:透明使用,性能上报
  4. 可视化运维:配置,扩容等
  5. 已存在的Redis直接接入与迁移

Redis布隆过滤器

基本:

参考链接:https://juejin.cn/post/6844904134894698510

问题的引出:如果存在50亿的电话号码,如何判断这10万个电话号码已经存在?

原理:一个很长的二进制向量和若干个哈希函数, 通过哈希做映射,如果走一遍的话均为1则存在

误差被设置为1的概率:1 - (1 - 1/m)^nk (m个向量 n个数据 k个哈希函数)

相比较于本地的过滤器,基于Redis的布隆过滤器可以同步且容量大

每个语言都有自己的写法,但是总的思想是一样的。

布隆过滤器调研与设计Go实现 https://juejin.cn/post/6863059963145617416

分布式布隆过滤器

  • 多个布隆过滤器
  • 基于pipeline提高效率

Redis开发规范

键值的设计

  • key名字设计

    • 可读性和可管理性 例如业务名:表名:id

    • 简洁行 控制key的长度

    • 不包含特殊字符

      短字符串是embstr(<=39) 长字符串为raw 限制长度会节省内存

  • value设计

    • 拒绝bigkey 官方提供了redis-cli --bigkeys / debug object key

    • 选择合适的数据结构

      比如现在记录某个人发了什么图片

      • 方案一 : set key value
      • 方案二: hset allPics picId userId
      • 方案三: hset picId/100 picID%100 userId
  • 过期时间管理

    • 不是垃圾桶 别啥都往里面放 以及要记得设置过期时间
    • 过期时间不宜集中

发现与删除Bigkey

  1. redis-cli --bigkeys 但是无法限制大小
  2. debug object key 可能会发生阻塞 可以使用llen,zcard,scard,hlen等命令来查询
  3. 主动报警 当检测到大于报警值的时候 发出警报

  1. 阻塞:删除Bigkey会特别慢 注意隐性删除
  2. Reids4.0 lazy delete 后台删除

命名使用技巧

  • O(N)以上命令关注N的数量。有遍历的需求可以使用hscan,sscan,zscan代替
  • 禁用命令:key *, flushall等
  • 合理使用select
  • Redis事务功能较弱
  • Redis集群版本再使用Lua上有特殊要求
  • 必要情况下使用monitor命令,建议不要长时间使用

客户端优化

  • 避免多个应用使用一个Redis实例:不想干的业务拆分,公共数据做服务化。
  • 使用连接池
    • maxTotal与maxIdle接近:Reids并发量与客户端执行时间(QPS/平均耗时)
image-20211231142644177 image-20211231142705016

Redis内存优化

查看内存情况

执行 info mermory

image-20220101154101654 image-20220101154302212

各个内存缓冲区

  • 客户端缓冲区
  1. 输出缓冲区
    1. 普通客户端
    2. slave客户端
    3. pubsub客户端
  2. 输入缓冲区
image-20220101154835620
  • 缓冲内存
image-20220101155521143
  • 对象内存
image-20220101155730913
  • 内存碎片
image-20220101155902621
  • 子进程
image-20220101160129643

内存管理

  • 设置内存上限
$ config set maxmemory 4g
$ config rewrite
  • 内存回收策略
    • 过期键的处理
      • 惰性删除
      • 定时删除
    • maxmemory-police来控制内存超出后的策略
      • Noeviction
      • Volatile-lru
      • Allkeys-random
      • volatile-random
      • volatile-ttl

内存优化

  • 选择合理的数据结构(针对对象内存)
image-20220101161917344 image-20220101162236333
  • 客户端优化 (出现了一次内存暴增)
image-20220101162743035

Redis开发一些坑

内核优化

image-20220102115741923
  • 对于 vm.overcommit_memory
image-20220102115856505 image-20220102120105047
  1. Redis设置合理的maxmemory,保证机器有20%~30%的闲置内存。

  2. 集中化管理AOF重写和RDB的bgsave。

  3. 设置vm.overcommit_memory=1,防止极端情况会造成fork失败。

  • 对于swappiness
image-20220102120612040 image-20220102120912814
  • 对于THP
image-20220102121236847
  • 对于OOM killer
image-20220102121447661
  • NTP 同步时间
  • ulimit
image-20220102121812055
  • TCP-backlog
image-20220102121840389

安全Redis

  • 被攻击的特征
    • 存在外网IP
    • 默认端口
    • 以root启动
    • 没有设置密码
    • bind设置为0.0.0.0
  • 安全七法则
    • 设置密码
    • 伪装危险命令
    • bind限制网卡
    • 防火墙
    • 定期备份
    • 不使用默认端口
    • 非root用户启动

热点Key

  • 客户端发现:客户端记录,但是不太建议
  • 代理:代理全增量统计
  • 服务端:解析monitor输出统计
  • 机器端:抓取Redis的TCP数据统计
image-20220102153933916
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值