Redis 入门教程

Redis 入门教程


Redis 基本介绍

Redis 的下载:

1、Redis 官网下载链接:Redis 官网 【下载的是 Liunx 版本,不是 windows 版本】
2、Redis 中文网下载链接:Redis 中文网 【下载的是 Liunx 版本,不是 windows 版本】
3、GitHub 下载链接:Redis 的 GitHub 地址 【建议使用,各种版本都有,这个是微软的,不是 Redis 的】
4、本博客站点下载:redis-2.8.9.zip 【建议使用,windows 版本的】
5、Redis 命令教程:Redis 命令教程

Redis 简介

Redis(Remote Dictionary Server ) : 即远程字典服务 ,是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、高性能的 NOSQL 系列的非关系型数据库 ,并提供多种语言的 API 。【———— 百度百科】


那么什么是 NOSQL 呢? NoSQL(NoSQL = Not Only SQL) 意为:不仅仅是 SQL ,是一项全新的数据库理念,泛指 非关系型数据库NOSQL 出现原因: 随着互联网 web2.0 网站的兴起,传统的关系数据库在应付 web2.0 网站,特别是超大规模和高并发的 SNS 类型的 web2.0 纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL 数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

Redis 优缺点

NOSQL 和关系型数据库比较,异同点如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+++ Redis 优点:
+ 1)成本:NOSQL 数据库简单易部署,基本都是开源软件,不需要像使用 oracle 那样花费大量成本购买使用,相比关系型数据库价格便宜。
+ 2)查询速度:NOSQL 数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及 NOSQL 数据库。
+ 3)存储数据的格式:NOSQL 的存储格式是 key=value 形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
+ 4)扩展性:关系型数据库有类似 join 这样的多表查询机制的限制导致扩展很艰难。
+ 5)是否区分大小写:Redis 不区分大小写。
+ 6)瓶颈:Redis 是单线程的,所以 Redis 的瓶颈不是 CPU ,而是机器内存和网络带宽。

+++ Redis 缺点:
- 1)维护的工具和资料有限,因为 NOSQL 是属于新的技术,不能和关系型数据库 10 几年的技术同日而语。
- 2)不提供对sql的支持,如果不支持 SQL 这样的工业标准,将产生一定用户的学习和使用成本。
- 3)不提供关系型数据库对事务的处理。(有些支持,有些不支持)


+++ 总结如下:
+ 1)关系型数据库与 NoSQL 数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用 NoSQL 的时候使用 NoSQL 数据库。
+ 2)让 NoSQL 数据库对关系型数据库的不足进行弥补。
+ 3)一般会将数据存储在关系型数据库中,在 NoSQL 数据库中备份存储关系型数据库的数据

Redis 应用场景

Redis 是用 C 语言开发的一个开源的 高性能键值对(key-value)数据库 ,官方提供测试数据,50 个并发执行 100000 个请求,读的速度是 110000 次 / s ,写的速度是 81000 次 / s


且 Redis 通过提供多种键值数据类型来适应不同场景下的存储需求。目前为止 , Redis 支持的键值数据类型 如下:

Redis 支持的键值数据类型:
1、字符串类型 string 2、哈希类型 hash 3、列表类型 list 4、集合类型 set 5、有序集合类型 sortedset
Redis 的应用场景:
1、缓存(数据查询、短连接、新闻内容、商品内容等等) 2、聊天室的在线好友列表 3、任务队列。(秒杀、抢购、12306 等等) 4、应用排行榜 5、网站访问统计 6、数据过期处理(可以精确到毫秒 7、分布式集群架构中的 session 分离 8、发布订阅系统 9、地图信息分析
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
+++ 主流的 NOSQL 产品

+++ 键值(Key-Value)存储数据库
相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
数据模型: 一系列键值对
优势: 快速查询
劣势: 存储的数据缺少结构化


+++ 列存储数据库
相关产品:Cassandra, HBase, Riak
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限


+++ 文档型数据库
相关产品:CouchDB、MongoDB
典型应用:Web应用(与 Key-Value 类似,Value 是结构化的)
数据模型: 一系列键值对
优势:数据结构要求不严格
劣势: 查询性能不高,而且缺乏统一的查询语法


+++ 图形(Graph)数据库
相关数据库:Neo4J、InfoGrid、Infinite Graph
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。

Redis 下载安装

Redis 的下载:

1、Redis 官网下载链接:Redis 官网 【下载的是 Liunx 版本,不是 windows 版本】
2、Redis 中文网下载链接:Redis 中文网 【下载的是 Liunx 版本,不是 windows 版本】
3、GitHub 下载链接:Redis 的 GitHub 地址 【建议使用,各种版本都有,这个是微软的,不是 Redis 的】
4、本博客站点下载:redis-2.8.9.zip 【建议使用,windows 版本的】

下载完成之后,解压即可使用 。解压之后如下图:

那么如何使用 Redis 呢? 双击打开 redis-server.exe ,如下图:

然后 双击打开 redis-cli.exe 即可使用,如下图:

Redis 配置文件详解

这个是 windows 下的配置文件【linux 的在 Redis 高级部分】,具体解释如下:

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

# 指定 Redis 运行的端口,默认是 6379
port 6379

# 设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么就关闭该连接。0 代表关闭此设置
timeout 0

tcp-keepalive 0

# 当 Redis 在后台运行的时候,Redis 默认会把 pid 文件放在 /var/run/redis.pid ,你也可以配置到其他地址。当运行多个 Redis 服务时,需要指定不同的 pid 文件和端口
pidfile /var/run/redis.pid

# 指定日志记录级别。Redis 总共支持四个级别:debug、verbose、notice、warning,默认为 notice
# debug 记录很多信息,用于开发和测试。varbose 有用的信息,不像 debug 会记录那么多。notice 就是普通的 verbose ,常用于生产环境。warning 只有非常重要或者严重的信息会记录到日志
loglevel notice

# 默认值为 stdout ,标准输出,若后台模式会输出到 /dev/null
logfile stdout

# 代表 Redis 默认有 16 个数据库,默认使用第 0 个数据库。如果需要切换,那就进入到客户端,使用命令:select n (n 代表第 n + 1 个数据库,n 最大为 15 )
databases 16


# daemonize no 默认情况下,redis不是在后台运行的,如果需要在后台运行,把该项的值更改为yes
daemonize yes

save 900 1
save 300 10
save 60 10000

stop-writes-on-bgsave-error yes

# 在启动 redis 时是否显示 logo
always-show-logo yes

rdbcompression yes

rdbchecksum yes

dbfilename dump.rdb

dir ./

slave-serve-stale-data yes

slave-read-only yes

repl-disable-tcp-nodelay no

slave-priority 100

appendonly no

appendfsync everysec

no-appendfsync-on-rewrite no

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

lua-time-limit 5000

slowlog-log-slower-than 10000

slowlog-max-len 128

hash-max-ziplist-entries 512
hash-max-ziplist-value 64

list-max-ziplist-entries 512
list-max-ziplist-value 64

set-max-intset-entries 512

zset-max-ziplist-entries 128
zset-max-ziplist-value 64

activerehashing yes

client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

hz 10

aof-rewrite-incremental-fsync yes

相关操作命令

TIPS:

Redis 中文网教程: Redis 教程
Redis 的官方文档: Redis 文档

redis 数据结构

redis 存储的是 key-value 格式 的数据。其中 key 都是字符串 ,而 value 却有 5 种不同的数据结构 ,如下图:

通用命令

通用命令 的相关操作以及截图如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+++ 通用命令

+ keys * 查询所有的键
+ type key 获取键对应的 value 的类型

- del key 删除指定的 key
- flushdb 清空当前数据库
- flushall 清空所有数据库
- clear 清空屏幕
- expire key n 表示 key 在 n 秒后会过期。例如:expire name 10 表示 name 这个键在 10 秒后会过期

+ dbsize 查看当前数据库 key 的数量
+ select 8 切换到第 9 个数据库,n 最大值为 15
+ exist key 判断某个键是否存在。例如:exist name 表示判断 name 这个键是否存在

+++ 通用命令


字符串类型

字符串类型 的常用命令相关操作以及截图如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+++ 字符串类型 string

+ 存储: set key value 举例:127.0.0.1:6379> set age 23
+ 追加: append key value 举例:127.0.0.1:6379> append age 24
+ 获取: get key 举例:127.0.0.1:6379> get age

- 删除: del key 举例:127.0.0.1:6379> del age

+ 长度: strlen key 举例:127.0.0.1:6379> strlen age
+ 截取: getrange key start end 举例:127.0.0.1:6379> getrange age 0 -1
+ 替换: setrange key index value 举例:127.0.0.1:6379> setrange age 0 G


+++ 字符串类型 string

字符串类型 的高级操作,这里只做简单介绍,更多详情介绍请 参阅官网 。高级命令如下:

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
# setnx 命令: Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。
127.0.0.1:6379> SETNX KEY_NAME VALUE

# mset 命令: Mset 命令用于同时设置一个或多个 key-value 对
127.0.0.1:6379> MSET key1 value1 key2 value2 .. keyN valueN

# setex 命令: Setex 为指定的 key 设置值及其过期时间。如果 key 已经存在, SETEX 命令将会替换旧的值。
127.0.0.1:6379> SETEX KEY_NAME TIMEOUT VALUE

# incr 命令: Incr 命令将 key 中储存的数字值增一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。注意 key 的值必须是 integer 类型的,就是整型
127.0.0.1:6379> INCR KEY_NAME

# incrby 命令: 对 key 的值自动加 n,类似于 i += n
127.0.0.1:6379> INCRBY KEY_NAME INCR_AMOUNT

# decr 命令: Decr 命令将 key 中储存的数字值减一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECR 操作。注意 key 的值必须是 integer 类型的,就是整型
127.0.0.1:6379> DECR KEY_NAME

# decrby 命令: 将 key 所储存的值减去指定的减量值。意思就是对 key 的值自动加 n,类似于 i += n 。
127.0.0.1:6379> DECRBY KEY_NAME DECREMENT_AMOUNT

# set 命令存储 json 特殊用法
127.0.0.1:6379> set user:1 {name:zhangsan,age:23}

# getset 命令: Getset 命令用于设置指定 key 的值,并返回 key 旧的值。
127.0.0.1:6379> GETSET KEY_NAME VALUE

哈希类型

哈希类型 的相关操作以及截图如下:

1
2
3
4
5
6
7
+++ 哈希类型 hash

+ 存储: hset key field value 举例:127.0.0.1:6379> hset myhash username guo
+ 获取: hget key field 举例:127.0.0.1:6379> hget myhash username
- 删除: hdel key field 举例:127.0.0.1:6379> hdel myhash username

+++ 哈希类型 hash

列表类型

列表类型 的相关操作以及截图如下:

列表类型示意图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+++ 列表类型 list :可以添加一个元素到列表的头部(左边)或者尾部(右边)。注意:列表类型里面的元素允许重复

+ 向左添加: lpush key value 举例:127.0.0.1:6379> lpush mylist a
+ 向右添加: rpush key value 举例:127.0.0.1:6379> rpush mylist c
+ 范围获取: lrange key start end 举例:127.0.0.1:6379> lrange mylist 0 -1
+ 指定获取: lindex key index 举例:127.0.0.1:6379> lindex mylist 1
+ 列表长度: llen key 举例:127.0.0.1:6379> llen mylist
+ 列表修剪: ltrim key start stop 举例:127.0.0.1:6379> ltrim mylist 0 1

- 向左删除: lpop key 举例:127.0.0.1:6379> lpop mylist
- 向右删除: rpop key 举例:127.0.0.1:6379> rpop mylist
- 移除元素: lrem key count value 举例:127.0.0.1:6379> lrem mylist 2 Java

+++ 列表类型 list

集合类型

集合类型 的相关操作以及截图如下:

1
2
3
4
5
6
7
8
9
10
11
+++ 集合类型 set  注意:集合类型里面的元素不允许重复且不保证顺序

+ 添加: sadd key value 举例:127.0.0.1:6379> sadd myset a
+ 获取: smembers key 举例:127.0.0.1:6379> smembers myset
+ 交集: sinter key1 key2 ...keyn 举例:127.0.0.1:6379> sinter myset myset2
+ 并集: sunion key1 key2 ...keyn 举例:127.0.0.1:6379> sunion myset myset2

- 删除: srem key value 举例:127.0.0.1:6379> srem myset a
- 差集: sdiff key1 key2 ...keyn 举例:127.0.0.1:6379> sdiff myset myset2

+++ 集合类型 set

有序集合类型

有序集合类型 的相关操作以及截图如下:

1
2
3
4
5
6
7
8
+++ 有序集合类型 sortedset  注意:不允许重复元素,且元素有顺序。
+++ 每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。

+ 添加: zadd key score value 举例:127.0.0.1:6379> zadd mysort 60 Jack
+ 获取: zrange key start end [withscores] 举例:127.0.0.1:6379> zrange mysort 0 -1 或者 zrange mysort 0 -1 withscores
- 删除: zrem key value 举例:127.0.0.1:6379> zrem mysort Jack

+++ 有序集合类型 sortedset

Redis 的持久化

持久化简介

什么是持久化呢? 因为 Redis 是一个内存数据库,当 Redis 服务器重启或者电脑重启,那么数据就会丢失。这个时候我们就要进行持久化操作,将 Redis 内存中的数据持久化保存到硬盘的文件中。

持久化机制

接下来介绍一下 Redis 的持久化机制。Redis 持久化机制有两种:RDB 机制AOF 机制 。具体详情介绍如下:


RDB 机制: 这是 Redis 持久化的默认方式,不需要进行配置,默认就使用这种机制。这种机会在一定的间隔时间中,检测 key 的变化情况,然后持久化数据。接下来我们实际操作一下,看一下这种机制如何实现。


第一步: 编辑 Redis 安装目录下的 redis.windwos.conf 配置文件,找到如下内容,然后修改一下配置,把 save 60 10000 注释掉,修改为 save 10 5 。这里的修改只是用于测试,测试完成之后改回原来的数据。修改如下:

redis.windwos.conf
1
2
3
4
5
6
7
8
9
# after 900 sec (15 min) if at least 1 key changed      中文意思是:15 分钟内,如果有 1 个 key 改变,那么就进行持久化
# after 300 sec (5 min) if at least 10 keys changed 中文意思是:5 分钟内,如果有 10 个 key 改变,那么就进行持久化
# after 60 sec if at least 10000 keys changed 中文意思是:1 分钟内,如果有 10000 个 key 改变,那么就进行持久化

save 900 1
save 300 10
# save 60 10000
# 10 秒内,如果有 5 个 key 改变,那么就进行持久化,即会在 Redis 安装目录生成一个以 rdb 为后缀的文件
save 10 5

第二步: 以指定配置文件名称的方式重新启动 Redis 服务器,启动过程如下图:


第三步: 测试 RDB 机制,看是否生成以 rdb 为后缀的持久化文件。过程如下图:


第四步: 以指定配置文件名称的方式重启 Redis 服务器,看里面是否还有数据。如下图:

温馨小提示: 记得把修改了的配置文件修改回去嗷!!!


接下来,我们介绍 AOF 机制: 日志记录的方式,可以记录每一条命令的操作。可以在每一次命令操作完成之后就持久化数据。那么现在就来操作一波吧!第一步: 编辑 redis.windwos.conf 配置文件,找到 appendonly no (关闭 aof)(在 392 行) ,然后将其修改为 appendonly yes (开启 aof)。修改好后保存。以下配置了解一下,不需要修改:

1
2
3
4
5
# 以下三行配置在 420 行,对应的功能都写好了

# appendfsync always 每一次操作都进行持久化
appendfsync everysec # 每隔一秒进行一次持久化
# appendfsync no 不进行持久化

第二步: 以指定配置文件名称的方式重新启动 Redis 服务器,启动过程如下图:


第三步: 测试 AOF 机制,看是否生成以 aof 为后缀的持久化文件。过程如下图:


第四步: 以指定配置文件名称的方式重启 Redis 服务器,看里面是否还有数据。如下图:

两种持久化机制不足:

RDB 机制: 数据可能无法完全保存,存在一些数据的丢失。
AOF 机制: 对性能影响比较严重,耗内存。

Jedis 快速入门

Jedis 简介

Jedis 是一款 Redis 官方推荐 java 操作 redis 数据库的工具,与 JDBC 很类似


使用步骤:

  • 第一步: 下载 jedis 的 jar 包 ,官方下载 或者 本博客下载
  • 第二步: 新建 JavaEE 项目,然后添加 jedis 的 jar 包。
  • 第三步: 启动 redis 服务器 redis-server.exe 。
  • 第四步: 编写 Java 程序对 redis 进行操作。
  • 第五步: 启动 redis 的客户端 redis-cli.exe 进行结果查看。

Jedis 的使用

第一步: 新建一个 JavaEE 项目【如果新建 Maven 项目会更简单,只要导入依赖即可】。如下图:




第二步: 启动 redis 服务器。找到自己 redis 安装目录,双击 redis-server.exe 即可启动。如下图:

第三步: 编写 Java 程序对 redis 进行操作。相关代码如下:

JedisTest.class
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
package jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 15:39
* @Description: Jedis 测试类
*/
public class JedisTest {

@Test
public void jedisTest() {

// 1、获取连接。如果使用空参构造,那么默认值就是 "localhost",6379
Jedis jedis = new Jedis("localhost", 6379);
// 2、进行操作
jedis.set("username", "guoshizhan");
// 3、关闭连接
jedis.close();

}

}

第四步: 启动 redis 的客户端 redis-cli.exe 进行结果查看。如下图:

Jedis 的各种操作

温馨小提示:

1、Jedis 里面的各种方法和 Redis 里面的各种命令是相互对应的,所以操作起来相对简单。
2、Jedis 里面也会有一些特殊的方法,这些方法都将在代码里体现。

第一种操作: Jedis 操作 redis 中的字符串类型 。代码如下:

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
package jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 15:39
* @Description: Jedis 测试类
*/
public class JedisTest {

@Test
public void jedisTest() {

// 1、获取连接
Jedis jedis = new Jedis("localhost", 6379);

// 2、进行操作
jedis.set("username", "guoshizhan");
jedis.set("age", "22");

String str = jedis.get("age");
System.out.println(str);

// setex() 方法存储可以指定时间过期的 key-value
// 以下代码表示把 activeCode:039752 键值对存入 Redis ,30 秒之后过期
jedis.setex("activeCode", 30, "039752");

// 3、关闭连接
jedis.close();

}

}

代码的执行结果如下图:


第二种操作: Jedis 操作 redis 中的哈希类型 。代码如下:

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
package jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.Map;
import java.util.Set;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 15:39
* @Description: Jedis 测试类
*/
public class JedisTest {

@Test
public void jedisTest() {

// 1、获取连接
Jedis jedis = new Jedis("localhost", 6379);

// 2、进行操作:存储 hash
jedis.hset("user", "username", "guoshizhan");
jedis.hset("user", "address", "HangZhou");
jedis.hset("user", "gender", "male");

// 3、获取 hash
String username = jedis.hget("user", "username");
System.out.println(username);

Map<String, String> stringMap = jedis.hgetAll("user");
Set<String> keySet = stringMap.keySet();
for (String key : keySet) {
String value = stringMap.get(key);
System.out.println(key + "---" + value);
}

// 4、关闭连接
jedis.close();

}

}

代码的执行结果如下图:


第三种操作: Jedis 操作 redis 中的列表类型 。代码如下:

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
package jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 15:39
* @Description: Jedis 测试类
*/
public class JedisTest {

@Test
public void jedisTest() {

// 1、获取连接
Jedis jedis = new Jedis("localhost", 6379);

// 2、进行操作:存储 list
jedis.lpush("myList", "a", "b", "c");
jedis.rpush("myList", "a", "b", "c");

// 3、范围获取
List<String> myList = jedis.lrange("myList", 0, -1);
System.out.println(myList);

// 4、删除操作
String eleLeft = jedis.lpop("myList"); // 从左边删除
System.out.println(eleLeft);
String eleright = jedis.rpop("myList"); // 从右边删除
System.out.println(eleright);
System.out.println(jedis.lrange("myList", 0, -1));

// 5、关闭连接
jedis.close();

}

}

代码的执行结果如下图:

第四种操作: Jedis 操作 redis 中的集合类型 。代码如下:

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
package jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.Set;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 15:39
* @Description: Jedis 测试类
*/
public class JedisTest {

@Test
public void jedisTest() {

// 1、获取连接
Jedis jedis = new Jedis("localhost", 6379);

// 2、进行操作:存储 set ,不重复存储且无序
jedis.sadd("mySet", "Java", "C++", "HTML", "Java");
Set<String> mySet = jedis.smembers("mySet");
System.out.println(mySet);

// 3、关闭连接
jedis.close();

}

}

代码的执行结果如下图:

第五种操作: Jedis 操作 redis 中的有序集合类型 。代码如下:

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 jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.Set;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 15:39
* @Description: Jedis 测试类
*/
public class JedisTest {

@Test
public void jedisTest() {

// 1、获取连接
Jedis jedis = new Jedis("localhost", 6379);

// 2、进行操作:存储 set ,不重复存储且无序
jedis.zadd("sortedSet", 9, "典韦");
jedis.zadd("sortedSet", 7, "亚瑟");
jedis.zadd("sortedSet", 5, "白起");

Set<String> sortedSet = jedis.zrange("sortedSet", 0, -1);
System.out.println(sortedSet);

// 3、关闭连接
jedis.close();

}

}

代码的执行结果如下图:

Jedis 连接池

Jedis 有直连方式,所谓直连指的是 Jedis 每次都会新建 TCP 连接,使用后再断开连接,对于频繁访问 Redis 的场景显然不是高效的使用方式。因此, Jedis 连接池 JedisPool 诞生了。 当我们需要连接 Redis 时,只需要向 JedisPool 池子中借,用完了再归还给 JedisPool 池子即可。


使用步骤:

  • 第一步: 下载 jedis 的 jar 包 ,把 commons-pool2-2.3.jar 加入到项目的 lib 文件夹中。jar 包下载: 本博客下载
  • 第二步: 编写相关代码:1、创建 JedisPool 连接池对象;2、调用方法 getResource() 方法获取 Jedis 连接。
  • 第三步: 查看结果。

第一步 的操作如下图:

第二步 的具体代码如下:

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
package jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 15:39
* @Description: Jedis 测试类
*/
public class JedisTest {

@Test
public void jedisPoolTest() {

// 1、创建 Jedis 连接池对象
JedisPool jedisPool = new JedisPool();

// 2、获取连接
Jedis jedis = jedisPool.getResource();

// 3、进行使用
jedis.set("jedisPool", "jedisPool");

// 4、关闭,就是把 jedis 对象归还到 jedisPool 连接池中
jedis.close();

}

}

代码的执行结果如下图:

Jedis 连接池工具类

感觉使用 Jedis 连接池创建对象比较麻烦,那我们就来写一个 Jedis 连接池工具类吧!开始撸码 ing ……


第一步:src 目录 下新建 utils 包 和 jedis 配置文件 jedis.properties 。如下图:

第二步: 编写 Jedis 配置文件 jedis.properties 和 Jedis 连接池工具类 JedisPoolUtils 。配置文件代码如下:

jedis.properties
1
2
3
4
5
6
7
8
9
10
11
# 主机地址,可以是服务器地址,也可以是本地
host=127.0.0.1

# 端口号
port=6379

# 最大连接数
maxTotal=50

# 最大空闲连接数
maxIdle=10

Jedis 连接池工具类代码如下:

JedisPoolUtils.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
package utils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 22:02
* @Description: Jedis 连接池工具类,用于加载连接池的配置文件,降低程序的耦合性
*/
public class JedisPoolUtils {

private static JedisPool jedisPool;

/**
* 定义静态代码块,当这个类一加载,它就去读取连接池的配置文件
*/
static {
// 1、读取配置文件
InputStream inputStream = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");

// 2、创建 properties 对象
Properties properties = new Properties();

// 3、关联连接池配置文件
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}

// 4、获取数据,设置到 JedisPoolConfig 中
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.parseInt(properties.getProperty("maxTotal")));
config.setMaxIdle(Integer.parseInt(properties.getProperty("maxIdle")));

// 5、初始化 jedisPool
jedisPool = new JedisPool(config, properties.getProperty("host"), Integer.parseInt(properties.getProperty("port")));
}

/**
* 获取连接的方法
*/
public static Jedis getJedis() {
return jedisPool.getResource();
}

}

第三步: 编写测试类代码。具体代码如下:

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
package jedis;

import org.junit.Test;
import redis.clients.jedis.Jedis;
import utils.JedisPoolUtils;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 15:39
* @Description: Jedis 测试类
*/
public class JedisTest {

@Test
public void jedisPoolTest() {

// 1、jedis 连接池工具类的使用
Jedis jedis = JedisPoolUtils.getJedis();

// 2、进行操作
jedis.set("jedisPoolUtils", "jedisPoolUtils");

// 3、关闭,就是把 jedis 对象归还到连接池中
jedis.close();

}

}

第四步: 查看测试结果。如下图:

附赠:jedis 的详细配置一览:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 最大活动对象数     
redis.pool.maxTotal=1000
# 最大能够保持idel状态的对象数
redis.pool.maxIdle=100
# 最小能够保持idel状态的对象数
redis.pool.minIdle=50
# 当池内没有返回对象时,最大等待时间
redis.pool.maxWaitMillis=10000
# 当调用borrow Object方法时,是否进行有效性检查
redis.pool.testOnBorrow=true
# 当调用return Object方法时,是否进行有效性检查
redis.pool.testOnReturn=true
# “空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.
redis.pool.timeBetweenEvictionRunsMillis=30000
# 向调用者输出“链接”对象时,是否检测它的空闲超时;
redis.pool.testWhileIdle=true
# 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3.
redis.pool.numTestsPerEvictionRun=50
# redis 服务器的 IP
redis.ip=xxxxxx
# redis 服务器的 Port
redis1.port=6379

Redis 案例

案例准备


案例需求: 提供 index.html 页面,页面中有一个省份【下拉列表】。当页面加载完成后,发送 ajax 请求,加载所有省份。
本案例所用资源下载: 案例资源下载(包含了代码部分)


第一步: 新建数据库和数据表。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE DATABASE RedisTest;    -- 创建数据库
USE RedisTest; -- 使用数据库

CREATE TABLE province( -- 创建表
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL

);

-- 插入数据
INSERT INTO province VALUES(NULL,'北京');
INSERT INTO province VALUES(NULL,'上海');
INSERT INTO province VALUES(NULL,'广州');
INSERT INTO province VALUES(NULL,'陕西');

第二步: 添加各种 jar 包到 lib 文件夹下【案例资源上面已经给了】。添加之后如下图:

第三步: 添加数据库配置文件 druid.properties 到 src 目录下,并完善包结构。如下图:

第四步: 导入 js 文件到 web 目录下。如图所示:

第五步:utils 包 下新建 JDBCUtilsJedisPoolUtils 两个工具类。对应代码如下:

utils/JDBCUtils.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
package utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
* JDBC工具类:使用 Durid 连接池
*/
public class JDBCUtils {

private static DataSource ds ;

static {
try {
// 1、加载配置文件
Properties properties = new Properties();
// 使用 ClassLoader 加载配置文件,获取字节输入流
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(is);

// 2、初始化连接池对象
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 获取连接池对象
*/
public static DataSource getDataSource(){
return ds;
}


/**
* 获取连接 Connection 对象
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}

}
utils/JedisPoolUtils.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
package utils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 22:02
* @Description: Jedis 连接池工具类,用于加载连接池的配置文件,降低程序的耦合性
*/
public class JedisPoolUtils {

private static JedisPool jedisPool;

/**
* 定义静态代码块,当这个类一加载,它就去读取连接池的配置文件
*/
static {
// 1、读取配置文件
InputStream inputStream = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");

// 2、创建 properties 对象
Properties properties = new Properties();

// 3、关联连接池配置文件
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}

// 4、获取数据,设置到 JedisPoolConfig 中
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.parseInt(properties.getProperty("maxTotal")));
config.setMaxIdle(Integer.parseInt(properties.getProperty("maxIdle")));

// 5、初始化 jedisPool
jedisPool = new JedisPool(config, properties.getProperty("host"), Integer.parseInt(properties.getProperty("port")));
}

/**
* 获取连接的方法
*/
public static Jedis getJedis() {
return jedisPool.getResource();
}

}

案例编码


第一步:domain 包 下新建一个 Province 类 ,和数据库 province 表相对应。代码如下:

domain/Province.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
package domain;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 23:33
* @Description: Province实体类
*/
public class Province {

private int id;
private String name;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

第二步:dao 包 下新建一个 ProvinceDao 接口 ,定义相关操作方法。代码如下:

dao/ProvinceDao.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package dao;

import domain.Province;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 23:35
* @Description: ProvinceDao 接口
*/
public interface ProvinceDao {

// 查询所有省份
public List<Province> findAll();

}

然后在 dao/impl 包 下新建 ProvinceDaoImpl 实现类 ,用于实现 ProvinceDao 接口。代码如下:

dao/impl/ProvinceDaoImpl.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
package dao.impl;

import dao.ProvinceDao;
import domain.Province;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import utils.JDBCUtils;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 23:38
* @Description: ProvinceDao 的实现类
*/
public class ProvinceDaoImpl implements ProvinceDao {

private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());

@Override
public List<Province> findAll() {

// 1、定义查询所有的 sql
String sql = "select * from province";

// 2、执行 sql
List<Province> list = template.query(sql, new BeanPropertyRowMapper<>(Province.class));

// 3、返回 list 集合
return list;

}

}

第三步:service 包 下新建一个 ProvinceService 接口 ,定义相关操作方法。代码如下:

service/ProvinceService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package service;

import domain.Province;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 23:40
* @Description: ProvinceService
*/
public interface ProvinceService {

// 查询所有,与 Redis 无关
public List<Province> findAll();

// 使用 Redis 会用到这个方法
public String findAllJson();

}

然后在 service/impl 包 下新建 ProvinceServiceImpl 实现类 ,用于实现 ProvinceService 接口。代码如下:

service/impl/ProvinceServiceImpl.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
package service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dao.ProvinceDao;
import dao.impl.ProvinceDaoImpl;
import domain.Province;
import redis.clients.jedis.Jedis;
import service.ProvinceService;
import utils.JedisPoolUtils;

import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 23:40
* @Description: ProvinceService 实现类
*/
public class ProvinceServiceImpl implements ProvinceService {

private ProvinceDao dao = new ProvinceDaoImpl();

@Override
public List<Province> findAll() {
return dao.findAll();
}

/**
* 使用 Redis 作为缓存
* @return 返回字符串类型
*/
@Override
public String findAllJson() {
// 1、获取连接
Jedis jedis = JedisPoolUtils.getJedis();
String province_json = jedis.get("province");

// 2、判断 province_json 是否为 null
if (province_json == null || province_json.length() == 0) {
System.out.println("Redis 中没有数据,正在查询数据库……");

List<Province> list = dao.findAll();
ObjectMapper mapper = new ObjectMapper();
try {
province_json = mapper.writeValueAsString(list);
} catch (JsonProcessingException e) {
e.printStackTrace();
}

jedis.set("province", province_json);
jedis.close();
} else {
System.out.println("Redis 中有数据,正在查询缓存……");
}

// 3、返回数据
return province_json;
}

}

第四步:web/servlet 包 下新建一个 ProvinceServlet 类 ,用于 Ajax 请求和页面数据展示 。代码如下:

web/servlet/ProvinceServlet.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
package web.servlet;

import com.fasterxml.jackson.databind.ObjectMapper;
import domain.Province;
import service.ProvinceService;
import service.impl.ProvinceServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 23:56
* @Description: Servlet
*/
@WebServlet("/provinceServlet")
public class ProvinceServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

// 1、调用 service 查询
ProvinceService service = new ProvinceServiceImpl();
List<Province> list = service.findAll();

// 2、序列化 list 为 json 数据
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(list);

// 3、响应结果
System.out.println(json);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}

接着在 web 目录下新建 index.html 页面,代码如下:

web/index.html
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/jquery-3.3.1.min.js"></script>
<script>
$(function () {
// 发送 ajax 请求,加载所有省份数据
$.get("provinceServlet", {}, function (data) {
// 1、获取 select
var province = $("#province");
// 2、遍历 json 数组
$(data).each(function () {
// 3、创建 <option>
var option = "<option name='" + this.id + "'>" + this.name + "</option>";
// 4、调用 select 的 append 追加 option
province.append(option);
});
});
});
</script>
</head>
<body>
<select id="province">
<option>---请选择省份---</option>
</select>
</body>
</html>

第五步: 启动 tomcat 查看结果。如下图:


使用 Redis 作为缓存

使用 Redis 可以减少数据库的交互,从而提高了效率。那么该如何操作呢?


第一步: 使用以下代码覆盖原先的 ProvinceServlet 类 。代码如下:

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
package web.servlet;

import service.ProvinceService;
import service.impl.ProvinceServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @Author: guoshizhan
* @Create: 2020/7/21 23:56
* @Description: Servlet
*/
@WebServlet("/provinceServlet")
public class ProvinceServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

// 1、调用 service 查询,这部分是与 Redis 相关的代码
ProvinceService service = new ProvinceServiceImpl();
String json = service.findAllJson();

// 2、响应结果
System.out.println(json);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}

第二步: 启动 tomcat ,查看测试结果。如下图:

进入这个页面后,多刷新几次页面,方便在控制台看到输出

TIPS: 到此为止,Redis 入门教程就结束了。接着往下看 Redis 进阶教程。

Redis 进阶

Redis 在 Linux 下的安装

Redis 官方 更推荐我们在 Linux 下使用 Redis ,而不是在 windows 系统下,这样才更符合企业级的开发。现在就来介绍 Redis 在 Linux 下的安装。


第一步: 下载 Redis 的 Linux 版本。 下载地址 如下图:

当然,也可以 使用命令下载 。如下:

1
2
3
4
5
# 01-如果没有安装 wget ,那就使用下列命令安装。如果安装了,可跳过这一步
yum install wget

# 02-下载 redis-6.0.6.tar.gz 压缩包。下面的这条命令官方有
wget http://download.redis.io/releases/redis-6.0.6.tar.gz

第二步: 解压 Redis 压缩包。【如果下载在 windows 系统了,还需要把压缩包上传到 Liunx 云服务器或者是虚拟机,这个自行解决,不难】解压缩的命令和结果如下图:

第三步: 编译 Redis 。 需要用到如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 01-安装 gcc 环境,如果安装过,可跳到 03 步骤【凡是涉及到安装的,都需要网络,没网络就完成不了了】
yum install gcc-c++

# 02-检查 gcc 是否安装成功以及查看版本
gcc -v

# 03-进入到刚刚解压出来的目录
cd redis-5.0.4

# 04-使用 make 命令编译。需要点时间,大概 1~2 分钟
make

# 05-进入到 src 目录
cd src

# 06-执行安装
make install

TIPS:

在 make 命令执行完后 , redis-5.0.4 目录下会出现编译后的 redis 服务程序 redis-server 和用于测试的客户端程序 redis-cli两个程序位于 redis-5.0.4/src 目录下。


第四步: 启动 Redis【不是后台启动,需要使用 CTRL + C 才能停止服务】。 需要用到如下命令:

1
2
3
4
5
# 01-进入到 src 目录
cd src

# 02-启动 Redis 服务端
./redis-server ../redis.conf

第五步: 配置后台启动,并存一个数据。 需要用到如下命令:

1
2
3
4
5
6
7
8
9
10
11
# 01-编辑配置文件
vi redis.conf

# 02-找到 daemonize ,把它的值改为 yes ,然后保存退出
daemonize yes

# 03-启动 Redis 服务端
./redis-server ../redis.conf

# 04-启动 Redis 客户端
./redis-cli -p 6379

对应的截图如下:


第六步: 查看 Redis 进程和关闭 Redis 。 需要用到如下命令:

1
2
3
4
5
6
7
8
# 01-查看 Redis 进程
ps -ef|grep redis

# 02-关闭 Redis 进程,不明白就看下列图片
shutdown

# 03-退出 Redis ,不明白就看下列图片
exit

Redis 性能测试

redis-benchmark 是一个官方自带的 性能测试工具 ,与 redis-server 一样,都位于 Redis 的安装目录 src 下 。如下图:

这个性能测试工具有一些可选参数【来源于:菜鸟教程 ,如下图:

那么我们如何使用这个测试工具呢?我们又该如何使用这些可选参数呢?使用命令如下:

1
2
3
4
5
6
7
# 01-进入到 Redis-server 所在目录,启动 Redis 服务端
./redis-server ../redis.conf

# 02-使用 redis-benchmark 测试 100 个并发连接 10 万个请求
./redis-benchmark -h localhost -p 6379 -c 100 -n 100000

# 03-如果还需要测试其他参数,请自行按照上述样式进行测试即可,这里就不作过多的测试了

测试结果以及对结果的分析如下图:

测试结果
对部分结果进行分析

三种特殊数据类型

geospatial-地理位置

有没有想过,微信定位,附近的人,打车距离计算等功能 是如何实现的?用 geospatial 就可以实现。这种数据类型是 根据经纬度计算两地之间的距离 【直线距离,两个极点除外】


那我们该如何去使用呢?先查询好经纬度:经纬度查询 ,然后进行各种操作。如下图:

小提示:

我们至少需要两个点才能进行操作。越多越好。以下是我准备的几个城市的经纬度:

1
2
3
4
5
6
7
8
9
10
11
+++ 准备好的城市经纬度

+ 北京: 经度: 116.23128 纬度: 40.22077
+ 上海: 经度: 121.48941 纬度: 31.40527
+ 佛山: 经度: 112.89262 纬度: 22.90026
+ 广州: 经度: 113.27324 纬度: 23.15792
+ 深圳: 经度: 113.88308 纬度: 22.55329
+ 成都: 经度: 104.10194 纬度: 30.65984
+ 重庆: 经度: 106.54041 纬度: 29.40268

+++ 准备好的城市经纬度

有了上述的经纬度之后,我们就可以使用命令进行操作了。 其实手动添加数据是真的烦,如果要添加 1000 个城市甚至更多,那我们岂不是要累死了。所以,我们可以自己通过写 Java 程序,去读取一个相关的配置文件,然后把多个城市或者全国的城市的坐标添加进去。【这个就自己实现了哈!!!】接下来开始命令操作吧!!!

GEOADD

官方介绍: GEOADD 命令 将指定的地理空间位置 (经度、纬度、名称) 添加到指定的 key 中。这些数据将会存储到 sorted set【所以 geospatial 它的底层就是有序集合】。这样的目的是为了方便使用 GEORADIUS 或者 GEORADIUSBYMEMBER 命令对数据进行半径查询等操作。

注意事项:

1、有效的经度 从 -180 度到 180 度
2、有效的纬度 从 -85.05112878 度到 85.05112878 度 。【所以两个极点是没有坐标的】
3、当坐标位置超出上述指定范围时,该命令将会返回一个错误。

1
2
3
4
5
6
7
8
9
10
11
+++ 使用 GEOADD 命令添加地理位置

+ 127.0.0.1:6379> geoadd ChinaCity 116.23 40.22 BeiJing
+ 127.0.0.1:6379> geoadd ChinaCity 121.48 31.41 ShangHai
+ 127.0.0.1:6379> geoadd ChinaCity 112.89 22.90 FoShan
+ 127.0.0.1:6379> geoadd ChinaCity 113.27 23.15 GuangZhou
+ 127.0.0.1:6379> geoadd ChinaCity 113.88 22.55 ShenZhen
+ 127.0.0.1:6379> geoadd ChinaCity 104.10 30.66 ChengDu
+ 127.0.0.1:6379> geoadd ChinaCity 106.54 29.40 ChongQing

+++ 使用 GEOADD 命令添加地理位置

到这为止,添加操作完成了。【 GEOADD 命令 是不是挺简单的,哈哈】如下图:

GEOPOS

官方介绍: GEOPOS 命令 从 key 里返回所有给定位置元素的位置(经度和纬度)。返回值: GEOPOS 命令 返回一个数组, 数组中的每个项都由两个元素组成: 第一个元素为给定位置元素的经度, 而第二个元素则为给定位置元素的纬度。当给定的位置元素不存在时, 对应的数组项为空值。

1
2
3
4
5
6
+++ GEOPOS 获取位置命令

+ 127.0.0.1:6379> geopos ChinaCity BeiJing
+ 127.0.0.1:6379> geopos ChinaCity ShangHai FoShan

+++ GEOPOS 获取位置命令

到这为止,获取操作完成了。【 GEOPOS 命令 是不是也挺简单的,哈哈】如下图:

GEODIST

官方介绍: GEODIST 命令 返回两个给定位置之间的距离。如果两个位置之间的其中一个不存在, 那么命令返回空值。指定的单位如下:

1
2
3
4
5
6
7
8
9
+++ 指定单位的参数 unit 必须是以下单位的其中一个:

+ m 表示单位为米。
+ km 表示单位为千米。
+ mi 表示单位为英里。
+ ft 表示单位为英尺。

+++ 如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。
+++ GEODIST 命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。

GEODIST 命令 的具体操作如下图:

GEORADIUS

GEORADIUS 命令 以给定的经纬度为中心, 返回与中心的距离不超过给定最大距离的所有的位置元素 。详细介绍如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

+++ 范围可以使用以下其中一个单位:
+ m 表示单位为米。
+ km 表示单位为千米。
+ mi 表示单位为英里。
+ ft 表示单位为英尺。

+++ 在给定以下可选项时, 命令会返回额外的信息:
+ WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
+ WITHCOORD: 将位置元素的经度和维度也一并返回。
+ WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。

+++ 命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:
+ ASC: 根据中心的位置, 按照 从近到远 的方式返回位置元素。
+ DESC: 根据中心的位置, 按照 从远到近 的方式返回位置元素。

+++ 该命令可以用于查找附近的人,通过半径查询,即圆形搜索。Over!!!

在默认情况下, GEORADIUS 命令 会返回所有匹配的位置元素 。 虽然用户可以使用 COUNT 选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT 选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT 选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的

1
2
3
4
5
6
7
8
9
+++ GEORADIUS

+ 127.0.0.1:6379> georadius ChinaCity 110 30 1000 km
+ 127.0.0.1:6379> georadius ChinaCity 110 30 1000 km withdist
+ 127.0.0.1:6379> georadius ChinaCity 110 30 1000 km withcoord
+ 127.0.0.1:6379> georadius ChinaCity 110 30 1000 km count 1
+ 127.0.0.1:6379> georadius ChinaCity 110 30 1000 km count 2

+++ GEORADIUS 命令

上述命令对应的结果如下图:


GEORADIUSBYMEMBER

GEORADIUSBYMEMBER 命令GEORADIUS 命令 一样, 都可以找出位于指定范围内的元素 。 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点。简而言之,GEORADIUSBYMEMBER 命令 就是指定成员的位置被用作查询的中心。


1
2
3
4
5
6
+++ GEORADIUSBYMEMBER 命令

+ 127.0.0.1:6379> georadiusbymember ChinaCity FoShan 1000 km
+ 127.0.0.1:6379> georadiusbymember ChinaCity FoShan 100 km

+++ GEORADIUSBYMEMBER 命令

命令操作及结果如下图:

GEOHASH

GEOHASH 命令 返回一个或多个位置元素的 Geohash 表示 。【了解一下】

命令和结果

特别提示

GEOADD 命令 中有这么一句话: 这些数据将会存储到 sorted set ,由此可知,geospatial 类型的底层是 sorted set ,所以我们可以使用有序集合的命令来操作 geospatial 类型 。具体操作如下图:

HyperLogLog 类型

官方介绍: HyperLogLog 是用来做基数统计的算法,它的的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。


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

1
2
3
4
5
6
7
8
9
10
+++ HyperLogLog 应用场景

+ 1、网站用户访问量(一个用户访问多次,但还是算作一个人)
+ 2、统计注册 IP 数
+ 3、统计每日访问 IP 数
+ 4、统计页面实时 UV 数
+ 5、统计在线用户数
+ 6、统计用户每天搜索不同词条的个数

+++ HyperLogLog 应用场景

HyperLogLog 类型 的命令只有三个,比较少。具体介绍如下:

1
2
3
4
5
6
7
+++ HyperLogLog 类型相关命令

+ PFADD key element [element ...] 添加指定元素到 HyperLogLog 中。
+ PFCOUNT key [key ...] 返回给定 HyperLogLog 的基数估算值。
+ PFMERGE destkey sourcekey [sourcekey ...] 将多个 HyperLogLog 合并为一个 HyperLogLog

+++ HyperLogLog 类型相关命令

TIPS: 如果允许容错,那么统计功能的实现建议使用 HyperLogLog 类型。

Bitmaps 位图

Bitmaps 的底层是 字符串类型,所以它的命令是在 字符串类型命令 里面。其各种命令如下:

1
2
3
4
5
6
7
+++ Bitmaps 命令

+ SETBIT KEY_NAME OFFSET VALUE : Setbit 命令用于对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
+ GETBIT KEY_NAME OFFSET : Getbit 命令用于对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
+ BITCOUNT KEY [start end] : Bitcount 命令用于获取 Bitmaps 指定范围值为 1 的个数。

+++ Bitmaps 命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
+++ Bitmaps 命令举例

+ 127.0.0.1:6379> setbit sign 1 0
+ 127.0.0.1:6379> setbit sign 2 1
+ 127.0.0.1:6379> setbit sign 3 0
+ 127.0.0.1:6379> setbit sign 4 1
+ 127.0.0.1:6379> setbit sign 5 0
+ 127.0.0.1:6379> setbit sign 6 1
+ 127.0.0.1:6379> setbit sign 7 0

+ 127.0.0.1:6379> getbit sign 4
+ 127.0.0.1:6379> bitcount sign

+++ Bitmaps 命令举例

Redis 事务

Redis 事务介绍

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


Redis 事务没有隔离级别的概念: 批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。


Redis 不保证原子性: 单个 Redis 命令的执行是原子性的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。也就是说, Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。


1
2
3
4
5
6
7
+++ Redis 事务的三个阶段【从开始到执行】

+ 开始事务【multi】
+ 命令入队【……】
+ 执行事务【exec】

+++ Redis 事务的三个阶段【从开始到执行】

Redis 事务相关命令

1
2
3
4
5
6
7
8
9
+++ Redis 事务相关命令

+ multi : 标记一个事务块的开始( queued )
+ exec : 执行所有事务块的命令( 一旦执行 exec 后,之前加的监控锁都会被取消掉 ) 
- discard : 取消事务,放弃事务块中的所有命令
+ watch key1 key2 ... : 监视一或多个 key ,如果在事务执行之前,被监视的 key 被其他命令改动,则事务被打断 ( 类似乐观锁 )
- unwatch : 取消 watch 对所有 key 的监控

+++ Redis 事务相关命令

编译型异常: 这个会导致提交失败。失败原因:命令错误或者语法错误。 如下图:

运行时异常: 事务提交能够成功。具体原因详见下方 TIPS 。如下图:

TIPS: 事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚 ,也不会造成后续的指令不做。

Redis 实现乐观锁

乐观锁: 顾名思义,总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁。 但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。


悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中 synchronizedReentrantLock 等独占锁就是悲观锁思想的实现。


两种锁的使用场景: 从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。


Redis 监视测试, 相关命令如下:

1
2
3
4
5
6
7
8
9
10
11
+++ Redis 监视测试

+ 127.0.0.1:6379> set money 1000
+ 127.0.0.1:6379> set spend 0
+ 127.0.0.1:6379> watch money
+ 127.0.0.1:6379> multi
- 127.0.0.1:6379> decrby money 200
+ 127.0.0.1:6379> incrby spend 200
+ 127.0.0.1:6379> exec

+++ Redis 监视测试

在这种单线程的操作下,结果没有出现问题【等会我们开多个线程,结果就不一样了,哈哈】。 如下图:

接下来,我们需要再开启一条线程,即线程二。如下图:

线程一
线程二

此时,我们的 watch 命令 就充当了 Redis 的乐观锁 操作。最终结果是线程一的事务提交失败。 结果如下图:

线程二

那么我们该如何来解决这个问题嘞? 首先: 如果发现事务提交失败,那么就先解除监控。第二: 既然修改失败,那就获取最新的值再次进行监控。最后: 执行相应的操作。 请看下图:

Spring Boot 整合 Redis

Spring Boot 是未来的主流,所以使用它 集成 Redis 是非常有必要的。接下来就逐步介绍集成过程及其使用。【这部分的代码都安排好了,点击右侧即可下载: 代码资源下载


第一步: 创建 Spring Boot 项目。【这个就自行创建,这里不过多介绍了】 接着我们导入依赖如下:

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
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId><!--netty-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.2.9.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
<scope>test</scope>
</dependency>

注意事项:

1、在 Spring Boot 2.x 之后,原来使用的 Jedis 被替换成了 lettuce
2、Jedis 采用直连方式连接,多个线程操作是不安全的。如需避免这种问题,就需要使用 Jedis 连接池技术。【类似 BIO 模式】
3、lettuce 的底层采用 netty ,实例可以在多个线程中共享,所以不存在线程安全问题了。【类似 BIO 模式】


第二步: 编写我们的 application.properties 配置文件。 代码如下:

1
2
3
4
5
6
7
8
# 由于当时 8080 端口被占用,所以就把端口设置为了 8082
server.port=8082

# 设置 Redis 的主机
spring.redis.host=127.0.0.1

# 设置 Redis 的端口号
spring.redis.port=6379

第三步: 编写我们的单元测试代码,并进行测试。 代码和测试结果如下:

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
package club.guoshizhan;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

/**
* @Author: guoshizhan
* @Create: 2020/7/31 15:38
* @Description: 测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest
class MyRedisApplicationTest {

@Autowired
private RedisTemplate redisTemplate;

@Test
void contextLoads() {

/**
* RedisTemplate 模板的相关介绍
* 1、opsForValue 用于操作 String
* 2、opsForList 用于操作 List
* 3、opsForset 用于操作 Set
* ……
*/
redisTemplate.opsForValue().set("Language", "Java");
System.out.println(redisTemplate.opsForValue().get("Language"));

}

}

Redis 的测试结果 如下:


自定义 redisTemplate

由上图可知,我们存储的键乱码了。为什么呢?这个是序列化的问题。 先看下图:


那我们如何来解决这个问题呢? 这个时候我们需要 自定义 redisTemplate ,这样便可解决序列化问题。先按下图把对应的包和类创建好,然后再开始编写我们自定义的类。 如下图:

接着我们在 guoshizhan 包下新建 entity 包,然后在 entity 包里新建 User 实体类。【还未实现序列化】 代码如下:

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
package club.guoshizhan.entity;

import org.springframework.stereotype.Component;

/**
* @Author: guoshizhan
* @Create: 2020/7/31 22:56
* @Description: User 实体类
*/
@Component
public class User {

private String name;
private int age;
private String address;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public User(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}

public User() {
}

@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}';
}

}

此时我们去编写一个测试方法,把这个方法放到 MyRedisApplicationTest 类 中。 代码如下:

1
2
3
4
5
6
7
8
@Test
public void testUser() {
// 开发中一般使用 json 来传递对象
User user = new User("Java", 25, "China");
String jsonUser = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user", jsonUser);
System.out.println(redisTemplate.opsForValue().get("user"));
}

测试结果以及 后续的一点儿小改动 如下图:



到这里为止,我们控制台输出的信息是不会乱码的,但是我们的 Redis 客户端却会出现乱码 。因为我们还使用的是系统默认的序列化方式,我们还没有自定义我们的 redisTemplate 。 所以,我们现在就来自定义我们的 redisTemplate 吧!!!


第一步: 编写我们的 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
46
47
48
49
package club.guoshizhan.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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;

import java.net.UnknownHostException;

/**
* @Author: guoshizhan
* @Create: 2020/7/31 17:29
* @Description: Redis 配置类
*/
@Configuration
public class RedisConfig {

// 编写我们自己的 redisTemplate 【代码来源于:RedisAutoConfiguration 类】
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) throws UnknownHostException {

RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory); // 连接工厂

Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); // json 序列化配置
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // String 的序列化

template.setKeySerializer(stringRedisSerializer); // key 采用 string 的序列化方式
template.setHashKeySerializer(stringRedisSerializer); // hash 的 key 采用 String 的序列化方式
template.setValueSerializer(serializer); // value 序列化方式采用 jackson
template.setHashValueSerializer(serializer); // hash 的 value 序列化方式采用 jackson
template.afterPropertiesSet();

return template;

}

}

第二步: 编写我们的 MyRedisApplicationTest 类。 代码如下:

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
package club.guoshizhan;

import club.guoshizhan.entity.User;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
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.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @Author: guoshizhan
* @Create: 2020/7/31 15:38
* @Description: 测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest
class MyRedisApplicationTest {

@Autowired
@Qualifier("redisTemplate") // 指定到我们自己编写的 redisTemplate
private RedisTemplate redisTemplate;

@Test
public void testUser() {
// 开发中一般使用 json 来传递对象
User user = new User("Java", 25, "China");
redisTemplate.opsForValue().set("user", user);
System.out.println(redisTemplate.opsForValue().get("user"));
}

}

第三步: 点击测试方法进行测试 结果如下图:


自定义 Redis 工具类

我们发现 使用系统默认的命令来操作 Redis 比较麻烦,我们想要自己定义一个 Redis 工具类来方便我们的操作。安排!!!


第一步: 我们在 guoshizhan 包下新建 utils 包,然后在 utils 包里新建 RedisUtils 工具类。 代码如下:

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
package club.guoshizhan.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
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;

/**
* @Author: guoshizhan
* @Create: 2020/8/1 0:08
* @Description: 自定义 Redis 工具类
*/
@Component
public class RedisUtils {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private StringRedisTemplate stringRedisTemplate;

/**
* 指定缓存失效时间
*
* @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 键
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}

/**
* 递减
*
* @param key 键
* @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;
}
}
}

第二步: 编写我们的 MyRedisApplicationTest 类。 代码如下:

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
package club.guoshizhan;

import club.guoshizhan.utils.RedisUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @Author: guoshizhan
* @Create: 2020/7/31 15:38
* @Description: 测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyRedisApplicationTest {

@Autowired
private RedisUtils redisUtils;

@Test
public void redisUtilsTest() {
redisUtils.set("name", "Java");
System.out.println(redisUtils.get("name"));
}

}

第三步: 点击测试方法进行测试。 结果如下图:


Redis 高级

Redis 配置文件详解

内存配置: 当需要配置内存大小时,可以使用 1k, 5GB, 4M 等类似的格式进行配置。 其转换方式如下 【不区分大小写】

1
2
3
4
5
6
7
8
9
10
11
# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
################################## INCLUDES ###################################

# Include one or more other config files here. This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings. Include files can include
# other files, so use this wisely.
#
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf

模块加载: Redis 启动时加载哪些模块。 配置如下:

1
2
3
4
5
6
7
################################## MODULES #####################################

# Load modules at startup. If the server is not able to load modules
# it will abort. It is possible to use multiple loadmodule directives.
#
# loadmodule /path/to/my_module.so
# loadmodule /path/to/other_module.so

网络配置: 配置 Redis 的主机、端口等等。。常用配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 指定 Redis 只能接受来自此 IP 绑定的网卡的请求,注意此默认值默认外网是不可访问的
bind 127.0.0.1

# 是否开启保护模式。如果没有指定 bind 和密码,Redis 只会本地进行访问,拒绝外部访问。
protected-mode yes

# 默认端口,建议生产环境不要使用默认端口避免被恶意扫描到
port 6379

# TCP 连接中已完成队列(完成三次握手之后)的长度
tcp-backlog 511

# 配置 unix socket 来让 Redis 支持监听本地连接。【此项默认是被注释掉了的,只因介绍从而把它放开】
unixsocket /tmp/redis.sock

# 配置 unix socket 使用文件的权限。【此项默认是被注释掉了的,只因介绍从而把它放开】
unixsocketperm 700

# 客户端连接空闲超过 timeout 将会被断开,为 0 则断开
timeout 0

# 配置 tcp keepalive 参数
tcp-keepalive 300
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
################################## NETWORK #####################################

# By default, if no "bind" configuration directive is specified, Redis listens
# for connections from all the network interfaces available on the server.
# It is possible to listen to just one or multiple selected interfaces using
# the "bind" configuration directive, followed by one or more IP addresses.
#
# Examples:
#
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1 ::1
#
# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
# internet, binding to all the interfaces is dangerous and will expose the
# instance to everybody on the internet. So by default we uncomment the
# following bind directive, that will force Redis to listen only into
# the IPv4 loopback interface address (this means Redis will be able to
# accept connections only from clients running into the same computer it
# is running).
#
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
# JUST COMMENT THE FOLLOWING LINE.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bind 127.0.0.1

# Protected mode is a layer of security protection, in order to avoid that
# Redis instances left open on the internet are accessed and exploited.
#
# When protected mode is on and if:
#
# 1) The server is not binding explicitly to a set of addresses using the
# "bind" directive.
# 2) No password is configured.
#
# The server only accepts connections from clients connecting from the
# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain
# sockets.
#
# By default protected mode is enabled. You should disable it only if
# you are sure you want clients from other hosts to connect to Redis
# even if no authentication is configured, nor a specific set of interfaces
# are explicitly listed using the "bind" directive.
protected-mode yes

# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379

# TCP listen() backlog.
#
# In high requests-per-second environments you need an high backlog in order
# to avoid slow clients connections issues. Note that the Linux kernel
# will silently truncate it to the value of /proc/sys/net/core/somaxconn so
# make sure to raise both the value of somaxconn and tcp_max_syn_backlog
# in order to get the desired effect.
tcp-backlog 511

# Unix socket.
#
# Specify the path for the Unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
# unixsocket /tmp/redis.sock
# unixsocketperm 700

# Close the connection after a client is idle for N seconds (0 to disable)
timeout 0

# TCP keepalive.
#
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
# of communication. This is useful for two reasons:
#
# 1) Detect dead peers.
# 2) Take the connection alive from the point of view of network
# equipment in the middle.
#
# On Linux, the specified value (in seconds) is the period used to send ACKs.
# Note that to close the connection the double of the time is needed.
# On other kernels the period depends on the kernel configuration.
#
# A reasonable value for this option is 300 seconds, which is the new
# Redis default starting with Redis 3.2.1.
tcp-keepalive 300

通用配置: 这个是我们基本的配置项。能配置后台启动、日志级别等等。 配置如下:

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
# 是否后台启动,默认是 no ,我们需要自己把它该为 yes
daemonize no

# 可以通过 upstart 和 systemd 来管理 Redis 的守护进程。选项如下:
# supervised no - 没有监督互动
# supervised upstart - 通过将 Redis 置于 SIGSTOP 式来启动信号
# supervised systemd - signal systemd 将 READY = 1 写入 $ NOTIFY_SOCKET
# supervised auto - 检测 upstart 或 systemd 方法基于 UPSTART_JOB 或 NOTIFY_SOCKET 环境变量
supervised no

# 如果以后台的方式运行 Redis ,我们需要指定一个 PID 文件
pidfile /var/run/redis_6379.pid

# 配置日志级别,默认是 notice 。参数如下:
# debug
# verbose
# notice
# warning
loglevel notice

# 配置日志文件的位置,就是把日志输出到哪一个文件
logfile ""

# 是否打开记录 syslog 功能【此项默认是被注释掉了的,只因介绍从而把它放开】
syslog-enabled no

# syslog 标识符。【此项默认是被注释掉了的,只因介绍从而把它放开】
syslog-ident redis

# 配置日志的来源。【此项默认是被注释掉了的,只因介绍从而把它放开】
syslog-facility local0

# 数据库的数量,默认值为 16 。默认使用的数据库是 DB 0,数据库范围在 0 -(database-1)之间
# 可以通过 SELECT 命令选择一个 db
# 集群环境默认只有 DB 0
databases 16

# 是否一直显示 logo ,就是启动 Redis 数据库有无那个立方体的图形显示
always-show-logo yes
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
################################# GENERAL #####################################

# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize yes

# If you run Redis from upstart or systemd, Redis can interact with your
# supervision tree. Options:
# supervised no - no supervision interaction
# supervised upstart - signal upstart by putting Redis into SIGSTOP mode
# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
# supervised auto - detect upstart or systemd method based on
# UPSTART_JOB or NOTIFY_SOCKET environment variables
# Note: these supervision methods only signal "process is ready."
# They do not enable continuous liveness pings back to your supervisor.
supervised no

# If a pid file is specified, Redis writes it where specified at startup
# and removes it at exit.
#
# When the server runs non daemonized, no pid file is created if none is
# specified in the configuration. When the server is daemonized, the pid file
# is used even if not specified, defaulting to "/var/run/redis.pid".
#
# Creating a pid file is best effort: if Redis is not able to create it
# nothing bad happens, the server will start and run normally.
pidfile /var/run/redis_6379.pid

# Specify the server verbosity level.
# 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

# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile ""

# To enable logging to the system logger, just set 'syslog-enabled' to yes,
# and optionally update the other syslog parameters to suit your needs.
# syslog-enabled no

# Specify the syslog identity.
# syslog-ident redis

# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0

# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
databases 16

# By default Redis shows an ASCII art logo only when started to log to the
# standard output and if the standard output is a TTY. Basically this means
# that normally a logo is displayed only in interactive sessions.
#
# However it is possible to force the pre-4.0 behavior and always show a
# ASCII art logo in startup logs by setting the following option to yes.
always-show-logo yes

快照配置: 与 Redis 的数据持久化 RDB 有关。【一定时间内执行了多少次,则会持久化到文件】 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 保存数据到磁盘 【RDB 方式】 :
# after 900 sec (15 min) if at least 1 key changed 【15 分钟(900 秒)内至少有一个 key 发生变化就保存数据到磁盘】
# after 300 sec (5 min) if at least 10 keys changed 【5 分钟(300 秒)至少有 10 个 key 发生变化就保存数据到磁盘】
# after 60 sec if at least 10000 keys changed 【1 分钟(60 秒)至少有 10000 个 key 发生变化就保存数据到磁盘】

save 900 1
save 300 10
save 60 10000

# 持久化出现错误后,是否依然进行继续进行工作
stop-writes-on-bgsave-error yes

# 存储至本地数据库时(持久化到 rdb 文件)是否压缩数据【这需要消耗一些 CPU 资源】,默认为 yes
rdbcompression yes

# 是否开启RC64校验,默认是 yes
rdbchecksum yes

# rdb 文件名称
dbfilename dump.rdb

# rdb 使用上面的 dbfilename 配置指令的文件名保存到这个目录
dir ./
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
################################ SNAPSHOTTING  ################################
#
# Save the DB on disk:
#
# save <seconds> <changes>
#
# Will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# In the example below the behaviour will be to save:
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
#
# Note: you can disable saving completely by commenting out all "save" lines.
#
# It is also possible to remove all the previously configured save
# points by adding a save directive with a single empty string argument
# like in the following example:
#
# save ""

save 900 1
save 300 10
save 60 10000

# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
#
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes

# Compress string objects using LZF when dump .rdb databases?
# For default that's set to 'yes' as it's almost always a win.
# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.
rdbcompression yes

# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
# This makes the format more resistant to corruption but there is a performance
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
# for maximum performances.
#
# RDB files created with checksum disabled have a checksum of zero that will
# tell the loading code to skip the check.
rdbchecksum yes

# The filename where to dump the DB
dbfilename dump.rdb

# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir ./

主从复制: 主从复制配置。 配置如下:

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
# 指定主节点。旧版本是:slaveof 。【此项默认是被注释掉了的,只因介绍从而把它放开】
replicaof

# master 的密码。【此项默认是被注释掉了的,只因介绍从而把它放开】
masterauth

# 当一个 slave 失去和 master 的连接,或者同步正在进行中,slave 的行为有两种可能:
# 如果 replica-serve-stale-data 设置为 “yes” (默认值),slave 会继续响应客户端请求,可能是正常数据,也可能是还没获得值的空数据。
# 如果 replica-serve-stale-data 设置为 “no” ,slave 会回复"正在从 master 同步(SYNC with master in progress)" 来处理各种请求,除了 INFO 和 SLAVEOF 命令。
replica-serve-stale-data yes

# 配置从是否为只读,开启后从则不能写入数据,旧版本是:slave-read-only yes
replica-read-only yes

# 同步策略: 磁盘或 socket,默认磁盘方式
repl-diskless-sync no

# 如果非磁盘同步方式开启,可以配置同步延迟时间,以等待 master 产生子进程通过 socket 传输 RDB 数据给 slave 。
# 默认值为 5 秒,设置为 0 秒则每次传输无延迟。
repl-diskless-sync-delay 5

# slave 根据指定的时间间隔向 master 发送 ping 请求。默认 10 秒。【此项默认是被注释掉了的,只因介绍从而把它放开】
repl-ping-replica-period 10

# 同步的超时时间:slave 在与 master SYNC 期间有大量数据传输,造成超时
# 在 slave 角度,master 超时,包括数据、ping 等
# 在 master 角度,slave 超时,当 master 发送 REPLCONF ACK pings ,确保这个值大于指定的 repl-ping-slave-period ,否则在主从间流量不高时每次都会检测到超时
repl-timeout 60

# 是否在 slave 套接字发送 SYNC 之后禁用 TCP_NODELAY
# 如果选择 yes,Redis 将使用更少的 TCP 包和带宽来向 slaves 发送数据。但是这将使数据传输到 slave 上有延迟,Linux 内核的默认配置会达到 40 毫秒。
# 如果选择 no ,数据传输到 salve 的延迟将会减少但要使用更多的带宽。
# 默认我们会为低延迟做优化,但高流量情况或主从之间的跳数过多时,可以设置为 “yes” 。
repl-disable-tcp-nodelay no

# 设置数据备份的 backlog 大小。同步的超时时间
repl-backlog-size 1mb

# 从最后一个 slave 断开开始计时多少秒后,backlog 缓冲将会释放。同步的超时时间
repl-backlog-ttl 3600

# 优先级
replica-priority 100

# 如果 master 少于 N 个延时小于等于M秒的已连接slave,就可以停止接收写操作。N 个 slave 需要是 “oneline” 状态。
# 延时是以秒为单位,并且必须小于等于指定值,是从最后一个从slave接收到的ping(通常每秒发送)开始计数。
# 该选项不保证N个slave正确同步写操作,但是限制数据丢失的窗口期。
# 例如至少需要3个延时小于等于10秒的slave用下面的指令:【此项默认是被注释掉了的,只因介绍从而把它放开】
min-replicas-to-write 3
min-replicas-max-lag 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
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
################################# REPLICATION #################################

# Master-Replica replication. Use replicaof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
#
# +------------------+ +---------------+
# | Master | ---> | Replica |
# | (receive writes) | | (exact copy) |
# +------------------+ +---------------+
#
# 1) Redis replication is asynchronous, but you can configure a master to
# stop accepting writes if it appears to be not connected with at least
# a given number of replicas.
# 2) Redis replicas are able to perform a partial resynchronization with the
# master if the replication link is lost for a relatively small amount of
# time. You may want to configure the replication backlog size (see the next
# sections of this file) with a sensible value depending on your needs.
# 3) Replication is automatic and does not need user intervention. After a
# network partition replicas automatically try to reconnect to masters
# and resynchronize with them.
#
# replicaof <masterip> <masterport>

# If the master is password protected (using the "requirepass" configuration
# directive below) it is possible to tell the replica to authenticate before
# starting the replication synchronization process, otherwise the master will
# refuse the replica request.
#
# masterauth <master-password>

# When a replica loses its connection with the master, or when the replication
# is still in progress, the replica can act in two different ways:
#
# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will
# still reply to client requests, possibly with out of date data, or the
# data set may just be empty if this is the first synchronization.
#
# 2) if replica-serve-stale-data is set to 'no' the replica will reply with
# an error "SYNC with master in progress" to all the kind of commands
# but to INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,
# SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB,
# COMMAND, POST, HOST: and LATENCY.
#
replica-serve-stale-data yes

# You can configure a replica instance to accept writes or not. Writing against
# a replica instance may be useful to store some ephemeral data (because data
# written on a replica will be easily deleted after resync with the master) but
# may also cause problems if clients are writing to it because of a
# misconfiguration.
#
# Since Redis 2.6 by default replicas are read-only.
#
# Note: read only replicas are not designed to be exposed to untrusted clients
# on the internet. It's just a protection layer against misuse of the instance.
# Still a read only replica exports by default all the administrative commands
# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
# security of read only replicas using 'rename-command' to shadow all the
# administrative / dangerous commands.
replica-read-only yes

# Replication SYNC strategy: disk or socket.
#
# -------------------------------------------------------
# WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY
# -------------------------------------------------------
#
# New replicas and reconnecting replicas that are not able to continue the replication
# process just receiving differences, need to do what is called a "full
# synchronization". An RDB file is transmitted from the master to the replicas.
# The transmission can happen in two different ways:
#
# 1) Disk-backed: The Redis master creates a new process that writes the RDB
# file on disk. Later the file is transferred by the parent
# process to the replicas incrementally.
# 2) Diskless: The Redis master creates a new process that directly writes the
# RDB file to replica sockets, without touching the disk at all.
#
# With disk-backed replication, while the RDB file is generated, more replicas
# can be queued and served with the RDB file as soon as the current child producing
# the RDB file finishes its work. With diskless replication instead once
# the transfer starts, new replicas arriving will be queued and a new transfer
# will start when the current one terminates.
#
# When diskless replication is used, the master waits a configurable amount of
# time (in seconds) before starting the transfer in the hope that multiple replicas
# will arrive and the transfer can be parallelized.
#
# With slow disks and fast (large bandwidth) networks, diskless replication
# works better.
repl-diskless-sync no

# When diskless replication is enabled, it is possible to configure the delay
# the server waits in order to spawn the child that transfers the RDB via socket
# to the replicas.
#
# This is important since once the transfer starts, it is not possible to serve
# new replicas arriving, that will be queued for the next RDB transfer, so the server
# waits a delay in order to let more replicas arrive.
#
# The delay is specified in seconds, and by default is 5 seconds. To disable
# it entirely just set it to 0 seconds and the transfer will start ASAP.
repl-diskless-sync-delay 5

# Replicas send PINGs to server in a predefined interval. It's possible to change
# this interval with the repl_ping_replica_period option. The default value is 10
# seconds.
#
# repl-ping-replica-period 10

# The following option sets the replication timeout for:
#
# 1) Bulk transfer I/O during SYNC, from the point of view of replica.
# 2) Master timeout from the point of view of replicas (data, pings).
# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings).
#
# It is important to make sure that this value is greater than the value
# specified for repl-ping-replica-period otherwise a timeout will be detected
# every time there is low traffic between the master and the replica.
#
# repl-timeout 60

# Disable TCP_NODELAY on the replica socket after SYNC?
#
# If you select "yes" Redis will use a smaller number of TCP packets and
# less bandwidth to send data to replicas. But this can add a delay for
# the data to appear on the replica side, up to 40 milliseconds with
# Linux kernels using a default configuration.
#
# If you select "no" the delay for data to appear on the replica side will
# be reduced but more bandwidth will be used for replication.
#
# By default we optimize for low latency, but in very high traffic conditions
# or when the master and replicas are many hops away, turning this to "yes" may
# be a good idea.
repl-disable-tcp-nodelay no

# Set the replication backlog size. The backlog is a buffer that accumulates
# replica data when replicas are disconnected for some time, so that when a replica
# wants to reconnect again, often a full resync is not needed, but a partial
# resync is enough, just passing the portion of data the replica missed while
# disconnected.
#
# The bigger the replication backlog, the longer the time the replica can be
# disconnected and later be able to perform a partial resynchronization.
#
# The backlog is only allocated once there is at least a replica connected.
#
# repl-backlog-size 1mb

# After a master has no longer connected replicas for some time, the backlog
# will be freed. The following option configures the amount of seconds that
# need to elapse, starting from the time the last replica disconnected, for
# the backlog buffer to be freed.
#
# Note that replicas never free the backlog for timeout, since they may be
# promoted to masters later, and should be able to correctly "partially
# resynchronize" with the replicas: hence they should always accumulate backlog.
#
# A value of 0 means to never release the backlog.
#
# repl-backlog-ttl 3600

# The replica priority is an integer number published by Redis in the INFO output.
# It is used by Redis Sentinel in order to select a replica to promote into a
# master if the master is no longer working correctly.
#
# A replica with a low priority number is considered better for promotion, so
# for instance if there are three replicas with priority 10, 100, 25 Sentinel will
# pick the one with priority 10, that is the lowest.
#
# However a special priority of 0 marks the replica as not able to perform the
# role of master, so a replica with priority of 0 will never be selected by
# Redis Sentinel for promotion.
#
# By default the priority is 100.
replica-priority 100

# It is possible for a master to stop accepting writes if there are less than
# N replicas connected, having a lag less or equal than M seconds.
#
# The N replicas need to be in "online" state.
#
# The lag in seconds, that must be <= the specified value, is calculated from
# the last ping received from the replica, that is usually sent every second.
#
# This option does not GUARANTEE that N replicas will accept the write, but
# will limit the window of exposure for lost writes in case not enough replicas
# are available, to the specified number of seconds.
#
# For example to require at least 3 replicas with a lag <= 10 seconds use:
#
# min-replicas-to-write 3
# min-replicas-max-lag 10
#
# Setting one or the other to 0 disables the feature.
#
# By default min-replicas-to-write is set to 0 (feature disabled) and
# min-replicas-max-lag is set to 10.

# A Redis master is able to list the address and port of the attached
# replicas in different ways. For example the "INFO replication" section
# offers this information, which is used, among other tools, by
# Redis Sentinel in order to discover replica instances.
# Another place where this info is available is in the output of the
# "ROLE" command of a master.
#
# The listed IP and address normally reported by a replica is obtained
# in the following way:
#
# IP: The address is auto detected by checking the peer address
# of the socket used by the replica to connect with the master.
#
# Port: The port is communicated by the replica during the replication
# handshake, and is normally the port that the replica is using to
# listen for connections.
#
# However when port forwarding or Network Address Translation (NAT) is
# used, the replica may be actually reachable via different IP and port
# pairs. The following two options can be used by a replica in order to
# report to its master a specific set of IP and port, so that both INFO
# and ROLE will report those values.
#
# There is no need to use both the options if you need to override just
# the port or the IP address.
#
# replica-announce-ip 5.5.5.5
# replica-announce-port 1234

安全设置: 可在此处设置 Redis 的密码。配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 设置密码。举个例子: requirepass 123456 。【此项默认是被注释掉了的,只因介绍从而把它放开】
requirepass foobared

#命令重命名。设置命令为空时禁用命令。【此项默认是被注释掉了的,只因介绍从而把它放开】
rename-command CONFIG ""



# 在 Redis 客户端查看是否有密码以及设置密码等
# 查看是否有密码
config get requirepass

# 设置密码
config set requirepass "123456"

# 登陆验证密码
auth 123456
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
################################## SECURITY ###################################

# Require clients to issue AUTH <PASSWORD> before processing any other
# commands. This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#
# requirepass foobared

# Command renaming.
#
# It is possible to change the name of dangerous commands in a shared
# environment. For instance the CONFIG command may be renamed into something
# hard to guess so that it will still be available for internal-use tools
# but not available for general clients.
#
# Example:
#
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
#
# It is also possible to completely kill a command by renaming it into
# an empty string:
#
# rename-command CONFIG ""
#
# Please note that changing the name of commands that are logged into the
# AOF file or transmitted to replicas may cause problems.

客户端设置: 设置能同时连接到 Redis 的最大客户端的数量。 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
################################### CLIENTS ####################################

# Set the max number of connected clients at the same time. By default
# this limit is set to 10000 clients, however if the Redis server is not
# able to configure the process file limit to allow for the specified limit
# the max number of allowed clients is set to the current file limit
# minus 32 (as Redis reserves a few file descriptors for internal uses).
#
# Once the limit is reached Redis will close all the new connections sending
# an error 'max number of clients reached'.
# 设置能同时连接到 Redis 的最大客户端的数量
# maxclients 10000

内存限制: * *

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 内存限制:能够配置最大内存容量【此项默认是被注释掉了的,只因介绍从而把它放开】
maxmemory <bytes>

# 如果达到上方最大的内存限制,Redis 如何选择删除 key
# volatile-lru -> 根据 LRU 算法删除设置过期时间的 key
# allkeys-lru -> 根据 LRU 算法删除任何 key
# volatile-random -> 随机移除设置过过期时间的 key
# allkeys-random -> 随机移除任何 key
# volatile-ttl -> 移除即将过期的 key(minor TTL)
# noeviction -> 不移除任何 key,只返回一个写错误
# 注意:对所有策略来说,如果 Redis 找不到合适的可以删除的 key 都会在写操作时返回一个错误。
# 目前为止涉及的命令: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
maxmemory-policy noeviction

# LRU 和最小 TTL 算法的样本个数【此项默认是被注释掉了的,只因介绍从而把它放开】
maxmemory-samples 5
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
############################## MEMORY MANAGEMENT ################################

# Set a memory usage limit to the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# according to the eviction policy selected (see maxmemory-policy).
#
# If Redis can't remove keys according to the policy, or if the policy is
# set to 'noeviction', Redis will start to reply with errors to commands
# that would use more memory, like SET, LPUSH, and so on, and will continue
# to reply to read-only commands like GET.
#
# This option is usually useful when using Redis as an LRU or LFU cache, or to
# set a hard memory limit for an instance (using the 'noeviction' policy).
#
# WARNING: If you have replicas attached to an instance with maxmemory on,
# the size of the output buffers needed to feed the replicas are subtracted
# from the used memory count, so that network problems / resyncs will
# not trigger a loop where keys are evicted, and in turn the output
# buffer of replicas is full with DELs of keys evicted triggering the deletion
# of more keys, and so forth until the database is completely emptied.
#
# In short... if you have replicas attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for replica
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key among the ones with an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
#
# LRU means Least Recently Used
# LFU means Least Frequently Used
#
# Both LRU, LFU and volatile-ttl are implemented using approximated
# randomized algorithms.
#
# Note: with any of the above policies, Redis will return an error on write
# operations, when there are no suitable keys for eviction.
#
# 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

# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
# algorithms (in order to save memory), so you can tune it for speed or
# accuracy. For default Redis will check five keys and pick the one that was
# used less recently, you can change the sample size using the following
# configuration directive.
#
# The default of 5 produces good enough results. 10 Approximates very closely
# true LRU but costs more CPU. 3 is faster but not very accurate.
#
# maxmemory-samples 5

# Starting from Redis 5, by default a replica will ignore its maxmemory setting
# (unless it is promoted to master after a failover or manually). It means
# that the eviction of keys will be just handled by the master, sending the
# DEL commands to the replica as keys evict in the master side.
#
# This behavior ensures that masters and replicas stay consistent, and is usually
# what you want, however if your replica is writable, or you want the replica to have
# a different memory setting, and you are sure all the writes performed to the
# replica are idempotent, then you may change this default (but be sure to understand
# what you are doing).
#
# Note that since the replica by default does not evict, it may end using more
# memory than the one set via maxmemory (there are certain buffers that may
# be larger on the replica, or data structures may sometimes take more memory and so
# forth). So make sure you monitor your replicas and make sure they have enough
# memory to never hit a real out-of-memory condition before the master hits
# the configured maxmemory setting.
#
# replica-ignore-maxmemory yes

RDB 触发机制:

1、执行 save 方法会生成 dump.rdb 文件。
2、执行 flushall 命令生成 dump.rdb 文件。
3、退出 Redis 也会生成 dump.rdb 文件。

AOF 配置: 持久化方式 AOF 的配置。 配置如下:

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
# 每次启动时 Redis 都会先把这个文件的数据读入内存里,先忽略 RDB 文件【默认是不开启 AOF 模式的】
appendonly no

# 配置生成的 AOF 文件的名称
appendfilename “appendonly.aof”

# fsync() 系统调用告诉操作系统把数据写到磁盘上,而不是等更多的数据进入输出缓冲区。有些操作系统会真的把数据马上刷到磁盘上;有些则会尽快去尝试这么做。
# Redis 支持三种不同的模式:
# no: 不要立刻刷,只有在操作系统需要刷的时候再刷。比较快。
# always: 每次写操作都立刻写入到 aof 文件。慢,但是最安全。
# everysec: 每秒写一次。折中方案。
# 默认的 everysec 通常来说能在速度和数据安全性之间取得比较好的平衡。
appendfsync everysec

# 如果 AOF 的同步策略设置成 “always” 或者 “everysec” ,并且后台的存储进程(后台存储或写入 AOF 日志)会产生很多磁盘 I/O 开销。某些 Linux 的配置下会使 Redis 因为 fsync() 系统调用而阻塞很久。
# 注意,目前对这个情况还没有完美修正,甚至不同线程的 fsync() 会阻塞我们同步的 write(2) 调用。
# 为了缓解这个问题,可以用下面这个选项。它可以在 BGSAVE 或 BGREWRITEAOF 处理时阻止 fsync()。
# 这就意味着如果有子进程在进行保存操作,那么 Redis 就处于"不可同步"的状态。
# 这实际上是说,在最差的情况下可能会丢掉 30 秒钟的日志数据。(默认 Linux 设定)
# 如果把这个设置成 "yes" 带来了延迟问题,就保持 "no" ,这是保存持久数据的最安全的方式。
no-appendfsync-on-rewrite no

#自动重写AOF文件。如果AOF日志文件增大到指定百分比,Redis能够通过 BGREWRITEAOF 自动重写AOF日志文件。
#工作原理:Redis记住上次重写时AOF文件的大小(如果重启后还没有写操作,就直接用启动时的AOF大小)
#这个基准大小和当前大小做比较。如果当前大小超过指定比例,就会触发重写操作。
#你还需要指定被重写日志的最小尺寸,这样避免了达到指定百分比但尺寸仍然很小的情况还要重写。
#指定百分比为0会禁用AOF自动重写特性。

auto-aof-rewrite-percentage 100
# 文件达到大小阈值的时候进行重写
auto-aof-rewrite-min-size 64mb

#如果设置为yes,如果一个因异常被截断的AOF文件被redis启动时加载进内存,redis将会发送日志通知用户
#如果设置为no,erdis将会拒绝启动。此时需要用"redis-check-aof"工具修复文件。

aof-load-truncated yes

#加载时Redis识别出AOF文件以“REDIS”开头字符串,
#并加载带此前缀的RDB文件,然后继续加载AOF
aof-use-rdb-preamble yes
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
############################## APPEND ONLY MODE ###############################

# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.

appendonly no

# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof"

# The fsync() call tells the Operating System to actually write data on disk
# instead of waiting for more data in the output buffer. Some OS will really flush
# data on disk, some other OS will just try to do it ASAP.
#
# Redis supports three different modes:
#
# no: don't fsync, just let the OS flush the data when it wants. Faster.
# always: fsync after every write to the append only log. Slow, Safest.
# everysec: fsync only one time every second. Compromise.
#
# The default is "everysec", as that's usually the right compromise between
# speed and data safety. It's up to you to understand if you can relax this to
# "no" that will let the operating system flush the output buffer when
# it wants, for better performances (but if you can live with the idea of
# some data loss consider the default persistence mode that's snapshotting),
# or on the contrary, use "always" that's very slow but a bit safer than
# everysec.
#
# More details please check the following article:
# http://antirez.com/post/redis-persistence-demystified.html
#
# If unsure, use "everysec".

# appendfsync always
appendfsync everysec
# appendfsync no

# When the AOF fsync policy is set to always or everysec, and a background
# saving process (a background save or AOF log background rewriting) is
# performing a lot of I/O against the disk, in some Linux configurations
# Redis may block too long on the fsync() call. Note that there is no fix for
# this currently, as even performing fsync in a different thread will block
# our synchronous write(2) call.
#
# In order to mitigate this problem it's possible to use the following option
# that will prevent fsync() from being called in the main process while a
# BGSAVE or BGREWRITEAOF is in progress.
#
# This means that while another child is saving, the durability of Redis is
# the same as "appendfsync none". In practical terms, this means that it is
# possible to lose up to 30 seconds of log in the worst scenario (with the
# default Linux settings).
#
# If you have latency problems turn this to "yes". Otherwise leave it as
# "no" that is the safest pick from the point of view of durability.

no-appendfsync-on-rewrite no

# Automatic rewrite of the append only file.
# Redis is able to automatically rewrite the log file implicitly calling
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
#
# This is how it works: Redis remembers the size of the AOF file after the
# latest rewrite (if no rewrite has happened since the restart, the size of
# the AOF at startup is used).
#
# This base size is compared to the current size. If the current size is
# bigger than the specified percentage, the rewrite is triggered. Also
# you need to specify a minimal size for the AOF file to be rewritten, this
# is useful to avoid rewriting the AOF file even if the percentage increase
# is reached but it is still pretty small.
#
# Specify a percentage of zero in order to disable the automatic AOF
# rewrite feature.

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# An AOF file may be found to be truncated at the end during the Redis
# startup process, when the AOF data gets loaded back into memory.
# This may happen when the system where Redis is running
# crashes, especially when an ext4 filesystem is mounted without the
# data=ordered option (however this can't happen when Redis itself
# crashes or aborts but the operating system still works correctly).
#
# Redis can either exit with an error when this happens, or load as much
# data as possible (the default now) and start if the AOF file is found
# to be truncated at the end. The following option controls this behavior.
#
# If aof-load-truncated is set to yes, a truncated AOF file is loaded and
# the Redis server starts emitting a log to inform the user of the event.
# Otherwise if the option is set to no, the server aborts with an error
# and refuses to start. When the option is set to no, the user requires
# to fix the AOF file using the "redis-check-aof" utility before to restart
# the server.
#
# Note that if the AOF file will be found to be corrupted in the middle
# the server will still exit with an error. This option only applies when
# Redis will try to read more data from the AOF file but not enough bytes
# will be found.
aof-load-truncated yes

# When rewriting the AOF file, Redis is able to use an RDB preamble in the
# AOF file for faster rewrites and recoveries. When this option is turned
# on the rewritten AOF file is composed of two different stanzas:
#
# [RDB file][AOF tail]
#
# When loading Redis recognizes that the AOF file starts with the "REDIS"
# string and loads the prefixed RDB file, and continues loading the AOF
# tail.
aof-use-rdb-preamble yes

注意啦!!!

1、如果我们的 AOF 文件有错误,我们的 Redis 文件时启动不起来的。因此,我们需要修复 AOF 文件。
2、Redis 给我们提供了一个工具: redis-check-aof ,用于修复受损的 AOF 文件。
3、修复命令为: redis-check-aof –fix appendonly.aof

Redis 的发布订阅

Redis 发布订阅(pub/sub) 是一种 消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 举个常用的例子:我们微信关注的公众号会给我们发送消息就属于这种通信模式。【Redis 客户端可以订阅任意数量的频道】

发布订阅消息图

下表列出了关于 Redis 发布订阅常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 订阅一个或多个符合给定模式的频道。
PSUBSCRIBE pattern [pattern ...]

# 查看订阅与发布系统状态。
PUBSUB subcommand [argument [argument ...]]

# 将信息发送到指定的频道。
PUBLISH channel message

# 退订所有给定模式的频道。
PUNSUBSCRIBE [pattern [pattern ...]]

# 订阅给定的一个或多个频道的信息。
SUBSCRIBE channel [channel ...]

# 指退订给定的频道。
UNSUBSCRIBE [channel [channel ...]]

使用 Redis 客户端来实现订阅发布。 如下图:

TIPS: 使用 Java 来实现订阅发布先放一放,以后有空来完善。

应用场景: Redis 的发布订阅应用场景在我们生活中还是挺常见的。如下:

1
2
3
4
5
6
+++ Redis 订阅发布应用场景

+ 1、构建实时消息系统,比如普通的即时聊天,群聊等功能。
+ 2、微信的公共号订阅消息推送等。

+++ Redis 订阅发布应用场景

Redis 的集群搭建

集群简介

Redis 是一个开源的 key value 存储系统,受到了广大互联网公司的青睐。但在 Redis3.0 版本之前,它只支持单例模式,在 3.0 版本及以后才支持集群。 Redis 集群采用 P2P 模式,是完全去中心化的,不存在中心节点或者代理节点。并且 Redis 集群是没有统一的入口的,客户端(client)连接集群的时候连接集群中的任意节点(node)即可。集群内部的节点是相互通信的(PING-PONG 机制),每个节点都是一个 Redis 实例。


为了实现 Redis 集群的高可用,即判断节点是否健康(能否正常使用),redis-cluster 有这么一个投票容错机制: 如果集群中超过半数的节点投票认为某个节点挂了,那么这个节点就挂了(fail)。这是判断节点是否挂了的方法。那么如何判断集群是否挂了呢? -> 如果集群中任意一个节点挂了,而且该节点没有从节点(备份节点),那么这个集群就挂了。这是判断集群是否挂了的方法。


那么为什么任意一个节点挂了(没有从节点)这个集群就挂了呢? 因为集群内置了 16384 个 slot(哈希槽),并且把所有的物理节点映射到了这 16384[0-16383] 个 slot 上,或者说把这些 slot 均等的分配给了各个节点。当需要在 Redis 集群存放一个数据(key-value)时,Redis 会先对这个 key 进行 crc16 算法,然后得到一个结果。再把这个结果对 16384 进行求余,这个余数会对应 [0-16383] 其中一个槽,进而决定 key-value 存储到哪个节点中。所以一旦某个节点挂了,该节点对应的 slot 就无法使用,那么就会导致集群无法正常工作。综上所述,每个 Redis 集群理论上最多可以有 16384 个节点。


集群所需环境: 1、Redis 集群至少需要 3 个节点, 因为投票容错机制要求超过半数节点认为某个节点挂了该节点才是挂了,所以 2 个节点无法构成集群。2、要保证集群的高可用,需要每个节点都有从节点,也就是备份节点,所以 Redis 集群至少需要 6 台服务器。因为我没有那么多服务器,也启动不了那么多虚拟机,所在这里搭建的是伪分布式集群,即一台服务器虚拟运行 6 个 redis 实例,修改端口号为(7001-7006),当然实际生产环境的 Redis 集群搭建和这里是一样的。

简介来源: redis集群搭建(非常详细,适合新手)

集群搭建

第一步: 开启虚拟机,打开四个窗口连接我们的同一台虚拟机。 如下图:

第二步: 复制 Redis 配置文件,为每一台从机进行配置。 如下图:

第三步: 修改 redis.confredis-1.confredis-2.confredis-3.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
# redis.conf 配置文件需修改的地方
logfile "6379.log" # 这个是修改后的内容
dbfilename dump6379.rdb # 这个是修改后的内容


# redis-1.conf 配置文件需修改的地方
port 6380 # 这个是修改后的内容
pidfile /var/run/redis_6380.pid # 这个是修改后的内容
logfile "6380.log" # 这个是修改后的内容
dbfilename dump6380.rdb # 这个是修改后的内容


# redis-2.conf 配置文件需修改的地方
port 6381 # 这个是修改后的内容
pidfile /var/run/redis_6381.pid # 这个是修改后的内容
logfile "6381.log" # 这个是修改后的内容
dbfilename dump6381.rdb # 这个是修改后的内容


# redis-3.conf 配置文件需修改的地方
port 6382 # 这个是修改后的内容
pidfile /var/run/redis_6382.pid # 这个是修改后的内容
logfile "6382.log" # 这个是修改后的内容
dbfilename dump6382.rdb # 这个是修改后的内容

第四步: 开启我们的四个 Redis 服务器,并进行查看。 具体操作如下图:

开启 Redis 主机 开启 Redis 1 号从机 开启 Redis 2 号从机 开启 Redis 3 号从机查看 Redis 服务

说明一下: 这个部分的操作是为下面的 Redis 的主从复制 服务的。所以,上述的搭建过程并不是真正意义上的集群搭建。

Redis 的主从复制

主从复制简介

通过持久化功能,Redis 保证了即使在服务器重启的情况下也不会丢失(或少量丢失)数据,但是由于数据是存储在一台服务器上的,如果这台服务器出现故障,比如硬盘坏了,也会导致数据丢失。为了避免单点故障,我们需要将数据复制多份部署在多台不同的服务器上,即使有一台服务器出现故障其他服务器依然可以继续提供服务。 这就要求当一台服务器上的数据更新后,自动将更新的数据同步到其他服务器上,这时候就用到了 Redis 的主从复制。

Redis 提供了复制(replication)功能来自动实现多台 Redis 服务器的数据同步。 我们可以通过部署多台 Redis ,并在配置文件中指定这几台 Redis 之间的主从关系,主服务器负责写入数据,同时把写入的数据实时同步到从服务器,这种模式叫做 主从复制即 master/slave ,并且 Redis 默认 master 用于写,slave 用于读,向 slave 写数据会导致错误。 注意: 默认情况下,每一台 Redis 服务器都是主节点【如果还没有进行相应的配置】。

主从复制搭建

主从复制会使用到的命令如下:

1
2
3
127.0.0.1:6379> info replication    # 查看当前库信息
127.0.0.1:6379> slaveof host port # 配置主机【就是去认老大】
127.0.0.1:6379> slaveof no one # 如果主机宕机了,从机可使用这条命令让自己成为主机【谋朝篡位】

第一步: 开启四台 Redis 服务器的客户端。 如下图所示:


第二步: 我们使用 1 号机(6380 端口)去配置主机。(此时的 1 号机仍是主机,配置之后会变为从机) 配置如下图:

第三步: 配置完成后,我们去看看我们的主机状态(6379 端口)。 如下图:

第四步: 注意事项与故障测试。 这部分使用文字描述,具体操作自行实现。描述如下:

1
2
3
4
5
6
7
8
9
10
11
12
+++ 注意事项与故障测试

+ 1、真正的主从复制的配置应该在配置文件中配置,这样配置才是永久的,而我们使用命令配置只是暂时的。
+ 2、主机负责写,从机负责读且从机不能写。(从机如果去写,会报错: (error) READONLY You can't write against a read only replica.)

- 如果主机服务器(6379 端口)突然宕机了(我们把它断开连接),那么从机依旧会连接到主机,但是没有写操作。
- 如果此时主机又恢复正常了,从机依旧可以获取到主机写的信息。

+ 如果是使用命令行来配置的主从复制,这个时候从机如果重启,就会变会到主机,就无法获取到他原来主机的值。
+ 这个时候如果把它变回从机,那么它又可以获取到原来主机的值。

+++ 注意事项与故障测试

宕机后手动配置主机

如果我们的 6379 机子宕机了,那怎么办呢?【现在我们先手动配置,稍后讲哨兵模式(自动配置)】 如下图:

我们先把集群配置成上图的第二种方式。【注意观察 6380 机子的信息】 如下图:

6381 机子 6382 机子 6380 机子 6379 主机


此时,我们把 6379 主机断开(使用 shutdown + exit 命令) ,那就没有主机了。那么 6380 机子可能成为主机吗?(需手动选择) 系统能不能在主机宕机之后自动选择主机呢?(哨兵模式,稍后讲) 我们先来手动配置,把 6380 机子设置为主机。如下图:

那么问题来了,如果 6379 又恢复正常了,谁会是主机呢?【6380 仍是主机,否则怎么叫谋朝篡位呢】 如下图:


温馨提示:

主机宕机的手动解决方案就是这样了。接下来介绍 自动解决方案: 哨兵模式

哨兵模式详解

哨兵模式简介

主从切换技术的方法是: 当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要 人工干预,费事费力,还会造成一段时间内服务不可用, 这不是一种推荐的方式。


更多时候, 我们优先考虑 哨兵模式从 Redis2.8 开始正式提供了 Sentinel(哨兵)架构来解决这个问题。哨兵模式 就是谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。


哨兵模式 是一种特殊的模式, 首先 Redis 提供了哨兵的命令, 哨兵是一个独立的进程, 作为进程, 它会独立运行。其原理是哨兵通过发送命令, 等待 Redis 服务器响应, 从而监控运行的多个 Redis 实例。 哨兵模式如下图:

这里的哨兵有两个作用:

1、通过发送命令, 让 Redis 服务器返回监控其运行状态的信息, 包括主服务器和从服务器。
2、当哨兵监测到 master 宕机, 会自动将 slave 切换成 master, 然后通过发布订阅模式通知其他的从服务器,并修改配置文件,让它们切换主机。

然而,一个哨兵进程对 Redis 服务器进行监控, 可能会出现问题【可能哨兵挂了】。 为此, 我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了 多哨兵模式 。如下图:

假设主服务器宕机,哨兵 1 先检测到这个结果, 系统并不会马上进行 failover 过程, 仅仅是哨兵 1 主观的认为主服务器不可用, 这个现象称为 主观下线 。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票, 投票的结果由一个哨兵发起, 进行 failover[故障转移] 操作。切换成功后, 就会通过 发布订阅模式 , 让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为 客观下线

哨兵模式配置

第一步: 明确我们的主从配置模式。 现在的配置模式 【一主三从】 如下图:

第二步: 配置我们的哨兵配置文件 sentinel.conf 配置文件内容及截图如下:

sentinel.conf
1
2
3
4
5
6
7
###########################################################################################

# sentinel【固定的】 monitor【固定的】 监控的名称 主机 端口号 是否选举
sentinel monitor myredis 127.0.0.1 6379 1
# 上述的数字 1 表示: 当主机挂了,哨兵进行投票给从机,哪个从机票数多,谁就能够成为主机。

###########################################################################################

第三步: 启动哨兵模式。 如下图:

第四步: 关闭主机,查看哨兵模式是否起作用。 如下图:



那么问题来了,如果 6379 机子恢复了,会出现什么情况呢? 如下图:

优缺点及问题

1
2
3
4
5
6
7
8
9
10
11
12
+++ 哨兵模式优缺点

+++ 优点
+ 1、哨兵集群,基于主从复制模式,所有的主从配置优点,它全有。
+ 2、主从可以切换,故障可以转移,系统的可用性就会更好。
+ 3、哨兵模式就是主从模式的升级,手动到自动,更加健壮!

--- 缺点
- 1、Redis 不好在线扩容, 集群容量一旦到达上限, 在线扩容就十分麻烦!
- 2、实现哨兵模式的配置其实是很麻烦的,里面有很多选择!

+++ 哨兵模式优缺点

问题如下图:就是我恢复 6379 机子时,主机发生了变化,不是 6381了, 而是 6382。由于时间原因,暂未查明,以后看到把它搞定。

哨兵模式配置文件

哨兵模式配置文件 详解如下:

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

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

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

#哨兵sentinel监控的redis主节点的 ip port
#master-name可以自己命名的主节点名字,只能由字母A-z、数字0-9、这三个字符".-_"组成。
#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 XXX
#指定多少毫秒之后主节点没有应答哨兵,此时哨兵主观上认为主节点下线,默认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 内存数据库没有, 也就是缓存没有命中, 于是向持久层数据库查询。发现也没有,于是本次查询失败。 当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库,这会给持久层数据库造成很大的压力,这时候就相当于出现了 缓存穿透


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

缓存空对象:当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。


但是这种方法会存在两个问题:

  • 1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键。
  • 2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

缓存击穿

这里需要注意缓存穿透和缓存击穿的区别, 缓存击穿 是指一个 key 非常热点(如微博热搜), 在不停的扛着大并发, 大并发集中对这一个点进行访问, 当这个 key 在失效的瞬间, 持续的大并发就穿破缓存, 直接请求数据库, 就像在一个屏障上凿开了一个洞。


当某个 key 在过期的瞬间, 有大量的请求并发访问, 这类数据一般是热点数据, 由于缓存过期, 会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。


解决方案
1、设置热点数据永不过期:从缓存层面来看, 没有设置过期时间, 所以不会出现热点 key 过期后产生的问题。
2、加互斥锁分布式锁:使用分布式锁, 保证对于每个key同时只有一个线程去查询后端服务, 其他线程没有获得分布式锁的权限, 因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

缓存雪崩

缓存雪崩 是指在某一个时间段工缓存集中过期失效。产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。11!I


解决方案
1、Redis 高可用
这个思想的含义是, 既然red is有可能挂掉, 那我多增设几台red is, 这样一台挂掉之群。(异地多活!)
2、限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的询数据和写缓存,其他线程等待。
3、数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分将发生大并发访问前手动触发加载缓存不同的key, 设置不同的过期时间, 让缓存失后其他的还可以继续工作,其实就是搭建的集的线程数量。比如对某个key只允许一个线程查分可能大量访问的数据就会加载到缓存中。在即效的时间点尽量均匀。

  

📚 本站推荐文章
  👉 从 0 开始搭建 Hexo 博客
  👉 计算机网络入门教程
  👉 数据结构入门
  👉 算法入门
  👉 IDEA 入门教程

可在评论区留言哦

一言句子获取中...

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×