Redis

几种常见的NoSql

  1. memcache

    • 最早的NoSql数据库
    • 数据都存储在内存之中,不进行持久化
    • 支持简单的key-value模式
    • 一般是作为缓存数据库辅助持久化的数据库
  2. redis

    • 几乎覆盖了所有Memcache的功能
    • 数据基本都存储在内存之中,并且支持持久化处理,主要用作备份回复
    • 除了支持简单的key-value模式,还支持多种数据结构的存储,比如 list、set、hash、zset等。
    • 一般来说作为缓存数据库辅助持久化的数据库
  3. mongoDB

    • 高性能、开源、模式自由的文档型数据库
    • 数据都在内存之中,如果内存不够的话,就会将不常用的数据保存到硬盘之中
    • 虽然是key-value模式,但是对value提供了json类型的使用方式
    • 支持二进制数据及大型的对象
    • 根据数据的特点可以替代RDBMS,直接成为独立的数据库
  4. HBase

    • 是Hadoop项目之中的数据库
    • 需要对大量的数据进行随机、实时读取操作的场景之中性能很高。
    • 可使用普通的计算机处理操作10亿行数据,还可以处理数百万列元素的数据表

Redis的基本介绍

一些基本的使用场景

  1. 取最新N个,可以通过Redis简单就能实现

    典型的取网站最新文章,可以将最新的5000条评论ID放在Redis的List集合中,并将超出集合部分从数据库获取

  2. 排行榜应用取TopN

    这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,可以使用Redis的sorted set,将要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。

  3. 需要精准设定过期时间的应用

    比如可以把上面说到的sorted set的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序从而实现定期清除数据了,不仅是清除Redis中的过期数据,你完全可以把Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。

  4. 计数器应用

    Redis的命令都是原子性的,你可以轻松地利用INCR,DECR命令来构建计数器系统。

  5. Uniq操作,获取某段时间所有数据排重值

    这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。

  6. 实时系统,反垃圾系统

    通过上面说到的set功能,你可以知道一个终端用户是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等。没有做不到,只有想不到。

  7. 缓存

    将数据直接存放到内存中,性能优于Memcached,数据结构更多样化。

Redis目录结构

目录或文件 作用
redis-benchmark.exe redis的性能测试工具
redis-check-aof.exe aof的检查和修复工具
redis-check-dump.exe rdb文件的检查和修复工具
redis-cli.exe client客户端访问命令
redis-server.exe 服务器启动程序
redis.windows.conf 配置文件,是个文本文件

Redis服务启动与关闭

note
  • 01
  • 02
  • 03
  • 04
  • 05
启动服务器: cmd redis-server.exe redis.windows.conf 默认端口号:6379 关闭服务器:直接关闭窗口

Linux版Redis安装(运维)

  1. 下载redis安装包

    服务器执行以下命令下载redis安装包

    console
    • 01
    • 02
    cd /export/software weget http://download.redis.io/releases/redis-6.2.6.tar.gz
  2. 解决redis压缩包到指定目录

    执行以下的命令信息,将redis的压缩包进行解压

    console
    • 01
    • 02
    cd /export/software tar -zxvf redis-6.2.6.tar.gz -C
  3. 安装C程序的运行环境

    执行以下的命令来安装C程序运行环境

    • 01
    yum -y install gcc-c++
  4. 安装较新版本的tcl

    下载安装较新版本的tcl [使用命令下载]

    • 01
    • 02
    • 03
    • 04
    • 05
    • 06
    • 07
    • 08
    cd /export/software wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz 解压tcl tar -zxvf tcl8.6.1-src.tar.gz -C ../server/ 进入指定目录 cd ../server/tcl8.6.1/unix/ ./configure make && make install
  5. 在线安装

    • 01
    yum -y install tcl
  6. 编译redis

    • 01
    • 02
    • 03
    • 04
    cd /export/server/redis-6.2.6/ #或者使用命令 make 进行编译 make MALLOC=libc make test && make install PREFIX=/export/server/redis-6.2.6
  7. 修改redis配置文件

    • 01
    • 02
    • 03
    • 04
    • 05
    • 06
    • 07
    • 08
    • 09
    • 10
    • 11
    • 12
    • 13
    cd /export/server/redis-6.2.6/ mkdir -p /export/server/redis-6.2.6/log mkdir -p /export/server/redis-6.2.6/data vim redis.conf # 修改第61行 bind localhost # 修改第128行 后台 daemonize yes # 修改第163行 logfile "/export/server/redis-6.2.6/log/redis.log" # 修改第247行 dir /export/server/redis-6.2.6/data
  8. 启动redis

    • 01
    • 02
    cd /export/server/redis-6.2.6/ bin/redis-server redis.conf
  9. 关闭redis

    text
    • 01
    bin/redis-cli -h localhost shutdown
  10. 连接redis客户端

    • 01
    • 02
    cd /export/server/redis-6.2.6/ bin/redis-cli -h localhost

Redis的数据类型

redis基本命令

清空所有缓存

  • 01
flushall

删除某个key(同时删除value)

  • 01
del keyname

返回当前key的类型

  • 01
TYPE key

随机返回一个key

  • 01
RANDOMKEY

设置KEY的过期时间

以秒作为单位进行设置

  • 01
EXPIRE key seconds

以毫秒作为单位进行设置

  • 01
PERSIST keyName seconds

去除KEY的过期时间

以秒的单位返回key的剩余过期时间

  • 01
TTL key

以毫秒的方式返回key的剩余过期时间

  • 01
PTTL key

查找所有符合某种模式的key(正则表达式)

  • 01
KEYS pattern

检查KEY是否存在

  • 01
EXISTS keyName

序列化Key值,并且返回序列化结果

  • 01
DUMP key

字符串类型操作命令

下表列出了 redis 字符串 基本的相关命令:

序号 命令及描述 示例
1 SET key value 设置指定key的值 示例:SET hello "world"
2 GET key 获取指定key的值 示例:GET hello
3 GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 示例:GETSET hello world2
4 MGET key1 [key2..] 获取所有(一个或多个)给定 key 的值。 示例:MGET hello world
5 SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 示例:SETEX hello 10 world3
6 SETNX key value 只有在 key 不存在时设置 key 的值。 示例:SETNX ydlclass redisvalue
7 STRLEN key 返回 key 所储存的字符串值的长度。 示例:STRLEN ydlclass
8 MSET key value [key value ] 同时设置一个或多个 key-value 对。 示例:MSET ydlclass2 ydlclassvalue2 ydlclass3 ydlclassvalue3
9 MSET key value [key value ] 同时设置一个或多个 key-value 对。 示例:MSET ydlclass2 ydlclassvalue2 ydlclass3 ydlclassvalue3
10 PSETEX key value expireTime 同时设置一个或多个 key-value 对。 示例:PSETEX ydlclass6 6000 ydlclass6value
11 INCR key 将 key 中储存的数字值增一。 示例: set ydlclass7 1 INCR ydlclass7 GET ydlclass7
12 INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) 示例:INCRBY ydlclass7 2 get ydlclass7
13 INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment) 示例:INCRBYFLOAT ydlclass7 0.8
14 DECR key 将 key 中储存的数字值减一。 示例: set ydlclass8 1 DECR ydlclass8 GET ydlclass8
15 DECRBY key decrement key 所储存的值减去给定的减量值(decrement) 示例:DECRBY ydlclass8 3
16 APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。 示例:APPEND ydlclass8 hello

示例

  1. 设置值 获取值
    set hello world [建议使用 set hello "world",可以存储空格]

    get hello

  2. mset mget 一次性操作多组数据
    mset hello world just like key "this value"

    mget hello just key

  3. 没有这个键我们才设置
    setnx dlclass value

  4. 将key的值 加一,减一
    incr stock
    decr stock

  5. 设置 a值存活时间5秒,值是b 验证码
    setex a 5 b

对hash列表的操作

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

Redis之中每一个hash可以存储2的32-1键对值(40多亿)

上面这个图或许还不够简单,可以直接简单的认为是一个存储对象的方式

所谓的大key其实就是对象名,小key其实就是对象中的属性 (无法实现多层对象相互嵌套的效果)

下表列出了 redis hash 基本的相关命令:

序号 命令及描述 示例
1 HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。
复杂度O(1)
示例:HSET key1 field1 value1
2 HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。
复杂度O(1)
示例:HSETNX key1 field2 value2
3 HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。 示例:HMSET key1 field3 value3 field4 value4
4 HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。
复杂度O(1)
示例: HEXISTS key1 field4 HEXISTS key1 field6
5 HGET key field 获取存储在哈希表中指定字段的值。
复杂度O(1)
示例:HGET key1 field4
6 HGETALL key 获取在哈希表中指定 key 的所有字段和值
复杂度O(N)
示例:HGETALL key1
7 HKEYS key 获取所有哈希表中的字段
复杂度O(N)
示例:HKEYS key1
8 HLEN key 获取哈希表中字段的数量
复杂度O(1)
示例:HLEN key1
9 HMGET key field1 [field2] 获取所有给定字段的值
复杂度O(N)
示例:HMGET key1 field3 field4
10 HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。
复杂度O(1)
示例: HSET key2 field1 1 HINCRBY key2 field1 1 HGET key2 field1
11 HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。
复杂度O(1)
示例:HINCRBYFLOAT key2 field1 0.8
12 HVALS key 获取哈希表中所有值
复杂度O(N)
示例:HVALS key1
13 HDEL key field1 [field2] 删除一个或多个哈希表字段
复杂度O(N)
示例: HDEL key1 field3 HVALS key1

示例

  1. 设置值 获取值

    hset user username itlils
    hset user age 18
    hget user username

  2. 批量设置

    hmset user1 username itnanls age 19

  3. 获取所有的键值对

    hgetall user

  4. 获取所有小key

    hkeys user

  5. 获取所有值

    HVALS user

  6. 删除

    hdel user age

对list列表的操作

Redis列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 2^32-1个元素,一个列表可以超过40亿个

(不知道为什么,REDISINSIGHT不支持使用BRPOP RLPOP等超时执行命令)

下表列出了列表相关的基本命令

序号 命令及描述 示例
1 LPUSH key value1 [value2] 将一个或多个值插入到列表头部
O(1)~O(n)
示例:LPUSH list1 value1 value2
2 LRANGE key start stop 查看list当中所有的数据
O(n)
示例:LRANGE list1 0 -1
3 LPUSHX key value 将一个值插入到已存在的列表头部(如果列表不存在不插入)
O(1)
示例:LPUSHX list1 value3 LINDEX list1 0
4 RPUSH key value1 [value2] 在列表中添加一个或多个值到尾部
O(1)~O(n)
示例: RPUSH list1 value4 value5 LRANGE list1 0 -1
5 RPUSHX key value 为已存在的列表添加单个值到尾部
O(1)
示例:RPUSHX list1 value6
6 LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素
O(1)
示例:LINSERT list1 BEFORE value3 beforevalue3
7 LINDEX key index 通过索引获取列表中的元素
O(n)
示例:LINDEX list1 0
8 LSET key index value 通过索引设置列表元素的值
O(n)
示例:LSET list1 0 hello
9 LLEN key 获取列表长度
O(1)
示例:LLEN list1
10 LPOP key 移出并获取列表的第一个元素
O(1)
示例:LPOP list1
11 RPOP key 移除列表的最后一个元素,返回值为移除的元素。
O(1)
示例:RPOP list1
12 BLPOP key 移除列表的最后一个元素,如果没有元素,要么等到超时,要么弹出最后一个元素
O(1)
示例:BLPOP list1 2000
13 BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
O(1)
示例:BRPOP list1 2000
14 RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
O(1)
示例:RPOPLPUSH list1 list2
15 BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
O(1) 时间复杂度未知
示例:BRPOPLPUSH list1 list2 2000
16 LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
O(n)
示例:LTRIM list1 0 2
17 DEL key1 key2 删除指定key的列表 示例:DEL list2

对Set集合的操作

  • Redis的Set集合其实和Java的Set集合是类似的,有所不同的是Redis的Set结合其实是基于String类型的,所以对象是无法使用Set集合来进行存储的。
  • Redis中集合是通过哈希表实现的,所以添加、删除,查找的复杂度都是O(1)。
  • 集合中最大的成员数为2的32-1 (4294967295,每个集合都可以存储40多亿个成员)

下表列出Redis集合基本命令:

序号 命令及描述 示例
1 SADD key member1 [member2] 向集合添加一个或多个成员
O(k) 为元素的个数
示例:SADD set1 setvalue1 setvalue2
2 SMEMBERS key 返回集合中的所有成员
O(n) 为元素的个数
示例:SMEMBERS set1
3 SCARD key 获取集合的成员数
O(n) 为元素的个数
示例:SCARD set1
4 SDIFF key1 [key2] 返回给定所有集合的差集
O(k) 多个集合的元素综合
示例: SADD set2 setvalue2 setvalue3 SDIFF set1 set2
5 SDIFFSTORE destination key1 [key2] 返回给定所有集合的差集并存储在 destination 中
本质上相当于做了一次去重的操作 O(n^2)
示例:SDIFFSTORE set3 set1 set2
6 SINTER key1 [key2] 返回给定所有集合的交集
本质上相当于计算所有的所有集合交集点
O(n * m * k *-··· ···)
示例:SINTER set1 set2
7 SINTERSTORE destination key1 [key2] 返回给定所有集合的交集并存储在 destination 中
O(n * m * k *-··· ···)
示例:SINTERSTORE set4 set1 set2
8 SISMEMBER key member 判断 member 元素是否是集合 key 的成员
O(n * m)
示例:SISMEMBER set1 setvalue1
9 SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合
O(n * m)
示例:SMOVE set1 set2 setvalue1
10 SPOP key 移除并返回集合中的一个随机元素
O(1)
示例:SPOP set2
11 SRANDMEMBER key [count] 返回集合中一个或多个随机数
O(count)
示例:SRANDMEMBER set2 2
12 SREM key member1 [member2] 移除集合中一个或多个成员
O(n * numberN)
示例:SREM set2 setvalue1
13 SUNION key1 [key2]] 返回所有给定集合的并集
O(n + m + k + ··· ···)
示例:SUNION set1 set2
14 SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中
O(n + m + k + ··· ···)
示例:SUNIONSTORE set5 set1 set2

对ZSet的操作-重要类型

  • Redis有序集合和集合一样也是string类型元素的集合,并且不允许存在重复的成员
  • 它用来保存需要排序的数据,例如排行榜,一个班的语文成绩,一个公司的员工工资,一个论坛的帖子等。
  • 有序集合之中,每个元素都带有score(权重),以此来对元素进行排序
  • 它有三个元素:key、member和score。以语文成绩为例,key是考试名称(其中考试、期末考试等),member是学生名字,score是成绩
    • 互联网,微博热搜,最热新闻,统计网站pv
序号 命令及描述 示例
1 ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数
O(n)
向ZSet中添加页面的PV值 ZADD pv_zset 120 page1.html 100 page2.html 140 page3.html
2 ZCARD key 获取有序集合的成员数
O(n)
获取所有的统计PV页面数量 ZCARD pv_zset
3 ZCOUNT key min max 计算在有序集合中指定区间分数的成员数
O(n)
获取PV在120-140在之间的页面数量 ZCOUNT pv_zset 120 140
4 ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment
O(n)
给page1.html的PV值+1 ZINCRBY pv_zset 1 page1.html
5 ZINTERSTORE destination numkeys key [key ...] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中
O(n * m)
创建两个保存PV的ZSET: ZADD pv_zset1 10 page1.html 20 page2.html ZADD pv_zset2 5 page1.html 10 page2.html ZINTERSTORE pv_zset_result 2 pv_zset1 pv_zset2
6 ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合指定区间内的成员
O(n )
获取所有的元素,并可以返回每个key对一个的score ZRANGE pv_zset_result 0 -1 WITHSCORES
7 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员
O(n)
获取ZSET中120-140之间的所有元素 ZRANGEBYSCORE pv_zset 120 140
8 ZRANK key member 返回有序集合中指定成员的索引
O(n)
获取page1.html的pv排名(升序) ZRANK pv_zset page3.html
9 ZREM key member [member ...] 移除有序集合中的一个或多个成员
O(n * m)
移除page1.html ZREM pv_zset page1.html
10 ZREVRANGE key start stop [WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到低
O(n * m * k) k为符合条件的个数
按照PV降序获取页面 ZREVRANGE pv_zset 0 -1
11 ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
O(n * m * k) k为符合条件的个数
获取page2.html的pv排名(降序) ZREVRANK pv_zset page2.html
12 ZSCORE key member 返回有序集中,成员的分数值
O(n)
获取page3.html的分数值 ZSCORE pv_zset page3.html

对位图BitMaps的操作

  • 计算机最小的存储单元是位bit,而Bitmaps就是针对位的这一基本单位进行存储操作而设计出来的一种数据类型。相比于其他的数据类型,更加节省空间
  • Bitmap不是一种数据结构,操作本质上其实是基于String结构的,一个String最大可以存储512M的数据,那么作为一个Bitmaps则可以设置2^32个位的数据
  • Bitmaps单独提供了一整套的命令,所以在Redis之中使用Bitmaps和使用字符串的方法有所不同。可以把Bitmaps想象成一个以为作为单元的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps之中被称为偏移量offset
  • BitMaps命令说明:将每个独立用户是否访问过网站存放在Bitmaps之中,将访问过的用户记作1,没有访问过的用户记作0,用偏移量作为用户的id。

设置BitMaps的值

command
  • 01
SETBIT key offset value

setbit命令设置的value只能是0或者是1两个数值之一

  • 设置键的第offset个位的值(从0开始算起),假设现在有20个用户,uid = 0, 5 ,11 ,15 ,19的用户对网站进行了访问,那么当前bitmaps初始化的结果如图所示。

  • 具体操作过程如下,unique:users:2022-04-05代表的是2022-04-05这一天独立访问用户的BitMaps (这里的unique:users:2022-04-05看起来虽然很长,其实这个只是一个普通的key名字罢了)

    cmd
    • 01
    • 02
    • 03
    • 04
    • 05
    setbit unique:users:2022-04-05 0 1 setbit unique:users:2022-04-05 5 1 setbit unique:users:2022-04-05 11 1 setbit unique:users:2022-04-05 15 1 setbit unique:users:2022-04-05 19 1
  • 很多应用的用户id以一个指定的数字(例如10000)来开头,直接将用户id和bitmaps的偏移量对应势必会造成一定的浪费,通常的做法是每次做setbi操作的时候讲用户id减去这个指定的数字[就是用户编号的初始值1000->1001->1002······]

  • 在第一次初始化Bitmaps时,加入偏移量非常大,那么整个初始化过程执行的会比较慢,可能会造成Redis的阻塞。

获取BitMa ps的值

  • 01
GETBIT key offset

获取键的第offset位的值(从0开始算),例:下面操作获取id=8的用户是否已经在2022-04-05这一天访问过,返回0说明没有访问过。

  • 01
getbit unique:users:2022-04-05 8

对BitMaps进行运算

  • 01
BITOP operation destkey key [key,······]

bitop是一个复合操作,它可以做多个Bitmaps的and(交集)、or(并集)、not(非)、xor(异或) 操作并且将结果保存在destkey之中。

(例如:可以计算两个BitMaps key之间偏移量一致同为1的数值有多少个,进行统计计算)

需求:统计在某个范围内符合相关Bitmaps设置为1的总数有多少

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
先有两个BitMaps 1. setbit unique:users:2022-04-04 1 1 setbit unique:users:2022-04-04 2 1 setbit unique:users:2022-04-04 5 1 setbit unique:users:2022-04-04 9 1 2. setbit unique:users:2022-04-05 1 1 setbit unique:users:2022-04-05 2 1 setbit unique:users:2022-04-05 5 1 setbit unique:users:2022-04-05 9 1 3. 进行位图的运算操作 bitop and unique:users:and:2022-04-04_05 unique:users:2022-04-04 unique:users:2022-04-05 bitcount unique:users:and:2022-04-04_05

bitop 进行什么操作 存放结果的集合对象 原bitmaps对象 目标bitmaps对象

对HyperLogLog结构的操作

所谓的HyperLogLog结构其实是一种用于大数据量的统计,比如页面访问量统计或者用户访问量统计。

要统计一个页面的访问量(pv),可以直接用redis计数器或者直接存数据库的方式来实现。但是如果我们一个页面需要根据用户的不同来计算访问数的话,我们还使用旧的方式即使用数据库来存或者以以前的数据类型来存储的话,那么我们的redis的压力会变得很大,因为要频繁修改的同时还需要做逻辑去重。

UV计算示例

也就是说UV具有三个基本的命令

添加数据

PFADD key element [element···]

计算总数

PFCOUNT key [key···]

数据融合

PFMERGE destkey sourcekey [sourcekey ···]

Redis继承的HyperLogLog使用语法主要有PFADD和PFCOUNT,顾名思义的,一个是来添加数据,一个是来统计的。为什么会用PF,因为这个数据结构的发明人叫做Philippe Flajolet教授。

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
PFADD UV USER1 USER2 USER3 USER4 USER5 USER6 USER7 USER8 USER9 USER10 PFCOUNT UV PFADD US USER1 USER2 USER3 USER11 USER12 USER13 PFMERGE UV+US UV US PFCOUNT UV+US -- 最终结果会是13

HyperLogLog算法一开始就是为了大数据量的统计而发明出来的,所以很适合用在数据量很大的,然后又没有要求做到没有一点误差的计算之中,并且HyperLogLog提供的不精确的去重计数方案,他的不精确度只有0.81%,所以完全可以将HyperLogLog的算法用在计算一些不完全依靠准确度的数据上。

HyperLogLog为什么这么适合用在进行大量数据的统计上面

  • Redis HyperLogLog是用来做计数统计的算法,HyperLogLog的优点是,在输入元素的数量或者是体积非常非常大的时候,计算基数所需要用到的空间却是总是固定的、并且大小始终是很小的
  • 在Redis之中,每一个HyperLogLog的键都只需要花费12KB大小的内存,就可以计算接近2^64个不同元素的基数。这和计算基数时,元素越多往往会花费更多内存的集合形成鲜明对比
  • 但是,因为HyperLogLog只会根据输入元素来计算基数,而不会存储元素本身,所以HyperLogLog不能像数组一样,返回输入的各个元素
什么是基数

比如:数据集{1,3,5,7,5,7,8},那么这个数据集的基数集{1,3,5,7,8},基数为5个,基数估计就是在误差可以接受的范围内,快速的去计算基数。

Redis的持久化操作

Redis的持久化操作有两种不同的实现方式,第一种是基于RDB方式的来实现;第二种是基于AOF的持久化方案。

RDB持久化方案

Redis会定期保存数据快照到一个rdb文件之中,并在启动时自动加载rdb文件,恢复之前保存的数据。如果想要对RDB配置的话,可以在Redis配置快照保存的时机。

  • 01
save [seconds] [changes]

意为在seconds秒内如果发生了changes次数据修改,则进行一次RDB快照保存,例如:

  • 01
  • 02
save 60 100 save 600 500

会让Redis每60秒检查一次数据变更情况,如果发生了100次或者以上的数据变更,则进行RDB快照保存。可以配置多条save指令,让Redis执行多级的快照保存策略。Redis默认开启RDB快照。也可以通过Save或者BGSave命令手动触发RDB快照保存。SAVE和BGSAVE两个命令都会调用rdbsave函数,但它们调用的方式各有不同:

  • SAVE直接调用rdbSave,阻塞Redis主进程,直到保存完成为止。在主线程阻塞期间,服务器不能处理客户端的任何请求。
  • BGSAVE则fork出一个子进程,子进程负责调用rdbSave,并在保存完成之后向主进程发送信号,通知保存已完成。Redis服务器在BGSave执行期间仍然可以继续处理客户端的请求。

RDB方案优点

  1. 对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
  2. 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介之中),作为非常可靠的灾难恢复手段。
  3. RDB方案缺点
  4. 快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据
  5. 如果数据集非常大且CPU不够强大的话,Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力。

RDB配置

  1. 修改Redis的配置文件

    • 01
    • 02
    • 03
    • 04
    • 05
    • 06
    • 07
    • 08
    cd /export/server/redis-6.2.6 vim redis.conf # 第 行 save 900 1 save 300 10 save 60 10000 save 5 1

    这三个选项是redis 配置文件默认自带的存储机制。表示每隔多少秒,有多少个key发生变化就生成一份dump.rdb文件,作为redis的快照文件

    例如:save 60 10000 表示在60s内,有10000个key发生变化,就会生成一份redis的快照

  2. 重新启动redis服务

    每次生成新的dump.rdb都会覆盖掉之前的旧的快照

    • 01
    • 02
    • 03
    ps -ef | grep redis bin/redis-cli -h 192.168.200.131 shutdown bin/redis-server redis.conf

AOF持久化方案

介绍

采用AOF持久方式时,Redis

开启AOF

AOF默认是关闭的,如果要开启的话,进行如下的配置:

  • 01
  • 02
# 第594行 appendonly yes

配置AOF

AOF提供了三种fsync配置: always/everysec/no,通过配置项[appendfsync]指定:

  1. appendfsync no: 不进行fsync,将flush文件的时机交给OS决定,速度最高
  2. appendfsync always: 每写入一条日志就进行一次fsync操作,数据安全性最高,但是速度也是最慢的
  3. appendfsync everysec:这种的做法,交由后台线程每秒fsync一次

AOD rewrite

随后AOF不断地记录写操作日志,因为所有的写操作都会被记录下来,所以必定会出现一些无用的日志。大量无用的日志会让AOF文件过大,也会让数据回复的时间变长。不过Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新的状态的最小写操作集

AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行:

console
  • 01
  • 02
auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
  • Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF在该基础上增长了100%后,自动进行AOF rewrite 32m--->10万-->64M--->40M--100万--80--》50M-->>100m
  • auto-aof-rewrite-min-size最开始的AOF文件必须要触发这个文件才能触发,后面的每次重写都不会根据这个变量了。该变量仅仅对初始化启动Redis有效。

AOF优点

  1. 最安全,在启动appendfsync为always的时候,任何已经写入的数据都不会丢失,使用在启动appendfsync everysec也最多只会丢失掉1s的数据。
  2. AOF文件在发生断电等问题的时候也不会发生损坏,即使出现了某条日志只写入了一半的情况
  3. AOD文件易读,可以修改,在进行某些错误的数据清除操作之后,只要AOF文件没有rewrite,就可以直接把AOF文件备份出来把错误的命令删除掉,然后回复数据。

AOF缺点

  1. AOF文件通常会比RDB文件更大一些
  2. 性能消耗会比RDB高
  3. 数据恢复速度会比RDB慢

Redis的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求指定更为合理的持久化策略:

  • AOF + fsync always的设置虽然能够绝对确保数据安全,但是每个操作都会触发一次fsync,会对Redis的性能造成比较明显的影响。
  • AOF + fsync every second是比较好的这种方案,每秒fsync一次
  • AOF + fsync never会提供AOF持久化方案下的最优性能

使用RDB持久化通常会提供比使用AOF更高的性能,但需要注意RDB的策略配置

RDB or AOF

每一次RDB快照和AOF Rewrite都需要Redis主进程进行fork操作。fork操作本身可能会产生较高的耗时,与CPU和Redis占用的内存大小有关。根据具体的情况合理的配置RDB快照和AOF Rewrite时机,避免过于频繁的fork带来的延迟。

Redis在fork子进程的时候需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝48MB的数据。在使用单Xeon 2.27Ghz的物理机上,这一fork行动操作将会导致耗时216ms。

Redis事务

Redis事务简介

Redis事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有的命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会

总结说:Redis事务就是一次性、顺序性、排他性的执行一个队列之中的一系列命令

  • Redis事务没有隔离级别的概念

    批量操作在发生EXEC命令前辈放入队列缓存,并不会被实际的执行,也就是不存在事务内的查询要看到事务里的更新,事务外查询是不能看到的。

  • Redis不保证原子性 (只能靠脚本文件自身来实现)

    Redis之中,单条命令是原子性执行的,但是事务不保证原子性,并且不能提供回滚的。事务中任意命令执行失败,其余的命令依旧会被执行。

    一个事务从开始到实际执行会经历以下的三个阶段:

    • 第一阶段:开始事务
    • 第二阶段:命令入队
    • 第三阶段:执行事务

Redis事务相关命令

  • MULTI

    开启事务,redis会将后续的命令逐个放入列队之中,然后使用EXEC命令(Linux系统的基本命令)来原子化执行这个命令队列

    使用MULTI命令后,输入的命令不是返回OK,而是会返回QUEUED,只有再执行了EXEC命令之后,才会结束多命令原子性执行的特点

  • EXEC

    执行事务之中的所有操作命令

  • DISCARD

    取消事务,放弃执行事务块中的所有命令

  • WATCH

    监视一个或者多个key,如果事务在执行之前,这个key(或者多个key)被其他的命令修改,则事务被中断,不会执行事务之中的任何命令

  • UNWATCH

    取消WATCH对所有的key的监视

Redis事务的执行逻辑,使用MULTI开启事务之后,在实际使用exec执行时,有多种三种可能会导致事务最终无法顺利执行

  1. 要执行的命令之中存在着错误的语法

    • 01
    • 02
    • 03
    • 04
    • 05
    • 06
    • 07
    • 08
    • 09
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    >set key1 x1 OK >set key2 x2 OK >multi >set key1 v1 QUEDED >sets key2 v2 ERR unknown command `sets`, with args beginning with: `key2`, `v2`, >exec (error) EXECABORT Transaction discarded because of previous errors. -- 出现这种错误的时候,Redis的事物规则是不执行所有的命令 >get key1 "x1" >get key2 "x2"
  2. 在执行事务期间,如果出现类型不匹配,那么Redis事务最终处理时,会将错误的类型跳过,而执行其他的命令。

    • 01
    • 02
    • 03
    • 04
    • 05
    • 06
    • 07
    • 08
    • 09
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    >set key1 x1 OK >lpush key2 x2 OK >multi OK >set key1 x3 QUEDED >set key2 x4 QUEDED >exec 1)ok 2)(error) WRONGTYPE Operation against a key holding the wrong kind of value >get key1 "x1" >lrange key2 0 -1 "x2"
  3. 使用DISCARD取消事务

    • 01
    • 02
    • 03
    • 04
    • 05
    • 06
    • 07
    • 08
    • 09
    • 10
    • 11
    • 12
    • 13
    • 14
    >set key1 x1 OK >set key2 x2 OK >multi OK >set key1 v1 QUEDED >set key2 v2 QUEDED >discard OK >exec ERR EXEC without MULTI

Redis事务演示

**MULTI开始一个事务:**给k1、k2分别赋值,在事务之中修改k1、k2,执行事务之后,查看k1、k2的值都被修改。

console
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
set key1 v1 set key2 v2 multi set key1 11 set key2 22 exec get key1 get key2

事务失败处理:语法错误(编译器错误),在开启事务后,修改k1值为11,k2值为22,但k2语法错误,最终导致事务提交失败,k1、k2保留原值。[成功的实现了原子性的控制]

console
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
set key1 v1 set key2 v2 multi set key1 11 sets key 22 exec get key1 get key2

**Redis类型错误(运行时错误):**在开启事务之后,修改k1值为11,k2的值为22,但将k2的类型作为List,在运行时检测类型错误,最终导致事务提交失败,此时事务并没有回滚,而是跳过错误命令继续执行,最终导致了k1的值改变,k2保留原值。

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
set key1 v1 set key2 v2 multi set key1 11 set key2 22 exec get key1 get key2

**DISCARD取消事务:**不改变键对值,保留原来的数值

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
set key1 v1 set key2 v2 multi set key1 v1 set key2 v2 discard get key1 get key2

为什么Redis不支持使用事务

在Redis之中,多数的事务失败是由于语法、数据结构类型而导致的,语法错误说明可以在执行语句进行原子性执行队列之前先进行检查,而类型错误则可以在实际执行的时候来进行检测, Redis为了提高性能,而最终放弃了事务,而是通过将多段命令转化为原子性的方式来实现。这不同于关系型数据库的处理方式,让Redis的性能得到了最大程度的提高,但也让我们需要更加注意Redis数据的问题。

数据删除与淘汰策略

Redis存储结构细节

Redis中的数据特征

Redis是一种内存级的数据库,所有数据均存放于内存之中,内存中的数据则可以通过TTL指令获取其状态信息

  • 01
>TTL KEY_NAME (如果根据KEY的状态来返回相关的信息)

TTL返回的值一般有三种情况:正数,-1,-2

**正数:**代表着该数据在内存之中还能存活的时间

**-1:**代表永远有效的税局

**-2:**代表已经过期了的数据或者被删除的数据又或者未定义的数据,反正就是查不到的数据都会返回-2

删除策略就是针对已经过期数据的处理策略,已过期的数据是真的就立即删除了吗?其实想想就知道肯定不可能是这样的,我们会有多种删除的策略,是分情况进行处理的,在不同的场景下使用不同的删除方式会有不同的效果,这也正是我们要将的数据删除策略的问题。

时效性数据的存储结构

在Redis之中,如果给数据设置它的失效周期呢?数据的时效在Redis之中如何存储呢?看下图

过期数据是一块独立的存储空间,Hash结构,field是内存地址,value是过期时间,保存了所有key的过期描述,在最终进行过期处理的时候,对该空间的数据进行检测,当时间到期之后通过field找到内存该地址处的数据,然后进行相关的操作。

删除(淘汰)策略

不同策略的区别

本质上其实就是考虑一个清除数据的频率的问题。如果频率过高(定时清除),就会导致CPU占用过高,当然内存的占用将会得到大幅的降低;如果频率过低(惰性清除),就会导致内存占用过高,CPU占用当然也是最低的。但这也会导致我们的内存经常会是满的状态。当然也有折中的处理方式(定期删除),这种处理方式是我们处理Redis数据最为均衡的方式,一方面它使得CPU的性能得到一定的使用也不会过高,也不会造成性能过低的问题。

定时删除

创建一个定时器,当key设置有过期时间,并且过期时间到达时,就由定时器任务立即执行对键的删除操作

  • 优点:节约内存,到时就删除,快速释放掉不必要的内存占用
  • 缺点:CPU压力很大,无论CPU此时负载量有多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量
  • 总结:用处理器性能来换取存储空间(那时间换空间)

(类似于使用EDI的方式,通过定时的方式触发EDI来处理内存的问题)

惰性删除

数据到达过期时间,不做处理。等到下次访问该数据的时候,我们需要进行判断,判断数据是否过期等

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

定期删除

Redis的定期删除方案的执行步骤:

  • Redis启动服务器初始化时,读取配置server.hz的值,默认为10

  • 每秒钟执行server.hz次serverCron() -------> databasesCron() -------> activeExpireCycle()

  • **activeExpireCycle()**方法将会对每一个expires[*]逐一进行检测,每次执行耗时:250ms/server.hz

    (这里的250ms,其实是Redis算法来决定的,由于Redis是单线程的程序,为了清除过期数据的时候不会影响到主要业务逻辑使用,Redis将一秒中的3/4抽取出来作为正常业务的处理,1/4的时间切换到数据删除,并且只要CPU能力足够支持,可以做到server.hz次的检查处理)

  • 对于某一个expires[*]检查的时候,就会随机的挑选出W个key进行检测

    如果key超时,删除key (如果检查到expire已经超时,则删除该key)
    如果一轮之中的key的数量>W25%,那么循环该过程 (这个区域大量过期,循环删除)
    如果一轮之中删除的key的数量<=W
    25%,检查下一个expires[*],0-15循环 (符合条件下一个expires)
    W的取值 = ACTIVE_EXPIRE_LOOKUPS_PER_LOOP属性值 (配置值)

  • 参数current_db用于记录activeExpireCycle()进入哪一个expire[N]执行,即上次处理到哪一个位置

  • 如果acitveExpireCycle()执行时间到期,下次从current_db继续向下执行

总的来说:定期删除就是周期性的轮询redis库之中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频率

  • **特点1:**CPU性能占用设置有峰值,检测频率可以进行自定义设置
  • **特点2:**内存压力不是很大,长期占用内存的冷数据将会被持续清理
  • **总结:**周期性的抽查存储空间(随机的抽查,重点抽查)

数据淘汰策略(逐出算法)

所谓数据淘汰策略,其实就是当Redis的内存占满了,这个时候如果来了数据,就可能会出现,没有足够的内存存放新数据的问题。那么redis就需要临时删除一些数据为当前指令的执行提供足够的空间。清理数据的策略称为逐出算法。

注意:逐出数据的过程不是100%能够产生新的空间的,如果一次没有成功产生空间那么将会反复尝试去释放空间,如果对所有存储的数据都尝试后仍然失败将会提示报错信息。

command
  • 01
(error) OOM command not allowed when used memory >'maxmemory'

策略配置

影响数据淘汰的相关配置如下:

  1. 最大可使用的内存,即占用物理内存的比例,默认值为70,实际设置的时候,可以按照生产环境来进行设定,通过将其设置为50%以上

    command
    • 01
    maxmemory ?db
  2. 每次选取待删除数据的个数,采用随机获取数据的方式作为待检测删除数据

    command
    • 01
    maxmemory-samples count
  3. 对数据进行删除的选择策略

    command
    • 01
    maxmemory-policy policy

    拿数据删除的策略policy到底有多少种呢?一共是3类8种

    **第一类:**检测易失数据(可能会过期的数据集server.db[i].expires) 同一个库

    command
    • 01
    • 02
    • 03
    • 04
    volatile-lru:挑选最近最少使用的数据淘汰 least recently used volatile-lfu:挑选最近使用次数最少的数据淘汰 least frequently used volatile-ttl:挑选将要过期的数据淘汰 volatile-random:任意选择数据淘汰

    **第二类:**检测全库数据(所有数据集server.db[i].dict)

    command
    • 01
    • 02
    • 03
    allkeys-lru:挑选最近最少使用的数据淘汰 allkeLyRs-lfu:挑选最近使用次数最少的数据淘汰 allkeys-random:任意选择数据淘汰,相当于随机淘汰

    **第三类:**放弃数据驱逐 (就是直接停止服务,不往里面塞数据了)

    command
    • 01
    no-enviction(驱逐):禁止驱逐数据(redis4.0中默认策略),会引发OOM(Out Of Memory)

    注意:这些策略是配置到哪个属性上?怎么配置?如下所示

    command
    • 01
    maxmemory-policy volatile-lru

    数据淘汰策略配置依据

    使用INFO命令输出监控信息,查询缓存hit和miss的次数,根据业务需求调优Redis配置

Redis的主从复制架构

主从复制

首先我们要理解互联网应用因为其独有特性我们演化出来了三高架构:高并发、高性能、高可用

**高并发:**应用提供某一业务需要支持很多的客户端同时进行访问的能力,而这一能力被我们成为并发

**高性能:**性能带给我们最直观的体验就是单纯的速度很快,时间很短

**高可用:**一年中应用服务正常运行的时间占全年时间的百分比,如下图所示:

业界可用性目标5个9,即99.999%,即服务器年宕机时长低于315秒,约5.25分钟

主从复制的概念

知道了三高的概念之后,我们想:你的Redis是否处于高可用状态,其实就是要分析Redis的风险和问题

**概念:**主从复制即将master之中的数据即时、有效的将其复制到slave之中

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

**职责:**master和slave各自的职责不一样

面对的问题

问题1机器故障

  • 现象:硬盘故障、系统崩溃
  • 本质:数据丢失,很可能会导致对业务造成灾难性质的打击
  • 结论:使用Redis没办法解决该问题

问题2容量瓶颈

  • 现象:内存不足,从16G升级到64G,64G升级到128G等无限升级的办法
  • 本质:没钱,弄不了
  • 结论:不仅仅是Redis的问题

结论:

为了避免单点Redis服务器故障,需要准备多台服务器,互相连通。将数据复制到多个副本保存在不同的服务器上,连接在一起,并且保证数据是同步的。即使有其中一台服务器处于宕机,其他服务器依然可以提供服务,实现Redis的高可用,同时实现数据冗余备份。

多条服务器连接方案:

  • 提供数据方:master

    主服务器,主节点,主库主客户端

  • 接收数据方:slave

    从服务器,从节点,从库

    从客户端

  • 需要解决的问题:

    数据同步(master的数据复制到slave之中)

    master:

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

    slave:

    读数据
    写数据(禁止)

主从复制的作用

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

主从复制工作流程

主从复制过程大体上可以被分为3个阶段

  • 建立链接阶段(即准备阶段)

  • 数据同步阶段

  • 命令传播阶段(反复同步)

    而命令的传播其实有4种,分别如下:

1. 主从复制的工作流程(三个阶段)

阶段一:建立连接

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

流程如下:

  1. 步骤1:设置master的地址和端口,保存master信息
  2. 步骤2:建立socket信息
  3. 步骤3:发送ping命令(定时器任务)
  4. 步骤4:身份验证
  5. 步骤5:发送slave端口信息 (双方交换对方的端口信息,从而有效的建立socket链接)

master和slave互联 (建立链接)

接下来就要通过某种方式将master和slave连接在一起

方式一:客户端发送命令 (从属于masterIp的masterPort端口)

command
  • 01
slaveof masterip masterport

方式二:启动服务器参数

command
  • 01
redis-server --slaveof masterip masterport

方式三:服务器配置(主流方式)

command
  • 01
slaveof masterip masterport

slave系统信息

command
  • 01
  • 02
master_link_down_since_seconds masterhost & masterport

master系统信息

command
  • 01
uslave_listening_port(多个)

master和slave断连 (断开链接)

断开slave与master的连接,slave断开连接后,不会删除已经存在的数据,只是不再接收master发送的数据

command
  • 01
slaveof no one

授权访问

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

command
  • 01
requirepass password

master配置文件设置密码

command
  • 01
  • 02
config set requirepass password config get requirepass

slave客户端发送命令设置密码

  • 01
auth password

slave配置文件设置密码

  • 01
masterauth password

slave启动服务器设置密码

  • 01
redis-server -a password

阶段二:数据同步

  • 在slave初次连接master后,复制master中的所有数据到slave
  • 将slave的数据库状态更新成master当前的数据库状态

同步过程如下:

  1. 步骤1:请求同步数据
  2. 步骤2:创建RDB同步数据
  3. 步骤3:恢复RDB同步数据
  4. 步骤4:请求部分同步数据
  5. 步骤5:恢复部分同步数据

当前的状态是为了使得slave拥有master端的全部数据,包含RDB过程接收的数据,并且让master拥有slave当前数据同步的位置。

数据同步阶段master说明

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

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

command
  • 01
repl-backlog-size ?mb

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

数据同步阶段slave说明

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

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

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

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

阶段三:命令传播

  • 当master数据库被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作被称为命令传播
  • master将接收到的数据变更命令发送到slave,slave接受命令后执行命令

部分复制的原因

出现部分复制可能有以下原因:

  1. 命令传播的阶段如果出现了断网的现象
  2. 网络闪断闪连:忽略
  3. 短时间网络中断:部分复制
  4. 长时间网络中断:全量复制

部分复制的其实主要有三个核心要素:

  1. 服务器的运行id(run id)
  2. 主服务器的复制积压缓冲区
  3. 主从服务器的复制偏移量
  • 服务器运行ID(runid)

概念:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id
组成:运行id是由40位字符组成的,是一个随机的十六进制的字符串
例如:fdc9ff13b9bbaab28db42b3d50f852bb5e3fcdce

作用:运行id被用于在服务器之间进行传输,用于识别身份
如果想要两次操作均对一台服务器进行,进行每次操作的时候必须携带对应的运行id,用于对方识别

实现方法:运行id在每台服务器启动时自动生成的,master在首次链接slave时候,会将自己的运行的ID发送给slave,slave保存此ID,通过info Server命令,可以查看节点的runid。

  • 复制缓冲区

概念:复制缓冲区,又名叫做复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器,执行过的命令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区
复制缓冲区默认数据存储空间大小是1M
当入队元素的数量大于队列长度的时候
作用:用于保存master收到主客户端的指令时,除了将指令执行,会将该指令存储到缓冲区之中

复制缓冲区内部工作原理:

组成:

  • 偏移量

    概念:一个数字,描述复制缓冲区之中的指令字节位置
    分类:

    • master复制偏移量:记录发送给所有slave的指令字节对应的位置(多个)
    • slave复制偏移量:记录slave接收master发送过来的指令字节对应的位置(一个)
      作用:同步信息,比对master与slave的差异,当slave断线后,恢复数据使用
      数据来源:
    • master端:发送一次记录一次
    • slave端:接收一次记录一次
  • 字节值

工作原理

  • 通过offset区分不同的slave当前数据传播的差异
  • master记录已发送的信息对应的offset
  • slave记录已接收的信息对应的offset
2.流程更新(全量复制/部分复制)

我们再次的总结一下主从复制的三个阶段的工作流程:

3.心跳机制

进入命令传播阶段,master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线

master心跳:

  • 内部指令:PING
  • 周期:由repl-ping-slave-period决定,默认10s
  • 作用:判断slave是否在线
  • 查询:INFO replication获取slave最后一次链接时间间隔,lag项维持在0或1视为正常

slave心跳任务:

  • 内部指令:REPLCONF ACK {offset}
  • 周期:1s
  • 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
  • 作用2:判断master是否在线

心跳阶段注意事项:

  • 当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步

    command
    • 01
    • 02
    min-slaves-to-write 2 min-slaves-max-log 8

    slave数量少于2个,或者所有slave的延迟都大于等于8秒时,强制关闭master写功能,停止数据同步

  • slave数量由slave发送的REPLCONF ACK命令做确认工作

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

所以主从复制的整个流程可以被概括为以下的内容:

搭建本地主从架构

真实的生产环境,主从部署在不同的物理地址

具体步骤

  1. 复制两台redis redis 6380 redis 6381

    • 01
    • 02
    cp redis-6.2.6/ redis 6380 cp redis-6.2.6/ redis 6381
  2. 分别修改配置文件:

    • 01
    • 02
    • 03
    • 04
    port 6380 pidfile /var/run/redis_6380.pid logfile "/export/server/redis6380/log/redis.log" dir /export/server/redis6380/data
  3. 启动

    • 01
    ./bin/redis-server redis.conf slaveof 192.168.200.131 6379
  4. 效果

    登录6380

    • 01
    ./bin/redis-cli -h 192.168.200.131 -p 6380
    • 01
    info

  5. 测试

    1. 主节点写数据,从节点获取数据

      • 01
      192.168.200.131:6379>set a b
      • 01
      192.168.200.131:6380>get a
    2. 从节点不能写数据

      command
      • 01
      • 02
      192.168.200.131:6381>set aa bb (error) READONLY You can't write against a read only replica

搭建不同服务器的主从架构

具体步骤

首先在主机上直接正常的启动Redis主服务即可,不用修改配置文件

  • 01
redis-server [conffile absoulute path]

直接在从机的Redis上的conf文件,修改配置文件 (slaveof节点Redis一样可以充当其他slaveof的主机)

首先需要配置一些从机的redis.conf参数,使得从机能够从主机之中获取信息,并且实现同步

conf
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
#自身密码 requirepass "password" # 必须跟master的密码一致,否则Sentinel故障转移会失败 #主从结构配置 replicaof IP port # 标明要跟从的主结构的IP和端口号 masterauth "password" # 标明主节点的密码 #开启后台运行 daemonize yes #设置pid文件路径 pidfile "/usr/local/redis/logs/slaveof_redis_6380pid.log" #设置redis日志文件路径 logfile "/usr/local/redis/logs/slaveof_redis_6380.log" dir "/usr/local/redis/bin" # Generated by CONFIG REWRITE # 如果不手动声明以下的配置,Redis将会默认生成 save 3600 1 # 开启RDB持久化 save 300 100 save 60 10000 latency-tracking-info-percentiles 50 99 99.9 user default on sanitize-payload #193332a189d3b3e274a52afd292df5c1658430af341546a8e3f7f2d0048e027e ~* &* +@all

(因为不能暴露实际公网IP 和 端口,这里没有提供具体的参数,可以对着写)

主从复制常见的问题 - 架构师

频繁的全量复制

  • 伴随着系统的运行,master的数据量会变得越来越大,一旦master重启,runid将发生变化,会导致全部slave的全量复制操作。

内部优化调整方案

  1. master内部创建master_replid变量,使用runid相同的策略生成,长度41位,并发送给所有slave

  2. 在master关闭时,执行shutdown save,进行rdb持久化将runid与offset保存到rdb文件之中

    • 01
    • 02
    repl-id repl-offset 通过redis-check-rdb命令可以查看该信息
  3. master重启后加载rdb文件,恢复数据,重启后,将rdb文件之中保存的repl-id与repl-offset加载到内存之中

    • 01
    • 02
    master_repl_id = repl master_repl_offset =repl_offset 通过info命令可以查看该信息

作用:本机保存上次runid,重启后恢复该值,使所有slave认为还是之前的master

  • 第二种出现频繁全量复制的问题现象:网络环境不佳,出现网络中间,slave不提供服务

问题原因:复制缓冲区过小,断网之后slave的offset越界,触发全量复制

最终结果:slave反复进行全量复制

解决方案:修改复制缓冲区的大小

  • 01
repl-backlog-size 20mb

建议设置如下:

  1. 测算从master到slave的重连平均时长second 10s
  2. 获取master平均每秒产生写命令数据总量write_size_per_second 10w
  3. 最优复制缓冲区空间 = 2 * second *write_size_per_second 10w * 10b 20mb

频繁的网络中断

  1. 问题现象:master的CPU占用比过高或者slave频繁断开链接

问题原因

slave每1s发送REPLCONFACK命令到master
当slave接到了慢查询时(key *,hgetall等),会大量占用CPU性能
master每1s调用复制定时

最终结果:master各种资源(输出缓冲区、带宽、链接等)被严重占用

解决方案:通过设置合理的超时时间,确认是否释放slave

  • 01
repl-timeout seconds

改参数定义了超时时间的阈值(默认60s),超过该值,释放slave

  • 问题现象:slave与master链接断开

问题原因

master发送ping指令频度较低
master设定超时时间较短
ping指令在网络中存在丢包

解决方案:提高ping指令的发送频度

  • 01
repl-ping-slave-period seconds

超时时间repl-time的时间至少是ping指令频度的5倍到10倍,否则slave很容易判断超时

数据不一致

问题现象:多个slave获取相同数据不同步

问题原因:网络信息不同步,数据发送有延迟

解决方案:

优化主从间的网络环境,通常放置在同一个机房部署,如使用阿里云等云服务器时,需要注意此现象拉专线的方式来解决这个问题
监控主从节点延迟(通过offset)判断,如果slave延迟过大,暂时屏蔽程序对该slave的数据进行访问

  • 01
slave-serve-stale-data yes|no

开启后仅相应info,slaveof等少数命令(慎用,除非对数据一致性要求很高)

Redis之中的Sentinel架构

哨兵简介

如果redis的master宕机了,此时就需要像Sentinel一样,通过哨兵的方式,选举出一个新的master。

要实现这些功能,那我们就需要redis的哨兵

所谓的哨兵机制其实和Zookeeper的Sentinel的机制几乎是一模一样的,也是通过分布式的方式,对多态可正常运行使用的redis服务器进行监控,如果出现故障的时候通过投票机制选择新的master并将所有的slave连接到新的master。

哨兵作用

哨兵的作用:

  • 监控:监控master和slave

    不断的检查master和slave是否正常运行

    master存活检测,master与slave运行情况检测

  • 通知(提醒):当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知

  • 自动故障转移:断开master与slave链接,选取一个slave作为master,将其他salve连接到新的master,并告知客户端新的服务器地址

注意:哨兵也是一台redis服务器,只是不提供数据相关服务,通常哨兵的数量配置为单数

哨兵的工作原理

哨兵在进行主从切换的过程之中经历三个阶段

  • 监控
  • 通知
  • 故障转移

监控

用于同步各个节点的状态信息

  • 获取各个sentinel的状态(是否在线)

  • 获取master的状态

    • 01
    • 02
    • 03
    • 04
    master属性 prunid prole:master 各个slave的详细信息
  • 获取所有slave的状态(根据master中的slave的信息)

    • 01
    • 02
    • 03
    • 04
    • 05
    slave属性 prunid prole:slave pmaster_host、master_port poffset

    其内部的工作原理具体如下:

通知

sentinel在通知阶段要不断的去获取master/slave的信息,然后在各个sentinel之间进行共享,具体的流程如下:

故障转移

当master宕机后sentinel是如何知晓并且判断出master是真的宕机

当sentinel认定master下线之后,此时需要决定更换master,那这件事由哪个sentinel来做?

在选举的时候每一个人手里都有一票,而每一个人的又都想当这个处理事故的人,那怎么办?大家就开始抢,于是每个人都会发出一个指令,在内网里边告诉大家我要当选举人,比如说现在的sentinel1和sentinel4发出这个选举指令了,那么sentinel2既能接到sentinel1的也能接到sentinel4的,接到了他们的申请以后呢,sentinel2他就会把他的一票投给其中一方,投给谁呢?谁先过来我投给谁,假设sentinel1先过来,所以这个票就给到了sentinel1。那么给过去以后呢,现在sentinel1就拿到了一票,按照这样的一种形式,最终会有一个选举结果。对应的选举最终得票多的,那自然就成为了处理事故的人。需要注意在这个过程中有可能会存在失败的现象,就是一轮选举完没有选取,那就会接着进行第二轮第三轮直到完成选举。

接下来就是由选举胜出的sentinel去从slave中选一个新的master出来的工作,这个流程是什么样的呢?

首先它有一个在服务器列表中挑选备选master的原则

  • 不在线的OUT

  • 响应慢的OUT

  • 与原master断开时间久的OUT

  • 优先原则

    优先级 offset runid

选出新的master之后,发送指令( sentinel )给其他的slave:

  • 向新的master发送slaveof no one
  • 向其他slave发送slaveof 新masterIP端口

总结:故障转移阶段

  1. 发现问题,主观下线与客观下线
  2. 竞选负责人
  3. 优选新master
  4. 新master上任,其他slave切换master,原master作为slave故障恢复后连接

搭建本地启用哨兵

配置哨兵

  • 配置一拖二的主从结构(利用之前的方式启动即可)
  • 配置三个哨兵(配置相同,端口不同)

端口号

  1. 设置哨兵监听的主服务器信息,Sentinel_number表示参与投票的哨兵数量

    • 01
    sentinel monitor master_name master_host master_port sentinel_number
  2. 设置判断服务器宕机时长,该设置控制是否进行主从切换

    • 01
    sentinel down-after-milliseconds master_name million_seconds
  3. 设置故障切换的最大超时时长

    • 01
    sentinel failover-timeout master_name million_seconds
  4. 设置主从切换之后,同时进行数据同步的slave数量,数值越大,要求网络资源越高,数值越小,则同步时间越长

    • 01
    sentinel parallel-syncs master_name sync_slave_number

    启动哨兵

    • 01
    redis-sentinel filename

搭建哨兵

  1. copy出三份 redis运行包

    • 01
    • 02
    • 03
    cp -R redis6380 sentinel26379 cp -R redis6380 sentinel26380 cp -R redis6380 sentinel26381
  2. 分别修改sentinel.conf

    • 01
    • 02
    • 03
    • 04
    • 05
    • 06
    • 07
    • 08
    • 09
    port 26379 daemonize yes pidfile "/var/run/redis-sentinel26379.pid" logfile "/export/server/sentinel26379/log/log.log" dir "/export/server/sentinel26379/data" sentinel monitor mymaster 192.168.200.131 6379 2 sentinel resolve-hostnames no sentinel announce-hostnames no
  3. 分别启动sentinel

    • 01
    ./bin/redis-sentinel sentinel.conf
  4. 链接sentinel

    • 01
    ./bin/redis-cli -h 192.168.200.131 -p 26379
  5. 查看状态

    • 01
    info

  6. 尝试故障迁移 杀掉主服务机器

    登录6380 info

  7. 尝试6379恢复

    登录6380info

服务器搭建主从Sentinel

首先要保证服务器有Sentinel-Server的服务,从而保证能够架起Sentinel故障转移的服务

然后将Sentienl的官方提供的Sample Conf文件进行一定程度的修改。

conf
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
# 端口号 port portNumber # 后台运行 daemonize yes # pid日志 pidfile "/usr/local/redis/logs/redis-sentinel-26380.pid" # log日志 logfile "/usr/local/redis/logs/redis-sentinel-26380.log" # 文件存储目录 dir "/tmp" # 主节点IP 端口号 哨兵数量 sentinel monitor mymaster IP port 3 # 标记被监视的主节点是谁 # 多久认为挂了 sentinel down-after-milliseconds mymaster 30000 # 主节点Redis密码 sentinel auth-pass mymaster password # 如果要保证故障转移能成功执行,主从密码必须一致 # 主从替换是否成功时长 sentinel failover-timeout mymaster 180000 # 在进行故障转移的时候,多少个slave可以进行备份 sentinel parallel-syncs mymaster 1

然后通过 redis-sentinel + 配置文件的路径,即可开启多个Redis Sentinel

效果演示

在原先已经构建好的Redis主从架构下,故意将主机挂了的时候,Sentinel的日志文件将会出现一段新的记录:

log
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
# -sdown slave IP:Port IP Port @ mymaster IP port port:X 03 Feb 2024 16:39:35.317 # +sdown master mymaster IP Port port:X 03 Feb 2024 16:39:35.475 * Sentinel new configuration saved on disk port:X 03 Feb 2024 16:39:35.476 # +new-epoch 6 port:X 03 Feb 2024 16:39:35.480 * Sentinel new configuration saved on disk port:X 03 Feb 2024 16:39:35.480 # +vote-for-leader 5b7e0c340c0587afc06fa045634d7923d316bf66 6 port:X 03 Feb 2024 16:39:35.965 # +config-update-from sentinel 5b7e0c340c0587afc06fa045634d7923d316bf66 Ip port @ mymaster Ip port port:X 03 Feb 2024 16:39:35.965 # +switch-master mymaster Ip port Ip port port:X 03 Feb 2024 16:39:35.965 * +slave slave Ip:port Ip port @ mymaster Ip port port:X 03 Feb 2024 16:39:35.965 * +slave slave Ip:6379 Ip port @ mymaster Ip port port:X 03 Feb 2024 16:39:35.970 * Sentinel new configuration saved on disk

提示Redis主机从 Ip:port 变为了 Ipport,也就是在master主机发生了故障之后,Sentinel架构迅速进行了故障转移。

而转移结果则是将主从的redis.conf文件进行了一定程度的修正,首先是Ip port的redis.conf则会发生更改,去除了原先的主节点的信息,并且添加了一些持久化的配置

conf
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
#自身密码 requirepass "pasword" #主从结构配置 masterauth "pasword" #开启后台运行 daemonize yes #设置pid文件路径 pidfile "/usr/local/redis/logs/slaveof_redis_6380pid.log" #设置redis日志文件路径 logfile "/usr/local/redis/logs/slaveof_redis_6380.log" dir "/usr/local/redis/bin" # Generated by CONFIG REWRITE save 3600 1 save 300 100 save 60 10000 latency-tracking-info-percentiles 50 99 99.9 user default on sanitize-payload #193332a189d3b3e274a52afd292df5c1658430af341546a8e3f7f2d0048e027e ~* &* +@all

同样的所有的Sentinel Conf文件将会被重写

conf
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
# 端口号 port 26379 # 后台运行 daemonize yes # pid日志 pidfile "/usr/local/redis/logs/redis-sentinel-26379.pid" # log日志 logfile "/usr/local/redis/logs/redis-sentinel-26379.log" # 文件存储目录 dir "/tmp" # 主节点IP 端口号 哨兵数量 sentinel monitor mymaster Ip port 3 # 多久认为挂了 # 主节点Redis密码 sentinel auth-pass mymaster password # 主从替换是否成功时长 # 在进行故障转移的时候,多少个slave可以进行备份 # Generated by CONFIG REWRITE latency-tracking-info-percentiles 50 99 99.9 user default on nopass sanitize-payload ~* &* +@all sentinel myid 86e77e138660782f54563562d7c7c51d4e940978 sentinel config-epoch mymaster 6 sentinel leader-epoch mymaster 6 sentinel current-epoch 6 sentinel known-replica mymaster Ip port sentinel known-replica mymaster Ip port sentinel known-sentinel mymaster Ip port 5b7e0c340c0587afc06fa045634d7923d316bf66 sentinel known-sentinel mymaster Ip port a9a518015b6f711f6b2c6efdd3bbb8d32cc72ddc

而原本没有写功能的从机也会因为故障转移获得写入数据的权利(Redis原本是读写分离的)

(重新恢复原master主机之后,尝试执行CLI,结果是无法执行)

(而原先无法写入的从机,由于变为了master主机,反而变得可以写入数据了)

并且在新添加的数据也同步到了其他的Redis服务上

需要注意一点的是,Redis的master主机发生故障的时候,只有从机要变成主机的服务的Redis.conf会发生变化,原先挂掉的Redis.conf以及其他从机的Redis.conf都不会发生改变。

Java代码来链接Sentinel

Spring/原生Web

  1. 在 创建一个新的类 ReidsSentinelTest
  2. 构建JedisPoolConfig配置对象
  3. 创建一个HashSet,用来保存哨兵节点配置信息(记得一定要写端口号)
  4. 构建JedisSentinelPool连接池
  5. 使用sentinelPool连接池获取连接
java
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
package com.ydlclass.redis; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisSentinelPool; import java.util.HashSet; import java.util.Set; /** * @Created by IT李老师 * 公主号 “IT李哥交朋友” * 个人微 itlils */ public class ReidsSentinelTest { JedisSentinelPool jedisSentinelPool; //1. 在 创建一个新的类 ReidsSentinelTest //2. 构建JedisPoolConfig配置对象 //3. 创建一个HashSet,用来保存哨兵节点配置信息(记得一定要写端口号) //4. 构建JedisSentinelPool连接池 //5. 使用sentinelPool连接池获取连接 @BeforeTest public void beforeTest(){ //创建jedis连接池 JedisPoolConfig config=new JedisPoolConfig(); //最大空闲连接 config.setMaxIdle(10); //最小空闲连接 config.setMinIdle(5); //最大空闲时间 config.setMaxWaitMillis(3000); //最大连接数 config.setMaxTotal(50); Set<String> sentinels=new HashSet<>(); sentinels.add("192.168.200.131:26379"); sentinels.add("192.168.200.131:26380"); sentinels.add("192.168.200.131:26381"); jedisSentinelPool= new JedisSentinelPool("mymaster",sentinels,config); } @Test public void keysTest(){ Jedis jedis = jedisSentinelPool.getResource(); Set<String> keys = jedis.keys("*"); for (String key : keys) { System.out.println(key); } } @AfterTest public void afterTest(){ jedisSentinelPool.close(); } }

SpringBoot版

yaml
  • 01
  • 02
  • 03
  • 04
  • 05
spring: redis: sentinel: nodes: 192.168.200.131:26379,192.168.200.131:26380,192.168.200.131:26381 //哨兵的ip和端口 master: mymaster //这个就是哨兵配置文件中 sentinel monitor mymaster 192.168.200.131 6379 2 配置的mymaster

Redis cluster集群

现状问题:业务发展过程中遇到的峰值瓶颈

  • redis提供的服务OPS可以达到10w/s,当前的业务OPS已经达到10w/s
  • 内存单机容量达到256G,当前业务需求内存容量1T
  • 使用集群的方式可以快速的解决上述问题

集群简介

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

集群的作用:

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

Cluster集群结构设计

数据存储设计

  1. 通过算法设计,计算出key应该保存的位置

  2. 将所有的存储空间计划切割成16384份,每台主机保存一部分

    需要注意:每份代表的是一个存储空间,不是一个key的保存空间

  3. 将key按照计算出结果放到对应的存储空间

    那么redis的集群是如何增加可扩展性的方式是通过类似于分槽的方式,按照hash命中的方式来实现扩展的。

    • 各个数据库相互通信,保存各个库中槽的编号数据
    • 一次命中,直接返回
    • 一次未命中,告知具体位置

Cluster集群结构搭建

首先要明确的几个要点:

  • 配置服务器(3主3从)
  • 建立通信(Meet)
  • 分槽(Slot)
  • 搭建主从(master-slave)

1 创建6个redis单体服务 7001-7006。修改redis.conf

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
port 7001 bind 192.168.200.131 protected-mode no daemonize yes pidfile /var/run/redis_7001.pid logfile "/export/server/redis7001/log/redis.log" dir /export/server/redis7001/data/ appendonly yes cluster-enabled yes cluster-config-file nodes-7001.conf cluster-node-timeout 15000

2 让六台机器组成集群

  • 01
redis-cli --cluster create 192.168.200.131:7001 192.168.200.131:7002 192.168.200.131:7003 192.168.200.131:7004 192.168.200.131:7005 192.168.200.131:7006 --cluster-replicas 1

输入yes

客户端连接cluster

  • 01
./redis-cli -c -h 192.168.200.131 -p 7001

设置值,跳转到正确的服务器上。

Cluster配置

  • 是否启用cluster,加入cluster节点

    • 01
    cluster-enabled yes|no
  • cluster配置文件名,该文件属于自动生成,仅用于快速查找文件并查询文件内容

    • 01
    cluster-config-file filename
  • 节点服务响应超时时间,用于判定该节点是否下线或切换为从节点

    • 01
    cluster-node-timeout milliseconds
  • master连接的slave最小数量

    • 01
    cluster-migration-barrier min_slave_number

Cluster节点操作命令

  • 查看集群节点信息

    • 01
    cluster nodes
  • 更改slave指向新的master

    • 01
    cluster replicate master-id
  • 发现一个新节点,新增master

    • 01
    cluster meet ip:port
  • 忽略一个没有solt的节点

    • 01
    cluster forget server_id
  • 手动故障转移

    • 01
    cluster failover

集群操作命令:

  • 创建集群

    • 01
    redis-cli –-cluster create masterhost1:masterport1 masterhost2:masterport2 masterhost3:masterport3 [masterhostn:masterportn …] slavehost1:slaveport1 slavehost2:slaveport2 slavehost3:slaveport3 -–cluster-replicas n

    注意:master与slave的数量要匹配,一个master对应n个slave,由最后的参数n决定

    master与slave的匹配顺序为第一个master与前n个slave分为一组,形成主从结构

  • 添加master到当前集群中,连接时可以指定任意现有节点地址与端口

    • 01
    redis-cli --cluster add-node new-master-host:new-master-port now-host:now-port
  • 添加slave

    • 01
    redis-cli --cluster add-node new-slave-host:new-slave-port master-host:master-port --cluster-slave --cluster-master-id masterid
  • 删除节点,如果删除的节点是master,必须保障其中没有槽slot

    • 01
    redis-cli --cluster del-node del-slave-host:del-slave-port del-slave-id
  • 重新分槽,分槽是从具有槽的master中划分一部分给其他master,过程中不创建新的槽

    • 01
    redis-cli --cluster reshard new-master-host:new-master:port --cluster-from src- master-id1, src-master-id2, src-master-idn --cluster-to target-master-id -- cluster-slots slots

    注意:将需要参与分槽的所有masterid不分先后顺序添加到参数中,使用,分隔

    指定目标得到的槽的数量,所有的槽将平均从每个来源的master处获取

  • 重新分配槽,从具有槽的master中分配指定数量的槽到另一个master中,常用于清空指定master中的槽

    • 01
    redis-cli --cluster reshard src-master-host:src-master-port --cluster-from src- master-id --cluster-to target-master-id --cluster-slots slots --cluster-yes

Java操作

原生

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
package com.ydlclass.redis; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisPoolConfig; import java.io.IOException; import java.util.HashSet; import java.util.Set; /** * @Created by IT李老师 * 公主号 “IT李哥交朋友” * 个人微 itlils */ public class RedisClusterTest { JedisCluster jedisCluster; @BeforeTest public void beforeTest(){ //创建jedis连接池 JedisPoolConfig config=new JedisPoolConfig(); //最大空闲连接 config.setMaxIdle(10); //最小空闲连接 config.setMinIdle(5); //最大空闲时间 config.setMaxWaitMillis(3000); //最大连接数 config.setMaxTotal(50); Set<HostAndPort> nodes=new HashSet<>(); nodes.add(new HostAndPort("192.168.200.131", 7001)); nodes.add(new HostAndPort("192.168.200.131", 7002)); nodes.add(new HostAndPort("192.168.200.131", 7003)); nodes.add(new HostAndPort("192.168.200.131", 7004)); nodes.add(new HostAndPort("192.168.200.131", 7005)); nodes.add(new HostAndPort("192.168.200.131", 7006)); jedisCluster= new JedisCluster(nodes,config); } @Test public void addTest(){ jedisCluster.set("c", "d"); String str = jedisCluster.get("c"); System.out.println(str); } @AfterTest public void afterTest(){ try { jedisCluster.close(); } catch (IOException e) { e.printStackTrace(); } } }

SpringBoot配置文件

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
spring: redis: pool: max-idle: 100 min-idle: 1 max-active: 1000 max-wait: -1 cluster: nodes: - 192.168.200.131:7001 - 192.168.200.131:7002 - 192.168.200.131:7003 - 192.168.200.131:7004 - 192.168.200.131:7005 - 192.168.200.131:7006 database: 0 timeout: 15000 connect-timeout: 5000

Redis的命名规范

  • 使用统一的命名规范
    • 一般使用业务名(或数据名)为前缀,用冒号分隔,例如,业务名:表名id。
    • 例如:shop:usr:msg_code(电商:用户:验证码)
  • 控制key名称的长度,不要使用过长的key
    • 在保证语义清晰的情况下,尽量减少key的长度。有些常用单词可使用缩写,例如:user缩写为u,messages缩写为msg
  • 名称中不要包含特殊字符
    • 包含空格、单双引号以及其他转义字符

Redis性能监控工具和命令

redis中的监控指标如下:

  • 性能指标:Performance

    响应请求的平均时间:

    properties
    • 01
    latency

    平均每秒处理请求总数

    properties
    • 01
    instantaneous_ops_per_sec

    缓存查询命中率(通过查询总次数与查询得到非nil数据总次数计算而来)

    properties
    • 01
    hit_rate(calculated)
  • 内存指标:Memory

    当前内存使用量

    properties
    • 01
    used_memory

    内存碎片率(关系到是否进行碎片整理)

    properties
    • 01
    mem_fragmentation_ratio

    为避免内存溢出删除的key的总数量

    properties
    • 01
    evicted_keys

    基于阻塞操作(BLPOP等)影响的客户端数量

    properties
    • 01
    blocked_clients
  • 基本活动指标:Basic_activity

    当前客户端连接总数

    properties
    • 01
    connected_clients

    当前连接slave总数

    properties
    • 01
    connected_slaves

    最后一次主从信息交换距现在的秒

    properties
    • 01
    master_last_io_seconds_ago

    key的总数

    properties
    • 01
    keyspace
  • 持久性指标:Persistence

    当前服务器最后一次RDB持久化的时间

    properties
    • 01
    rdb_last_save_time

    当前服务器最后一次RDB持久化后数据变化总量

    properties
    • 01
    rdb_changes_since_last_save
  • 错误指标:Error

    被拒绝连接的客户端总数(基于达到最大连接值的因素)

    properties
    • 01
    rejected_connections

    key未命中的总次数

    properties
    • 01
    keyspace_misses

    主从断开的秒数

    properties
    • 01
    master_link_down_since_seconds

要对redis的相关指标进行监控,我们可以采用一些用具:

  • CloudInsight Redis
  • Prometheus
  • Redis-stat
  • Redis-faina
  • RedisLive
  • zabbix

也有一些命令工具:

  • benchmark

    测试当前服务器的并发性能

    properties
    • 01
    redis-benchmark [-h ] [-p ] [-c ] [-n <requests]> [-k ]

    范例1:50个连接,10000次请求对应的性能

    properties
    • 01
    redis-benchmark

    范例2:100个连接,5000次请求对应的性能

    properties
    • 01
    redis-benchmark -c 100 -n 5000
  • redis-cli

    monitor:启动服务器调试信息

    • 01
    monitor

    slowlog:慢日志

    获取慢查询日志

    properties
    • 01
    slowlog [operator]

    get :获取慢查询日志信息

    len :获取慢查询日志条目数

    reset :重置慢查询日志

    相关配置

    properties
    • 01
    • 02
    slowlog-log-slower-than 1000 #设置慢查询的时间下线,单位:微妙 slowlog-max-len 100 #设置慢查询命令对应的日志显示长度,单位:命令数