当前位置:   article > 正文

Redis超全精讲!_redis 精讲

redis 精讲

目录


课程目标

  • 能够掌握Redis不同数据类型操作
  • 能够使用Java API操作Redis
  • 能够理解Redis的两种持久化方式
  • 能够理解Redis的主从复制架构
  • 能够理解Redis的Sentinel架构
  • 能够理解Redis集群架构

1.NoSQL介绍

1.1 什么是NoSQL

  • NoSQL最常见的解释是“non-relational”, 很多人也说它是“Not Only SQL”
  • NoSQL仅仅是一个概念,泛指非关系型的数据库
  • 区别于关系数据库,它们不保证关系数据的ACID特性
  • NoSQL是一项全新的数据库革命性运动,提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入

1.2 NoSQL的特点

1.2.1 应用场景
  • 高并发的读写
  • 海量数据读写
  • 高可扩展性
  • 速度快
1.2.2 不适用场景
  • 需要事务支持
  • 基于sql的结构化查询存储,处理复杂的关系,需要即席查询(用户自定义查询条件的查询)

1.3 NoSQL数据库

1.3.1 memcache
  • 很早出现的NoSql数据库
  • 数据都在内存中,一般不持久化
  • 支持简单的key-value模式
  • 一般是作为缓存数据库辅助持久化的数据库
1.3.2 redis介绍
  • 几乎覆盖了Memcached的绝大部分功能
  • 数据都在内存中,支持持久化,主要用作备份恢复
  • 除了支持简单的key-value模式,还支持多种数据结构的存储,比如 list、set、hash、zset等。
  • 一般是作为缓存数据库辅助持久化的数据库
  • 现在市面上用得非常多的一款内存数据库
1.3.3 mongoDB介绍
  • 高性能、开源、模式自由(schema free)的文档型数据库
  • 数据都在内存中, 如果内存不足,把不常用的数据保存到硬盘
  • 虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能
  • 支持二进制数据及大型对象
  • 可以根据数据的特点替代RDBMS ,成为独立的数据库。或者配合RDBMS,存储特定的数据。
1.3.4 列式存储HBase介绍

HBase是Hadoop项目中的数据库。它用于需要对大量的数据进行随机、实时读写操作的场景中。HBase的目标就是处理数据量非常庞大的表,可以用普通的计算机处理超过10亿行数据,还可处理有数百万列元素的数据表。

2. Redis介绍

redis官网地址:
https://redis.io/
中文网站
http://www.redis.cn/

2.1 Redis的基本介绍

  • Redis是当前比较热门的NoSQL系统之一
  • 它是一个开源的、使用ANSI C语言编写的key-value存储系统(区别于MySQL的二维表格形式存储)
  • 和Memcache类似,但很大程度补偿了Memcache的不足,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失

2.2 Redis的应用场景

  • 计数器
  • TopN、排行榜(微博的热搜榜、热门话题、抖音直播间的热门直播间、淘宝电商的排行榜)
  • 去重的计数
  • 实时系统,用于存储一些规则
  • 定时过期的一些应用(短信验证码)
  • 缓存(保护数据库不被高并发压垮)
2.2.1 取最新N个数据的操作

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

2.2.2 排行榜应用,取TOP-N操作

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

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

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

2.2.4 计数器应用

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

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

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

2.2.6 实时系统,反垃圾系统

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

2.2.7 缓存

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

2.3 Redis的特点

  • 高效性

    • 速度非常快,单机能够支持的并发、读写的速度达10W以上(Kafka更快——80W-150W)
  • 原子性

    • Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
  • 支持多种数据结构类型,操作非常灵活

    • string(字符串)
    • list(列表)
    • hash(哈希)
    • set(集合)
    • zset(有序集合)
  • 稳定性:持久化,主从复制(集群)

  • 其他特性:支持过期时间,支持事务,消息订阅。

3. Redis单机环境安装(Linux)

3.1 下载redis安装包

bigdata-pro-m07服务器执行以下命令下载redis安装包

cd /opt/software
wget http://download.redis.io/releases/redis-3.2.8.tar.gz
chmod u+x redis-3.2.8.tar.gz
  • 1
  • 2
  • 3

3.2 解压redis压缩包到指定目录

tar -zxvf redis-3.2.8.tar.gz -C /opt/modules/
  • 1

3.3 安装C程序运行环境

yum -y install gcc-c++
  • 1

3.4 安装较新版本的tcl

3.4.1 使用压缩包进行安装
cd /opt/software
wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz

chmod u+x tcl8.6.1-src.tar.gz

# 解压tcl
tar -zxvf tcl8.6.1-src.tar.gz -C /opt/modules/

# 进入指定目录
cd /opt/modules/tcl8.6.1/unix

# 进入root
su
      
./configure
make  && make  install
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
3.4.2 在线安装tcl(推荐)
yum  -y  install  tcl
  • 1

3.5 编译redis

cd /opt/modules/redis-3.2.8
#或者使用命令  make  进行编译
make MALLOC=libc  
make test && make install PREFIX=/opt/modules/redis-3.2.8
  • 1
  • 2
  • 3
  • 4
3.5.1 修改redis配置文件
cd /opt/modules/redis-3.2.8
mkdir log
mkdir data

vim redis.conf
# 修改第61行
bind node1.itcast.cn
# 修改第128行
daemonize yes
# 修改第163行
logfile "/opt/modules/redis-3.2.8/log/redis.log"
# 修改第247行
dir /opt/modules/redis-3.2.8/data
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
3.5.2 启动redis
cd  /opt/modules/redis-3.2.8/
bin/redis-server  redis.conf
  • 1
  • 2
3.5.3 关闭redis
bin/redis-cli -h bigdata-pro-m07 shutdown
  • 1

注意:

  • 在生产环境,关闭redis的时候,不用使用 kill -9,应该使用redis-cli -h 主机名 -p 端口 shutdown
  • 因为如果直接kill -9,可能redis的一些数据会丢失
3.5.4 连接redis客户端
bin/redis-cli -h bigdata-pro-m07
  • 1

在这里插入图片描述

4. Redis的数据类型

redis当中一共支持五种数据类型,分别是:

  • string字符串
  • list列表
  • set集合
  • zset有序集合

通过这五种不同的数据类型,可以实现各种不同的功能,也可以应用在各种不同的场景。
在这里插入图片描述

Redis当中各种数据类型结构如上图:
Redis当中各种数据类型的操作
https://www.runoob.com/redis/redis-keys.html

4.1 对字符串string的操作

下表列出了常用的 redis 字符串命令

命令及描述示例
SET key value 设置指定 key 的值示例:SET hello world
GET key 获取指定 key 的值示例:GET hello
GETSET key value将给定 key 的值设为 value ,并返回 key 的旧值(old value)示例:GETSET hello world2
MGET key1 [key2…]获取所有(一个或多个)给定 key 的值示例:MGET hello world
SETEX key seconds value将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)示例:SETEX hello 10 world3
SETNX key value只有在 key 不存在时设置 key 的值示例:SETNX itcast redisvalue
STRLEN key返回 key 所储存的字符串值的长度示例:STRLEN itcast
MSET key value [key value …]同时设置一个或多个 key-value 对示例:MSET itcast2 itcastvalue2 itcast3 itcastvalue3
MSETNX key value [key value …] 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在示例:MSETNX itcast4 itcastvalue4 itcast5 itcastvalue5
PSETEX key milliseconds value 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。示例:PSETEX itcast6 6000 itcast6value
INCR key 将 key 中储存的数字值增一。示例:SET itcast7 1 、INCR itcast7 、GET itcast7
INCRBY key increment将 key 所储存的值加上给定的增量值(increment)示例:INCRBY itcast7 2、GET itcast7
INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment)示例:INCRBYFLOAT itcast7 0.8
DECR key 将 key 中储存的数字值减一。示例:SET itcast8 1、DECR itcast8、GET itcast8
DECRBY key decrement key 所储存的值减去给定的减量值(decrement)示例:DECRBY itcast8 3
APPEND key value如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。示例:APPEND itcast8 hello

注意:

  • 在执行一些累加器的操作时,千万不能使用 set/get来操作
  • 要使用INCR/DESC/INCRBY

在这里插入图片描述

4.2 对hash列表的操作

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Redis 中每个 hash 可以存储 2的32次方 - 1 键值对(40多亿)

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

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

4.3 对list列表的操作

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 2的32次方 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
下表列出了列表相关的基本命令:

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

4.4 对set集合的操作

  • Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据
  • Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
  • 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

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

命令及描述示例
SADD key member1 [member2] 向集合添加一个或多个成员示例:SADD set1 setvalue1 setvalue2
SMEMBERS key 返回集合中的所有成员示例:SMEMBERS set1
SCARD key 获取集合的成员数示例:SCARD set1
SDIFF key1 [key2] 返回给定所有集合的差集示例:SADD set2 setvalue2 setvalue3、SDIFF set1 set2
SDIFFSTORE destination key1 [key2] 返回给定所有集合的差集并存储在 destination 中示例:SDIFFSTORE set3 set1 set2
SINTER key1 [key2] 返回给定所有集合的交集示例:SINTER set1 set2
SINTERSTORE destination key1 [key2] 返回给定所有集合的交集并存储在 destination 中示例:SINTERSTORE set4 set1 set2
SISMEMBER key member 判断 member 元素是否是集合 key 的成员示例:SISMEMBER set1 setvalue1
SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合示例:SMOVE set1 set2 setvalue1
SPOP key 移除并返回集合中的一个随机元素示例:SPOP set2
SRANDMEMBER key [count] 返回集合中一个或多个随机数示例:SRANDMEMBER set2 2
SREM key member1 [member2] 移除集合中一个或多个成员示例:SREM set2 setvalue1
SUNION key1 [key2] 返回所有给定集合的并集示例:SUNION set1 set2
SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中示例:SUNIONSTORE set5 set1 set2

4.5 对key的操作

下表给出了与 Redis 键相关的基本命令:

命令及描述示例
DEL key 该命令用于在 key 存在时删除 key。示例:del itcast5
DUMP key
序列化给定 key ,并返回被序列化的值。示例:DUMP key1
EXISTS key 检查给定 key 是否存在。示例:exists itcast
EXPIRE key seconds 为给定 key 设置过期时间,以秒计。示例:expire itcast 5
PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计。示例:PEXPIRE set3 3000
KEYS pattern 查找所有符合给定模式( pattern)的 key示例:keys *
PERSIST key 移除 key 的过期时间,key 将持久保持。示例:persist set2
PTTL key 以毫秒为单位返回 key 的剩余的过期时间。示例:pttl set2
TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。示例:ttl set2
RANDOMKEY 从当前数据库中随机返回一个 key 。示例: randomkey
RENAME key newkey 修改 key 的名称示例:rename set5 set8
RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey 。示例:renamenx set8 set10
TYPE key 返回 key 所储存的值的类型。示例:type set10

4.6 对ZSet的操作

  • Redis有序集合和集合一样也是string类型元素的集合,且不允许重复的成员
  • 它用来保存需要排序的数据,例如排行榜,一个班的语文成绩,一个公司的员工工资,一个论坛的帖子等。
  • 有序集合中,每个元素都带有score(权重),以此来对元素进行排序
  • 它有三个元素:key、member和score。以语文成绩为例,key是考试名称(期中考试、期末考试等),member是学生名字,score是成绩。

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

命令及描述示例
ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数向ZSet中添加页面的PV值 ZADD pv_zset 120 page1.html 100 page2.html 140 page3.html
ZCARD key 获取有序集合的成员数获取所有的统计PV页面数量 ZCARD pv_zset
ZCOUNT key min max 计算在有序集合中指定区间分数的成员数获取PV在120-140在之间的页面数量 ZCOUNT pv_zset 120 140
ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment给page1.html的PV值+1 ZINCRBY pv_zset 1 page1.html
ZINTERSTORE destination numkeys key [key …] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中创建两个保存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
ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合指定区间内的成员获取所有的元素,并可以返回每个key对一个的score ZRANGE pv_zset_result 0 -1 WITHSCORES
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员获取ZSET中120-140之间的所有元素 ZRANGEBYSCORE pv_zset 120 140
ZRANK key member 返回有序集合中指定成员的索引获取page1.html的pv排名(升序) ZRANK pv_zset page3.html
ZREM key member [member …] 移除有序集合中的一个或多个成员移除page1.html ZREM pv_zset page1.html
ZREVRANGE key start stop [WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到低按照PV降序获取页面 ZREVRANGE pv_zset 0 -1
ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序获取page2.html的pv排名(降序) ZREVRANK pv_zset page2.html
ZSCORE key member 返回有序集中,成员的分数值获取page3.html的分数值 ZSCORE pv_zset page3.html

4.7 对位图BitMaps的操作

  • 计算机最小的存储单位是位bit,Bitmaps是针对位的操作的,相较于String、Hash、Set等存储方式更加节省空间
  • Bitmaps不是一种数据结构,操作是基于String结构的,一个String最大可以存储512M,那么一个Bitmaps则可以设置2^32个位
  • Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量

在这里插入图片描述

  • BitMaps 命令说明:将每个独立用户是否访问过网站存放在Bitmaps中, 将访问的用户记做1, 没有访问的用户记做0, 用偏移量作为用户的id 。
4.7.1 设置值
SETBIT key offset value
  • 1

setbit命令设置的vlaue只能是0或1两个值

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

在这里插入图片描述

  • 具体操作过程如下, unique:users:2016-04-05代表2016-04-05这天的独立访问用户的Bitmaps
setbit unique:users:2016-04-05 0 1
setbit unique:users:2016-04-05 5 1
setbit unique:users:2016-04-05 11 1
setbit unique:users:2016-04-05 15 1
setbit unique:users:2016-04-05 19 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 很多应用的用户id以一个指定数字(例如10000) 开头, 直接将用户id和Bitmaps的偏移量对应势必会造成一定的浪费, 通常的做法是每次做setbit操作时将用户id减去这个指定数字。
  • 在第一次初始化Bitmaps时, 假如偏移量非常大, 那么整个初始化过程执行会比较慢, 可能会造成Redis的阻塞。
4.7.2 获取值
GETBIT key offset
  • 1
  • 获取键的第offset位的值(从0开始算),例:下面操作获取id=8的用户是否在2016-04-05这天访问过, 返回0说明没有访问过
getbit unique:users:2016-04-05 8
  • 1
4.7.3 获取Bitmaps指定范围值为1的个数
BITCOUNT key [start end]
  • 1

例:下面操作计算2016-04-05这天的独立访问用户数量:

bitcount unique:users:2016-04-05
  • 1
4.7.4 Bitmaps间的运算
BITOP operation destkey key [key, …]
  • 1
  • bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。 假设2016-04-04访问网站的userid=1, 2, 5, 9, 如图3-13所示:

在这里插入图片描述

setbit unique:users:2016-04-04 1 1
setbit unique:users:2016-04-04 2 1
setbit unique:users:2016-04-04 5 1
setbit unique:users:2016-04-04 9 1
  • 1
  • 2
  • 3
  • 4

例1:下面操作计算出2016-04-04和2016-04-05两天都访问过网站的用户数量, 如下所示。

bitop and unique:users:and:2016-04-04_05 unique:users:2016-04-04 unique:users:2016-04-05
bitcount unique:users:2016-04-04_05
  • 1
  • 2

例2:如果想算出2016-04-04和2016-04-03任意一天都访问过网站的用户数量(例如月活跃就是类似这种) , 可以使用or求并集, 具体命令如下:

bitop or unique:users:or:2016-04-04_05 unique:users:2016-04-04 unique:users:2016-04-05
bitcount unique:users:or:2016-04-04_05
  • 1
  • 2

在这里插入图片描述

4.8 对HyperLogLog结构的操作

4.8.1 应用场景

HyperLogLog常用于大数据量的统计,比如页面访问量统计或者用户访问量统计。

要统计一个页面的访问量(PV),可以直接用redis计数器或者直接存数据库都可以实现,如果要统计一个页面的用户访问量(UV),一个用户一天内如果访问多次的话,也只能算一次,这样,我们可以使用SET集合来做,因为SET集合是有去重功能的,key存储页面对应的关键字,value存储对应的userid,这种方法是可行的。但如果访问量较多,假如有几千万的访问量,这就麻烦了。为了统计访问量,要频繁创建SET集合对象。

Redis实现HyperLogLog算法,HyperLogLog 这个数据结构的发明人 是Philippe Flajolet(菲利普·弗拉若莱)教授。Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

4.8.2 UV计算示例
bigdata-pro-m07:6379> help @hyperloglog

  PFADD key element [element ...]
  summary: Adds the specified elements to the specified HyperLogLog.
  since: 2.8.9

  PFCOUNT key [key ...]
  summary: Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).
  since: 2.8.9

  PFMERGE destkey sourcekey [sourcekey ...]
  summary: Merge N different HyperLogLogs into a single one.
  since: 2.8.9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Redis集成的HyperLogLog使用语法主要有pfadd和pfcount,顾名思义,一个是来添加数据,一个是来统计的。为什么用pf?是因为HyperLogLog 这个数据结构的发明人 是Philippe Flajolet教授 ,所以用发明人的英文缩写,这样容易记住这个语法了。

下面我们通过一个示例,来演示如何计算uv。

bigdata-pro-m07:6379> PFADD uv1 user1
(integer) 1
bigdata-pro-m07:6379> pfcount uv1
(integer) 1
bigdata-pro-m07:6379> pfadd uv1 user2
(integer) 1
bigdata-pro-m07:6379> pfcount uv1
(integer) 2
bigdata-pro-m07:6379> pfadd uv1 user3
(integer) 1
bigdata-pro-m07:6379> pfcount uv1
(integer) 3
bigdata-pro-m07:6379> pfadd uv1 user4
(integer) 1
bigdata-pro-m07:6379> pfcount uv1
(integer) 4
bigdata-pro-m07:6379> pfadd uv1 user5 user6 user7 user8 user9 user10
(integer) 1
bigdata-pro-m07:6379> pfcount uv1
(integer) 10
bigdata-pro-m07:6379> pfadd uv1 user5 user6 user7 user8 user9 user1
(integer) 0
bigdata-pro-m07:6379> pfcount uv1
(integer) 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

HyperLogLog算法一开始就是为了大数据量的统计而发明的,所以很适合那种数据量很大,然后又没要求不能有一点误差的计算,HyperLogLog 提供不精确的去重计数方案,虽然不精确但是也不是非常不精确,标准误差是 0.81%,不过这对于页面用户访问量是没影响的,因为这种统计可能是访问量非常巨大,但是又没必要做到绝对准确,访问量对准确率要求没那么高,但是性能存储方面要求就比较高了,而HyperLogLog正好符合这种要求,不会占用太多存储空间,同时性能不错

pfadd和pfcount常用于统计,需求:假如两个页面很相近,现在想统计这两个页面的用户访问量呢?这里就可以用pfmerge合并统计了,语法如例子:

bigdata-pro-m07:6379> pfadd page1 user1 user2 user3 user4 user5
(integer) 1
bigdata-pro-m07:6379> pfadd page2 user1 user2 user3 user6 user7
(integer) 1
bigdata-pro-m07:6379> pfmerge page1+page2 page1 page2
OK
bigdata-pro-m07:6379> pfcount page1+page2
(integer) 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
4.8.3 HyperLogLog为什么适合做大量数据的统计
  • Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
  • 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
  • 但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数?
比如:数据集{1, 3, 5, 7, 5, 7, 8},那么这个数据集的基数集{1, 3, 5, 7, 8},基数(不重复元素)为5。基数估计就是在误差可接受的范围内,快速计算基数。

5. Redis Java API操作

Redis不仅可以通过命令行进行操作,也可以通过JavaAPI操作,通过使用Java API来对Redis数据库中的各种数据类型操作。

离线架构和实时架构流程:
在这里插入图片描述

5.1 创建maven工程并导入依赖

5.1.1 创建Maven工程
groupIdcn.itcast
artifactIdredis_op
5.1.2 导入POM依赖
<dependencies>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.14.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
                <!--    <verbal>true</verbal>-->
            </configuration>
        </plugin>
    </plugins>
</build>
  • 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
  • 30
  • 31
  • 32
  • 33
  • 34

5.2 创建包结构和类

  1. 在test目录创建 cn.itcast.redis.api_test 包结构
  2. 创建RedisTest类

5.3 连接以及关闭redis客户端

因为后续测试都需要用到Redis连接,所以,我们先创建一个JedisPool用于获取Redis连接。此处,我们基于TestNG来测试各类的API。使用@BeforeTest在执行测试用例前,创建Redis连接池。使用@AfterTest在执行测试用例后,关闭连接池。

实现步骤:

  1. 创建JedisPoolConfig配置对象,指定最大空闲连接为10个、最大等待时间为3000毫秒、最大连接数为50、最小空闲连接5个
  2. 创建JedisPool
  3. 使用@Test注解,编写测试用例,查看Redis中所有的key
    a) 从Redis连接池获取Redis连接
    b) 调用keys方法获取所有的key
    c) 遍历打印所有key
package cn.itcast.redis.api_test;

import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.List;
import java.util.Set;

/**
 * @author :caizhengjie
 * @description:
 * 1. 创建JedisPoolConfig配置对象,指定最大空闲连接为10个、最大等待时间为3000毫秒、最大连接数为50、最小空闲连接5个
 * 2.	创建JedisPool
 * 3.	使用@Test注解,编写测试用例,查看Redis中所有的key
 * a)	从Redis连接池获取Redis连接
 * b)	调用keys方法获取所有的key
 * c)	遍历打印所有key
 * @date :2021/1/26 11:28 上午
 */
public class RedisTest {

    private JedisPool jedisPool;

    @BeforeTest
    public void redisConnectionPool(){

        // 创建JedisPoolConfig配置对象
        JedisPoolConfig config = new JedisPoolConfig();
        // 指定最大空闲连接为10个
        config.setMaxIdle(10);
        // 最小空闲连接5个
        config.setMinIdle(5);
        // 最大等待时间为3000毫秒
        config.setMaxWaitMillis(3000);
        // 最大连接数为50
        config.setMaxTotal(50);

        jedisPool = new JedisPool(config,"bigdata-pro-m07");

    }

    @Test
    public void keysTest(){
        // 从redis连接池获取redis连接
        Jedis jedis = jedisPool.getResource();
        // 调用keys方法获取所有的key
        Set<String> keySet = jedis.keys("*");

        for (String key : keySet){
            System.out.println(key);
        }
    }

    @AfterTest
    public void afterTest(){

        // 关闭连接池
        jedisPool.close();

    }
}
  • 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
  • 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
  • 64
  • 65

注意:

  • 操作Redis一般要使用Jedis的连接池,这样可以有效的复用连接资源
  • 在IDEA中,有时候提示可能不完整,其实Jedis连接池,可以指定端口号

5.4 操作string类型数据

  1. 添加一个string类型数据,key为pv,用于保存pv的值,初始值为0
  2. 查询该key对应的数据
  3. 修改pv为1000
  4. 实现整形数据原子自增操作 +1
  5. 实现整形该数据原子自增操作 +1000
/**
 * 操作string类型数据
 */
@Test
public void stringTest(){
    // 从redis连接池获取redis连接
    Jedis jedis = jedisPool.getResource();

    // 1.添加一个string数据类型,key为pv,用于保存pv的值,初始值为0
    jedis.set("pv","0");

    // 2.查询key对应的数据
    System.out.println("pv:"+jedis.get("pv"));

    // 3.修改pv为1000
    jedis.set("pv","1000");

    // 4.实现整形数据原子自增操作+1
    jedis.incr("pv");

    // 5.实现整形数据原子自增操作+1000
    jedis.incrBy("pv",1000);

    System.out.println(jedis.get("pv"));

}
  • 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

注意:

  • Redis操作string其实和SHELL命令是一样
  • 将来在编写Flink程序/Spark Streaming程序操作Redis的时候,注意操作完Redis之后,执行close,将连接返回到连接池。

5.5 操作hash列表类型数据

  1. 往Hash结构中添加以下商品库存
    a) iphone11 => 10000
    b) macbookpro => 9000
  2. 获取Hash中所有的商品
  3. 新增3000个macbookpro库存
  4. 删除整个Hash的数据
/**
 * 操作hash列表类型数据
 */
@Test
public void hashTest(){
    // 从redis连接池获取redis连接
    Jedis jedis = jedisPool.getResource();

    // 1.往Hash结构中添加以下商品库存
    // (a)iPhone11 => 10000
    // (b)MacBookPro => 9000
    jedis.hset("goods","iPhone11","10000");
    jedis.hset("goods","MacBookPro","9000");

    // 2.获取Hash中所有的商品
    Set<String> goodSet = jedis.hkeys("goods");
    System.out.println("所有商品:");
    for (String good : goodSet) {
        System.out.println(good);
    }

    // 3.新增3000个MacBookPro库存
//        String storeMacBook = jedis.hget("goods","MacBookPro");
//        long longStore = Long.parseLong(storeMacBook);
//        long addStore = longStore + 3000;
//        jedis.hset("goods","MacBookPro",addStore + "");
    jedis.hincrBy("goods","MacBookPro",3000);

    // 4.删除整个Hash的数据
    jedis.del("goods");

    jedis.close();

}
  • 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
  • 30
  • 31
  • 32
  • 33
  • 34

注意:

  • 当我们后续在编写Flink、Spark Streaming流处理程序使用Java操作Redis时候,涉及到一些数字的累加
  • 一定要使用incr、hincrBy

5.6 操作list类型数据

  1. 向list的左边插入以下三个手机号码:18511310001、18912301231、18123123312
  2. 从右边移除一个手机号码
  3. 获取list所有的值
    /**
     * 操作list类型数据
     */
    @Test
    public void listTest(){
        // 从redis连接池获取redis连接
        Jedis jedis = jedisPool.getResource();

        // 1.向list的左边插入以下三个手机号:18511310001、18511310002、18511310003
        jedis.lpush("tel_list","18511310001","18511310002","18511310003");

        // 2.从右面移除一个手机号码
        jedis.rpop("tel_list");

        // 3.获取list所有的值
        List<String> telList = jedis.lrange("tel_list",0,-1);
        for (String tel : telList) {
            System.out.println(tel);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

注意:

  • List可以用来存储重复的元素,而且是有序的
  • 获取所有的元素,lrange(key, 0, -1)

5.7 操作set类型的数据

使用set来保存uv值,为了方便计算,将用户名保存到uv中。

  1. 往一个set中添加页面 page1 的uv,用户user1访问一次该页面
  2. user2访问一次该页面
  3. user1再次访问一次该页面
  4. 最后获取 page1的uv值
    /**
     * 操作set类型的数据
     */
    @Test
    public void setTest(){
        // 从redis连接池获取redis连接
        Jedis jedis = jedisPool.getResource();

        // 求UV就是求独立有多少个不重复
        // 1.往一个set中添加页面page1的uv,用户user1访问一次该页面
        jedis.sadd("uv","user1");
        // 2.user2访问一次该页面
        jedis.sadd("uv","user2");
        // 3.user1访问一次该页面
        jedis.sadd("uv","user1");

        // 最后获取page1的uv值
        System.out.println("uv:" + jedis.scard("uv"));

        jedis.close();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

注意:

  • 计算UV主要是去重
  • 将来所有的一些要求高效率去重的业务场景,都可以使用Set操作

6. Redis的持久化

由于redis是一个内存数据库,所有的数据都是保存在内存当中的,内存当中的数据极易丢失,所以redis的数据持久化就显得尤为重要,在redis当中,提供了两种数据持久化的方式,分别为RDB以及AOF,且Redis默认开启的数据持久化方式为RDB方式。

6.1 RDB持久化方案

6.1.1 介绍

Redis会定期保存数据快照至一个rdb文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。可以在配置文件中配置Redis进行快照保存的时机:

save [seconds] [changes]
  • 1

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

save 60 100
  • 1

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

  • SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
  • BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求。
6.1.2 RDB方案优点
  1. 对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
  2. 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。
  3. 使用RDB文件进行数据恢复比使用AOF要快很多
6.1.3 RDB方案缺点
  1. 快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据
  2. 如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力
6.1.4 RDB配置
  1. 修改redis的配置文件
cd /opt/modules/redis-3.2.8
vim redis.conf
# 第202行
save 900 1
save 300 10
save 60 10000
save 5 1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

  1. 重新启动redis服务
    每次生成新的dump.rdb都会覆盖掉之前的老的快照
ps -ef | grep redis
bin/redis-cli -h bigdata-pro-m07 shutdown
bin/redis-server redis.conf
  • 1
  • 2
  • 3

6.2 AOF持久化方案

6.2.1 介绍

采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。

6.2.2 开启AOF

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

# 第594行
appendonly yes
  • 1
  • 2
6.2.3 配置AOF

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

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

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

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

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
  • 1
  • 2
  • Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite
  • auto-aof-rewrite-min-size最开始的AOF文件必须要触发这个文件才触发,后面的每次重写就不会根据这个变量了。该变量仅初始化启动Redis有效。
6.2.5 AOF优点
  1. 最安全,在启用appendfsync为always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据
  2. AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复
  3. AOF文件易读,可修改,在进行某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。
6.2.6 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的策略配置

6.3 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。

在这里插入图片描述

7. Redis 高级使用

7.1 Redis 事务

7.1.1 Redis事务简介

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

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

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

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

Redis不保证原子性:

  • Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
    一个事务从开始到执行会经历以下三个阶段:
    • 第一阶段:开始事务
    • 第二阶段:命令入队
    • 第三阶段、执行事务

Redis事务相关命令:

  • MULTI:开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令队列
  • EXEC:执行事务中的所有操作命令
  • DISCARD:取消事务,放弃执行事务块中的所有命令
  • WATCH:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令
  • UNWATCH:取消WATCH对所有key的监视
7.1.2 Redis事务演示
  1. MULTI开始一个事务:给k1、k2分别赋值,在事务中修改k1、k2,执行事务后,查看k1、k2值都被修改。
bigdata-pro-m07:6379> set k1 v1
OK
bigdata-pro-m07:6379> set k2 v2
OK
bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k1 11
QUEUED
bigdata-pro-m07:6379> set k2 22
QUEUED
bigdata-pro-m07:6379> exec
1) OK
2) OK
bigdata-pro-m07:6379> get k1
"11"
bigdata-pro-m07:6379> get k2
"22"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  1. 事务失败处理:语法错误(编译器错误),在开启事务后,修改k1值为11,k2值为22,但k2语法错误,最终导致事务提交失败,k1、k2保留原值。
bigdata-pro-m07:6379> flushdb
OK
bigdata-pro-m07:6379> keys *
(empty list or set)
bigdata-pro-m07:6379> set k1 v1
OK
bigdata-pro-m07:6379> set k2 v2
OK
bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k1 11
QUEUED
bigdata-pro-m07:6379> sets k2 22
(error) ERR unknown command 'sets'
bigdata-pro-m07:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
bigdata-pro-m07:6379> get k1
"v1"
bigdata-pro-m07:6379> get k2
"v2"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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

bigdata-pro-m07:6379> flushdb
OK
bigdata-pro-m07:6379> keys *
(empty list or set)
bigdata-pro-m07:6379> set k1 v1
OK
bigdata-pro-m07:6379> set k2 v2
OK
bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k1 11
QUEUED
bigdata-pro-m07:6379> lpush k2 22
QUEUED
bigdata-pro-m07:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
bigdata-pro-m07:6379> get k1
"11"
bigdata-pro-m07:6379> get k2
"v2"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

DISCARD取消事务

bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k6 v6
QUEUED
bigdata-pro-m07:6379> set k7 v7
QUEUED
bigdata-pro-m07:6379> discard
OK
bigdata-pro-m07:6379> get k6
(nil)
bigdata-pro-m07:6379> get k7
(nil)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
7.1.3 为什么Redis不支持事务回滚?

多数事务失败是由语法错误或者数据结构类型错误导致的,语法错误说明在命令入队前就进行检测的,而类型错误是在执行时检测的,Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。Redis之所以保持这样简易的事务,完全是为了保证高并发下的核心问题——性能。

7.2 Redis 过期策略

Redis是key-value数据库,可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
过期策略通常有以下三种:

  • 定时过期
    每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期
    只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。 极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期
    每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

7.3 内存淘汰策略

Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据,在Redis的配置文件中描述如下:

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#最大内存策略:当到达最大使用内存时,你可以在下面5种行为中选择,Redis如何选择淘汰数据库键
#当内存不足以容纳新写入数据时

# volatile-lru -> remove the key with an expire set using an LRU algorithm
# volatile-lru :在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把 redis 既当缓存,又做持久化存储的时候才用。

# allkeys-lru -> remove any key according to the LRU algorithm
# allkeys-lru : 移除最近最少使用的key (推荐)

# volatile-random -> remove a random key with an expire set
# volatile-random : 在设置了过期时间的键空间中,随机移除一个键,不推荐

# allkeys-random -> remove a random key, any key
# allkeys-random : 直接在键空间中随机移除一个键,弄啥叻

# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# volatile-ttl : 在设置了过期时间的键空间中,有更早过期时间的key优先移除 不推荐

# noeviction -> don't expire at all, just return an error on write operations
# noeviction : 不做过键处理,只返回一个写操作错误。 不推荐

# Note: with any of the above policies, Redis will return an error on write
#       operations, when there are no suitable keys for eviction.
# 上面所有的策略下,在没有合适的淘汰删除的键时,执行写操作时,Redis 会返回一个错误。下面是写入命令:
#       At the date of writing these commands are: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
# 过期策略默认是:
# The default is:
# maxmemory-policy noeviction
  • 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
  • 30
  • 31
  • 32
  • 33
  • 34

实际项目中设置内存淘汰策略:maxmemory-policy allkeys-lru,移除最近最少使用的key。

8. Redis的主从复制架构

8.1 简介

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。

在这里插入图片描述
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

8.1.1 一主一从

如下图所示左边是Master节点,右边是slave节点,即主节点和从节点。从节点也是可以对外提供服务的,主节点是有数据的,从节点可以通过复制操作将主节点的数据同步过来,并且随着主节点数据不断写入,从节点数据也会做同步的更新。
在这里插入图片描述
从节点起到的就是数据备份的效果。

8.1.2 一主多从

除了一主一从模型之外,Redis还提供了一主多从的模型,也就是一个master可以有多个slave,也就相当于有了多份的数据副本。
在这里插入图片描述
可以做一个更加高可用的选择,例如一个master和一个slave挂掉了,还能有其他的slave数据备份。

8.2 主从复制原理

  1. 当从数据库启动后,会向主数据库发送SYNC命令
  2. 主数据库接收到SYNC命令后开始在后台保存快照(RDB持久化),并将保存快照期间接收到的命令缓存下来
  3. 快照完成后,Redis(Master)将快照文件和所有缓存的命令发送给从数据库
  4. Redis(Slave)接收到RDB和缓存命令时,会开始载入快照文件并执行接收到的缓存的命令
  5. 一旦初始化完成,后续每当主数据库接收到写命令时,就会将命令同步给从数据库。所以3和4只会在初始化的时候执行

8.3 主从复制的应用场景

8.3.1 备份容错
  • 如果只有一个节点,会存在单点故障问题
8.3.2 读写分离

在这里插入图片描述

  • 通过主从复制可以实现读写分离,以提高服务器的负载能力
  • 在常见的场景中(例如:电商网站),读的频率大于写
  • 当单机Redis无法应付大量的读请求时(尤其是消耗资源的请求),就可以通过主从复制功能来建立多个从数据库节点,主数据库只进行写操作,从数据库负责读操作
  • 这种主从复制,比较适合用来处理读多写少的场景,而当单个主数据库不能满足需求时,就需要使用Redis 3.0后推出的集群功能
8.3.3 从数据库持久化
  • Redis中相对耗时的操作就是持久化,为了提高性能,可以通过主从复制创建一个或多个从数据库,并在从数据库中启用持久化,同时在主数据库中禁用持久化(例如:禁用AOF)
  • 当从数据库崩溃重启后主数据库会自动将数据同步过来,无需担心数据丢失
  • 而当主数据库崩溃时,后续我们可以通过哨兵(Sentinel)来解决

8.4 另外两台服务器安装Redis

8.4.1 安装Redis依赖环境

在bigdata-pro-m08和bigdata-pro-m09执行以下命令安装依赖环境

yum -y install gcc-c++
  • 1
8.4.2 上传Redis压缩包

在bigdata-pro-m08和bigdata-pro-m09服务器上面上传Redis压缩包,然后进行解压,并将安装包上传到/opt/software路径下

cd /opt/software
tar -zxvf redis-3.2.8.tar.gz -C /opt/softwares/
  • 1
  • 2
8.4.3 服务器安装tcl

在bigdata-pro-m08和bigdata-pro-m09服务器执行以下命令在线装TCL

yum  -y  install  tcl
  • 1
8.4.4 编译redis

bigdata-pro-m08和bigdata-pro-m09执行以下命令进行编译Redis
执行以下命令进行编译:

cd /opt/software/redis-3.2.8/
#或者使用命令  make  进行编译
make MALLOC=libc   
make test && make install PREFIX=/opt/software/redis-3.2.8/
  • 1
  • 2
  • 3
  • 4
8.4.5 修改redis配置文件
bigdata-pro-m08服务器修改配置文件

执行以下命令修改Redis配置文件

cd /opt/software/redis-3.2.8/
mkdir data
mkdir log

vim redis.conf
# 修改第61行
bind bigdata-pro-m08
# 修改第128行
daemonize yes
# 修改第163行
logfile "/opt/software/redis-3.2.8/log/redis.log"
# 修改第247行
dir /opt/software/redis-3.2.8/data
# 修改第266行,配置bigdata-pro-m08为第一台服务器的slave节点
slaveof bigdata-pro-m07 6379
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
bigdata-pro-m09服务器修改配置文件

执行以下命令修改Redis配置文件

cd /opt/software/redis-3.2.8/
mkdir data
mkdir log

vim redis.conf
# 修改第61行
bind bigdata-pro-m08
# 修改第128行
daemonize yes
# 修改第163行
logfile "/opt/software/redis-3.2.8/log/redis.log"
# 修改第247行
dir /opt/software/redis-3.2.8/data
# 修改第266行,配置bigdata-pro-m09为第二台服务器的slave节点
slaveof bigdata-pro-m07 6379
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

8.5 启动Redis服务

bigdata-pro-m08和bigdata-pro-m089执行以下命令启动Redis服务

bin/redis-server redis.conf
  • 1

启动成功便可以实现redis的主从复制,bigdata-pro-m07可以读写操作,bigdata-pro-m08与bigdata-pro-m09只支持读取操作。

9. Redis中的Sentinel架构

9.1 Sentinel介绍

  • 哨兵是主要用来保障Redis主从复制架构是高可用的,是能够自动进行主节点切换的
  • 它可以监控主从复制中的节点,当主节点崩溃的时候,会自动进行切换
  • 一般哨兵的配置节点数不能是1个,最好是有几个主从节点,就配置几个哨兵。不能哨兵自己出现单点故障
  • 哨兵在Linux系统上是一个独立的进程,它的默认端口号是26379
  • 当我们去查看操作哨兵的时候,需要指定客户端的连接端口号为:26379
    例如:
    在这里插入图片描述

在Server1 掉线后:
在这里插入图片描述
升级Server2 为新的主服务器:
在这里插入图片描述

9.2 配置哨兵

在这里插入图片描述

9.2.1 三台机器修改哨兵配置文件

三台机器执行以下命令修改redis的哨兵配置文件

vim sentinel.conf
  • 1

配置监听的主服务器

  1. 修改bigdata-pro-m07的sentinel.conf文件
#修改第15行, bind配置,每台机器修改为自己对应的主机名
bind bigdata-pro-m07
# 在下方添加配置,让sentinel服务后台运行
daemonize yes
#修改第71行,三台机器监控的主节点,现在主节点是bigdata-pro-m07服务器
sentinel monitor mymaster bigdata-pro-m07 6379 2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

参数说明

  • sentinel monitor代表监控
  • mymaster代表服务器的名称,可以自定义
  • bigdata-pro-m07代表监控的主服务器,6379代表端口
  • 2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。

如果Redis是有密码的,需要指定密码

# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass <master-name> <password>
  • 1
  • 2
  1. 分发到bigdata-pro-m08和bigdata-pro-m09
scp sentinel.conf bigdata-pro-m08:$PWD
scp sentinel.conf bigdata-pro-m09:$PWD
  • 1
  • 2
  1. 分别修改配置中bind的服务器主机名
bigdata-pro-m08
# 修改第18行
bind bigdata-pro-m08

bigdata-pro-m09
# 修改第18行
bind bigdata-pro-m09
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
9.2.2 三台机器启动哨兵服务
bin/redis-sentinel sentinel.conf
  • 1

三台服务器的进程信息:

[caizhengjie@bigdata-pro-m07 redis-3.2.8]$ ps -ef | grep redis
caizhen+ 16605     1  0 00:37 ?        00:00:03 bin/redis-sentinel bigdata-pro-m07:26379 [sentinel]
root     16681     1  0 00:53 ?        00:00:00 bin/redis-server bigdata-pro-m07:6379
caizhen+ 16685  1233  0 00:53 pts/0    00:00:00 grep --color=auto redis
  • 1
  • 2
  • 3
  • 4
9.2.3 bigdata-pro-m07服务器杀死redis服务进程
查看Sentinel master的状态
bin/redis-cli -h bigdata-pro-m08 -p 26379
使用ping命令检查哨兵是否工作,如果正常会返回PONG
bigdata-pro-m08:26379> ping
PONG
bigdata-pro-m08:26379> info
... ... ...

# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=10.211.55.9:6379,slaves=2,sentinels=3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这时的主节点是bigdata-pro-m07

使用kill -9命令杀死redis服务进程,模拟redis故障宕机情况
过一段时间之后,就会在bigdata-pro-m08与bigdata-pro-m09服务器选择一台服务器来切换为主节点

# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=10.211.55.11:6379,slaves=2,sentinels=3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这时的主节点是bigdata-pro-m09

9.3 Redis的sentinel模式代码开发连接

通过哨兵连接,要指定哨兵的地址,并使用JedisSentinelPool来创建连接池。

实现步骤:

  1. 在 cn.itcast.redis.api_test 包下创建一个新的类 ReidsSentinelTest
  2. 构建JedisPoolConfig配置对象
  3. 创建一个HashSet,用来保存哨兵节点配置信息(记得一定要写端口号)
  4. 构建JedisSentinelPool连接池
  5. 使用sentinelPool连接池获取连接
package cn.itcast.redis.api_test;

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;

/**
 * @author :caizhengjie
 * @description:
 * 1.   在 cn.itcast.redis.api_test 包下创建一个新的类 RedisSentinelTest
 * 2.	构建JedisPoolConfig配置对象
 * 3.	创建一个HashSet,用来保存哨兵节点配置信息(记得一定要写端口号)
 * 4.	构建JedisSentinelPool连接池
 * 5.	使用sentinelPool连接池获取连接
 * @date :2021/1/28 4:07 下午
 */
public class RedisSentinelTest {

    private JedisSentinelPool jedisSentinelPool;

    @BeforeTest
    public void redisConnectionPool(){

        // 创建JedisPoolConfig配置对象
        JedisPoolConfig config = new JedisPoolConfig();
        // 指定最大空闲连接为10个
        config.setMaxIdle(10);
        // 最小空闲连接5个
        config.setMinIdle(5);
        // 最大等待时间为3000毫秒
        config.setMaxWaitMillis(3000);
        // 最大连接数为50
        config.setMaxTotal(50);

        HashSet<String> sentinelSet = new HashSet<>();
        sentinelSet.add("bigdata-pro-m07:26379");
        sentinelSet.add("bigdata-pro-m08:26379");
        sentinelSet.add("bigdata-pro-m09:26379");

        jedisSentinelPool = new JedisSentinelPool("mymaster",sentinelSet,config);

    }

    @Test
    public void keysTest(){
        // 1.要操作redis,先要获取redis连接。现在通过哨兵连接池来获取连接
        Jedis jedis = jedisSentinelPool.getResource();
        // 2.执行keys操作
        Set<String> keySet = jedis.keys("*");
        // 3.遍历所有key
        for (String key : keySet) {
            System.out.println(key);
        }
        // 4.再将连接返回到连接池
        jedis.close();
    }
    @AfterTest
    public void afterTest(){

        // 关闭连接池
        jedisSentinelPool.close();

    }
}
  • 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
  • 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
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

运行结果:

k1
k2

===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

10. Redis 集群

Redis最开始使用主从模式做集群,若master宕机需要手动配置slave转为master;后来为了高可用提出来哨兵模式,该模式下有一个哨兵监视master和slave,若master宕机可自动将slave转为master,但它也有一个问题,就是不能动态扩充;所以在Redis 3.x提出cluster集群模式。

10.1 引言

Redis Cluster是Redis官方提供的Redis集群功能,为什么要实现Redis Cluster?

  1. 主从复制不能实现高可用
  2. 随着公司发展,用户数量增多,并发越来越多,业务需要更高的QPS,而主从复制中单机的QPS可能无法满足业务需求;
  3. 数据量的考虑,现有服务器内存不能满足业务数据的需要时,单纯向服务器添加内存不能达到要求,此时需要考虑分布式需求,把数据分布到不同服务器上;
  4. 网络流量需求,业务的流量已经超过服务器的网卡的上限值,可考虑使用分布式来进行分流;
  5. 离线计算,需要中间环节缓冲等其他需求;

在存储引擎框架(MySQL、HDFS、HBase、Redis、Elasticsearch等)中,只要数据量很大时,单机无法承受压力,最好的方式就是:数据分布进行存储管理。

对Redis 内存数据库来说:全量数据,单机Redis节点无法满足要求,按照分区规则把数据分到若干个子集当中。

在这里插入图片描述

10.1.1 Redis集群解决的问题
  • 高可用
  • 解决单机Redis内存是有限的问题(技术组件不是内存越多越好,因为内存配置得越高,例如JVM的Heap内存配置得很高后,就会导致内存碎片整理很耗时,垃圾回收会发生卡顿,导致集群的效率下降)
  • 解决单机Redis网络受限的问题
10.1.2 分布式存储的重点——分区
  • 顺序分布:MySQL——根据顺序分区的方式,例如:根据主键来进行分区(分库分表),一般是在Java web开发中会遇到
  • 按照哈希取余的方式来进行分区(类似于MapReduce的默认分区策略)
    • 问题:当分区的数量发生变化的时候,会导致key产生较大影响,原先分布在第一个节点上的数据,分区数量调整后,指定到了其他的分区
  • 按照一致性Hash的方式来进行分区
    • 是一个环状的Hash空间,它的分区算法是和哈希取余算法不一样的
    • 首先将每一个分区的标号(0、1、2)进行算法计算,然后将计算出来的值,放入到环状的Hash空间空
    • 再将key同样进行算法计算,然后将计算出来的值,同样也放入到环状的Hash空间中
    • 最后,找到key在hash空间中距离自己位置最近的分区,放入到该分区中
    • 这样,当分区的数量发生变化的时候,影响不会太大
  • Redis集群是使用槽的方式来进行分区的
    • 现有有一个槽的空间(0-16383),需要将这些空间分布到不同的节点中
    • node1: 0 -3xxx
    • node2: 3xxx- 6xxx
    • 有一个key,首先进行CRC16算法&16383 = 值,Redis会判断这个值应该在哪个槽中

一致性哈希分区图解:
在这里插入图片描述
虚拟槽分区图解:
在这里插入图片描述

10.2 Redis Cluster 设计

Redis Cluster是分布式架构,有多个节点,每个节点都负责进行数据读写操作,每个节点之间会进行通信。Redis Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。

在这里插入图片描述
结构特点:

  • 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽;
  • 节点的fail是通过集群中超过半数的节点检测失效时才生效;
  • 客户端与redis节点直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可;
  • redis-cluster 把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value;
  • Redis集群预分好16384个桶(Slot),当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) & 16384的值,决定将一个key放到哪个桶中;

在这里插入图片描述

Redis 集群的优势:

  • 缓存永不宕机:启动集群,永远让集群的一部分起作用。主节点失效了子节点能迅速改变角色成为主节点,整个集群的部分节点失败或者不可达的情况下能够继续处理命令;
  • 迅速恢复数据:持久化数据,能在宕机后迅速解决数据丢失的问题;
  • Redis可以使用所有机器的内存,变相扩展性能;
  • 使Redis的计算能力通过简单地增加服务器得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长;
  • Redis集群没有中心节点,不会因为某个节点成为整个集群的性能瓶颈;
  • 异步处理数据,实现快速读写;

Redis 3.0以后,节点之间通过去中心化的方式提供了完整的sharding(数据分片)、replication(复制机制、Cluster具备感知准备的能力)、failover解决方案。

在这里插入图片描述

10.3 Redis Cluster 搭建

Redis3.0及以上版本实现,集群中至少应该有奇数个节点,所以至少有三个节点,官方推荐三主三从的配置方式。Redis 3.x和Redis4.x 搭建集群是需要手动安装ruby组件的,比较麻烦。
2018年十月 Redis 发布了稳定版本的 5.0 版本,推出了各种新特性,其中一点是放弃 Ruby的集群方式,改为 使用 C语言编写的redis-cli的方式,是集群的构建方式复杂度大大降低。Redis cluster tutorial:https://redis.io/topics/cluster-tutorial

在这里插入图片描述
基于Redis-5.0.8版本,在三台机器上搭建6个节点的Redis集群:三主三从架构。

10.3.1 环境准备

关闭以前Redis主从复制和哨兵模式监控的所有服务,备注:如果以前没有安装过Redis服务,不用执行此步骤操作。

# ============= node1.itcast.cn、node2.itcast.cn和node3.itcast.cn =============
# 关闭哨兵服务SentinelServer
ps -ef | grep redis
kill -9 哨兵的进程ID

# 关闭Redis服务
redis-cli -h bigdata-pro-m07 -p 6379 SHUTDOWN
redis-cli -h bigdata-pro-m08 -p 6379 SHUTDOWN
redis-cli -h bigdata-pro-m09 -p 6379 SHUTDOWN
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

安装Redis编译环境:GCC和TCL。

yum -y install gcc-c++ tcl
  • 1
10.3.2 上传和解压

将Redis-5.0.8软件安装包上传至 /opt/software 目录,并解压与安装。

chmod u+x redis-5.0.8.tar.gz 
tar -zxvf redis-5.0.8.tar.gz -C /opt/modules/
  • 1
  • 2
10.3.3 编译安装

编译Redis 源码,并安装至【/opt/modules/redis-5.0.8-bin】目录。

# bigdata-pro-m07, 编译、安装、创建软连接
# 进入源码目录
cd /opt/modules/redis-5.0.8
# 编译
make
# 安装至指定目录
make PREFIX=/opt/modules/redis-5.0.8-bin install

# 创建安装目录软连接
ln -s redis-5.0.8-bin redis
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

配置环境变量(如果以前安装过Redis,配置过环境变量,就不用配置)。

vim /etc/profile
# ======================== 添加如下内容 ========================
# REDIS HOME
export REDIS_HOME=/opt/modules/redis
export PATH=:$PATH:$REDIS_HOME/bin

# 执行生效
source /etc/profile
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
10.3.4 拷贝配置文件

从Redis-5.0.8源码目录下拷贝配置文件:redis.conf至Redis 安装目录。

# ====================== bigdata-pro-m07 上操作 ======================
# 拷贝配置文件
cd /opt/modules/redis-5.0.8
cp redis.conf /opt/modules/redis
  • 1
  • 2
  • 3
  • 4
10.3.5 修改配置文件

每台机器上启动2个Redis服务,一个主节点服务:7001,一个从节点服务:7002,如下图所示:
在这里插入图片描述
在Redis安装目录下创建7001和7002目录,分别存储Redis服务配置文件、日志及数据文件。

# 创建目录:7001和7002
cd /opt/modules/redis
mkdir -p 7001 7002
  • 1
  • 2
  • 3

拷贝配置文件:redis.conf至7001目录,并重命名为redis_7001.conf。

cd /opt/modules/redis
cp redis.conf 7001/redis_7001.conf
  • 1
  • 2

编辑配置文件:redis_7001.conf,内容如下:

cd /opt/modules/redis/7001
vim redis_7001.conf
## =========================== 修改内容说明如下 ===========================
## 69行,配置redis服务器接受链接的网卡
bind 0.0.0.0
## 88行,关闭保护模式
protected-mode no
## 92行,设置端口号
port 7001
## 136行,redis后台运行
daemonize yes
## 158行,Redis服务进程PID存储文件名称
pidfile /var/run/redis_7001.pid

## 171行,设置redis服务日志存储路径
logfile "/opt/modules/redis-5.0.8-bin/7001/log/redis.log"
## 263行,设置redis持久化数据存储目录
dir /opt/modules/redis-5.0.8-bin/7001/data/

## 699行,启动AOF方式持久化
appendonly yes

## 832行,启动Redis Cluster
cluster-enabled yes
## 840行,Redis服务配置保存文件名称
cluster-config-file nodes-7001.conf
## 847行,超时时间
cluster-node-timeout 15000
  • 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

创建日志目录和数据目录:

mkdir log
mkdir data
  • 1
  • 2

配置7002端口号启动Redis服务,操作命令如下:

## 拷贝配置文件
cd /opt/modules/redis
cp 7001/redis_7001.conf 7002/redis_7002.conf

## 修改配置文件:redis_7002.conf
cd /opt/modules/redis/7002
vim redis_7002.conf
# 进入vim编辑之后,执行以下代码将7001全部替换成7002
:%s/7001/7002/g   # 表示:%s/old/new/g  g表示全部替换 

# 创建目录
mkdir log
mkdir data
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
10.3.6 发送安装包

将bigdata-pro-m07上配置好的Redis安装包,发送至bigdata-pro-m08和bigdata-pro-m09,每台机器运行2个Redis服务,端口号分别为7001和7002,具体命令如下:

# 发送安装包
cd /opt/modules/
scp -r redis-5.0.8-bin bigdata-pro-m08:$PWD
scp -r redis-5.0.8-bin bigdata-pro-m09:$PWD

# 创建软连接
ln -s redis-5.0.8-bin redis

# 配置环境变量
vim /etc/profile
# ======================== 添加如下内容 ========================
# REDIS HOME
export REDIS_HOME=/opt/modules/redis
export PATH=:$PATH:$REDIS_HOME/bin
# 执行生效
source /etc/profile
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

10.4 启动Redis服务

在三台机器,分别启动6个Redis服务,命令如下:

# 启动7001端口Redis服务
cd /opt/modules/redis
bin/redis-server 7001/redis_7001.conf

# 启动7002端口Redis服务
bin/redis-server 7002/redis_7002.conf
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Redis服务启动完成以后,查看如下:

[root@bigdata-pro-m07 redis]# ps -ef | grep redis
root     25362     1  0 03:37 ?        00:00:00 bin/redis-server 0.0.0.0:7001 [cluster]
root     25367     1  0 03:37 ?        00:00:00 bin/redis-server 0.0.0.0:7002 [cluster]
root     25372 20674  0 03:37 pts/0    00:00:00 grep --color=auto redis
  • 1
  • 2
  • 3
  • 4
10.4.1 启动集群

Redis5.x版本之后,通过redis-cli客户端命令来进行创建集群,注意:Redis对主机名解析不友好,使用IP地址。

# 任意选择一台机器执行如下命令,创建集群
bin/redis-cli --cluster create 10.211.55.9:7001 10.211.55.9:7002 10.211.55.10:7001 10.211.55.10:7002 10.211.55.11:7001 10.211.55.11:7002 --cluster-replicas 
  • 1
  • 2

启动集群日志信息如下:

>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.211.55.10:7002 to 10.211.55.9:7001
Adding replica 10.211.55.11:7002 to 10.211.55.10:7001
Adding replica 10.211.55.9:7002 to 10.211.55.11:7001
M: 85967aece18ad0a0dbba0bd8ab5dc231daa37211 10.211.55.9:7001
   slots:[0-5460] (5461 slots) master
S: 0e819904c77c695451f50b83c6a65fd83d1e4760 10.211.55.9:7002
   replicates b870f6c001ba485caffa2ade9a152d163909a548
M: 29b88a543dd91233f5c0b7f25b8fa05495799f9f 10.211.55.10:7001
   slots:[5461-10922] (5462 slots) master
S: 644fe21de168518e8ece15785a96078fd8926498 10.211.55.10:7002
   replicates 85967aece18ad0a0dbba0bd8ab5dc231daa37211
M: b870f6c001ba485caffa2ade9a152d163909a548 10.211.55.11:7001
   slots:[10923-16383] (5461 slots) master
S: aed488ce6dcbde4fc23e8b160a0ea8583ec783ab 10.211.55.11:7002
   replicates 29b88a543dd91233f5c0b7f25b8fa05495799f9f
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 10.211.55.9:7001)
M: 85967aece18ad0a0dbba0bd8ab5dc231daa37211 10.211.55.9:7001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 29b88a543dd91233f5c0b7f25b8fa05495799f9f 10.211.55.10:7001
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 0e819904c77c695451f50b83c6a65fd83d1e4760 10.211.55.9:7002
   slots: (0 slots) slave
   replicates b870f6c001ba485caffa2ade9a152d163909a548
M: b870f6c001ba485caffa2ade9a152d163909a548 10.211.55.11:7001
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 644fe21de168518e8ece15785a96078fd8926498 10.211.55.10:7002
   slots: (0 slots) slave
   replicates 85967aece18ad0a0dbba0bd8ab5dc231daa37211
S: aed488ce6dcbde4fc23e8b160a0ea8583ec783ab 10.211.55.11:7002
   slots: (0 slots) slave
   replicates 29b88a543dd91233f5c0b7f25b8fa05495799f9f
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
  • 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
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
10.4.2 测试集群

在任意一台机器,使用redis-cli客户端命令连接Redis服务:

redis-cli -c -p 7001
  • 1

输入命令:cluster nodes(查看集群信息)和info replication(主从信息):

127.0.0.1:7001> cluster nodes
29b88a543dd91233f5c0b7f25b8fa05495799f9f 10.211.55.10:7001@17001 master - 0 1611909783813 3 connected 5461-10922
0e819904c77c695451f50b83c6a65fd83d1e4760 10.211.55.9:7002@17002 slave b870f6c001ba485caffa2ade9a152d163909a548 0 1611909784865 5 connected
85967aece18ad0a0dbba0bd8ab5dc231daa37211 10.211.55.9:7001@17001 myself,master - 0 1611909779000 1 connected 0-5460
b870f6c001ba485caffa2ade9a152d163909a548 10.211.55.11:7001@17001 master - 0 1611909786961 5 connected 10923-16383
644fe21de168518e8ece15785a96078fd8926498 10.211.55.10:7002@17002 slave 85967aece18ad0a0dbba0bd8ab5dc231daa37211 0 1611909788005 4 connected
aed488ce6dcbde4fc23e8b160a0ea8583ec783ab 10.211.55.11:7002@17002 slave 29b88a543dd91233f5c0b7f25b8fa05495799f9f 0 1611909785916 6 connected
127.0.0.1:7001> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=10.211.55.10,port=7002,state=online,offset=280,lag=0
master_replid:f67ea78cd33268a6c217b99b249858c8372837ae
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:280
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:280
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

测试数据,设置Key值和查询Key的值。

127.0.0.1:7001> keys *
(empty list or set)
127.0.0.1:7001> set k1 v1
-> Redirected to slot [12706] located at 10.211.55.11:7001
OK
10.211.55.11:7001> set k2 v2
-> Redirected to slot [449] located at 10.211.55.9:7001
OK
10.211.55.9:7001> set k3 v3
OK
10.211.55.9:7001> get k1
-> Redirected to slot [12706] located at 10.211.55.11:7001
"v1"
10.211.55.11:7001> get k2
-> Redirected to slot [449] located at 10.211.55.9:7001
"v2"
10.211.55.9:7001> get k3
"v3"
10.211.55.9:7001> KEYS *
1) "k3"
2) "k2"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
10.4.3 启动关闭集群

编写脚本,方便启动和关闭Redis集群:redis-cluster-start.sh和redis-cluster-stop.sh。

  • 进入Redis安装目录中bin目录,创建脚本文件
# 进入bigdata-pro-m07
cd /opt/modules/redis/bin

touch redis-cluster-start.sh 
touch redis-cluster-stop.sh

# 给以执行权限
chmod u+x redis-cluster-start.sh 
chmod u+x redis-cluster-stop.sh
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 启动集群:redis-cluster-start.sh
#!/bin/bash

REDIS_HOME=/opt/modules/redis
# Start Server
## bigdata-pro-m07
ssh bigdata-pro-m07 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7001/redis_7001.conf"
ssh bigdata-pro-m07 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7002/redis_7002.conf"
## bigdata-pro-m08
ssh bigdata-pro-m08 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7001/redis_7001.conf"
ssh bigdata-pro-m08 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7002/redis_7002.conf"
## bigdata-pro-m09
ssh bigdata-pro-m09 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7001/redis_7001.conf"
ssh bigdata-pro-m09 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7002/redis_7002.conf"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 关闭集群:redis-cluster-stop.sh
#!/bin/bash

REDIS_HOME=/opt/modules/redis
# Stop Server
## bigdata-pro-m07
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m07 -p 7001 SHUTDOWN
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m07 -p 7002 SHUTDOWN
## bigdata-pro-m08
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m08 -p 7001 SHUTDOWN
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m08 -p 7002 SHUTDOWN
## bigdata-pro-m09
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m09 -p 7001 SHUTDOWN
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m09 -p 7002 SHUTDOWN
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

启动脚本的时候注意root权限和无密钥登陆。

10.4.4 主从切换

测试Redis Cluster中主从服务切换,首先查看集群各个服务状态:
在这里插入图片描述
在bigdata-pro-m09上将7001端口Redis 服务关掉:SHUTDOWN

redis-cli -h bigdata-pro-m09 -p 7001 SHUTDOWN
  • 1

重新启动bigdata-pro-m09上7001端口Redis服务,再次查看集群状态信息:

bin/redis-server 7001/redis_7001.conf 
# 连接Redis集群集群
redis-cli -c -p 7001
cluster nodes
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述

10.5 Redis Cluster 管理

redis-cli集群命令帮助:

redis-cli --cluster help
  • 1

在这里插入图片描述
在实际项目中可能由于Redis Cluster中节点宕机或者增加新节点,需要操作命令管理,主要操作如下。

在这里插入图片描述

10.6 JavaAPI操作redis集群

连接Redis集群,需要使用JedisCluster来获取Redis连接。

实现步骤:

  1. 在cn.itcast.redis.api_test包下创建一个新的类:RedisClusterTest
  2. 创建一个HashSet,用于保存集群中所有节点的机器名和端口号
  3. 创建JedisPoolConfig对象,用于配置Redis连接池配置
  4. 创建JedisCluster对象
  5. 使用JedisCluster对象设置一个key,然后获取key对应的值
  • JedisPool——操作单机版本的Redis
  • JedisSentinelPool——操作哨兵系统的主从结构
  • JedisCluster——操作Redis集群的

注意事项:

  • 在构建JedisCluster的时候,需要将集群中的主、从节点所有节点都添加到Set里面
  • 如果使用JedisCluster操作Redis时候,不再需要获取Redis连接,直接去操作Redis即可,因为JedisCluster已经封装好了对应的操作
package cn.itcast.redis.api_test;

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;

/**
 * @author :caizhengjie
 * @description:TODO
 * @date :2021/1/29 8:54 下午
 */
public class RedisClusterTest {

    private JedisPoolConfig jedisPoolConfig;
    private JedisCluster jedisCluster;

    @BeforeTest
    public void beforeTest(){
        // 1. 创建一个HashSet<HostAndPort>,用于保存集群中所有节点的机器名和端口号
        HashSet<HostAndPort> hostAndPortSet = new HashSet<>();
        hostAndPortSet.add(new HostAndPort("bigdata-pro-m07", 7001));
        hostAndPortSet.add(new HostAndPort("bigdata-pro-m07", 7002));
        hostAndPortSet.add(new HostAndPort("bigdata-pro-m08", 7001));
        hostAndPortSet.add(new HostAndPort("bigdata-pro-m08", 7002));
        hostAndPortSet.add(new HostAndPort("bigdata-pro-m09", 7001));
        hostAndPortSet.add(new HostAndPort("bigdata-pro-m09", 7002));

        // 2. 创建JedisPoolConfig对象,用于配置Redis连接池配置
        JedisPoolConfig config = new JedisPoolConfig();
        // 指定最大空闲连接为10个
        config.setMaxIdle(10);
        // 最小空闲连接5个
        config.setMinIdle(5);
        // 最大等待时间为3000毫秒
        config.setMaxWaitMillis(3000);
        // 最大连接数为50
        config.setMaxTotal(50);

        // 3. 创建JedisCluster对象
        jedisCluster = new JedisCluster(hostAndPortSet);
    }

    @Test
    public void clusterOpTest(){
        // 设置一个key
        jedisCluster.set("pv", "1");

        // 获取key
        System.out.println(jedisCluster.get("pv"));

    }

    @AfterTest
    public void afterTest(){
        try {
            jedisCluster.close();
        } catch (IOException e) {
            System.out.println("关闭Cluster集群连接失败!");
            e.printStackTrace();
        }

    }
}
  • 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
  • 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
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

10.7 Redis集群面试题

从Redis 3.0发布提供Redis Cluster以后,经历Redis 4.x、Redis5.x和Redis 6.x一系列版本,Redis Cluster更加成熟、稳定,推荐企业使用此种架构,通常公司也是使用此种架构。如果使用Redis Cluster集群,面试中碰到的问题有一些坑,还望注意。

  • 问题一:Redis的多数据库机制,了解多少?
    在这里插入图片描述
  • 问题二:懂Redis的批量操作么?
    在这里插入图片描述
  • 问题三:Redis集群机制中,你觉得有什么不足的地方吗?
    在这里插入图片描述
  • 问题四:在Redis集群模式下,如何进行批量操作?
    在这里插入图片描述
  • 问题五:懂Redis事务么?
    在这里插入图片描述

11. Redis高频面试题

在应用程序和MySQL数据库中建立一个中间层:Redis缓存,通过Redis缓存可以有效减少查询数据库的时间消耗,但是引入redis又有可能出现缓存穿透、缓存击穿、缓存雪崩等问题。
在这里插入图片描述

11.1 缓存穿透

缓存穿透: key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。

一言以蔽之:查询Key,缓存和数据源都没有,频繁查询数据源

比如用一个不存在的用户id获取用户信息,无论论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

解决缓存穿透的方案主要有两种:

  • 方案一:当查询不存在时,也将结果保存在缓存中。但是这可能会存在一种问题:大量没有查询结果的请求保存在缓存中,这时我们就可以将这些请求的key设置得更短一些;
  • 方案二:提前过滤掉不合法的请求,可以使用Redis中布隆过滤器:布隆过滤器可以快速地过滤掉缓存中不存在的key,但是有一个问题:布隆过滤器不能准确地判断这个已经存在的key真的存在。

11.2 缓存击穿

缓存击穿: key对应的数据库存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

一言以蔽之:查询Key,缓存过期,大量并发,频繁查询数据源

业界比较常用的做法:使用互斥锁。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db(查询数据库),而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据。

String get(String key) {  
   String value = redis.get(key);  
   if (value  == null) {  
// 如果key不存在,则设置为1
    if (redis.setnx(key_mutex, "1")) {  
        // 设置key的过期时间为3分钟  
        redis.expire(key_mutex, 3 * 60)  
// 从db中加载数据,但注意:只有一个线程能进入到这里,其他线程访问的时候已有课key_mutex
        value = db.get(key);  
// 从数据库中加载成功,则设置对应的数据
        redis.set(key, value);  
        redis.delete(key_mutex);  
    } else {  
        //其他线程休息50毫秒后重试  
        Thread.sleep(50);  
        get(key);  
    }  
  }  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

通过sexnx(“互斥锁”, 1)

  • sexnx表示如果key不存在的时候,才会设置一个key,如果存在就直接返回
  • 这种方式可以确保只有一个线程能够进入到加载数据库的逻辑中

11.3 缓存雪崩

缓存雪崩:缓存服务器重启或者大量缓存集中在某一个时间段失效,会出现大量并发去直接访问数据库,导致数据库的压力过大,系统崩溃。

一言以蔽之:缓存不可用(服务器重启或缓存失效),频繁查询数据源

与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。缓存正常从Redis中获取,示意图如下:
在这里插入图片描述
缓存失效瞬间示意图如下:
在这里插入图片描述
缓存失效时的雪崩效应对底层系统的冲击非常可怕!大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

解决办法:

  • 不能让所有的key集中在某一个时刻失效,可以将过期时间设置为随机
  • 不然后台系统直接操作数据库,通过消息队列来隔离业务系统和数据

11.4 Redis的命名规范是?

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

以上内容仅供参考学习,如有侵权请联系我删除!
如果这篇文章对您有帮助,左下角的大拇指就是对博主最大的鼓励。
您的鼓励就是博主最大的动力!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/781301
推荐阅读
相关标签
  

闽ICP备14008679号