Redis

78 阅读30分钟

NoSQL 特点

  • 方便扩展(数据之间无关系, 很好扩展)
  • 大数据 高性能(Redis 一秒写8万次, 读11万次, NoSQL的缓存记录级, 是一种细粒度的缓存, 性能会比较高)
  • 数据类型是多样性的(不需事先设计数据库!随取随用)
  • 传统 RDBMS 和 NoSQL 区别
    • 传统的 RDBMS
      1. 结构化组织
      2. SQL
      3. 数据和关系都存在单独的表中
      4. 操作语言,数据定义语言
      5. 严格的一致性
      6. 基础的事务
    • NoSQL
      1. 不仅仅是数据
      2. 没有固定的查询语言
      3. 键值对, 列存储, 文档存储, 图形存储(社交关系)
      4. 最终一致性
      5. CAP 定理 和 BASE理论(异地多活)
      6. 高性能 高可用 高可扩展
  • 3V + 3高: 海量,多样,实时; 高并发,高可拓,高性能

大公司业务模型

  • 业务模型
    1. 商品的基本信息(名称,价格,商家信息): 关系型数据库 MySQL / Oracle
    2. 商品的描述、评论(文字比较多): 文档型数据库中 MongoDB
    3. 图片: 分布式文件系统(FastDFS); 淘宝自己的(TFS); Google自己的(GFS); Hadoop(HDFS);
    4. 商品关键字(搜索): solr elasticsearch; 淘宝自己的(ISearch)
    5. 商品热门的波段信息(秒杀): 内存数据库 Redis Tair Memache...
    6. 商品的交易, 外部的支付接口: 第三方应用
  • 数据层解决方案: 统一数据服务层 UDSL(在网站应用集群和底层数据源之间,构建一层代理,统一数据层)
    • 模型数据映射
      1. 实现业务模型各属性与底层不同类型数据源的模型数据映射
      2. 目前支持关系数据库,iSearch, redis, mongodb
    • 统一的查询和更新API
      1. 提供了基于业务模型的统- -的查 询和更新的API,简化网站应用跨不同数据源的开发模式。
    • 性能优化策略
      1. 字段延迟加载,按需返回设置
      2. 基于热点缓存平台的二级缓存。
      3. 异步并行的查询数据:异步并行加载模型中来自不同数据源的字段
      4. 并发保护:拒绝访问频率过高的主机IP或IP段
      5. 过滤高危的查询:例如会导致数据库崩溃的全表扫描

数据层.png

热点缓存设计.png

NoSQL 四大分类

  • KV 键值对
    • 新浪: Redis
    • 美团: Redis + Tair
    • 阿里, 百度: Redis + memecache
  • 文档型数据库(bson 格式和json 一样)
    • MongoDB
      1. 是一个基于分布式文件存储的数据库(C++编写), 主要用来处理大量的文档
      2. 是一个介于关系型数据库和非关系型数据中中间的产品, 功能最丰富, 最像关系型数据库的
    • ConthDB
  • 列存储数据库
    • HBase
    • 分布式文件系统
  • 图关系数据库(社交, 拓扑图): 不是存图形, 放的是关系(朋友圈社交网络, 广告推荐)
    • Neo4j
    • InfoGrid

NoSQL 四大分类.png

Redis 入门

  • 官网
  • Redis: Remote Dictionary Server, 远程字典服务
  • 会周期性的把更新的数据写入磁盘 或 把修改操作写入追加的记录文件, 并在些基础上实现master-slave(主从)同步

Redis 能干嘛

  1. 内存存储 持久化, 内存中是断电即失, 所以说持久化很重要(rdb、aof)
  2. 效率高,可以用于高速缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器(浏览量! )

特性

  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事务

Redis 是单线程的

  • 为什么使用单线程: Redis 是很快的, Redis 基于内存操作, CPU 不是 Redis 性能瓶颈. Redis 的瓶颈是根据机器的内存和网络带宽(需通过网络传输)决定的, 既然可用单线程来实现, 就使用单线程了
  • Redis 是 C语言写的, 官方提供的数据为 100000+ 的QPS, 不比 Memecache 差
  • 为什么单线程还这么快: Redis 是将所有的数据全部放在内存中的, 所以说使用单线程操作效率是最高的, 多线程(CPU上下文会切换, 耗时的操作), 对于内存来说, 若没有上下文效率就是最高的, 多次读写都是在一个 CPU 上的, 在内存情况下, 这个方案是最佳的方案

安装

  • 默认端口号: 6379
  • Windows 安装
    • Window 在 Github 上下载(停更很久了, 不建议在 Window 上开发)
    • Github 下载 > 解压到指定目录
    • redis-server.exe : 启动服务
    • redis-cli.ext : 启动客户端
    • redis-check-aof.exe : 检查aof持久化文件是否配置正确
    • redis-benchmark.exe : 测试性能的
  • Linux 安装
    1. 官网下载安装包: wget http://download.redis.io/releases/redis-版本号.tar.gz
    2. 移动到 /opt 目录(安装包程序放在 /opt 下): mv redis-版本号.tar.gz /opt
    3. 解压: tar -zxvf redis-版本号.tar.gz
    4. 进入解压后的文件: cd redis-版本号 > 可以看到配置文件 redis.conf
    5. 基本的环境安装:
      yum install gcc-c++ # 安装 c++ 环境
      gcc -v # 查看结果
      make # 初始化需要的文件
      make install # 安装需要的文件
      
    6. redis 的默认安装路径(所有的安装路径都一样): /usr/local/bin
      • 能看到 redis-serverredis-cli
    7. 管理配置文件:
      • 创建一个目录 mkdir iconfig
      • 将 Rddis 配置文件拷贝到此配置文件目录: cp /opt/redis-版本号/redis.conf iconfig
    8. Redis 默认不是后台启动, 修改配置文件
      vim iconfig/redis.conf # 打开配置文件
      
      bind 127.0.0.1 # 限制可访问的主机, 默认本机, 0.0.0.0 任意计算机
      port 6379 # 默认端口号
      databases 16 # 默认有多少个数据库
      daemonize no # 是否以守护进行运行(后台进程)
      
    9. 启动 Redis 服务(配置文件路径): redis-server /usr/local/bin/iconfig/redis.conf
    10. 启动 Redis 客户端(端口号 6379): redis-cli -p 端口号

性能测试:

  • reddis-benchmark 命令参数

命令参数.png

查看结果.png

基础知识

  • 默认有16个(0~15)数据库, 默认使用第0个
  • Redis 可用作 数据库, 缓存, 消息中间件MQ

五大基本数据类型

基本使用

ps -ef|grep redis # 查看进程是否开启

# 关闭 Redis 服务
shutdown # 关闭 Redis 服务
exit # 退出 Redis 客户端

select 数据库编号 # 切换数据库, 默认16个(0~15)
DBSIZE # 当前数据库大小

flushall # 清空`全部`数据数据
flushdb # 清空`当前`数据库

ping # 测试连接, 返回 PONG

set key1 val1
get key1

clear # 清屏

对 key 的操作

keys * # 查看所有的 key
exists key1 # 查看当前 key 是否存在: 1 存在; 0 不存在
hexists key1 field1 # 查看当前对象的 field1 是否存在: 1 存在; 0 不存在
move key1 1 # 移除 key: `1`当前数据库

expire key1 多少秒后过期 # 设置过期时间
ttl key1 # 查看 key 多久后过期: -2 已过期

type key1 # 当前 key 的类型: string number

String/Number

get key1 # 获取值

set key1 val1
setnx key1 val1 # 不存在才设置(set if not exist), 在分布式锁中常用: 1 成功; 0 失败
setex key1 过期时间秒 val1 # 设置值时, 设置过期时间(set with expire)

# ============== 字符串 ==============
append key1 "追加字符串"
strlen key1 # 字符串长度
getrange key1 开始下标 结束下标 # 截取范围(可用负数): 闭区间
setrange key1 开始下标 替换值 # 替换值, 替换的长度是替换值的长度

# ============== 数值 ==============
incr key1 步长 # 自增(只能是数字): 步长默认为1
decr key1 步长 # 自减

# ============== 批量操作 ==============
mset key1 val1 key2 val2 # 批量设置
mget key1 key2 # 批量获取
msetnx key1 val1 key2 val2 # 不存在才设置(mset if not exist), 在分布式锁中常用: 1 成功; 0 失败

# ============== 对象操作 ==============
set user:1 {name:chh,age:18} # 不能有空格: 只能通过 user:1 来获取
mset user:1:name chh user:1:age 18 # 只能通过 user:1:name 来获取

# ============== 组合命令 ==============
getset key1 val1 # 先获取值 再设置值

List

# ============== 添加 ==============
lpush key1 val1 # 左侧推入
rpush key1 val1 # 右侧推入
lset key1 下标 val1 # 设置指定下标的值(更新操作): 此下标必须存在
linsert key1 before/after "指定值" val1 # 在“指定值” 前/后 插入值

# ============== 移除 ==============
lpop key1 # 左侧弹出
rpop key1 # 右侧弹出
rpoplpush key1 key2 # 从key1右侧弹出; 并key2左侧推入
lrem key1 移除个数 移除的值 # 移除指定值(精确匹配)
ltrim key1 开始下标 结束下标 # 切换开始下标前 & 结束下标后面的

# ============== 获取 ==============
llen key # 获取长度
lrange key1 0 -1 # 截取范围: 闭区间
lindex key1 下标 # 根据下标获取值

Set 集合

sadd key1 val1 # 新增
srem key1 val1 # 移除
spop key1 # 随机移除成员

srandmember key1 个数 # 随机返回`指定个数`成员(默认1个): s_rand_member
smove key1 key2 val1 # 移动: 将 val1 从 key1 中, 移动到 key2 中

smembers key1 # 获取所有成员: member(成员)
sismember key1 val1 # 是否存在此成员: 1 存在; 0 不存在
scard key1 # 获取元素个数

sdiff key1 key2 # 差集
sinter key1 key2 # 交集 intersection
sunion key1 key2 # 并集 union

Hash(Map 集合)

hset key1 field1 val1 # 设置
hsetnx key1 field1 val1 # 不存在才设置(set if not exist), 在分布式锁中常用: 1 成功; 0 失败
hmset key1 field1 val1 field2 val2

hdel key1 field1 # 删除

hget key1 field1 # 获取
hmget key1 field1 field2
hgetall key1 # 获取全部的数据: field1(字段) val1 field2 val2
hkeys key1 # 所有的 key
hvals key1 # 所有的 val

hincrby key1 field1 # 自增
hdecrby key1 field1 # 自减

hlen key1 # 字段的数量

Zset(有序集合)

  • 存储班级成绩表, 工资表排序
  • 消息的缓急程序
  • 排行榜
zadd key1 score1 val1 # 增加(键 权重 值)
zrange key1 开始下标 结束下标 # 截取范围(从小到大排序): 闭区间
zrevrange key1 开始下标 结束下标 # 截取范围(从大到小排序): 闭区间

zrangebyscore key1 最小值 最大值 withscores # 通过权重来截取范围: -inf(无穷小) +inf(无穷大); withscores 显示权重
zcount key1 最小值 最大值 # 统计此范围成员的数量

zrem key1 val1 # 删除

zcard key1 # 成员个数
        

三种特殊数据类型

geo 地理位置

geoadd key1 log lat 地点标识名 # 添加经纬度度(两极的无法直接添加, 一般是下载城市数据, 通过代码一次性导入)
geoadd china:city 116.40 39.90 beijin # 例 添加北京

geopos key1 地点标识名 # 获取到坐标值

geodist key1 地点标识名1 地点标识名2 单位 # 两个地点之间的距离: m(默认) km mi(英里) ft(英尺)

# 以给定的`经纬度`为中心, 找出某一半径内的元素(附近的人): withdist 返回直线距离; withcoord 返回经纬度; count 返回的数量
georadius key1 log lat 半径 单位 withdist withcoord count 返回的数量
# 以给定的`地点标识名`为中心, 找出某一半径内的元素(附近的人)
georadiusbymember key 地点标识名 半径 单位 withdist withcoord count 返回的数量

zrange key1 开始下标 结束下标
zrem key1 地点标识名1 # 删除

hyperloglog

  • 统计基数(不重复的元素): 最多只使用 12kb 的内存
  • 统计 UV 任务, 0.81% 的误差, 不允许容错, 就使用 set 或自己的数据类型即可
pfadd key1 val1 val2 val3 ... # 添加元素

pfcount key1 # 统计基数(不重复的数据)

pfmerge key1 合并源1 合并源2 # 合并数据

bitmaps(位存储)

  • 统计用户信息: 活跃/不活跃; 登录/未登录; 打卡/未打卡
  • 只有两个状态的, 都可使用 bitmaps, 都是操作二进制, 只有01两个状态
  • 类似于数组
setbit key1 位下标 状态 # 状态只有 0 / 1
getbit key1

bitcount key1 [开始下标 结束下标] # 统计所有状态为 1 的个数

Redis 配置文件

# ============ unit 限制内存的使用大小, 大小写不敏感 ============
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes

# ============ 引入多个配置文件 ============
# include /path/to/local.conf
# include /path/to/other.conf

# ============ 网络配置(NETWORK) ============
bind 127.0.0.1 # 可通过本机哪个 ip 地址访问
# bind 192.168.1.100 10.0.0.1 # 绑定多个网卡
# bind 0.0.0.0 # 允许全部的网卡
protected-mode yes # 是否开启 受保护模式
port 6379 # 端口号: 默认 6379

# ============ 通用配置(GENERAL) ============
daemonize no # 是否以守护进程运行(后台) 修改成 yes
pidfile /var/run/redis_6379.pid # 配置文件的 pid 文件路径: 以守护进程方式运行, 必须指定

loglevel notice # 日志级别
# debug (a lot of information, useful for development/testing) # 开发/测试
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) # 生产环境使用
# warning (only very important / critical messages are logged)
logfile "" # 日志的文件位置名

databases 16 # 默认的数据库数量(0~15)

always-show-logo yes # 是否在启动时 显示 logo

# ============ 快照(rdb模式): 持久化 保存到硬盘 SNAPSHOTTING ============
# 持久化: 在规定的时间内, 执行了多少次操作, 则持久化到文件 .rdb .aof
save 900 1 # 若 900s(15min) 内, 1 个key修改, 就进行持久化操作
save 300 10 # 若 300s(5min) 内, 10 个key修改, 就进行持久化操作
save 60 10000 # 若 60(1min) 内, 10000 个key修改, 就进行持久化操作(高并发)
stop-writes-on-bgsave-error yes # 持久化错误, 是否继续工作
rdbcompression yes # 是否压缩 rdb 文件, 需消耗 cpu 资源
rdbchecksum yes # 保存 rdb 文件时, 进行错误的检查
dir ./ # 持久化保存的目录: 默认当前目录下

# ============ 主从复制(REPLICATION) ============


# ============ 安全配置(SECURITY) ============
requirepass 密码 # 默认不需要密码, 正常通过命令行设置密码
# config get requirepass -- 获取密码
# config set requirepass "密码" -- 设置密码
# auth 密码 -- 登录

# ============ 客户端限制(CLIENTS) ============
maxclients 10000 # 限制最大客户端连接数(10000 个)

# ============ 内存使用限制(MEMORY MANAGEMENT) ============
maxmemory <bytes> # 配置最大使用内存
maxmemory policy # 内存到达最大的处理策略
# volatile-lru -> remove the key with an expire set using an LRU algorithm 只对设置了过期的key进行LRU(默认)
# allkeys-lru -> remove any key according to the LRU algorithm 删除 LRU 算法的key
# volatile-random -> remove a random key with an expire set 随机删除即将过期的key
# allkeys-random -> remove a random key, any key 随机删除
# volatile-ttl -> remove the key with the nearest expire time (minor TTL) 删除即将过期的
# noeviction -> don't expire at all, just return an error on write operations 永不过期 返回错误

# ============ aof 配置(APPEND ONLY MODE) ============
appendonly no # 是否开启, 默认使用 rdb 方式持久化, 在大部分情况下, rdb 完全够用
appendfilename "appendonly.aof" # 持久化的文件名

appendfsync everysec # 每秒执行一个同步(sync), 可能会丢失这 1s 的数据
# appendfsync always # 每次修改都会同步(sync), 消耗性能
# appendfsync no # 不执行同步(sync), 由操作系统同步数据, 速度最快

no-appendfsync-on-rewrite no # 是否重写: 当记录日志的文件超过规定值, 是否重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb # 最大限制为 64M, 超过大小, fork一个新的进程来将我们的文件进行重写

Redis 持久化

RDB

  • 在指定的时间间隔内, 将内存中的数据快照写入磁盘, 恢复时, 直接读取快照文件
  • 持久化过程
    1. Redis 会单独创建(fork)一个子进程来进行持久化, 先将数据写入到一个临时文件中,
    2. 待持久化过程都结束后, 再用此临时文件替换上次持久化好的文件
      • 整个过程中, 主进程不进行IO操作, 确保了极高的性能
      • 若需大规模数据的恢复, 且对于数据恢复的完整性不是非常敏感, 那 rdb 比 aof 更高效
  • rdb 保存的文件是 dump.rdb
  • 备份的触发条件
    1. 执行 flushall 命令
    2. 退出客户端 exit
    3. 满足配置文件中 save 的规则
  • 恢复 rdb 文件内的数据
    1. 查看 rdb 文件, 存放路径: config get dir
    2. rdb 文件放在我们的 redis 启动目录就可以, redis 启动时会自动检查 dump.rdb, 并恢复数据
  • 优缺点
    • 优点
      1. 适合大规模的数据恢复
      2. 对数据的完整性要求不高
    • 缺点
      1. 需要一定的时间间隔进行操作, 若 redis 意外宕机, 则最后一次修改数据就没有了
      2. fork 进程的时候, 会占用一定的内存空间

rdb.png

配置文件

# ============ 快照(rdb模式): 持久化 保存到硬盘 SNAPSHOTTING ============
# 持久化: 在规定的时间内, 执行了多少次操作, 则持久化到文件 .rdb .aof
save 900 1 # 若 900s(15min) 内, 1 个key修改, 就进行持久化操作
save 300 10 # 若 300s(5min) 内, 10 个key修改, 就进行持久化操作
save 60 10000 # 若 60(1min) 内, 10000 个key修改, 就进行持久化操作(高并发)
stop-writes-on-bgsave-error yes # 持久化错误, 是否继续工作
rdbcompression yes # 是否压缩 rdb 文件, 需消耗 cpu 资源
rdbchecksum yes # 保存 rdb 文件时, 进行错误的检查
dir ./ # 持久化保存的目录: 默认当前目录下

AOF(Append Only File)

将我们所有命令都记录下来, 相当于 history, 恢复时把这个文件全部执行一遍

  • 以日志的形式记录每个写操作, 只许追加文件但不可改写文件, Redis 启动时,会读取该文件内容, 全部执行一次
  • aof 保存的文件是 appendonly.aof
  • aof 文件有错误, redis 启动不起来, 通过 redis-check-aof --fix appendonly.aof 来修复(输入 y)
  • 优缺点
    • 优点
      1. 每次修改都同步, 文件的完整性更好
      2. 默认每秒同步一次, 可能会丢失一秒的数据
      3. 设置从不同步, 效率是最高的
    • 缺点
      1. 相对于数据文件来说, aof 远远大于 rdb, 恢复的速度更慢
      2. aof 运行效率也比 rdb 慢, 所以我们 redis 默认配置就是 rdb

rdb.png

配置文件

# ============ aof 配置(APPEND ONLY MODE) ============
appendonly no # 是否开启, 默认使用 rdb 方式持久化, 在大部分情况下, rdb 完全够用
appendfilename "appendonly.aof" # 持久化的文件名

appendfsync everysec # 每秒执行一个同步(sync), 可能会丢失这 1s 的数据
# appendfsync always # 每次修改都会同步(sync), 消耗性能
# appendfsync no # 不执行同步(sync), 由操作系统同步数据, 速度最快

no-appendfsync-on-rewrite no # 是否重写: 当记录日志的文件超过规定值, 是否重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb # 最大限制为 64M, 超过大小, fork一个新的进程来将我们的文件进行重写

aof.png

扩展

  1. RDB持久化方式能够在指定的时间间隔内对你的数据进行快照存储
  2. AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF命令以Redis协议追加保存每次写的操作到文件末尾, Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
  3. 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
  4. 同时开启两种持久化方式
    • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
    • RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段
  5. 性能建议
    • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则
    • 如果Enable(开启) AOF , 好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的I0, 二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率, AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值
    • 如果不Enable AOF, 仅靠Master-Slave Repllcation实现高可用性也可以,能省掉一大笔I0,也减少了rewrite时带来的系统波动。代价是如果 Master/Slave 同时倒掉, 会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个,微博就是这种架构

Redis 事务操作

  • Redis 事务本质: 一组命令的集合, 一个事务中所有命令都被序列化, 在事务执行过程中, 会按顺序执行
  • Redis 单条命令是保持原子性的, 但事务不保证原子性
  • Redis 事务没有隔离级别的概念
  • 开启事务 > 命令入队 > 执行事务
# ========== 队列 set set set 执行 ==========
multi # 开启事务
set k1 v1 # 命令入队
set k2 v2 # 命令入队
set k3 v3 # 命令入队
exec # 执行事务
discard # 取消事务

# ========== 编译型异常(代码有问题, 命令错误), 所有命令都不会执行 ==========
multi
set k1 v1
set k2 v2
getset k2 # 错误的命令: 正确的是 getset k2 v21; EXECABORT Transaction discarded because of previous errors
exec # 执行事务报错

# ========== 运行时异常(1/0): 错误命令抛出异常, 其它命令正常执行 ==========
set k1 "v1"
multi
incr k1 # 错误的命令: 字符串不能自增 (error) ERR value is not an integer or out of range
set k2 v2 # OK
get k2 # OK
exec # 执行事务报错

监控(锁)

  • 悲观锁: 很悲观, 认为任何时候都会出问题, 无论做什么都会加锁(性能差)
  • 乐观锁:
    1. 很悲观, 认为任何时候都不会出问题, 无论做什么都不会加锁, 更新数据时判断一下, 在此期间是否有人修改过这个数据
    2. 获取 version
    3. 更新时比较 version
set money 100 # 收的钱
set out 0 # 花的钱

watch money # 监控指定的 key
multi # 开启事务
decrby money 20 # 收的钱 -20
incrby out 20 # 花的钱 +20
exec # 执行事务

# 无其他线程修改数据: 可正常完成事务, 返回值
# 有其他线程修改数据(修改其中的一个金额): 不会执行事务, 返回 nil
unwatch # 解除监视, 再重新执行监视操作(重新获取 version)

Redis 实现订阅发布(消息队列)

subscribe 管道名 # 订阅指定的消息
unsubscribe 管道名 # 退订指定的消息

psubscribe 管道名正则 # 订阅符合正则的消息
punsubscribe 管道名正则 # 退订符合正则的消息

publish 管道名 消息内容 # 发布消息: 所有订阅了此管道的人, 都会接收到

image.png

Redis 主从复制: 从机只能读 不能写

  • 主节点(master/leader): 写为主; 从节点(slave/follower): 读为主
  • 至少需要一主二从, 哨兵模式需要??????
  • 数据的复制是单向的, 只能由主节点 => 从节点
  • 主从复制的作用
    1. 数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
    2. 故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
    3. 负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务, 分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
    4. 高可用基石: 除了上述作用以外, 主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
info replication # 查看当前库的信息

# role:master # 当前角色
# ============ 从机看主机的信息 ============
# master_host:127.0.0.1 # 主机ip
# master_port:6379 # 主机端口号
# ============ 主机看从机的信息 ============
# connected_slaves:2 # 从机多少个
# salve0:ip=127.0.0.1,port=6380,state=online,offset=42,lag=1 # 从机1的信息

生成配置文件

#!/usr/bin/bash
for port in $(seq 0 5) # 循环生成配置文件
do
# 计算端口号
let "port+=6379"
# 写入配置到配置文件(替换成真实的配置文件存放路径)
cat << EOF >/usr/local/bin/iconfig/redis-${port}.conf
# 真实的配置内容
port $port # 1. 端口号

daemonize yes # 2. 是否以守护进程运行(后台)
pidfile /var/run/redis_${port}.pid # 3. 配置文件的 pid 文件路径: 以守护进程方式运行, 必须指定

logfile "${port}.log" # 4. 日志文件路径

dbfilename dump${port}.rdb # 5. rdb 保存路径
EOF
done

循环启动服务

#!/usr/bin/bash
for port in $(seq 0 5) # 循环
do
# 计算端口号
let "port+=6379"
# 指定配置文件 启动服务: 替换成真实的配置文件存放路径
redis-server /usr/local/bin/iconfig/redis-${port}.conf
done
ps -ef | grep redis # 查看服务启动结果

配置主从(命令行): 真实情况是在配置文件里 配置

默认情况下, 每台 Redis 服务器都是主节点, 一般情况下配置从机就可以了

redis-cli -p 从机1端口号 # 进入`从机`
slaveof 主机ip 主机端口号 # 指定主服务器的 host port(认父亲)

配置主从(配置文件): 常用

默认情况下, 每台 Redis 服务器都是主节点, 一般情况下配置从机就可以了

bind 0.0.0.0 # 使得Redis服务器可以跨网络访问

requirepass "密码" # 设置密码

slaveof 192.168.11.128 6379 # 指定主服务器的 host port(认父亲)

masterauth 123456 # 主服务器密码

层层链路模式: 爷爷 > 爸爸 > 儿子 > 孙子

  • 当爷爷走了, 爸爸就当头(谋朝篡位): 在爸爸中执行命令 slaveof no one
  • 当爷爷回来, 也是个光杆, 需要重新配置

说明

  • 主机断开连接, 从机依旧连接到主机的, 但是没有写操作, 这个时候,主机如果回来了, 从机依旧可以直接获取到主机写的信息
  • 若使用命令行配置的主从, 从机重启, 会变回主机, 重新设置成从机, 立马会从主机中获取值

复制原理

  • Slave(从机) 启动成功连接到 Master(主机) 后会发送一个 sync(同步) 命令
  • Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, Master 将传送整个数据文件到slave ,并完成一次完全同步
  • 全量复制(首次同步): 在 slave 服务在接收到数据库文件数据后,将其存盘并加载到内存中
  • 增量复制(后续同步): Master 继续将新的所有收集到的修改命令依次传给slave ,完成同步

Redis 哨兵模式(自动选举老大的模式, 所有的集群都用哨兵模式)

  • 谋朝篡位的自动版, 能够后台监控主机是否故障, 根据投票数自动将从库转换成主库
  • 哨兵模式: 哨兵是一个独立的进程, 其原理是哨兵通过发送命令, 等待 Redis 服务器响应, 从而监控是否还活着
    • 通过发送命令, 让 Redis 服务器返回运行状态(包括主服务器和从服务器)
    • 当哨兵监测到 Master 宕机, 会自动将 slave 切换成 Master, 然后通过发布订阅模式通知其他从服务器修改配置文件, 从新认父亲
  • 监测过程
    1. 若主服务器宕机, 哨兵1先检测到这个结果, 系统并不会马上进行failover过程, 仅仅是哨兵1主观认为不可用, 这个现象成为主观下线
    2. 当后面的哨兵也检测到主服务器不可用,且数据达到一定值时, 哨兵间进行一次投票, 投票多的成为主服务器
    3. 投票结果由一个哨兵发起, 进行 failover(故障转移)操作, 切换成功后, 会通过发布订阅模式, 让各个哨兵把自己监控的从服务器实现切换主机, 这个过程称为客观下线
  • 配置哨兵
    • 修改配置文件: cp /opt/redis-版本号/sentinel.conf iconfig/sentinel-端口号.conf
      # protected-mode no # 禁止保护模式(关闭了保护模式,便于测试)
      
      # 'sentinel monitor' 代表监控; 'mymaster' 代表服务器的名称(可自定义); 主服务器ip; 主服务器端口号; 2 代表`大于等于2个`哨兵认为主服务器不可用的时候, 才进行failover(故障转移)操作
      sentinel monitor mymaster 192.168.11.128 6379 2 # 配置监听的主服务器
      
      # sentinel auth-pass <master-name> <password>
      sentinel auth-pass mymaster 主服务器密码 # 验证服务器密码: mymaster 服务器名称;
      
    • 启动哨兵: redis-sentinel iconfig/sentinel-端口号.conf 启动样式.png
    • 当主服务器宕机: 谋朝篡位过程 谋朝篡位过程.png
    • 当原主服务器再次回来, 会转换成此节点当从机
  • 优缺点
    • 优点
      1. 哨兵集群, 基于主从复制模式, 所有的主从配置优点, 它全有
      2. 主从可切换, 故障可转移, 系统的可用性就会更好
      3. 哨兵模式就是主从模式的升级, 手动到自动, 更加健壮
    • 缺点
      1. Redis 不好在线扩容的, 集群容量达到上限, 在线扩容就十分麻烦
      2. 实现哨兵模式的配置其实是很麻烦的, 里面配置项很多

哨兵模式的全部配置

# Example sentinel.conf
port 26379 # 哨兵 sentinel 实例运行的端口默认 26379

dir /tmp # 哨兵 sentinel 工作目录

# 哨兵 sentinel 监控的是 redis 主节点的 ip port
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
# master-name: 可自命名的主节点名字, A-z、0-9、.-_ 组成。
# quorum: 配置几个哨兵认为主服务器宕机, 才进行failover(故障转移)操作
sentinel monitor mymaster 127.0.0.1 6379 2

# 哨兵1 连接主从的密码, 注意`主从密码必须一致`: 当在 Redis 中开启了密码(requirepass foobared)
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 配置的密码 # 验证服务器密码: mymaster 服务器名称;

# 指定 多少ms 主节点未应答哨兵, 哨兵主观上认为主节点下线, 默认30秒
sentinel down-after-milliseconds mymaster 30000 # mymaster 服务器名称

# 故障转移的操作: 指定在发生 failover(故障转移) 主从切换时最多可以有多少个slave同时对新的master进行同步,
# 这个数字越小,完成 failover 所需的时间就越长,
# 这个数字越大,就意味着越多的 slave 因为 replication 而不可用。
# 可以通过将这个值设为 1, 来保证每次只有一个slave处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

# 故障转移的超时时间 failover-timeout 可以用在以下这些方面
# 1. 同一个 sentinel 对同一个 master 两次 failover 之间的间隔时间。
# 2. 当一个 slave 从一个错误的 master 那里同步数据开始计算时间。直到s1ave被纠正为向正确的master那里同步数据时。
# 3. 当想要取消一个正在进行的 failover 所需要的时间
# 4. 当进行 failover 时, 配置所有 slaves 指向新的 master 所需的最大时间, 不过, 即使过了这个超时, slaves 依然会被正确配置为指向 master, 但是就不按 parallel-syncs 所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000 # mymaster 服务器名称;


# SCRIPTS EXECUTION
# 配置当某一事件发生时 所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
# 对于脚本的运行结果有以下规则:
# 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为 10
# 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
# 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
# 一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行
# 通知型脚本: 当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个
# 通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh # mymaster 服务器名称; 脚本路径

# 谋朝串位后 的回调
# 当一个 master 由于 failover 而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是 "failover"
# <role> 是“Teader"或者"observer"中的一个。
# 参数 from-ip, from-port, to-ip,to-port是用来和旧的master和新的master (即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

单机哨兵.png

哨兵集群.png

缓存穿透及解决

概念

查询一个数据, 但 redis 缓存中没有, 于是向持久层数据库 Mysql 查询, 发现也没有, 当用户很多时(或恶意攻击), 缓存都不会命中, 都是请求持久层数据库, 给持久层数据库造成很大的压力, 就相当于出现了缓存穿透

解决方案1: 布隆过滤器

布隆过滤器是一种数据结构, 对所有可能查询的参数以 hash 形式存储, 在控制层先进行校验, 不符合则丢弃请求

布隆过滤器.png

解决方案2: 存储空值 10s 左右

  • 问题
    1. 若空值被缓存, 就意味着需更多的空间来存储
    2. 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致, 对需保持一致性的业务会有影响

缓存穿透.png

缓存击穿及解决

概念

一个 key 非常热点, 在不停的扛着大并发, 当这个 key 缓存失效的瞬间, 大量的并发就直接请求 MySQL,

解决方案

  1. 设置热点数据永不过期
  2. 加互斥锁
    • 分布式锁: 保证每个 key, 同时只能有一个线程去查询后端服务, 其它线程未获取分布式锁的权限, 因此只能等待, 这种方式将高并发的压力转移到分布式锁上, 因此对分布式的锁考验很大

缓存击穿.png

缓存击穿.png

缓存雪崩及解决

概念

  • 指在某一个时间段, 缓存集中过期失效 或 Rdids 宕机
  • 集中过期, 不是非常致命, 最致命的是服务器某个节点宕机 断网 断电
  • 淘宝双11, 停掉一些服务(客服,退货), 保证主要的服务可用

解决方案

  1. Redis 高可用: 既然可能挂掉, 就多增设几台 redis, 一台挂掉, 其它可继续工作(集群, 异地多活)
  2. 限流降级: 在缓存失效后, 通过加锁或队列来控制读数据库写缓存的线程数量
  3. 数据预热: 在正式部署前, 先把可能的数据先预先访问一遍, 即在将发生大并发访问前 手动触发加载需要的key, 设置不同的过期时间, 让缓存失效的时间尽量均匀

image.png