Captain's Geek-Island Captain's Geek-Island
首页
生活如斯乎
架构师的路
  • 分类
  • 标签
  • 归档
沉洋官网 (opens new window)

SleepyOcean

走,找新大陆去
首页
生活如斯乎
架构师的路
  • 分类
  • 标签
  • 归档
沉洋官网 (opens new window)
  • 计算机基础

  • 并发专题

  • 性能调优专题

  • 工具专题

  • 源码框架专题

  • 设计模式

  • 分布式专题

    • Redis - 分布式缓存
      • 一、Redis数据类型
        • 1)使用场景
        • 2)基础操作
        • 3)通用操作
        • 4)三大特殊数据类型
      • 二、Redis特性
        • 1)淘汰策略
        • 2)事务
        • 3)持久化
        • 3)Redis高性能的原因
        • 4)Redis缓存相关问题
      • 四、Redis集群架构
    • Zookeeper - 分布式协调服务
    • Kafka - 分布式消息
  • 实战专题

  • 技术杂文

  • 云原生专题

  • 大数据分析专题

  • 前端专题

  • 运维专题

  • 经验专题

  • 面试专题

  • 软实力专题

  • 架构师的路
  • 分布式专题
SleepyOcean
2020-03-28

Redis - 分布式缓存

Redis 是 C 语言开发的一个开源的(遵从 BSD 协议)高性能键值对(key-value)的内存数据库,可以用作数据库、缓存、消息中间件等。它是一种 NoSQL(not-only sql,泛指非关系型数据库)的数据库。

Redis 作为一个内存数据库:

  • 性能优秀,数据在内存中,读写速度非常快,支持并发 10W QPS。

  • 单进程单线程,是线程安全的,采用 IO 多路复用机制。

  • 丰富的数据类型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等。

  • 支持数据持久化。

    可以将内存中数据保存在磁盘中,重启时加载。

  • 主从复制,哨兵,高可用。

  • 可以用作分布式锁。

  • 可以作为消息中间件使用,支持发布订阅。

# 一、Redis数据类型

博文 - redis数据类型图表

# 1)使用场景

string类型的应用场景

最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存和spring session + redis实现session共享。

# 单值缓存
> SET key value
> GET key

# 对象缓存
> SET user:1 value(json格式数据)
> MSET user:1:name sleepyocean user:1:balance 1888
> MGET user:1:name user:1:balance

# 分布式锁
线程1: > SETNX product:10001 true		// 返回1代表获取锁成功
线程2: > SETNX product:10001			// 返回0代表获取锁失败
... ... 执行业务操作
> DEL product:10001					// 执行完业务释放锁

> SET product:10001 true ex 10 nx	// 防止程序意外终止导致死锁

# 计数器
> INCR article:readcount:{文章ID}
> GET article:readcount:{文章ID}

# 分布式系统全局序列号
> INCRBY orderId 1000		// redis批量生产序列号,提升性能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

list类型的应用场景

使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。

hash类型的应用场景

这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。

set类型的应用场景

因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。利用交集、并集、差集等操作,可以计算共同value,全部的value,自己独有的value等功能。

zset类型的应用场景

sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用、取TOP-n操作、延时任务和范围查找。

# 2)基础操作

1)docker搭建redis环境,参见我的另篇博文:docker搭建基础中间件 (opens new window)

2)进入redis-cli

$ docker exec -it redis redis-cli
1

3)练习开始,输入操作指令

string

# set {key} {value}。设定key持有指定的字符串value,如果该key存在则进行覆盖操作,总是返回OK。
> set name sleepy
OK

# get {key}。获取key的value。如果与该key关联的value不是String类型,redis将返回错误信息,因为get命令只能用于获取String value;如果该key不存在,返回null。
> get name
"sleepy"

# getset {key} {value}。先获取该key的值,然后在设置该key的值。
> getset name ocean
"sleepy"
> get name
"ocean"

# incr {key}。将指定的key的value原子性的递增1. 如果该key不存在,其初始值为0,在incr之后其值为1。如果value的值不能转成整型,如hello,该操作将执行失败并返回相应的错误信息
> incr age
(integer) 1

# decr {key}。将指定的key的value原子性的递减1.如果该key不存在,其初始值为0,在incr之后其值为-1。如果value的值不能转成整型,如hello,该操作将执    行失败并返回相应的错误信息。
> decr age
(integer) 0

# incrby {key} {increment}。将指定的key的value原子性增加increment,如果该key不存在,器初始值为0,在incrby之后,该值为increment。如果该值不能转成    整型,如hello则失败并返回错误信息
> incrby price 10
(integer) 10

# decrby {key} {decrement}。将指定的key的value原子性减少decrement,如果该key不存在,器初始值为0,在decrby之后,该值为decrement。如果该值不能    转成整型,如hello则失败并返回错误信息
> decrby price 8
(integer) 2

# append {key} {value}。如果该key存在,则在原有的value后追加该值;如果该key    不存在,则重新创建一个key/value
> append name ,sleepy
(integer) 12
> get name
"ocean,sleepy"
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

list

# lpush {key} {value1} {value2}...。在指定的key所关联的list的头部插入所有的values,如果该key不存在,该命令在插入的之前创建一个与该key关联的空链表,之后再向该链表的头部插入数据。插入成功,返回元素的个数。
> lpush date date3 date2 date1
(integer) 3

# rpush {key} {value1} {value2}...。在该list的尾部添加元素。
> rpush date date4 date5 date6
(integer) 6

# lrange {key} {start} {end}。获取链表中从start到end的元素的值,start、end可为负数,若为-1则表示链表尾部的元素,-2则表示倒数第二个,依次类推… 
> lrange date 0 5
1) "date1"
2) "date2"
3) "date3"
4) "date4"
5) "date5"
6) "date6"

# lpushx {key} {value}。仅当参数中指定的key存在时(如果与key管理的list中没有值时,则该key是不存在的)在指定的key所关联的list的头部插入value。
> lpushx date date0
(integer) 7
> lrange date 0 6
1) "date0"
2) "date1"
3) "date2"
4) "date3"
5) "date4"
6) "date5"
7) "date6"

# rpushx {key} {value}。在该list的尾部添加元素。
> rpushx date date7
(integer) 8
> lrange date 0 7
1) "date0"
2) "date1"
3) "date2"
4) "date3"
5) "date4"
6) "date5"
7) "date6"
8) "date7"

# lpop {key}。返回并弹出指定的key关联的链表中的第一个元素,即头部元素。
> lpop date
"date0"

# rpop {key}。从尾部弹出元素。
> rpop date
"date7"
> lrange date 0 7
1) "date1"
2) "date2"
3) "date3"
4) "date4"
5) "date5"
6) "date6"

# rpoplpush {resource} {destination}。将「resource」链表中的尾部元素弹出并添加另一个key「destination」的头部。
> rpoplpush date redate
"date6"
> lrange date 0 7
1) "date1"
2) "date2"
3) "date3"
4) "date4"
5) "date5"
> lrange redate 0 7
1) "date6"

# llen {key}。返回指定的key关联的链表中的元素的数量。
> llen date
(integer) 5

# lset {key} {index} {value}。设置链表中的index的脚标的元素值,0代表链表的头元素,-1代表链表的尾元素。
> lset date 2 datenew2
OK
> lrange date 0 7
1) "date1"
2) "date2"
3) "datenew2"
4) "date4"
5) "date5"
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
71
72
73
74
75
76
77
78
79
80
81
82

hash

# hset {key} {field1} {value1} {field2} {value2} ... ...
# 或 hmset {key} {field1} {value1} {field2} {value2} ... ...。为指定的key设定一个或多个field/value对
> hset person name oasis age 23
(integer) 2

# hget {key} {field}。返回指定的key中的field的值
> hget person name
"oasis"

# hmget {key} {field}。取key中的多个field值
> hmget person name age
1) "oasis"
2) "23"

# hkeys {key}。获取所有的key
> hkeys person
1) "name"
2) "age"

# hvals {key}。获取所有的value
> hvals person
1) "oasis"
2) "23"

# hgetall {key}。获取key中的所有field 中的所有field-value
> hgetall person
1) "name"
2) "oasis"
3) "age"
4) "23"

# hdel {key} {field1} {field2} ...。可以删除一个或多个字段,返回是被删除的字段个数
> hdel person age
(integer) 2
> hgetall person
1) "name"
2) "oasis"

# del key。删除整个list
> del person
(integer) 1
> hgetall person
(empty array)

# hincrby {key} {field} {increment}。设置key中field的值增加increment,如: age增加20
> hincrby person age 20
(integer) 20
> hgetall person
1) "age"
2) "20"

# hexists {key} {field}。判断指定的key中的field是否存在
> hexists person age
(integer) 1
> hexists person name
(integer) 0

# hlen {key}。获取key所包含的field的数量
> hlen person
(integer) 1
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

set

# sadd {key} {value1} {value2} ...
> sadd lang java c cpp python c
(integer) 4

# smembers {key}
> smembers lang
1) "c"
2) "python"
3) "java"
4) "cpp"

# srem {key} {member1} {member2} ...
> srem lang c
(integer) 1
> smembers lang
1) "python"
2) "java"
3) "cpp"

# sismember {key} {member}。判断参数中指定的成员是否在该set中,1表示存在,0表示不存在或者该key本身就不存在(无论集合中有多少元素都可以极速的返回结果)
> sismember lang java
(integer) 1


# 集合运算
> sadd lang1 java c cpp python c
(integer) 4
> sadd lang2 js html python
(integer) 3
# 差集运算
> sdiff lang1 lang2
1) "c"
2) "java"
3) "cpp"
# 交集运算
> sinter lang1 lang2
1) "python"
# 并集运算
> sunion lang1 lang2
1) "java"
2) "cpp"
3) "c"
4) "js"
5) "python"
6) "html"
# xxx-store {destination} {key1} {key2} ...。将集合运算存储到「destination」集合中
> sdiffstore sdifflang lang1 lang2
(integer) 3
> sinterstore sinterlang lang1 lang2
(integer) 1
> sunionstore sunionlang lang1 lang2
(integer) 6

# scard {key}。获取set中的成员数量
> scard lang1
(integer) 4

# srandmember {key} {count}。随机返回set中的「count」个成员
> srandmember lang1 2
1) "c"
2) "java"

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

zset

# zadd {key} {score1} {member1} {socre2} {member2} ... ...。将所有成员以及该成员的分数存放到sorted-set中。如果该元素已经存在则会用新的分数替换原有的分数。返回值是新加入到集合中的元素个数。(根据分数升序排列)
> zadd toplang 3 c++ 4 golang 5 js 6 html 7 css 10 java 9 c 8 python 12 php
(integer) 9

# zrange {key} {start} {stop} [withsocres]。获取集合中角标为start-end的成员,[withscore]参数表明返回的成员包含其分数。
> zrange toplang 0 10
1) "c++"
2) "golang"
3) "js"
4) "html"
5) "css"
6) "python"
7) "c"
8) "java"
9) "php"

# zscore {key} {member}。返回指定成员的分数
> zscore toplang python
"8"

# zcard {key}
> zcard toplang
(integer) 3

# zrem {key} {member1} {member2} ...
> zrem toplang php
(integer) 1
> zcard toplang
(integer) 8

# zrangebysocre {key} {min} {max} [withscores] [limit {offset} {count}]。返回分数在[min,max]的成员并按照分数从低到高排序。[withscore]:显示分数;[limit offset count];offset,表明从脚标为offset的元素开始并返回count个成员
> zrangebyscore toplang 5 10 withscores limit 2 3
1) "css"
2) "7"
3) "python"
4) "8"
5) "c"
6) "9"

# zincrby {key} {increment} {member}。设置指定成员的增加分数。返回值是修改后的分数
> zincrby toplang 3 python
"11"

> zrange toplang 0 10 withscores
 1) "c++"
 2) "3"
 3) "golang"
 4) "4"
 5) "js"
 6) "5"
 7) "html"
 8) "6"
 9) "css"
10) "7"
11) "c"
12) "9"
13) "java"
14) "10"
15) "python"
16) "11"

# zcount {key} {min} {max}。获取分数在[min,max]之间的成员个数
> zcount toplang 3 6
(integer) 4

# zrank {key} {member}。返回成员在集合中的排名(从小到大,从0开始)
> zrank toplang js
(integer) 2

# zrevrank {key} {member}。返回成员在集合中的排名(从大到小,从0开始)
> zrevrank toplang java
(integer) 1

> zrange toplang 0 10
1) "c++"
2) "golang"
3) "js"
4) "html"
5) "css"
6) "c"
7) "java"
8) "pyhton"

# zremrangebyrank {key} {start} {stop}。按照排名范围删除元素
> zremrangebyrank toplang 3 5
(integer) 3
> zrange toplang 0 10
1) "c++"
2) "golang"
3) "js"
4) "java"
5) "python"

> zrange toplang 0 10 withscores
 1) "c++"
 2) "3"
 3) "golang"
 4) "4"
 5) "js"
 6) "5"
 7) "java"
 8) "10"
 9) "python"
10) "11"

# zremrangebyscore {key} {min} {max}。按照分数范围删除元素
> zremrangebyscore toplang 3 5
(integer) 3
127.0.0.1:6379> zrange toplang 0 10 withscores
1) "java"
2) "10"
3) "python"
4) "11"

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

# 3)通用操作

# keys {pattern}。获取所有与pattern匹配的key ,返回所有与该key匹配的keys。 *表示任意一个或者多个字符, ?表示任意一个字符
> keys *
 1) "date1"
 2) "person"
 3) "age"
 4) "price"
 5) "toplang"
 6) "lang2"
 7) "1"
 8) "0"
 9) "redate"
10) "sunionlang"
11) "sdifflang"
12) "name"
13) "lang1"
14) "lang"
15) "date"
16) "sinterlang"

# del {key1} {key2} ...
> del date1 lang1 lang2 sunionlang sinterlang sdifflang
(integer) 6
> keys *
 1) "person"
 2) "age"
 3) "price"
 4) "toplang"
 5) "1"
 6) "0"
 7) "redate"
 8) "name"
 9) "lang"
10) "date"

# exists {key}。判断该key是否存在,1代表存在,0代表不存在
> exists date1
(integer) 0

# rename {key} {newkey}
> rename person man
OK
> keys *
 1) "age"
 2) "price"
 3) "toplang"
 4) "1"
 5) "0"
 6) "redate"
 7) "name"
 8) "lang"
 9) "date"
10) "man"

> expire redate 10
(integer) 1
> exists redate
(integer) 1
# 10s 以后再次查询
> exists redate
(integer) 0

# ttl {key}。获取该key所剩的超时时间,如果没有设置超时,返回-1,如果返回-2表示超时不存在
> ttl man
(integer) -1
> ttl redate
(integer) -2
> expire date 300
(integer) 1
> ttl date
(integer) 286

# persist {key}。持久化key

# type {key}。获取指定key的类型。该命令将以字符串的格式返回。返回的字符串为string 、list 、set 、hash 和 zset,如果key不存在返回none。
> type name
string
> type date
list
> type man
hash
> type lang
set
> type toplang
zset
> type redate
none
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

其他命令补充:

  • select 编号:切换数据库(redis默认有16个数据库,默认使用第0个)
  • dbsize:查看当前数据库存储数据的大小
  • flushdb:清空当前数据库
  • flushall:清空所有的数据库

# 4)三大特殊数据类型

a)geospatial(地理位置)

把某个具体的位置信息(经度,纬度,名称)添加到指定的key中,数据将会用一个sorted set存储,以便稍后能使用 GEORADIUS (opens new window)和 GEORADIUSBYMEMBER (opens new window)命令来根据半径来查询位置信息。

# 规则:两极无法直接添加,一般会下载城市数据,通过java程序一次性导入

# 添加城市的经度和纬度
> geoadd  china:city 121.47 31.23   shanghai

# 获取指定城市的经度和纬度
> geopos china:city beijin

# 获取城市之间的距离(单位: m米、km千米、mi英里、ft英尺)
> geodist china:city beijin shanghai km

# 查找当前经纬度下,半径1000km的城市
> georadius china:city  110.00 30.00  1000km
# 查找当前经纬度下,半径1000km的城市和直线距离
> georadius china:city  110.00 30.00  1000km  withdist
# 查找当前经纬度下,半径1000km的城市经纬度
> georadius china:city  110.00 30.00  1000km  withcoord

# 找出指定城市1000km附近的城市
> georadiusbymember china:city beijin 1000km

# 返回一个或多个位置的geohash
> geohash china:city beijin chongqin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

使用场景:地图

Redis.Version >= 3.2

b)hyperloglog

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

# 创建一组元素
> pfadd mykey a b c

# 统计mykey元素的基数数量
> pfcount mykey     

# 合并mykey1和mykey2到mykey3
> pf merge  mykey3 mykey1 mykey2   
1
2
3
4
5
6
7
8

使用场景:统计一个网站的访问人数

Redis.Version >= 2.8.9

c)bitmap(位图)

bitmap就是通过最小的单位bit来进行0或者1的设置,表示某个元素对应的值或者状态。

# 设置值
> setbit  sign  1  0  

# 取值
> getbit sign   1 

# 统计这周的打卡记录
> bitcount sign   
1
2
3
4
5
6
7
8

使用场景:统计打卡,考勤

Redis.Version >= 2.2.0

# 二、Redis特性

# 1)淘汰策略

博文 - redis

补充:Redis 4.0 加入了 LFU(least frequency use)淘汰策略,包括 volatile-lfu 和 allkeys-lfu,通过统计访问频率,将访问频率最少,即最不经常使用的 KV 淘汰。

# 2)事务

redis事务的本质:一组命令的集合,一个事务中所有的命令都会被序列化,在事务执行的过程中,会按照顺序执行

redis中的事务具有一致性、顺序性、排他性

redis单条命令保持原子性,但是事务不保证原子性

redis中没有隔离级别的概念

开启事务后,所有的命令在事务中并没有被执行,只有在发起执行命令的时候才会执行

redis事务步骤:

  • 开启事务(multi)
  • 命令入队
  • 执行事务(exec)
  • 取消事务(discard)

注意:事务执行的过程中可能存在异常

  • 编译时异常(命令有错),事务中所有的命令都不会被执行
  • 运行时异常,如果事务队列中存在语法性,那么执行命令的时候,其他命令可以正常执行

# 3)持久化

三种持久化方式: RDB持久化、AOF持久化和RDB-AOF混合持久化

  • RDB(快照持久化):快照形式是直接把内存中的数据保存到一个 dump 的文件中,定时保存,保存策略。

redis.conf的配置示例:

#在900s内如果有1条数据被写入,则产生一次快照。
save 900 1 
#在300s内如果有10条数据被写入,则产生一次快照
save 300 10 
#在60s内如果有10000条数据被写入,则产生一次快照
save 60 10000 

# stop-writes-on-bgsave-error 如果为yes则表示,当备份进程出错的时候,主进程就停止进行接受新的写入操作,这样是为了保护持久化的数据一致性的问题。
stop-writes-on-bgsave-error yes 

1
2
3
4
5
6
7
8
9
10

RDB 持久化方式的优点:全量数据快照,文件小,恢复快

RDB 持久化方式的缺点:无法保存最近一次快照之后的数据。内存数据全量同步,数据量大的状况下,会由于 I/O 而严重影响性能。

  • AOF持久化:把所有的对 Redis 的服务器进行修改的命令都存到一个文件里,命令的集合。

redis.conf的配置示例:

appendonly yes

# appendfsync no 
# appendsync always 每次有数据修改发生时都会写入AOF文件。
# appendfsync everysec 每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync everysec
1
2
3
4
5
6

AOF 持久化方式的优点:可读性高,适合保存增量数据,数据不易丢失。

AOF 持久化方式的缺点:文件体积大,恢复时间长。

  • RDB-AOF 混合持久化方式:持久化策略首先将缓存中数据以 RDB 方式全量写入文件,再将写入后新增的数据以 AOF 的方式追加在 RDB 数据的后面,在下一次做 RDB 持久化的时候将 AOF 的数据重新以 RDB 的形式写入文件。

# 3)Redis高性能的原因

  • Redis 完全基于内存,绝大部分请求是纯粹的内存操作,非常迅速,数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度是 O(1)。
  • 数据结构简单,对数据操作也简单。
  • 采用单线程,避免了不必要的上下文切换和竞争条件,不存在多线程导致的 CPU 切换,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有死锁问题导致的性能消耗。
  • 使用多路复用 IO 模型,非阻塞 IO。

# 4)Redis缓存相关问题

  • 缓存穿透(查不到数据)
    • 现象解释:缓存穿透是指,用户想要查询一个数据,发现redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求持久层数据库,这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透
    • 解决方案:
      • 布隆过滤器:布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
      • 缓存空对象:当存储层不命中后,即使返回的空对象也将其缓存起来,同时设置一个过期时间,之后再访问这个数据会从缓存中获取,保护了后端的数据源(潜在问题:空值缓存过多)
  • 缓存击穿(热点数据过期)
    • 现象解释:缓存击穿,是指一个key非常的热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求在数据库上,就像在屏障上面开了一个洞。当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新的数据,并且写缓存会导致数据库瞬间压力过大
    • 解决方案:
      • 设置热点数据永不过期:从缓存层面来看,没有设置过期时间,就不会产生这个问题
      • 加互斥锁:分布式锁,使用分布式锁,保证对于每个Key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此需要等待。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
  • 缓存雪崩(热点数据集中过期)
    • 现象解释:缓存雪崩,是指在某一个时间段,缓存集中过期失效,redis宕机产生一系列的连锁反应,造成整个系统崩溃
  • 缓存预热
    • 现象解释:缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
    • 解决方案:
      • 直接写个缓存刷新页面,上线时手工操作下
      • 数据量不大,可以在项目启动的时候自动进行加载
      • 定时刷新缓存
  • 缓存降级
    • 现象解释:当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户
    • 解决方案:降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。这里以参考日志级别设置预案
      • 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级
      • 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警
      • 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级
      • 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级

# 四、Redis集群架构

Redis 高可用架构有如下方案:

  • Redis Sentinel 集群 + 内网 DNS + 自定义脚本
  • Redis Sentinel 集群 + VIP + 自定义脚本
  • 封装客户端直连 Redis Sentinel 端口。JedisSentinelPool,适合 Java
  • Redis Sentinel 集群 + Keepalived/Haproxy
  • Redis M/S + Keepalived
  • Redis Cluster
  • Twemproxy
  • Codis

搭建高可用Redis集群架构实践请参考我的另一篇博文:Redis高可用集群搭建

#Redis #中间件
上次更新: 2020/12/08, 06:12:00

← 设计模式 - 总览 Zookeeper - 分布式协调服务 →

新鲜出炉
01
记录 - 快速搭建自动化部署平台
04-13
02
Docker搭建各类Paas服务
03-01
03
系统配置 - Android TV配置
02-12
更多文章>
Copyright © 2019-2022 SleepyOcean | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式