Redis

小德 2021-12-02 12:57:59
Categories: Tags:

Redis

Redis是什么?

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。称之为结构化数据库.

Redis能干什么?

Redis能干什么?

1.内存存储\持久化,内存中是断电即使,持久化很重要(rdb\aof)

2.效率高,用于高速缓存

3.发布订阅系统

4.地图信息分析

5.计时器\计数器

Redis特性

Redis特性

1.多样的数据类型

2.持久化

3.集群

4.事务

Redis推荐是在Linux服务器上搭建的

Linux安装步骤

1.解压:tar -zxvf redis-6.2.6.tar.gz

2.进入到指定刚才解压的目录里面:cd redis-3.0.0

3.使用 make命令:make

4.接下来将redis的的安装到指定的目录里面(下面的那个redis的的不需要提前创建的)

使用PREFIX = / usr / local / src / redis安装至指定目录里面.

make install PREFIX=/usr/local/src/redis

Linux启动步骤:

在/usr/local/bin的目录下新建目录xconfig,然后在原redis目录(/home/xiaode/redis-6.2.6/)中复制redis.conf到xconfig下,命令:cp /home/xiaode/redis-6.2.6/redis.conf usr/local/bin/xconfig。

启动Redis服务,命令:./redis-server xconfig/redis.conf

确定服务开启,看进程,命令:ps -ef|grep redis

启动Redis客户端,命令:./redis-cli -p 6379

关闭服务在客户端中输入,命令:shutdown

退出客户端,命令:exit

redis desktop manager连接远程linux中的Redis

1、首先保证本机能ping通linux的ip地址;如果不能请执行命令关闭防火墙

1
service iptables stop

2、配置文件根目录使用vim redis.conf 打开 配置文件;确认在redis.conf配置文件中设置了requirepass 密码(找不到使用 / 查找)

1
#requirepass foobared``更改为``requirepass 123456

3、保证redis.conf中的 bind 127.0.0.1 被注释掉了

4、最终确认密码是否生效 ./redis-cli 登陆上客户端后输入auth 123456 回车;会返回OK

修改后台启动设置daemonize no改成yes

redis-benchmark是一个压力测试工具

命令:redis-benchmark -h localhost -p 6379 -c 100 -n 100000 (100并发,100000连接)

Redis基础知识

Redis基础知识

默认有16个数据库,切换数据库,命令:select 2

查看数据库大小,命令:dbsize

查看所有的key,命令:keys *

清空数据库,命令:flushdb

问题:(error) MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.

这是由于强制停止redis快照,不能持久化引起的,运行info命令查看redis快照的状态,连接redis后运行 config set stop-writes-on-bgsave-error no 命令即可

清空所有数据库,命令:flushall

为什么redis默认端口是6379:粉丝效应

6379在是手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字。

Alessia Merz 是一位意大利舞女、女演员。 Redis 作者 Antirez 早年看电视节目,觉得 Merz 在节目中的一些话愚蠢可笑,Antirez 喜欢造“梗”用于平时和朋友们交流,于是造了一个词 “MERZ”,形容愚蠢,与 “stupid” 含义相同。MERZ长期以来被Redis作者antirez及其朋友当作愚蠢的代名词。

后来 Antirez 重新定义了 “MERZ” ,形容”具有很高的技术价值,包含技艺、耐心和劳动,但仍然保持简单本质“。

到了给 Redis 选择一个数字作为默认端口号时,Antirez 没有多想,把 “MERZ” 在手机键盘上对应的数字 6379 拿来用了。

为什么mysql默认端口是3306

3306是作者女儿的名字的九宫格打法,然后redis的6379也是,是作者喜欢的明星的名字的九宫格位置打法.

Redis是单线程的,基于内存操作,CPU不是Redis的性能瓶颈,瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程。

Redis为什么单线程还这么快?

Redis是C语言写的,官方数据位100000+的QPS,完全不必同样使用key-value的Memecache差!

误区1:高性能的服务器一定是多线程的

误区2:多线程(CPU上下文切换)一定比单线程效率高

核心:redis将所有的数据全部放在内存中,所以使用单线程去操作效率最高,多线程会耗时操作,对于内存系统来说,没有CPU上下文切换,效率最高。

Redis五大基本数据类型

Redis五大基本数据类型

Redis-Key:

判断key是否存在,命令:exists key,存在返回1否则0

移除key,命令:move name 1(当前数据库)

设置过期时间,命令: expire key10(秒)

查看过期剩余时间,命令:ttl key

查看当前类型,命令:type key

String(字符串):

给value追加字符串,如果key不存在就会set,命令:append key xxx

查看value字符串长度,命令:strlen key1

自增1,命令:incr key

自减1,命令:decr key

步长增,命令:incrby key 10

步长减,命令:decrby key 10

value的字符串截取,命令:getrange key 0(start) 3 (end) [0-3]

0到 -1截取全部的字符串

value的字符串替换,命令:setrange key 0(从第几位开始替换位置) xx(替换值)

set的同时设置过期时间,命令:setex key 30 value

key已过期(不存在)则设置,未过期(存在)不设置,命令:(m)setnx key vlue

批量设置key,命令:mset k1 v1 k2 v2

批处理,msetnx key1 value1 key2 value2,要么一起成功,要么一起失败

对象:创建对象

1.set user:1 {name:zhangsan,age:23}

get user:1

2.mset user:1:name zhangsan user:1:age 23

mget user:1:name user:1:age

组合命令:

getset user:1:name “lisi”,先获得后设置,不存在放回nil

string的应用:计数器,统计多单位的数量粉丝数,对象缓存存储

List:基本数据类型,列表

list实际上是一个链表,值可以重复

消息队列(Lpush,Rpop),栈(Lpush,Lpop)

在redis里,我们可以把list玩成栈,队列,阻塞队列。

所有list命令都是以L开头的。

头部设置list值,命令:lpush list xxx

尾部设置list值,命令:rpush list xxx

通过区间获取值,命令:lrange list 0 -1

头部移除list值,命令:lpop list x(个数)

尾部移除list值,命令:rpop list x(个数)

获取单个值,命令:lindex list x(下标)

查看长度,命令:llen list

移除指定的值,命令:lrem list x(个数) 值,lrem list 2 b

截取,list会被改变,命令:ltrim list start end(从0开始)

组合命令,从旧list尾部移除,到新list头部添加

rpoplpush 当前list 新的list

可以更新list的值,前提是list存在元素,命令:lset list 0(下标) xxx

判断一个list是否存在,命令:exists list

linsert list before/after list值 插入值,before头部,after尾部

Set(集合)

set中的值不可以重复,无序的

设置set值,命令:sadd set xxx

查看set值,命令:smembers set

判断是否存在指定元素,命令:sismember set xxx

查看set的长度,命令:scard set

移除set的值,命令:srem set xxx

随机选取n个值,不写x默认一个,命令:srandmember set (x)

随机移除元素,命令:spop set x(个数)

将一个set的指定的值(kkk)移到新的set,命令:smove set set2 kkk

差集:sdiff set set2

交集:sinter set set2

并集:sunion set set2

set的应用:微博A所有关注的人放在set集合中,将它的粉丝也放在一个集合中,共同关注,共同爱好,六度分隔理论,二度好友

Hash(哈希map集合)

设置hashmap值,命令:hset hash key value

获得hashmap值,命令:hget hash key

批量设置,命令:hmset hash k1 v1 k2 v2

批量获取,命令:hmget hash k1 k2

获取所有的数据,命令:hgetall hash

删除指定的key,对应的value值也没了,命令:hdel hash key

获取hash的长度,命令:hlen hash

判断hash中指定字段是否存在,命令:hexists hash key

获取所有的key名,命令:hkeys hash

增加,命令:hincrby hash key value

减少,命令:hdecrby hash key value

存在不设置,不存在设置,命令:hsetnx hash key value

hash的应用:变更的数据,user的name和age,更适合对象的存储,string更适合存字符串。

Zset(有序集合)

在set的基础上,增加了一个值score

设置zset的值,命令:zadd zset 1(score) one

遍历zset的值,命令:zrange zset 0 -1

从小到大排序,命令:zrangebyscore zset -inf(负无穷) +inf(正无穷) withscores

范围:-inf(负无穷) +inf(正无穷),可以随便设置(-inf-2500)

移除元素,命令:命令:zrem zset key

查看zset的长度,命令:zcard zset

从大到小排序,命令:zrevrange zset 0 -1

获取区间的数量,命令:zcount zset 1 2

zset的应用:set排序,存储班级成绩,工资排序。排行榜,带权重的判断。

Redis三大特殊数据类型

Redis三大特殊数据类型

geospatial地理位置:在redis3.2版本推出,可以推算地理位置的信息,两地之间的距离,方圆几里的人

朋友的定位、附近的人、打车距离计算

添加地理位置,命令:geoadd china:city 112.98(经度) 28.11(纬度) changsha (两级无法添加,我们一般会下载城市数据,通过java程序一次性导入)

geoadd china:city 112.98 28.11 changsha

geoadd china:city 121.47 31.23 shanghai

geoadd china:city 106.50 29.53 chongqing

geoadd china:city 114.08 22.54 shenzhen

geoadd china:city 120.15 30.28 hangzhou

geoadd china:city 125.14 42.92 xian

获取指定地理位置,命令:geopos china:city changsha

返回两个位置之间的距离,命令:geodist china:city changsha shanghai km(长沙到上海的 距离km)

单位:m米,km千米,mi英里,ft英尺

附近的人,通过半径来搜索,获取所有附近人的地址

以给定的经纬度为中心,找出某一半径内的元素,命令: georadius china:city 110 30 1000 km withdist(直线距离) withcoord(经纬度) count n(限制查出的长度)

(以当前位置(经度110,纬度30)为原点,半径为1000km搜索城市)

所有查询的城市必须被录入

以给定城市的经纬度为中心,找出其半径内的元素,命令: georadiusbymember china:city changsha 1000 km withdist(直线距离) withcoord(经纬度) count n(限制查出的长度)

geohash:该命令返回11个字符的Geohash字符串!转成一维字符串,字符串越接近就相隔越近。

Geo底层实现原理就是Zset,可以用zset命令操作geo

删除地理位置:zrem china:city changsha

Hyperloglog

什么是是基数?1,3,5,7

{1,3,5,7,8,7}

基数=5

基数:不重复的元素的个数

简介

Redis2.8.9版本更新了Hyperloglog数据结构

基数统计的算法,网页的UV页面访问量,一个人访问多次还是算一个人。

传统方式:set保存用户id,相同用户id则覆盖,用元素个数统计访问量。弊端:保持大量的用户id,就比较麻烦,目的是为了计数而不是保护用户id。

Hyperloglog优点:占用内存是固定的,2^64不同的元素计数,只占用12kb!

0.81%错误率,统计UV,可以忽略不计。

添加,命令: pfadd mkey a b c d e f g h i j

统计个数,命令:pfcount mkey

合并(重复计一次)到新key,命令:pfmerge mkey3 mkey mkey2

容许容错,可以使用

Bitmaps

位存储

统计疫情感染人数,没感染为0,感染为1

统计用户信息,不活跃为0,活跃为1

两个状态的都可以使用bitmaps

位图、数据结构,操作二进制进行记录,只有0和1

365天=365bit 1字节=8bit 46个字节左右!

设置值,命令:setbit sign 0(位置) 0/1(值)

打卡为1,未打卡为0

周一 0 周二 1 周三 0 周四 0 周五1…

setbit sign 0 0
setbit sign 1 1
setbit sign 2 0
setbit sign 3 0
setbit sign 4 1

查看某一天是否打卡getbit sign 3(周四)

统计打卡的天数,命令:bitcount sign (start end)

事务

MYSQL事务的四大特性(ACID):原子性、隔离性、一致性、持久性

Redis单条命令保证原子性,Redis事务不保证原子性,Redis事务没有隔离级别的概念。

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

事务本质:一组命令的集合!一个事务中所有命令都会被序列化,按照顺序执行,有排他性。

redis事务:

正常执行事务

开启事务,命令:multi

执行事务,命令:exec

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 a
QUEUED
127.0.0.1:6379(TX)> set k2 b
QUEUED
127.0.0.1:6379(TX)> set k3 c
QUEUED
127.0.0.1:6379(TX)> exec

1) OK
2) OK
3) OK

撤销事务,命令:discard,事务队列中命令都不会执行

编译型异常(命令有错),事务中所有命令都不会执行

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k5 a
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5
(nil)

运行时异常(1/0),事务中存在语法错误,其他命令是可以正常执行的,错误命令会抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k "a"
QUEUED
127.0.0.1:6379(TX)> incr k
QUEUED
127.0.0.1:6379(TX)> set k2 bbb
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec

1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "bbb"

监控!Watch(乐观锁)面试常问

悲观锁:认为什么时候都会出问题,无论做什么都会加锁

乐观锁:认为什么时候都不会出问题,不会上锁,更新数据时判断一下,在此期间是否有人修改这个数据。、

获取version

更新的时候比较version

Redis监视测试

1.正常执行成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> set money 100  (总共100元)
OK
127.0.0.1:6379> set out 0 (花了0元)
OK
127.0.0.1:6379> watch money (监视money)
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec

1) (integer) 80
2) (integer) 20

2.使用watch充当乐观锁,有其他客户端突然插入,更新了数据

客户端A

1
2
3
4
5
6
7
8
9
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)>

客户端B突然执行,加了1000元

1
2
3
4
5
[xiaode@localhost bin]$ redis-cli
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK

客户端A监控,自动加锁,结果执行失败!

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> exec
(nil)

可以使用unwatch解锁,重新watch拿到新值进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> exec
(nil)
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 990
2) (integer) 30
127.0.0.1:6379>

如果发现事务执行失败,就先解锁,再重新Watch

Jedis

什么是Jedis?java操作redis的中间件

导入对应依赖

1
2
3
4
5
6
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>

连接redis数据库

小坑:

1.修改redis.conf的IP绑定和安全模式改为no

bind 192.168.93.129 -::1

protected-mode no

2.注意linux的防火墙要关闭

查看防火墙状态:firewall-cmd –state

停止防火墙:systemctl stop firewalld.service

禁止防火墙开机启动:systemctl disable firewalld.service

1
2
3
4
5
6
7
8
9
import redis.clients.jedis.Jedis;

public class TestPing {
public static void main(String[] args) {

Jedis jedis = new Jedis("192.168.93.129", 6379);
System.out.println(jedis.ping());
}
}

操作命令

常用API

1、创建 jedis对象img

2、键操作:img

3、字符串操作:img

4、整数和浮点数操作:img

5、列表(List)操作:img

6、集合(Set)操作:

img

7、哈希(Hash)操作:

img

8、有序集合(Zsort)操作: img

9、排序操作:

img

断开连接

jedis.close()

Jedis操作事务

jedis.multi();

SpringBoot整合Redis

jedis:采用直连,多线程操作是不安全的,使用jedis pool连接池,像BIO模式。

lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全,像NIIO模式。

1.导入jar

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.配置连接

1
2
springboot.redis.host=192.168.93.129
springboot.redis.port=6379

3.操作

1
2
3
4
5
@Autowired
private RedisTemplate redisTemplate;
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb(); redisTemplate.opsForValue().set("name","张三"); System.out.println(redisTemplate.opsForValue().get("name"));
connection.close();

关于对象的保存,需要序列化

RedisConfig类

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
package com.oydq.springbootredis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate1(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);

Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}

RedisUtil类

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
package com.oydq.springbootredis.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {


@Autowired
@Qualifier("redisTemplate1")
private RedisTemplate<String, Object> redisTemplate;

// =============================common============================

/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/

public boolean expire(String key, long time) {
try {
if (time > 0) {

redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/

public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}

/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/

public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 删除缓存
* @param key 可以传一个值 或多个
*/

@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}

// ============================String=============================

/**
* 普通缓存获取
* @param key 键
* @return 值
*/

public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}

/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/

public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/

public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return 134
*/

public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}

/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/

public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}

// ================================Map=================================

/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/

public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}

/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/

public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}

/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/

public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/

public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/

public boolean hset(String key, String item, Object value) {

try {

redisTemplate.opsForHash().put(key, item, value);

return true;

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/

public boolean hset(String key, String item, Object value, long time) {

try {

redisTemplate.opsForHash().put(key, item, value);

if (time > 0) {

expire(key, time);

}

return true;

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/

public void hdel(String key, Object... item) {

redisTemplate.opsForHash().delete(key, item);

}

/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/

public boolean hHasKey(String key, String item) {

return redisTemplate.opsForHash().hasKey(key, item);

}

/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/

public double hincr(String key, String item, double by) {

return redisTemplate.opsForHash().increment(key, item, by);

}

/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/

public double hdecr(String key, String item, double by) {

return redisTemplate.opsForHash().increment(key, item, -by);

}

// ============================set=============================

/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/

public Set<Object> sGet(String key) {

try {

return redisTemplate.opsForSet().members(key);

} catch (Exception e) {

e.printStackTrace();

return null;

}

}

/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/

public boolean sHasKey(String key, Object value) {

try {

return redisTemplate.opsForSet().isMember(key, value);

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/

public long sSet(String key, Object... values) {

try {

return redisTemplate.opsForSet().add(key, values);

} catch (Exception e) {

e.printStackTrace();

return 0;

}

}

/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/

public long sSetAndTime(String key, long time, Object... values) {

try {

Long count = redisTemplate.opsForSet().add(key, values);

if (time > 0)

expire(key, time);

return count;

} catch (Exception e) {

e.printStackTrace();

return 0;

}

}

/**
* 获取set缓存的长度
* @param key 键
* @return
*/

public long sGetSetSize(String key) {

try {

return redisTemplate.opsForSet().size(key);

} catch (Exception e) {

e.printStackTrace();

return 0;

}

}

/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/

public long setRemove(String key, Object... values) {

try {

Long count = redisTemplate.opsForSet().remove(key, values);

return count;

} catch (Exception e) {

e.printStackTrace();

return 0;

}

}

// ===============================list=================================

/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/

public List<Object> lGet(String key, long start, long end) {

try {

return redisTemplate.opsForList().range(key, start, end);

} catch (Exception e) {

e.printStackTrace();

return null;

}

}

/**
* 获取list缓存的长度
* @param key 键
* @return
*/

public long lGetListSize(String key) {

try {

return redisTemplate.opsForList().size(key);

} catch (Exception e) {

e.printStackTrace();

return 0;

}

}

/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/

public Object lGetIndex(String key, long index) {

try {

return redisTemplate.opsForList().index(key, index);

} catch (Exception e) {

e.printStackTrace();

return null;

}

}

/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/

public boolean lSet(String key, Object value) {

try {

redisTemplate.opsForList().rightPush(key, value);

return true;

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/

public boolean lSet(String key, Object value, long time) {

try {

redisTemplate.opsForList().rightPush(key, value);

if (time > 0)

expire(key, time);

return true;

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/

public boolean lSet(String key, List<Object> value) {

try {

redisTemplate.opsForList().rightPushAll(key, value);

return true;

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/

public boolean lSet(String key, List<Object> value, long time) {

try {

redisTemplate.opsForList().rightPushAll(key, value);

if (time > 0)

expire(key, time);

return true;

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/

public boolean lUpdateIndex(String key, long index, Object value) {

try {

redisTemplate.opsForList().set(key, index, value);

return true;

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/

public long lRemove(String key, long count, Object value) {

try {

Long remove = redisTemplate.opsForList().remove(key, count, value);

return remove;

} catch (Exception e) {

e.printStackTrace();

return 0;

}

}
}

测试类

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
package com.oydq.springbootredis;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.oydq.springbootredis.pojo.User;
import com.oydq.springbootredis.util.RedisUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class SpringbootRedisApplicationTests {

@Autowired
private RedisUtil redis;


@Test
void a() throws JsonProcessingException {
//真实开发一般使用json对象来传递对象,序列化
User user = new User(1, "张三", 18);
// String jsonUser = new ObjectMapper().writeValueAsString(user);
String json= JSON.toJSONString(user);
redis.set("user",user);
redis.set("abc","haha");
System.out.println(redis.get("user"));
System.out.println(redis.get("abc"));
}

}

Redis.conf详解

单位:大小写不敏感

单位:大小写不敏感

1
2
3
4
5
6
7
8
9
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

包含别的配置文件

包含别的配置文件

1
2
# include /path/to/local.conf
# include /path/to/other.conf
网络

网络

1
2
3
4
5
6
7
8
9
10
bind 192.168.1.100 10.0.0.1     # listens on two specific IPv4 addresses
# bind 127.0.0.1 ::1 # listens on loopback IPv4 and IPv6
# bind * -::* # like the default, all available interfaces

安全模式
protected-mode no

端口
port 6379

通用GENERAL

通用GENERAL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
以守护进程的方式,默认是no,我们需要自己开启yes
daemonize yes
配置文件的pid文件,以后台方式运行
pidfile /var/run/redis_6379.pid

日志级别
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice

日志文件名
logfile ""

默认16个数据库
databases 16

是否显示logo
always-show-logo no
SNAPSHOTTING快照

SNAPSHOTTING快照:持久化,在规定时间内,执行了多少操作,会被持久化到文件.rda .aof

redis是内存数据库,如果没有持久化,那么断电即失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在3600s内,如果至少有1个key进行了修改,我们进行持久化操作
# save 3600 1
在300s内,如果至少有100个key进行了修改,我们进行持久化操作
# save 300 100
在60s内,如果至少有10000个key进行了修改,我们进行持久化操作
# save 60 10000

持久化出错是否继续工作
stop-writes-on-bgsave-error no

是否压缩rdb文件,需要消耗一些cpu资源
rdbcompression yes

保持rdb文件时,进行错误校验
rdbchecksum yes

rdb文件保持的目录
dir ./
REPLICATION复制,后面讲主从复制

REPLICATION复制,后面讲主从复制

SECURITY安全

SECURITY安全

1
2
设置密码 
requirepass foobared xxx
CLIENTS 客户端限制

CLIENTS 客户端限制

1
2
能连接的最大客户端数量
maxclients 10000
MEMORY MANAGEMENT内存

MEMORY MANAGEMENT内存

1
2
3
4
5
6
7
8
9
10
11
12
设置最大的内存容量
maxmemory <bytes>

内存达到上限的处理策略
maxmemory-policy noeviction

1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
APPEND ONLY MODEAOF的配置

APPEND ONLY MODE AOF的配置:默认不开启aof持久化,默认rdb,一般rdb就够用了。

1
2
3
4
5
6
7
8
9
10
开启aof
appendonly no

持久化的文件的名字
appendfilename "appendonly.aof"


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

Redis持久化

RDB(Redis Data Base)

什么是RDB?

在指定时间间隔里将内存中的数据集快照写入磁盘,恢复时,将快照文件直接读到内存。

在主从复制中,rdb是备用的,在从机上。

rdb的缺点是最后一次持久化的数据可能丢失。

redis默认是RDB,一般不需要修改这个配置。

RDB保存文件是dump.rdb

自动生成dump.rdb文件,触发机制

1.满足配置里save规则,自动触发

save 3600 1

2.执行了flushall,自动触发

3.退出redis,自动触发

恢复RDB文件

1.只需要把rdb文件放到redis的启动目录即可。

2.查看需要存在的位置:config get 目录(该目录存在drump.rdb)

优点:如果对数据完整性要求不高,适合大规模的数据恢复。

缺点:需要一定的时间间隔进程操作,如果redis意外宕机,最后一次修改就没了。fork进程的时候,会占用一定内容空间。

有时候在生产环境,会对rdb文件进行备份。

AOF(Append Only File)

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

AOF保存的是appendonly.aof,默认无限添加。

redis.conf的==APPEND ONLY MODE==

默认不开启的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
开启aof
appendonly no

持久化的文件的名字
appendfilename "appendonly.aof"


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

no-appendfsync-on-rewrite no #是否重写
auto-aof-rewrite-percentage 100 #重写寄存值
auto-aof-rewrite-min-size 64mb #重新的最大存储值
如果aof大于64m,fork一个新的进程将我们的文件进行重写。

开启:appendonly yes

重启redis服务即可生效

如果aof文件有错位,这时候redis启动不起来,我们需要修复这个aof,redis给我们提供了redis-check-aof修复工具。

命令:redis-check-aof –fix appendonly.aof

优点:每一次修改都同步,文件完整性更加好,每秒同步一次,可能会丢失1s数据。

缺点:相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢,aof运行效率比rbd慢。

发布订阅

底层是字典。

Redis发布订阅是一种消息通信模式,发送者发送消息,订阅者接受消息。

Redis客户端可以订阅任意数量的频道。

订阅:subscribe 频道名称

发生:publish 频道名称

订阅者

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> subscribe aaa #订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "aaa"
3) (integer) 1
#当前推送的消息
1) "message"
2) "aaa"
3) "hello"
1) "message"
2) "aaa"
3) "redis"

发布者

1
2
3
4
5
6
#发生订阅信息
127.0.0.1:6379> publish aaa "hello"
(integer) 1
127.0.0.1:6379> publish aaa "redis"
(integer) 1

使用场景:实时消息系统,实时聊天,订阅关注系统。稍微复杂就会使用消息中间件来做MQ。

Redis主从复制

默认情况下,每台redis服务器都是主节点。

指一台redis服务器的数据,复制到其他的Redis服务器,前者为主节点,后者为从节点,数据复制是单向的,只能从主节点复制到从节点。

主从 复制,读写分离。80%的情况下都是读操作,减轻服务器压力,架构中经常用!一主二从。

作用:1.数据冗余,2.故障恢复,3.负载均衡,4.高可用(集群)基石

防止宕机,内存有限,至少配三个redis。单台redis服务器最大使用内存不应该超过20G

环境配置

只配置从库,不配置主库。

查看主从复制信息,命令:info replication

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> info replication
# Replication
role:master #角色
connected_slaves:0 #没有从机
master_failover_state:no-failover
master_replid:9c302ef04b1b0341f83b564227c5158b744bce2b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

一个配置文件打开一个redis服务器。

从机需要改配置,PID名,端口号,日志文件名,rdb文件名/aof文件名

一般只需要配置从机即可。

认老大!一主(6379)二从(6380,6381)

1
slaveof 127.0.0.1 6379
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
127.0.0.1:6380> info replication
# Replication
role:slave #从机
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_read_repl_offset:14
slave_repl_offset:14
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:1f658b85b3194f10ae56993c8f3e4eb8f91544c3
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
127.0.0.1:6381> info replication
# Replication
role:slave #从机
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_read_repl_offset:84
slave_repl_offset:84
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:1f658b85b3194f10ae56993c8f3e4eb8f91544c3
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:85
repl_backlog_histlen:0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
127.0.0.1:6379> info replication
# Replication
role:master #主机
connected_slaves:2 #两台从机
slave0:ip=127.0.0.1,port=6380,state=online,offset=238,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=238,lag=1
master_failover_state:no-failover
master_replid:1f658b85b3194f10ae56993c8f3e4eb8f91544c3
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:238
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:238

真实的主从配置应该在配置中配置,就是永久的,命令是暂时的。

主机可以写,从机只能读。

1
2
27.0.0.1:6381> set name a
(error) READONLY You can't write against a read only replica.
复制原理

复制原理:slave启动成功连接到master后会发一个sync命令,master接到命令,启动后台存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后,master将传送整个数据文件到slave,完成一次完全同步。

全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。

增量复制:master继续将新的所有收集到的修改命令依次传给slave,完成同步,但是只要是重新连接master,一次完整同步(全量复制)将被自动执行。

1.主机断开连接,从机依旧连接主机,但是没有写操作,主机如回来了,从机依旧可以从主机读取值。

2.从机断开连接,如果使用命令行配置的,重新启动就会变回主机。再变到从机,又可以拿到原主机的值。

层层链路

上个从节点做下个主节点

M-S(M)-S

主机宕机了,从机自己当老大:

1
slaveof no one
哨兵模式(Sentinel)

自动选取老大模式

主从切换技术:主机宕机后,需要手动把一台服务器切换为主机,需要人工干预,费时费力,还会造成一段时间服务不可用,这不是一种推荐方法,所以引入了哨兵模式架构来解决。

能够后台自动监控主机是否故障,如果故障,则根据投票数自动从从机切换为主机。

哨兵模式是一种特殊的模式,首先redis提供哨兵命令,哨兵是一个独立的进程,原理:哨兵通过发送命令,等redis服务器响应,从而监控运行的多个redis实例。

一个哨兵对redis服务进行监控,可能会出现问题,这时候我们可以使用多哨兵进行监控,各哨兵之间互相监控,形成多哨兵模式。

多个哨兵发现主机不响应,通过投票选出从机转化成主机。

1.新建配置哨兵配置文件,sentinel.conf

1
2
3
sentinel monitor myredis 127.0.0.1 6379 1
myredis:被监控的主机名称,随意
"1"的含义:一个哨兵检测到主机不响应则认定主机宕机,判定为客观下线。

2.启动哨兵

1
redis-sentinel xconfig/sentinel.conf
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
7752:X 23 Oct 2021 13:57:53.037 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
7752:X 23 Oct 2021 13:57:53.037 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=7752, just started
7752:X 23 Oct 2021 13:57:53.037 # Configuration loaded
7752:X 23 Oct 2021 13:57:53.037 * Increased maximum number of open files to 10032 (it was originally set to 1024).
7752:X 23 Oct 2021 13:57:53.037 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.6 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 7752
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'

7752:X 23 Oct 2021 13:57:53.038 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
7752:X 23 Oct 2021 13:57:53.040 # Sentinel ID is 28f4f524e90d820fb95436d9dfd9b5effb9dc050
7752:X 23 Oct 2021 13:57:53.040 # +monitor master myredis 127.0.0.1 6379 quorum 1
7752:X 23 Oct 2021 13:57:53.040 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
7752:X 23 Oct 2021 13:57:53.042 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379

如果主机宕机后,哨兵从从机里随机选择一个转成主机。

哨兵日志显示哪一个从机转成了主机:

1
2
7752:X 23 Oct 2021 14:01:55.948 # +failover-end master myredis 127.0.0.1 6379  #主机客观下线
7752:X 23 Oct 2021 14:01:55.948 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6381 #转换从机做主机

原主机如果在这之后连接上了,则当新主机的从机。

优点:

1.哨兵集群,基于主从复制,所有的主从配置优点,他都有

2.主从可以切换,故障可以转移,系统的可用性会更好

3.哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点:

1.redis不好在线扩容的,集群容量一旦到达上限,在线扩容就很麻烦

2.实现哨兵模式的配置其实很麻烦,里面有很多选择

哨兵模式的全部配置:

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
# Example sentinel.conf

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

# 哨兵sentinel的工作目录
dir /tmp

# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd


# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000

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


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

# SCRIPTS EXECUTION

#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。

#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
一个是事件的类型,
一个是事件的描述。
如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh

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

Redis缓存穿透,击穿和雪崩

缓存穿透

缓存穿透:用户查询一个数据,发现redis内存数据库中没有,于是去持久层数据库查询,发现也没有。直接频繁的查询持久层数据库,给数据库产生很大的压力,相当于出现了缓存穿透。

解决:

1.布隆过滤器:是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的压力。

2.缓存空对象:则返回空对象也缓存起来,设置一个过期时间,保护后段数据源。

存在问题:空值缓存需要更多的空间存储。即使设置了过期时间,也可能出现缓存层和存储层不同步。

缓存击穿:点

缓存击穿:一个key为热点,大并发集中对着一个key访问,当key失效了,大并发直接访问持久层数据库,凿开了一个大洞。

解决:

1.设置key永不过期,但是会浪费空间

2.加互斥锁,但对分布式锁考验很大

缓存雪崩:面

缓存雪崩:某一个时间段,缓存集体过期失效,大并发直接访问持久层数据库。redis宕机!

双十一:停掉一些服务,保证主要的服务可用。

解决:

1.redis高可用:既然redis可能 挂掉,就多设置几台redis服务器,异地多活。

2.限流降级(springcloud):缓存失效后,通过加锁或队列来控制数据库写缓存的线程数量,比如一个key只允许一个线程查询数据和写缓存,其他线程等待。

3.数据预热:正式部署前,先把可能的数据预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动加载缓存不同的key,设置不同的过期时间,让缓存失效时间点尽量均匀。