Redis学习

引言

本博客是根据黑马程序员Redis入门到精通教学视频学习时,所做的笔记


1. Redis入门

1.1 Redis简介

高性能键值对(key-value)数据库


1.2 Redis下载与安装

1.2.1 windows

下载链接:Tags · microsoftarchive/redis (github.com)

解压即可

image-20220815175728584


1.2.2 linux

  • 安装
1
2
3
4
5
6
7
8
9
10
11
//下载安装包
wget http://download.redis.io/releases/redis-4.0.0.tar.gz

//解压
tar –xvf redis-4.0.0.tar.gz

//进入解压后的目录
cd redis-4.0.0.tar.gz

//编译安装
make install
  • 服务端启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//默认启动 可以指定端口
redis-server
redis-server –-port 6379
redis-server –-port 6380

//配置文件启动
redis-server redis.conf

/*************配置文件*******************/
port 6379
daemonize yes //守护进程方式启动
logfile "6379.log" //设定日志文件名称
dir /redis-4.0.0/data //自定义目录
/********************************/
  • 客户端连接
1
2
3
4
5
6
7
//默认连接;127.0.0.1 6379
redis-cli

//连接指定服务器
redis-cli -h 127.0.0.1
redis-cli –port 6379
redis-cli -h 127.0.0.1 –port 6379

image-20220818165046688


1.3 Redis常用指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//信息添加
set key value

//信息查询
get key

//清屏
clear

//退出
quit
exit

//帮助
help 命令名称
help @组名 //tab快速切换

示例

image-20220815181101701


2. Redis数据类型

  • Redis 自身是一个 Map,其中所有的数据都是采用 key : value 的形式存储
  • 数据类型指的是存储的数据的类型,也就是 value 部分的类型,key 部分永远都是字符串

2.1 String

  • 存储的数据:单个数据,最简单的数据存储类型,也是最常用的数据存储类型
  • 存储数据的格式:一个存储空间保存一个数据
  • 存储内容:通常使用字符串,如果字符串以整数的形式展示,可以作为数字操作使用

2.1.1 基本操作

  • 添加修改

    1
    set key value
  • 获取数据

    1
    get key
  • 删除数据

    1
    del key
  • 添加/修改多个数据

    1
    mset key1 value1 key2 value2 key3 value3....

    image-20220815195054854

  • 获取多个数据

    1
    mget key1 key2 key3...

    image-20220815195106373

  • 获取数据字符个数

    1
    2
    strlen key
    //返回的是字符个数

    image-20220815195210687

  • 追加信息到原始信息后部(如果存在就追加,否则新建)

    1
    2
    append key value
    //返回的是追加后的字符个数

    image-20220815195330828


2.1.2 扩展操作

1
2
3
4
5
6
7
8
//增长指令,只有当value为数字时才能增长
incr key
incrby key increment
incrbyfloat key increment

//减少指令,有当value为数字时才能减少
decr key
decrby key increment

image-20220815200053917

  • string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算。
  • redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。
  • 注意:按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis 数值上限范围,将报错。 9223372036854775807(java中long型数据最大值,Long.MAX_VALUE)

tips:

  • redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性
  • 此方案适用于所有数据库,且支持数据库集群

设置数据具有指定的生命周期

1
2
3
4
5
6
7
//按秒设置数据指定的生命周期
setex key seconds value

//按毫秒设置
psetex key milliseconds value

value不重要

image-20220815200654589

tips

  • redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作

2.1.3 命名规范

image-20220815201703550

例如

1
2
set user:id:95001:age 22
//意思是 user表下 主键id=95001 的age 设置为22

2.2 Hash

  • 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
  • 需要的存储结构:一个存储空间保存多个键值对数据
  • hash类型:底层使用哈希表结构实现数据存储

image-20220815234452249

image-20220815234505171

hash存储结构优化

  • 如果field数量较少,存储结构优化为类数组结构
  • 如果field数量较多,存储结构使用HashMap结构

2.2.1 基本操作

  • 添加/修改数据

    1
    2
    hset key field value //插入(如果已存在同名的field,会被覆盖)
    hsetnx key field value //插入(如果已存在同名的field,不会被覆盖)
  • 获取数据

    1
    2
    hget key field //获取指定field的数据
    hgetall key //获取指定key下的所有数据
  • 删除数据

    1
    hdel key field1 [field2]
  • 添加/修改多个数据

    1
    hmset key field1 value1 field2 value2 ...
  • 获取多个数据

    1
    hmget key field1 field2 ...
  • 获取哈希表中字段的数量

    1
    hlen key
  • 获取哈希表中是否存在指定的字段

    1
    hexists key field

    image-20220815235925350


2.2.2 操作注意事项

  • hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到, 对应的值为(nil)
  • 每个 hash 可以存储 2^32 - 1 个键值
  • hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用
  • hgetall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数据访问瓶颈

2.2.3 购物车场景的设计

  • 每个用户的购物车中的商品记录保存成两条field
  • field专用于保存购买数量
    • 命名格式:商品id:nums
    • 保存数据:数值
  • field2专用于保存购物车中商品的信息hash的id

image-20220816002529451


2.3 List

  • 数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
  • 需要的存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序
  • list类型:保存多个数据,底层使用双向链表存储结构实现
  • 元素有序,且可重

image-20220816003057238


2.3.1 基本操作

  • 添加/修改数据

    1
    2
    3
    //lpush为从左边添加,rpush为从右边添加
    lpush key value1 value2 value3...
    rpush key value1 value2 value3...
  • 获取并移除数据

    1
    2
    lpop key //从左边移除
    rpop key //从右边移除
  • 获取数据

    1
    2
    3
    4
    5
    6
    7
    8
    //获取 从 start 到 stop 之间的数据,如果不知道list有多少个元素,stop的值可以为-1,代表倒数第一个元素
    lrange key start stop

    //获取 索引为 index 的数据 即类似a[0]
    lindex key index

    //获取list长度
    llen key

    image-20220816004939734


2.3.2 扩展操作

1
2
3
//规定时间内获取并移除数据,b=block,给定一个时间,如果在指定时间内放入了元素,就移除
blpop key1 key2... timeout
brpop key1 key2... timeout

image-20220816005707219

1
2
//移除指定元素 count:移除的个数 value:移除的值。 移除多个相同元素时,从左边开始移除
lrem key count value

image-20220816011027059

2.3.2 操作注意事项

  • list中保存的数据都是string类型的,数据总容量是有限的,最多2^32 - 1 个元素 (4294967295)。
  • list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队(rpush, rpop)操作,或以的形式进行入栈出栈(lpush, lpop)操作
  • 获取全部数据操作结束索引设置为-1 (倒数第一个元素)
  • list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加载

2.4 Set

  • 新的存储需求:存储大量的数据,在查询方面提供更高的效率
  • 需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询
  • set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的
  • 不重复且无序

image-20220816165530111


2.4.1 基本操作

  • 添加元素

    1
    sadd key member1 member2...
  • 查看元素

    1
    smembers key
  • 移除元素

    1
    srem key member
  • 查看元素个数

    1
    scard key
  • 查看某个元素是否存在

    1
    sismember key member

image-20220816180224791


2.4.2 扩展操作

  • 求两个集合的交、并、差集

    1
    2
    3
    4
    5
    6
    7
    8
    //交
    sinter key1 [key2]

    //并
    sunion key1 [key2]

    //差 key1 - key2 有顺序的
    sdiff key1 [key2]

    image-20220816180725380

  • 求两个集合的交、并、差集并存储到指定集合中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //交
    sinterstore destination key1 key2...

    //并
    sunionstore destination key1 key2...

    //差
    sdiffstore destination key1 key2...

    /**同上就不演示了**/
  • 将指定数据从原始集合中移动到目标集合中

    1
    2
    //从 source 中 移到 destination里 移动的值为member
    smove source destination member

    image-20220816181152078

  • 随机获取集合中指定数量的数据

    1
    2
    //获取个数为 count
    srandmember key [count]
  • 随机获取集合中的某个数据并将该数据移出集合

    1
    spop key [count]

    image-20220816181500696


2.4.3 注意事项

  • set 类型不允许数据重复,如果添加的数据在 set 中已经存在,将只保留一份
  • set 虽然与hash的存储结构相同,但是无法启用hash中存储值的空间

2.4.4 简单权限设置实现

image-20220816182057465

  • 权限1:查询所有、根据id查询
  • 权限2:查询所有、根据id删除
  • 用户001:具有权限1、2
  • 用并集实现

2.5 sorted_set

  • 不重但有序(score)
  • 新的存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
  • 需要的存储结构:新的存储模型,可以保存可排序的数据
  • sorted_set类型:在set的存储结构基础上添加可排序字段

image-20220816182503734


2.5.1 基本操作

  • 插入元素,需要指定 score 用于排序

    1
    zadd key score1 member1 [score2 member2]
  • 获取全部数据

    1
    2
    3
    4
    //正序 从 start 到 stop 当末尾添加withscore时,会将元素的score一起打印出来
    zrange key start stop [WITHSCORES]
    //反序
    zrevrange key start stop [WITHSCORES]
  • 删除数据

    1
    zrem key member [member ...]
  • 按条件获取数据

    1
    2
    3
    4
    5
    //正序 查询 key 中 score在[min , max]之间的数据 其中 limit 是从offset为索引开始位置,count为获取的数目
    zrangebyscore key min max [WITHSCORES] [LIMIT offset count]

    //反序
    zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]
  • 按条件删除数据

    1
    2
    zremrangebyrank key start stop
    zremrangebyscore key min max
  • 获取集合数据总量

    1
    2
    3
    4
    5
    //集合总个数
    zcard key

    //score 在min 到 max 范围的个数
    zcount key min max
  • 集合交、并操作

    1
    2
    3
    4
    5
    //交
    zinterstore destination numkeys key [key ...]

    //并
    zunionstore destination numkeys key [key ...]

注意

  • min与max用于限定搜索查询的条件
  • start与stop用于限定查询范围,作用于索引,表示开始和结束索引
  • offset与count用于限定查询范围,作用于查询结果,表示开始位置数据总量

2.5.2 扩展操作

  • 获取数据对应的索引(排名)

    1
    2
    3
    4
    // 注意是排名 不是具体的 socre 
    //比如说张三的score是100,李四是60
    zrank key member //正序 张三返回的是 0
    zrevrank key member//反序 张三返回的是1
  • score值获取与修改

    1
    2
    3
    4
    //获取score
    zscore key member
    //修改
    zincrby key increment member

2.5.3 注意事项

  • score保存的数据存储空间是64位,如果是整数范围是-9007199254740992~9007199254740992
  • score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用时候要慎重
  • sorted_set 底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果 (修改的返回值是0 )

2.6 数据类型实践案例

  • redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性
    • string类型,自增时默认转换成数值型计算,单线程,保证唯一性
  • redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作
    • redis的时效性
  • redis应用于各种结构型和非结构型高热度数据访问加速
    • user:id:3506728370:fans
  • redis 应用于购物车数据存储设计
    • hash
  • redis 应用于抢购,限购类、限量发放优惠卷、激活码等业务的数据存储设计
    • hash
  • redis 应用于具有操作先后顺序的数据控制
    • list
  • redis 应用于最新消息展示
    • list
  • redis 应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐,大V推荐等
    • set的随机获取集合指定数量的数据指令
  • redis 应用于同类信息的关联搜索,二度关联搜索,深度关联搜索
    • set的交并差等指令
  • redis 应用于同类型不重复数据的合并、取交集操作
    • set的交并差等指令
  • redis 应用于同类型数据的快速去重
    • set是无重复的集合
  • redis 应用于基于黑名单与白名单设定的服务控制
    • set
  • redis 应用于计数器组合排序功能对应的排名
    • sorted_set
  • redis 应用于定时任务执行顺序管理或任务过期管理
    • sorted_set
  • redis 应用于及时任务/消息队列执行管理
    • sorted_set 可设置用户优先级
  • redis 应用于按次结算的服务控制
    • string 利用incr操作超过最大值抛出异常的形式替代每次判断是否大于最大值
  • redis 应用于基于时间顺序的数据操作,而不关注具体时间
    • list的双端进出特征

3. 通用指令

3.1 key通用指令

3.1.1 基本操作

  • 查看key是否存在

    1
    exists key
  • 删除key

    1
    del key
  • 查看key‘的类型

    1
    type key

    image-20220816233526230


3.1.2 扩展操作(时效性操作)

  • 设置生命周期

    1
    2
    3
    4
    //秒
    expire key seconds
    //毫秒
    pexpire key milliseconds
  • 查看有效时间, 如果有有效时间则返回剩余有效时间, 如果为永久有效,则返回-1, 如果Key不存在则返回-2

    1
    2
    3
    4
    //秒
    ttl key
    //毫秒
    pttl key
  • 将有效的数据设置为永久有效

    1
    persist key

    image-20220816234257716


3.1.3 扩展操作(查询操作)

1
2
//根据key查询符合条件的数据
keys pattern

image-20220816234449916


3.1.4 其他操作

  • 为key改名

    1
    2
    3
    4
    //重命名key,为了避免覆盖已有数据,尽量少去修改已有key的名字,如果要使用最好使用renamenx

    rename key newKey //如果newkey已存在 则会覆盖
    renamenx key newKey//如果newkey已存在 则不会覆盖
  • 其他key通用操作

    1
    help @generic

3.2 数据库通用指令

  • redis为每个服务提供有16个数据库,编号从0到15
  • 每个数据库之间的数据相互独立

image-20220817001316425


3.2.1 db基本操作

  • 切换数据库

    1
    select index
  • 其他操作

    1
    2
    3
    quit	//退出
    ping //测试连通性
    echo message //输出一个message
  • 数据移动

    1
    2
    //把当前数据库的 key 移到 db号 数据库中
    move key db
  • 数据清除

    1
    2
    3
    dbsize 	//查询当前数据库有多少个key
    flushdb //清除当前数据库的数据
    flushall //清除所有数据库的所有数据

4. Jedis

  • Java用来连接Redis服务的工具
  • 需要导入对应jar包或者Maven依赖
1
2
3
4
5
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>

4.1 操作步骤

  • 连接Redis

    1
    2
    //参数为Redis所在的ip地址和端口号
    Jedis jedis = new Jedis(String host, int port)
  • 操作Redis(操作redis的指令和redis本身的指令一致)

    1
    2
    jedis.set(String key, String value);
    jedis.hset(String key, String field, String value);
  • 断开连接

    1
    jedis.close();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RedisConn {
public static void main(String[] args) {
//连接Redis
Jedis jedis = new Jedis("127.0.0.1", 6379);
//操作Redis
jedis.set("name", "GuTaicheng");

Map<String,String> map = new HashMap<>();
map.put("username", "GTC");
map.put("sex", "BOY");
map.put("age", "22");
jedis.hmset("user:009",map);
System.out.println("name==>"+jedis.get("name"));
System.out.println("user:009==>"+jedis.hgetAll("user:009").toString());
//断开连接
jedis.close();
}
}

//输出
name==>GuTaicheng
user:009==>{age=22, sex=BOY, username=GTC}

4.2 Jedis工具类制作

  • 配置文件jedis.properties

    1
    2
    3
    4
    jedis.host=127.0.0.1
    jedis.port=6379
    jedis.maxTotal=30
    jedis.maxIdle=10
  • 静态代码块初始化资源,放在静态代码块的话,初始化就只会执行一次,不会创建多个Jedis连接

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    static{
    //读取配置文件 获得参数值
    ResourceBundle rb = ResourceBundle.getBundle("jedis");
    host = rb.getString("jedis.host");
    port = Integer.parseInt(rb.getString("jedis.port"));
    maxTotal = Integer.parseInt(rb.getString("jedis.maxTotal"));
    maxIdle = Integer.parseInt(rb.getString("jedis.maxIdle"));
    poolConfig = new JedisPoolConfig();
    poolConfig.setMaxTotal(maxTotal);
    poolConfig.setMaxIdle(maxIdle);
    jedisPool = new JedisPool(poolConfig,host,port);
    }
  • 获取连接;对外访问接口,提供jedis连接对象,连接从连接池获取

    1
    2
    3
    4
    5
    //调用getJedis即可获得连接对象
    public static Jedis getJedis(){
    Jedis jedis = jedisPool.getResource();
    return jedis;
    }

ps:这里是很好的单例模式,可以参考本站另外一篇博客:jvm学习 - GuTaicheng’s Blog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class JedisUtil {
private static Jedis jedis = null;
private static String host = null;
private static int port;
private static int maxTotal;
private static int maxIdle;
//使用静态代码块,只加载一次
static {
//读取配置文件
ResourceBundle resourceBundle = ResourceBundle.getBundle("jedis");
//获取配置文件中的数据
host = resourceBundle.getString("jedis.host");
port = Integer.parseInt(resourceBundle.getString("jedis.port"));
//读取最大连接数
maxTotal = Integer.parseInt(resourceBundle.getString("jedis.maxTotal"));
//读取最大活跃数
maxIdle = Integer.parseInt(resourceBundle.getString("jedis.maxIdle"));
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(maxTotal);
jedisPoolConfig.setMaxIdle(maxIdle);
//获取连接池
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port);
jedis = jedisPool.getResource();
}

public static Jedis getJedis() {
return jedis;
}
}
1
2
//获取连接
Jedis jedis = JedisUtil.getJedis();

5. 持久化

5.1 持久化简介

5.1.1 什么是持久化?

利用永久性存储介质(如硬盘)将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制称为持久化。

5.1.2 为什么要持久化

防止数据的意外丢失,确保数据安全性

5.1.3 持久化过程保存什么

  • 将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据
  • 将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程

image-20220818154415461.png


5.2 RDB

5.2.1 启动方式

5.2.1.1 save指令

  • 命令

    1
    save
  • 作用:手动执行一次保存操作

save指令相关配置
  • dbfilename dump.rdb
    • 说明:设置本地数据库文件名,默认值为 dump.rdb
    • 经验:通常设置为dump-端口号.rdb
  • dir
    • 说明:设置存储.rdb文件的路径
    • 经验:通常设置成存储空间较大的目录中,目录名称data
  • rdbcompression yes
    • 说明:设置存储至本地数据库时是否压缩数据,默认为 yes,采用 LZF 压缩
    • 经验:通常默认为开启状态,如果设置为no,可以节省 CPU 运行时间,但会使存储的文件变大(巨大)
  • rdbchecksum yes
    • 说明:设置是否进行RDB文件格式校验,该校验过程在写文件和读文件过程均进行
    • 经验:通常默认为开启状态,如果设置为no,可以节约读写性过程约10%时间消耗,但是存储一定的数据损坏风险
1
2
3
4
5
6
7
port 6379
daemonize yes
logfile "6379.log"
dir /redis-4.0.0/data
dbfilename dump-6379.rdb
rdbcompression yes
rdbchecksum yes

save指令工作原理

image-20220818165917101

save指令的执行会阻塞当前Redis服务器,直到当前RDB过程完成为止,有可能会造成长时间阻塞,线上环境不建议使用


5.2.1.2 bgsave指令

  • 命令

    1
    bgsave
  • 作用

    手动启动后台保存操作,但不是立即执行

bgsave指令相关配置
  • 其余和save一样
  • stop-writes-on-bgsave-error yes
    • 说明:后台存储过程中如果出现错误现象,是否停止保存操作
    • 经验:通常默认为开启状态
bgsave指令工作原理

image-20220818170058246


5.2.1.3 save配置(自动保存)

  • 配置

    1
    save second changes
  • 作用

    满足限定时间范围内key的变化数量达到指定数量即进行持久化

  • 参数

    • second:监控时间范围
    • changes:监控key的变化量
  • 配置位置

    conf文件中进行配置

1
2
3
4
5
6
7
8
port 6379
daemonize yes
logfile "6379.log"
dir /redis-4.0.0/data
dbfilename dump-6379.rdb
rdbcompression yes
rdbchecksum yes
save 10 2 //意味着 10s 内 如果有两个key发生变化 就自动保存
save配置原理

image-20220818170613220

注意

  • save配置要根据实际业务情况进行设置,频度过高或过低都会出现性能问题,结果可能是灾难性的
  • save配置中对于second与changes设置通常具有互补对应关系(一个大一个小),尽量不要设置成包含性关系
  • save配置启动后执行的是bgsave操作

5.2.2 三种启动方式对比

image-20220818170802480

RBF优缺点

  • 优点
    • RDB是一个紧凑压缩的二进制文件,存储效率较高
    • RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
    • RDB恢复数据的速度要比AOF很多
    • 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复
  • 缺点
    • RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据
    • bgsave指令每次运行要执行fork操作创建子进程,要牺牲掉一些性能
    • Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象

5.3 AOF

5.3.1 AOF简介

  • AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令,以达到恢复数据的目的。与RDB相比可以简单描述为改记录数据为记录数据产生的过程
  • AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式

5.3.2 AOF写数据过程

image-20220818182539649


5.3.3 AOF写数据三种策略(appendfsync)

  • always

    • 每次写入操作均同步到AOF文件中,数据零误差,性能较低,不建议使用
  • everysec

    • 每秒将缓冲区中的指令同步到AOF文件中,数据准确性较高,性能较高建议使用,也是默认配置
    • 在系统突然宕机的情况下丢失1秒内的数据
  • no

    • 由操作系统控制每次同步到AOF文件的周期,整体过程不可控
  • 配置

    1
    appendonly yes|no
    • 作用
    • 是否开启AOF持久化功能,默认为不开启状态
  • 配置

    1
    appendfsync always|everysec|no
    • 作用
      • AOF写数据策略
  • 配置

    1
    appendfilename filename
    • 作用
      • AOF持久化文件名,默认文件名未appendonly.aof,建议配置为appendonly-端口号.aof

例:

1
2
3
4
5
6
7
8
9
10
port 6379
daemonize yes
logfile "6379.log"
dir /redis-4.0.0/data
dbfilename dump-6379.rdb
rdbcompression yes
rdbchecksum yes
appendonly yes
appendfsync always
appendfilename appendonly-6379.aof

image-20220818183656537

image-20220818183652737

可以简单阅读懂aof


5.3.4 AOF重写

随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF文件重
写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。简单说就是将对同一个数据的若干个条命令执行结
果转化成最终结果数据对应的指令进行记录。

比如上例的:实际上的name是 ”CCC“ 但是在AOF中保存的三次 set 在恢复时也会执行三次,大大的降低了效率

image-20220818183656537


5.3.4.1 AOF重写作用

  • 降低磁盘占用量,提高磁盘利用率
  • 提高持久化效率,降低持久化写时间,提高IO性能
  • 降低数据恢复用时,提高数据恢复效率

5.3.4.2 AOF重写规则

  • 进程内已超时的数据不再写入文件

  • 忽略无效指令重写时使用进程内数据直接生成,这样新的AOF文件

    只保留最终数据的写入命令

    • 如del key1、 hdel key2、srem key3、set key4 111、set key4 222等
  • 对同一数据的多条写命令合并为一条命令

    • 如lpush list1 a、lpush list1 b、 lpush list1 c 可以转化为:lpush list1 a b c
    • 为防止数据量过大造成客户端缓冲区溢出,对list、set、hash、zset等类型,每条指令最多写入64个元素

5.3.4.3 AOF重写方式

手动重写
1
bgrewriteaof

简单演示

  • 初始设置

    image-20220818184714946

  • 重写前

    image-20220818184830170

  • 重写后

    image-20220818184907371

image-20220818184940177

bgrewriteaof指令工作原理

image-20220818185119976


自动重写
1
2
3
4
//触发重写的最小大小
auto-aof-rewrite-min-size size
//触发重写须达到的最小百分比
auto-aof-rewrite-percentage percentage
  • 自动重写触发比对参数( 运行指令info Persistence获取具体信息 )

    1
    2
    3
    4
    //当前.aof的文件大小
    aof_current_size
    //基础文件大小
    aof_base_size
  • 自动重写触发条件

image-20220818184433543


5.3.4.4 AOF重写工作原理

  • 重写之前的AOF流程

    image-20220819004024975

  • 基于everysec开启重写后,会有一个重写缓冲区,提示信息是控制台上提示的重写在后台已开始……

image-20220819003732083

  • 是由aof重写缓冲区来提供数据进行重写

5.4 RDB VS AOF

image-20220819004257826

5.4.1 RDB与AOF的选择之惑

  • 对数据非常敏感建议使用默认的AOF持久化方案

    • AOF持久化策略使用everysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能,当出现问题时,最多丢失0-1秒内的数据。
  • 注意:由于AOF文件存储体积较大,且恢复速度较慢

  • 数据呈现阶段有效性建议使用RDB持久化方案

    • 数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段 点数据恢复通常采用RDB方案
  • 注意:利用RDB实现紧凑的数据持久化会使Redis降的很低

  • 综合比对

    • RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊
    • 如不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用AOF
    • 如能承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用RDB
    • 灾难恢复选用RDB
    • 双保险策略,同时开启 RDB 和 AOF,重启后,Redis优先使用 AOF 来恢复数据,降低丢失数据

6. 事务

6.1 事务简介

redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(一个队列)。当执行时,一次性
按照添加顺序依次执行,中间不会被打断或者干扰。

一个队列中,一次性、顺序性、排他性的执行一系列命令

比如说同时有两个线程,线程一设置了一个string=a,当线程一要读取这个key时,线程二修改了这个值,则导致线程一读取到和预想值不同的值(类似mysql的读脏数据),因此需要有事务;

image-20220819012549078


6.2 事务的基本操作

  • 开启事务

    1
    multi
    • 作用
      • 作设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
  • 取消事务

    1
    discard
    • 作用
      • 终止当前事务的定义,发生在multi之后,exec之前
  • 执行事务

    1
    exec
    • 作用
      • 设定事务的结束位置,同时执行事务。与multi成对出现,成对使用

image-20220819174736832

注意:加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行


6.3 事务的工作流程

image-20220819175023482


6.4 事务的注意事项

定义事务的过程中,命令格式输入错误怎么办?

  • 语法错误
    • 指命令书写格式有误 例如执行了一条不存在的指令
  • 处理结果
    • 如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会执行。包括那些语法正确的命令

image-20220819175516068

定义事务的过程中,命令执行出现错误怎么办?

  • 运行错误
    • 指命令格式正确,但是无法正确的执行。例如对list进行incr操作
  • 处理结果
    • 能够正确运行的命令会执行,运行错误的命令不会被执行

image-20220819175705795

注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。

  • Redis 命令只会因为错误的语法而失败,或是命令用在了错误类型的键上面,这些问题不能在入队时发现,这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中.
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

6.5 基于特定条件的事务执行

6.5.1 锁

  • 对 key 添加监视锁,在执行exec前如果key发生了变化,终止事务执行

    1
    watch key1, key2....
  • 取消对所有key的监视

    1
    unwatch
  • 事务内部 不能执行watch和unwatch,这两个命令在事务外部才有效

    image-20220819180550512


6.5.2 分布式锁

分布式锁其实是一个设计概念,也可以说是一个约定,不是redis自带的,原理是利用 setnx的不重性来约定实现的,就是个string类型

  • 使用 setnx 设置一个公共锁

    1
    2
    3
    4
    //上锁
    setnx lock-key value
    //释放锁
    del lock-key
    • 利用setnx命令的返回值特征,有值(被上锁了)则返回设置失败,无值(没被上锁)则返回设置成功
    • 操作完毕通过del操作释放锁
  • 这个 lock-key 就是自定义的一个约定规范,一般是lock-(要锁的key值)

  • 实际上就是判断 lock-(要锁的key值) 是否存在,存在返回为0,后台接收到后就不执行,不存在就返回为1,后台接收到继续执行

    image-20220819181520608


6.5.3 分布式锁加强

  • 使用 expire 为锁key添加时间限定,到时如果不释放,放弃锁

    1
    2
    expire lock-key seconds
    pexpire lock-key milliseconds
  • 由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。

    • 例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
    • 测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时
    • 锁时间设定推荐:最大耗时120%+平均网络延迟110%
    • 如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可

7. 删除策略

7.1 过期数据

  • Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态
    • XX :具有时效性的数据
    • -1 :永久有效的数据
    • -2 :已经过期的数据 或 被删除的数据 或 未定义的数据

7.2 数据删除策略

  • 定时删除
  • 惰性删除
  • 定期删除

存储结构

image-20220819193719942

redis中有两个字典,一个是键值对字典,一个是过期字典,保存键的过期时间

目标

在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降,甚至引发服务器宕机或
内存泄露


7.2.1 定时删除

  • 在设置某个key 的过期时间同时,创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作
  • 优点:节约内存,到时就删除,快速释放掉不必要的内存占用
  • 缺点:CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量
  • 总结:用处理器性能换取存储空间 (拿时间换空间

7.2.2 惰性删除

  • 数据到达过期时间,不做处理。等下次访问该数据时
    • 如果未过期,返回数据
    • 发现已过期,删除,返回不存在
  • 优点:节约CPU性能,发现必须删除的时候才删除
  • 缺点:内存压力很大,出现长期占用内存的数据
  • 总结:用存储空间换取处理器性能 (拿空间换时间)

7.2.3 定期删除

  • 周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度

  • 特点1:CPU性能占用设置有峰值,检测频度可自定义设置

  • 特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理

  • 优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。

  • 缺点:难以确定删除操作执行的时长和频率。如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好。如果执行的太少,那又和惰性删除一样了,过期键占用的内存不会及时得到释放。另外最重要的是,在获取某个键时,如果某个键的过期时间已经到了,但是还没执行定期删除,那么就会返回这个键的值,这是业务不能忍受的错误。

  • 总结:周期性抽查存储空间 (随机抽查,重点抽查)

image-20220819194542232

  • 如果删除的key大于 W的四分之一,说明是重点区域,current_db不变,下次继续该区域
  • 反之,current_db + 1

7.2.4 删除策略对比

image-20220819194801599


7.3 逐出算法(淘汰策略)

如果现在所有的数据都是永久的,那新数据进来就内存不足了,这个适合就要有逐出算法(淘汰策略)

  • Redis使用内存存储数据,在执行每一个命令前,会调用freeMemoryIfNeeded()检测内存是否充足。如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法
  • 注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。

7.3.1 影响数据逐出的相关配置

  • 最大可使用内存

    1
    maxmemory 

    占用物理内存的比例,默认值为0,表示不限制。生产环境中根据需求设定,通常设置在50%以上。

  • 每次选取待删除数据的个数

    1
    maxmemory-samples 

    选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据

  • 删除策略

    1
    maxmemory-policy 

    达到最大内存后的,对被挑选出来的数据进行删除的策略


7.3.2 逐出算法的删除策略

  • 检测易失数据(可能会过期的数据集server.db[i].expires)
    • volatile-lru:挑选最近最少使用的数据淘汰,就是很久没用的
    • volatile-lfu:挑选最近使用次数最少的数据淘汰,就是用的很少的
    • volatile-ttl:挑选将要过期的数据淘汰
    • volatile-random:任意选择数据淘汰
  • 检测全库数据(所有数据集server.db[i].dict )
    • allkeys-lru:挑选最近最少使用的数据淘汰,就是很久没用的
    • allkeys-lfu:挑选最近使用次数最少的数据淘汰,就是用的很少的
    • allkeys-random:任意选择数据淘汰
  • 放弃数据驱逐
    • no-enviction(驱逐):禁止驱逐数据(redis4.0中默认策略),会引发错误OOM(Out Of Memory)

LRU:最近使用 Least Recently Used

LFU(Least Frequently Used)算法,也就是最频繁被访问的数据将来最有可能被访问到

内存淘汰策略可以通过配置文件来修改,Redis.conf对应的配置项是maxmemory-policy 修改对应的值就行,默认是noeviction。

1
maxmemory-policy volatile-lru

8. redis.conf配置

参杂在每个章节,需要哪个功能配置哪个

1
2
3
4
5
6
/*************配置文件*******************/
port 6379
daemonize yes //守护进程方式启动
logfile "6379.log" //设定日志文件名称
dir /redis-4.0.0/data //自定义目录
/********************************/

9. 高级数据类型

9.1 Bitmaps

对于一个只有是或者不是的状态,可以采用0/1表示;比如:男生用1表示,女士用0表示;是为党员用1表示,不是党员用0表示等等

因此一个bit大小的数据,就可以保存8个状态,极大的节约了空间。

实际上还是string,只不过操作的是二进制上某一位的状态,比如:有100个员工打卡,第99位来打卡时,把第99位的0改位1。


9.1.1 基础操作

  • 获取指定key对应偏移量上的bit值

    1
    getbit key offset
  • 设置指定key对应偏移量上的bit值,value只能是1或0

    1
    setbit key offset value

image-20220819235124226


9.1.2 扩展操作

  • 对指定key按位进行交、并、非、异或操作,并将结果保存到destKey

    1
    2
    bitop op destKey key1 [key2...]
    //op如下
    • and:交
    • or:并
    • not:非
    • xor:异或
  • 统计指定key中1的数量

    1
    bitcount key [start end]

可以实现统计8月19号到8月30号中间没有打卡的人数.

image-20220820000145969

  • 从没打卡过:用 or

    image-20220820000226499

    5 - 4 = 1,一个人从没打过

  • 只要有一次没打卡:用 and

    image-20220820000349282

    5 - 2 = 3,有三个人有一次没打


9.2 HyperLogLog

9.2.1 基数

  • 基数是数据集去重后元素个数
  • HyperLogLog 是用来做基数统计的,运用了LogLog的算法

image-20220820000847462


9.2.2 基础操作

  • 添加数据

    1
    pfadd key element1, element2...
  • 统计数据

    1
    pfcount key1 key2....
  • 合并数据(就是合并多个HyperLogLog到 destkey中)

    1
    pfmerge destkey sourcekey [sourcekey...]

image-20220820001110016


9.2.3 注意事项

  • 用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据
  • 核心是基数估算算法,最终数值存在一定误差
  • 误差范围:基数估计的结果是一个带有 0.81% 标准错误的近似值
  • 耗空间极小,每个hyperloglog key占用了12K的内存用于标记基数
  • pfadd命令不是一次性分配12K内存使用,会随着基数的增加内存逐渐增大
  • Pfmerge命令合并后占用的存储空间为12K,无论合并之前数据量多少

9.3 GEO

和位置有关,坐标等等

可应用于附近的人之类的功能


9.3.1 基础操作

  • 添加坐标点

    1
    geoadd key longitude latitude member [longitude latitude member ...] 
  • 获取坐标点

    1
    geopos key member [member ...] 
  • 计算坐标点距离

    1
    geodist key member1 member2 [unit] 
  • 根据坐标求范围内的数据

    1
    georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
  • 根据点求范围内的数据

    1
    georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
  • 获取指定点对于坐标的hash值

    1
    geohash key member [member ...]

10. 主从复制

10.1 简介

多台服务器连接方案

  • 提供数据方:master
    • 主服务器,主节点,主库
    • 主客户端
  • 接收数据的方:slave
    • 从服务器,从节点,从库
    • 从客户端
  • 需要解决的问题
    • 数据同步
  • 核心工作
    • master的数据复制到slave中

10.1.1 什么是主从复制

主从复制即将master中的数据即时、有效的复制到slave中

特征:一个master可以拥有多个slave,一个slave只对应一个master

职责:

  • master:
    • 写数据
    • 执行写操作时,将出现变化的数据自动同步到slave
    • 读数据(可忽略)
  • slave:
    • 读数据
    • 写数据(禁止

10.1.2 主从复制的作用

  • 读写分离:master写、slave读,提高服务器的读写负载能力
  • 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
  • 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
  • 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
  • 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案

10.2 工作流程

  • 主从复制过程大体可以分为3个阶段
    • 建立连接阶段(即准备阶段)
    • 数据同步阶段
    • 命令传播阶段

image-20220820015330350


10.2.1 阶段一:建立连接阶段

建立slave到master的连接,使master能够识别slave,并保存slave端口号

image-20220820015446508


10.2.1.1 连接方式(slave连接master)

  • 方式一:客户端发送命令

    1
    2
    3
    slaveof <masterip> <masterport>
    //例
    slaveof 127.0.0.1 6379
  • 方式二:启动服务器参数

    1
    2
    3
    redis-server -slaveof <masterip> <masterport>
    //例
    redis-server -slaveof 127.0.0.1 6379
  • 方式三:服务器配置 (常用)写在slave的conf文件中,然后直接启动即可

    1
    slaveof <masterip> <masterport>

输入info可查看系统信息

image-20220820020139930

  • slave

    image-20220820020206164

  • master

    image-20220820020246289


10.2.1.2 主从断开连接

  • slave客户端发送命令

    1
    slaveof no one
    • 说明: slave断开连接后,不会删除已有数据,只是不再接受master发送的数据

10.2.1.3 授权访问

  • master客户端发送命令设置密码

    1
    requirepass <password>
  • master配置文件设置密码

    1
    2
    config set requirepass <password> 
    config get requirepass
  • slave客户端发送命令设置密码

    1
    auth <password>
  • slave配置文件设置密码

    1
    masterauth <password>
  • slave启动服务器设置密码

    1
    redis-server –a <password>

10.2.2 阶段二:数据同步阶段

10.2.2.1 同步流程(简)

image-20220820021021376

  • 全量复制

    • 将master执行bgsave之前,master中所有的数据同步到slave中
  • 部分复制(增量复制)

    • RDB是一个时刻的快照,在生成RDB文件和发送接收RDB文件的这段时间里,master也会在接收指令存储数据,这些指令会存放到复制缓冲区里(这里传输其实也会有指令进来,但是很少,因此做不到百分百)。

    • 将master执行bgsave操作中,新加入的数据(复制缓冲区中的数据)传给slave,slave通过bgrewriteaof指令来恢复数据


部分复制

部分复制的三个核心要素

  • 服务器的运行 id(run id)
  • 主服务器的复制积压缓冲区
  • 主从服务器的复制偏移量(offset)

服务器的运行 id(run id)
  • 概念:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id
    • 一台服务器重启之后运行id就不同了
  • 组成:运行id由40位字符组成,是一个随机的十六进制字符 例如- -
    • fdc9ff13b9bbaab28db42b3d50f852bb5e3fcdce
  • 作用:运行id被用于在服务器间进行传输,识别身份
    • 如果想两次操作均对同一台服务器进行,必须每次操作携带对应的运行id,用于对方识别
  • 实现方式:运行id在每台服务器启动时自动生成的,master在首次连接slave时,会将自己的运行ID发送给slave,slave保存此ID,通过info Server命令,可以查看节点的runid

复制缓冲区(复制积压缓冲区)
  • 概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器执行过的命 令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区
  • 由来:每台服务器启动时,如果开启有AOF或被连接成为master节点,即创建复制缓冲区
  • 作用:用于保存master收到的所有指令(仅影响数据变更的指令,例如set,select)
  • 数据来源:当master接收到主客户端的指令时,除了将指令执行,会将该指令存储到缓冲区中

image-20220820135938151

工作原理

image-20220820140110087


主从服务器复制偏移量(offset)
  • 概念:一个数字,描述复制缓冲区中的指令字节位置
  • 分类:
    • master复制偏移量:记录发送给所有slave的指令字节对应的位置(多个)
    • slave复制偏移量:记录slave接收master发送过来的指令字节对应的位置(一个)
  • 数据来源: master端:发送一次记录一次 slave端:接收一次记录一次
  • 作用:同步信息,比对master与slave的差异,当slave断线后,恢复数据使用

10.2.2.2 同步流程(精)

image-20220820140610825


10.2.2.3 注意事项

  • 数据同步阶段master说明

    • 如果master数据量巨大,数据同步阶段应避开流量高峰期避免造成master阻塞,影响业务正常执行

    • 复制缓冲区大小设定不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现数据已经存在丢失的情况,必须进行第二次全量复制,致使slave陷入死循环状态。(比如用户操作高峰期)

      1
      2
      //设置缓冲区大小
      repl-backlog-size 1mb

      image-20220820022435794

    • master单机内存占用主机内存的比例不应过大,建议使用50%-70%的内存,留下30%-50%的内存用于执 行bgsave命令和创建复制缓冲区

  • 数据同步阶段slave说明

    • 为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务

      1
      slave-serve-stale-data yes|no
    • 数据同步阶段,master发送给slave信息可以理解master是slave的一个客户端,主动向slave发送命令

    • 多个slave同时对master请求数据同步,master发送的RDB文件增多,会对带宽造成巨大冲击,如果master带宽不足,因此数据同步需要根据业务需求,适量错峰

    • slave过多时,建议调整拓扑结构,由一主多从结构变为树状结构,中间的节点既是master,也是 slave。注意使用树状结构时,由于层级深度,导致深度越高的slave与最顶层master间数据同步延迟较大,数据一致性变差,应谨慎选择


10.2.3 阶段三:命令传播阶段

  • 当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的
    状态,同步的动作称为命令传播

  • master将接收到的数据变更命令发送给slave,slave接收命令后执行命令


10.2.3.1 心跳机制

  • 进入命令传播阶段候,master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线
  • master心跳:
    • 指令:PING
    • 周期:由repl-ping-slave-period决定,默认10秒
    • 作用:判断slave是否在线
    • 查询:INFO replication 获取slave最后一次连接时间间隔,lag项维持在0或1视为正常(过大视为断线)
  • slave心跳任务
    • 指令:REPLCONF ACK {offset}
    • 周期:1秒
    • 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
    • 作用2:判断master是否在线

心跳阶段注意事项
  • 当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步操作

    1
    2
    min-slaves-to-write 2 
    min-slaves-max-lag 10
    • slave数量少于2个,或者所有slave的延迟都大于等于10秒时,强制关闭master写功能,停止数据同步
  • slave数量由slave发送REPLCONF ACK命令做确认

  • slave延迟由slave发送REPLCONF ACK命令做确认


10.2.3.2 主从复制完整流程

image-20220820141236587


10.3 主从复制常见问题

10.3.1 频繁的全量复制

image-20220820141858696


image-20220820141923236


10.3.2 频繁的网络中断

image-20220820142310562


image-20220820142318128


10.3.3 数据不一致

image-20220820142411678


11. 哨兵(sentinel)

11.1 简介

主从模式下,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这种方式并不推荐,实际生产中,我们优先考虑哨兵模式。这种模式下,master 宕机,哨兵会自动选举 master 并将其他的 slave 指向新的 master。

哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择一个哨兵,让这个哨兵去选择一个新的master并将所有slave连接到新的master。


11.2 哨兵的作用

  • 监控
    • 不断的检查master和slave是否正常运行。 master存活检测、master与slave运行情况检测
  • 通知(提醒)
    • 当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知。
  • 自动故障转移
    • 断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址

注意:
哨兵也是一台redis服务器,只是不提供数据服务 通常哨兵配置数量为单数(单数是为了在投票时减少票数相同的情况)


11.3 配置哨兵

  • 配置一拖二的主从结构

  • 配置三个哨兵(配置相同,端口不同)

    • 参看sentinel.conf
  • 启动哨兵

    1
    redis-sentinel sentinel端口号 .conf

image-20220820154648982


11.4 哨兵工作原理

11.4.1 阶段一:监控阶段

  • 用于同步各个节点的状态信息
    • 获取各个sentinel的状态(是否在线)
  • 获取master的状态
    • master属性
      • runid
      • role:master
      • 各个slave的详细信息
  • 获取所有slave的状态(根据master中的slave信息)
    • slave属性
      • runid
      • role:slave
      • master_host、master_port
      • offset

image-20220820154816190

这个阶段,第一个sentinel去获取完信息之后保存好,第二个sentinel来获取时在master处有一个sentinels的信息,就相当于发现了“战友”,于是哨兵2和哨兵1之间就会有有一个共享信息的通道

image-20220820155055524


11.4.2 阶段二:通知阶段

  • 各个哨兵将得到的信息相互同步(信息对称)

image-20220820155230059


11.4.3 阶段三:故障转移阶段

① 当有一个哨兵去监控master时,发现master宕机了。这个时候master的info中有一个flags配置会变成

1
flags:SRI_S_DOWN

​ 意为主观下线;同时这个哨兵会去通知其他哨兵这个master宕机了;

② 其他哨兵收到后都去检测master,如果总体下来,认同master确实宕机了的哨兵数量大于哨兵总数量的一半(配置文件可设置),那么flags会变成

1
flags:SRI_O_DOWN

​ 意为客观下线

image-20220820160001720

③ 开启哨兵之间的投票环节,选举一个负责人进行故障转移工作

image-20220820160108263

④ 具体选举新的master的条件

  • 由推选出来的哨兵对当前的slave进行筛选,筛选条件有:
    • 服务器列表中挑选备选master
    • 在线的
    • 响应块的
    • 与原master断开时间短的
    • 优先原则
      • 优先级 :按照slave优先级进行排序,slave priority越低,优先级就越高
      • offset :如果slave priority相同,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高
      • runid : 如果上面两个条件都相同,那么选择一个run id比较小的那个slave

⑤ 发送指令( sentinel )

  • 向新的master发送slaveof no one(断开与原master的连接)

  • 向其他slave发送slaveof 新masterIP端口(让其他slave与新的master相连)


11.5 为什么Redis哨兵集群只有2个节点无法正常工作?

哨兵集群必须部署2个以上节点。

如果两个哨兵实例,即两个Redis实例,一主一从的模式。

则Redis的配置quorum=1,表示一个哨兵认为master宕机即可认为master已宕机。

但是如果是机器1宕机了,那哨兵1和master都宕机了,虽然哨兵2知道master宕机了,但是这个时候,需要majority,也就是大多数哨兵都是运行的,2个哨兵的majority就是2(2的majority=2,3的majority=2,5的majority=3,4的majority=2),2个哨兵都运行着,就可以允许执行故障转移。

但此时哨兵1没了就只有1个哨兵了了,此时就没有majority来允许执行故障转移,所以故障转移不会执行。


12. 集群(Redis cluster)

12.1 简介

12.1.1 集群架构

  • 集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果

12.1.2 集群作用

  • 分散单台服务器的访问压力,实现负载均衡
  • 分散单台服务器的存储压力,实现可扩展性
  • 降低单台服务器宕机带来的业务灾难

12.2 Redis集群结构设计

12.2.1 数据存储设计

  • 通过算法设计,计算出key应该保存的位置
  • 将所有的存储空间计划切割成16384份,每台主机保存一部分 每份代表的是一个存储空间,不是一个key的保存空间
  • 将key按照计算出的结果放到对应的存储空间
  • 取模16384,有点像hash

image-20220820163459511


12.2.2 实现数据分布

Redis cluster有固定的16384个hash slot(哈希槽),对每个key计算CRC16值,然后对16384取模,可以获取key对应的hash slot。

Redis cluster中每个master都会持有部分slot(槽),比如有3个master,那么可能每个master持有5000多个hash slot。

hash slot让node的增加和移除很简单,增加一个master,就将其他master的hash slot移动部分过去,减少一个master,就将它的hash slot移动到其他master上去。每次增加或减少master节点都是对16384取模,而不是根据master数量,这样原本在老的master上的数据不会因master的新增或减少而找不到。并且增加或减少master时Redis cluster移动hash slot的成本是非常低的。

image-20220820163941839


12.2.3 集群内部节点通讯设计

  • 各个数据库互相连通,保存各个库中槽的编号数据
  • 一次命中,直接返回
  • 一次未命中,告知具体的位置,key再直接去找对应的库保存数据

image-20220820164033391

如果key在110中在A之间命中,如果是1120中,告知在B中,再去B中找

Redis cluster节点间采取gossip协议进行通信,所有节点都持有一份元数据(就是上面的数据编号),不同的节点如果出现了元数据的变更之后U不断地i将元数据发送给其他节点让其他节点进行数据变更。

节点互相之间不断通信,保持整个集群所有节点的数据是完整的。 主要交换故障信息、节点的增加和移除、hash slot信息等。

这种机制的好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;

缺点,元数据更新有延时,可能导致集群的一些操作会有一些滞后。


13. 企业级解决方案

缓存异常

13.1 缓存预热

13.1.1 问题排查

  • 请求数量较高
  • 主从之间数据吞吐量较大,数据同步操作频度较高

13.1.2 解决方案

  • 前置准备工作:
    • 日常例行统计数据访问记录,统计访问频度较高的热点数据
    • 利用LRU数据删除策略,构建数据留存队列 例如:storm与kafka配合
  • 准备工作:
    • 将统计结果中的数据分类,根据级别,redis优先加载级别较高的热点数据
    • 利用分布式多服务器同时进行数据读取,提速数据加载过程
    • 热点数据主从同时预热
  • 实施:
    • 使用脚本程序固定触发数据预热过程
    • 如果条件允许,使用了CDN(内容分发网络),效果会更好

13.1.3 总结

缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!


13.2 缓存雪崩

13.2.1 数据库服务器崩溃

  1. 系统平稳运行过程中,忽然数据库连接量激增
  2. 应用服务器无法及时处理请求
  3. 大量408,500错误页面出现
  4. 客户反复刷新页面获取数据
  5. 数据库崩溃
  6. 应用服务器崩溃
  7. 重启应用服务器无效
  8. Redis服务器崩溃
  9. Redis集群崩溃
  10. 重启数据库后再次被瞬间流量放倒

13.2.2 问题排查

  1. 在一个较短的时间内,缓存中较多的key集中过期
  2. 此周期内请求访问过期的数据,redis未命中,redis向数据库获取数据
  3. 数据库同时接收到大量的请求无法及时处理
  4. Redis大量请求被积压,开始出现超时现象
  5. 数据库流量激增,数据库崩溃
  6. 重启后仍然面对缓存中无数据可用
  7. Redis服务器资源被严重占用,Redis服务器崩溃
  8. Redis集群呈现崩塌,集群瓦解
  9. 应用服务器无法及时得到数据响应请求,来自客户端的请求数量越来越多,应用服务器崩溃
  10. 应用服务器,redis,数据库全部重启,效果不理想

13.2.3 问题分析

  • 短时间范围内
  • 大量key集中过期

13.2.4 解决方案(道)

  1. 更多的页面静态化处理
  2. 构建多级缓存架构 Nginx缓存+redis缓存+ehcache缓存
  3. 检测Mysql严重耗时业务进行优化 对数据库的瓶颈排查:例如超时查询、耗时较高事务等
  4. 灾难预警机制 监控redis服务器性能指标
    • CPU占用、CPU使用率
    • 内存容量
    • 查询平均响应时间
    • 线程数
  5. 限流、降级 短时间范围内牺牲一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐步放开访问

13.2.5 解决方案(术)

  1. LRU与LFU切换
  2. 数据有效期策略调整
    • 根据业务数据有效期进行分类错峰,A类90分钟,B类80分钟,C类70分钟
    • 过期时间使用固定时间+随机值的形式,稀释集中到期的key的数量
  3. 超热数据使用永久key
  4. 定期维护(自动+人工) 对即将过期数据做访问量分析,确认是否延时,配合访问量统计,做热点数据的延时
  5. 加锁 慎用!

13.2.6总结

缓存雪崩就是瞬间过期数据量太大,导致对数据库服务器造成压力。如能够有效避免过期时间集中,可以有效解决雪崩现象的出现 (约40%),配合其他策略一起使用,并监控服务器的运行数据,根据运行记录做快速调整。


13.3 缓存击穿

13.3.1 数据库服务器崩溃

  1. 系统平稳运行过程中
  2. 数据库连接量瞬间激增
  3. Redis服务器无大量key过期
  4. Redis内存平稳,无波动
  5. Redis服务器CPU正常
  6. 数据库崩溃

13.3.2 问题排查

  1. Redis中某个key过期,该key访问量巨大
  2. 多个数据请求从服务器直接压到Redis后,均未命中
  3. Redis在短时间内发起了大量对数据库中同一数据的访问

13.3.3 问题分析

  • 单个key高热数据
  • key过期

13.3.4 解决方案(术)

  1. 预先设定

    以电商为例,每个商家根据店铺等级,指定若干款主打商品,在购物节期间,加大此类信息key的过期时长

    注意:购物节不仅仅指当天,以及后续若干天,访问峰值呈现逐渐降低的趋势

  2. 现场调整

    • 监控访问量,对自然流量激增的数据延长过期时间或设置为永久性key
  3. 后台刷新数据

    • 启动定时任务,高峰期来临之前,刷新数据有效期,确保不丢失
  4. 二级缓存

    • 设置不同的失效时间,保障不会被同时淘汰就行
  5. 加锁 分布式锁,防止被击穿,但是要注意也是性能瓶颈,慎重!


13.3.5 总结

缓存击穿就是单个高热数据过期的瞬间,数据访问量较大,未命中redis后,发起了大量对同一数据的数据库问,导致对数据库服务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度较高,配合雪崩处理策略即可


13.4 缓存穿透

缓存穿透是指用户请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍。如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至导致数据库承受不住而宕机崩溃。

缓存穿透的关键在于在Redis中查不到key值,它和缓存击穿的根本区别在于传进来的key在Redis中是不存在的。假如有黑客传进大量的不存在的key,那么大量的请求打在数据库上是很致命的问题,所以在日常开发中要对参数做好校验,一些非法的参数,不可能存在的key就直接返回错误提示。

image-20220820171639971

解决方法:

  • 将无效的key存放进Redis中:

当出现Redis查不到数据,数据库也查不到数据的情况,我们就把这个key保存到Redis中,设置value=”null”,并设置其过期时间极短,后面再出现查询这个key的请求的时候,直接返回null,就不需要再查询数据库了。但这种处理方式是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。

  • 使用布隆过滤器:

如果布隆过滤器判定某个 key 不存在布隆过滤器中,那么就一定不存在,如果判定某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一个布隆过滤器,将数据库中的所有key都存储在布隆过滤器中,在查询Redis前先去布隆过滤器查询 key 是否存在,如果不存在就直接返回,不让其访问数据库,从而避免了对底层存储系统的查询压力。

如何选择:针对一些恶意攻击,攻击带过来的大量key是随机,那么我们采用第一种方案就会缓存大量不存在key的数据。那么这种方案就不合适了,我们可以先对使用布隆过滤器方案进行过滤掉这些key。所以,针对这种key异常多、请求重复率比较低的数据,优先使用第二种方案直接过滤掉。而对于空数据的key有限的,重复率比较高的,则可优先采用第一种方式进行缓存。


Redis学习
https://blog.gutaicheng.top/2022/08/15/Redis学习/
作者
GuTaicheng
发布于
2022年8月15日
许可协议