整体介绍
Redis 是一种非关系型数据库(NoSQL数据库),主要用作数据库、 缓存、 流式处理引擎和消息代理的开源内存的数据存储。
Redis数据库是一个开源的键值存储数据库,所有的数据全部存放在内存中,它的性能大大高于磁盘的IO,并且它可以支持数据库的持久化,支持横向扩展,主从复制等。
NoSQL介绍
1. 特点及优势
NoSQL(Not Only SQL)是一种非关系型数据库。
NoSQL相较于传统的关系型数据库,具有的特点如下:
- 不保证关系数据库的ACID特性。
- 不遵循SQL的标准。
- 不需要清除数据之前的关联特性。
NoSQL相较于传统的关系型数据库,具有的优势如下:
- 性能远超于传统关系型数据库。
- 易于扩展。
- 灵活的数据模型。
- 高可用。
关系型数据库的ACID特性:
- 原子性(Atomicity),不可分割性。
- 一致性(Consistency)。
- 隔离性(Isolation),独立性。
- 持久性(Durabilit).
2. 分类
NoSQL数据库的分类:
- 键值存储数据库: 所有数据都是通过键值方式存储,类似于HashMap。使用非常简单方便,性能也非常高。
- 列存储数据库: 通常用来应对分布式存储的海量数据,键仍然存在,但是他们的特点是指向了多个列。
- 文档型数据库: 以特定的文档格式存储数据,比如JSON格式。在处理网页等复杂的数据时,文档型数据库比传统的键值数据库的查询效率更高。
- 图形数据库: 利用类似于图的数据结构存储数据,结合图相关算法,高速访问。
安装和部署
1. Linux系统安装Redis
Linux环境为: CentOS8.2 X86架构下,Redis版本: 7.2.1
Redis的目录规划:
- 根目录: /redis/
- 安装包目录: /redis/install/
- 安装目录: /redis/redis7.2.1/
1.1 Redis安装步骤
步骤1: 下载Redis安装包
Redis安装包下载页面: https://redis.io/download/
Redis7.2.1安装包下载地址: https://codeload.github.com/redis/redis/tar.gz/refs/tags/7.2.1
步骤2: 上传Redis安装包至Redis安装包目录
# 进入MySQL安装包目录
cd /redis/install/
# 上传rpm安装包, 通过rz选择本机的rpm包进行上传(实现linux和windows之间的文件传输)
rz
步骤3: 解压Redis的tar.gz安装包至Redis安装目录
tar -zxf redis-7.2.1.tar.gz -C /redis/
步骤4: 安装gcc环境
yum install gcc
步骤5: 编译和安装Redis
# 进入Redis安装目录
cd /redis/redis7.2.1
# 编译和安装
make && make install
步骤6: 切换至默认安装Redis的路径,查看Redis的可执行文件
# 切换至安装路径
cd /usr/local/bin
# 查看Redis的相关文件
ls
至此Redis安装完成,安装Redis默认路径为**/usr/local/bin/**,其中各个模块功能介绍如下:
模块名称 | 模块功能 |
---|---|
redis-server | Redis服务器 |
redis-cli | Redis命令行客户端 |
redis-benchmark | Redis性能测试 |
redis-check-aof | AOF文件修复工具 |
redis-check-rdb | RDB文件修复工具 |
redis-sentinel | Sentienl服务器(2.8以后),Redis集群使用 |
1.2 Redis服务启动
方式1: 前台命令直接启动(一般不推荐)
# 进入Redis的默认安装路径
cd /usr/local/bin
# 启动
redis-server
方式2: 后台启动
步骤1: 修改Redis的配置文件,调整daemonize为yes,变成守护进程。
# 进入Redis安装目录
cd /redis/redis7.2.1
# 编辑redis的配置文件
vi redis.conf
步骤2: 启动Redis服务器
# 进入Redis的默认安装路径
cd /usr/local/bin
# 启动
redis-server /redis/redis7.2.1/redis.conf
方式3: 开启自启动
步骤1: 将Redis配置文件复制到/etc/redis/目录下
# 创建/etc/redis/目录
mkdir /etc/redis/
# 拷贝redis.conf配置文件至/etc/redis/目录下
cp /redis/redis7.2.1/redis.conf /etc/redis/6379.conf
步骤2: 复制启动脚本到/etc/init.d/目录下
cp /redis/redis7.2.1/utils/redis_init_script /etc/init.d/redis
步骤3: 查看/etc/init.d/redis文件配置信息,检查EXEC、 CLIEXEC、 CONF的路径是否正确并调整
# 查看文件配置信息
cat /etc/init.d/redis
# 编辑配置文件
vi /etc/init.d/redis
步骤4: 开启Redis自启命令
# 进入init.d目录
cd /usr/init.d
# 设置redis开机自启
chkconfig redis on
步骤5: 启停Redis服务器
# 启动redis服务器
service redis start
# 关闭redis服务器
service redis stop
1.3 Redis设置密码
通过配置文件方式设置的密码为永久密码,不会因为重启Redis服务而重置密码。
通过命令行界面设置的密码为临时密码,会因为重启Redis服务而重置密码。
其中如果配置文件和命令行文件都设置密码,Redis会以配置文件的密码为主。
方式1: 通过配置文件设置密码,设置requirepass参数的值
# 启动方式2
vi /redis/redis7.2.1/redis.conf
# 启动方式3
vi /etc/redis/6379.conf
方式2: 通过Redis命令行界面设置
# 设置Redis密码
config set requirepass 123456
# 查看设置的密码
config get requirepass
1.4 Redis连接方式
方式1: Redis远程连接
步骤1: 通过配置文件设置访问权限,注释bind 127.0.0.1 -::1和更改protected-mode参数值为no
# 启动方式2
vi /redis/redis7.2.1/redis.conf
# 启动方式3
vi /etc/redis/6379.conf
步骤2: 重启启动Redis服务
# 启动方式2
redis-server /redis/redis7.2.1/redis.conf
# 启动方式3
service redis start
注意: 设置密码之后,无法通过
service redis stop
停止Redis服务, 需要修改停止参数。
方式2: Redis客户端连接
# 通过-a参数连接
redis-cli -a 认证密码
# 先连接再通过密码认证
redis-cli # 进入redis客户端
auth 认证密码 # 认证redis
1.5 Redis使用问题
当设置Redis密码后,开机自启动方式无法通过service redis top
命令停止Redis服务。解决步骤如下:
步骤1: 编辑启停脚本文件,修改停止的参数
# 编辑启停脚本文件
vi /etc/init.d/redis
# 将38行的命令做调整为:
$CLIEXEC -a "认证密码"-p $REDISPORT shutdown
步骤2: 重启Redis服务器
# 停止Redis服务
redis-cli -a "认证密码" shutdown
# 启动Redis服务
service redis start
2. Windows系统安装Redis
Redis官方不建议在Windows下使用 Redis,所以官网不提供Windows版本的。所以只能借助其它三方实现的Windows版本进行搭建安装。
Windows环境为: Win11 X86架构下,Redis版本: 5.0.14.1
2.1 Redis安装步骤
步骤1: 下载Redis安装包
Redis安装包下载页面: https://github.com/tporadowski/redis/releases
Redis7.2.1安装包下载地址: https://github.com/tporadowski/redis/releases/download/v5.0.14.1/Redis-x64-5.0.14.1.zip
步骤2: 解压Redis安装包
2.2 Redis文件介绍
双击redis-server.exe应用程序即可启动Redis服务。
模块名称 | 模块功能 |
---|---|
redis-server.exe | Redis服务器启动的应用程序 |
redis-cli.exe | Redis命令行客户端启动的应用程序 |
redis-benchmark.exe | Redis性能测试的应用程序 |
redis-check-aof.exe | AOF文件修复工具 |
redis-check-rdb.exe | RDB文件修复工具 |
redis.windows.conf | 配置文件,将redis作为普通软件使用的配置,命令行关闭则redis关闭 |
redis.windows-service.conf | 配置文件,将redis作为系统服务的配置 |
数据类型
Redis数据库是有一个数据索引标识,默认情况下,Redis数据库使用0号数据库,通过Redis配置文件的databases参数来修改数据库总数,默认共有16个。
1. Key键命令
常用命令 | 含义 |
---|---|
select 数据库索引标识 | 切换Redis数据库 |
expire <key> 秒数 | 设定数据的过期时间 |
ttl <key> | 查看某个键值剩余有效时间,单位: 秒 |
pttl <key> | 查看某个键值剩余有效毫秒数,单位: 毫秒 |
persist <key> | 设置某个键值对永久有效 |
del <key> [<key> ...] | 删除键值对 |
keys * | 查看所有的键 |
exists <key> | 查看某个键是否存在,存在: 1; 不存在: 0 |
randomkey | 从当前数据库中随机取一个键 |
move <key> 整数索引标识 | 将当前数据库的键值移动到另一个数据库中 |
rename <key> <新名称> | 直接修改键的名称 |
renamex <key> <新名称> | 判断新名称是否存在,不存在时进行修改 |
type <key> | 查看值的数据类型 |
2. String数据
对比于JAVA中的数据类型: HashMap<String,String>
常用命令 | 含义 |
---|---|
set <key> <value> | 添加一个数据,value默认会以字符串保存,可通过":"对key进行板块分割 |
mset [<key> <value> ...] | 添加多个数据,value默认会以字符串保存,可通过":"对key进行板块分割 |
set <key> <value> EX 秒数 | 存储数据并设定过期时间,默认永久有效(-1),单位:秒 |
set <key> <value> PX 毫秒 | 存储数据并设定过期毫秒数,单位:毫秒 |
get <key> | 获取存储键的值 |
incr <key> | 对值为数值类型的数据进行自增1 |
incrby <key> n | 对值为数值类型的数据进行自增n |
decr <key> | 对值为数值类型的数据进行自减1 |
3. Hash数据
对比于JAVA中的数据类型: HashMap<String,HashMap<String,String>>
常用命令 | 含义 |
---|---|
hset <key> [<字段> <值>] | 添加数据 |
hget <key> <字段> | 获取某个字段的值 |
hgetall <key> | 获取某个key的全部字段和值 |
hexists <key> <字段> | 判断字段是否存在,存在: 1;不存在: 0 |
hdel <key> <字段> | 删除字段,删除成功: 1;删除失败: 0 |
hlen <key> | 获取某个key中键值对的个数 |
hkeys <key> | 获取某个key所有字段内容 |
hvals <key> | 获取某个key所有字段值内容 |
4. List数据
对比于JAVA中的数据类型: LinkedList
常用命令 | 含义 |
---|---|
lpush <key> <element> ... | 在某个key的头部添加元素 |
rpush <key> <element> ... | 在某个key的尾部添加元素 |
linsert <key> before/after <指定元素> <element> ... | 在指定位置的前后添加元素 |
lindex <key> <下标> | 通过元素的下标获取元素的值 |
lpop <key> | 获取并移除头部元素 |
rpop <key> | 获取并移除尾部元素 |
lrange <key> start stop | 获取指定范围的元素(负数从后向前) |
lrange <key> 0 -1 | 获取某个key的全部元素 |
rpoplpush 当前数组 目标数组 | 从当前元素的右侧弹出值,并插入到目标数组的左侧 |
bloop <key> ... timeout | 阻塞等待当前key的值产生,超时自动退出 |
5. Set数据
对比于JAVA中的数据类型: HashSet
常用命令 | 含义 |
---|---|
sadd <key> <value> ... | 添加数据 |
scard <key> | 查看数据个数 |
sismember <key> <value> | 判断集合中是否包含某个值 |
sismember <key> | 列出所有的值 |
sdiff <key1> <key2> | 两个key之间集合的差集 |
sinter <key1> <key2> | 两个key之间集合的交集 |
sunion <key1> <key2> | 两个key之间集合的并集 |
sdiffstore 目标 <key1> <key2> | 将两个key之间集合的差集,存储到目标集合中 |
sinterstore 目标 <key1> <key2> | 将两个key之间集合的交集,存储到目标集合中 |
sunionstore 目标 <key1> <key2> | 将两个key之间集合的并集,存储到目标集合中 |
smove <key> 目标 <value> | 将key的特定的value值移动到目标集合中 |
spop <key> | 随机删除key中的一个数据 |
srem <key> <value> | 移除key中的特定的value值 |
6. SortedSet数据
常用命令 | 含义 |
---|---|
zadd <key> [<score> <value> ...] | 添加带分数的值 |
zcard <key> | 查询某个key的值的个数 |
zrem <key> <value>... | 移除key中的特定的value值 |
zrange <key> start stop | 获取指定范围的值(负数从后向前) |
zrangescore <key> start stop [withscores] [limit offset count] | 根据分数值来获取对应范围的有限值 |
zcount <key> start stop | 统计某个key在分数值内的数量 |
zrank <key> <value> | 根据分数获取指定的排名 |
7. 其它数据
- Bitmap: 位图操作
- HyperLogLong: 基数统计
- Geospatial: 地理空间及索引半径查询
持久化
Redis持久化的两种方案:
- 直接保存当前已经存储的数据,相当于复制内存中的数据到磁盘上,需要恢复数据时直接读取即可。
- 保存存放数据的所有过程,只需要将整个过程完整地重演一遍,就可以保证与之前数据内容一致。
1. RDB模式
RDB模式是保存已经存储的数据。
RDB保存数据的两种方式:
save
: 在Redis主进程中开启保存事物操作。bgsave
: 单独会开一个子进程后台执行保存事物操作。
save
和 bgsave
在执行后会产生一个dump.rdb文件,将内存中的数据保存在文件中。当服务器重启之后,会自动加载文件中的数据到对应的数据库。
调整配置参数:
save 300 10
,当300秒内有10个写入即执行保存数据操作。
2. AOF模式
AOF模式是保存存放数据的所有过程信息 。
AOF模式通过日志的形式,每次执行命令都将保存在appendonly.aof文件中。当服务器重启时,会将所有的命令再次执行一遍。
AOF模式写日志的策略:
always
: 每次执行写操作都会保存一次。everysec
: 每秒保存一次(默认配置)。no
: 系统自定义。
调整配置参数:
- 将配置文件中appendonly的参数值由no改为yes。
- 调整配置文件中appendfsync的参数值,参考值: always/everysec/no。
- auto-aof-rewrite-percentage 100,按照百分比计算是否自动写入(额外参数)。
- auto-aof-rewrite-min-size 64mb,当达到64M时,自动触发重写(额外参数)。
调整完成之后,重新启动Redis服务,即可看到服务器的目录下新增appendonly.aof文件。
命令行手动触发重写AOF文件: bgrewriteaof
。
3. 持久化方式对比
模式名称 | 优点 | 缺点 |
---|---|---|
RDB | 加载速度快,数据体积小 | 存储速度慢,大量消耗资源,会发生数据丢失 |
AOF | 存储速度快,消耗资源小,支持实时存储 | 加载数据慢,数据体积大 |
事物和锁机制
事物: 保证多条命令一次性完整执行而中途不受到其它命令的干扰。
事物的系列操作:
multi
: 开启事务exec
: 执行事务discard
: 取消事务
事务实际上是一个命令队列,没有MySQL可以再事务中单独得到结果的特性。将所有的命令装在队列中,但并不会执行,等到执行或取消时,再统一执行。
锁机制: 悲观锁和乐观锁
- 悲观锁: 时刻认为别人回来抢占资源,禁止一切外来访问,直到释放锁,具有强烈的排他性质。
- 乐观锁: 并不认为会有其他人来抢占资源,所以会直接对数据进行操作,在操作时再去验证是否会有其他人抢占资源
Redis中的锁属于乐观锁,会通过当前对象的版本号做控制,而在MySQL中属于悲观锁。
锁机制的系列操作:
watch
: 监视某个目标,如果执行事务之前被监视的目标发生了修改,则在执行时会取消本次事务,返回nil。unwatch
: 取消监视。
Redis与应用程序交互
1. 在Maven中使用Redis
步骤1: 在pom文件中引入Redis依赖和slf4j依赖
<!--Jedis - Redis的依赖库-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.0.0</version>
</dependency>
<!--slf4j - 解决Jedis运行时提示的问题org.slf4j.impl.StaticLoggerBinder-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.9</version>
</dependency>
步骤2: 编写测试是否能够成功连接Redis服务
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("认证密码"); // 如果reids设置的有密码,通过该行进行认证
jedis.close();
}
举例1: 通过jedis设置并查询String数据
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
jedis.set("a", "123");
System.out.println(jedis.get("a"));
}
}
举例2: 通过jedis设置并查询Hash数据
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
jedis.hset("b", "name", "张三");
Map<String, String> temp = new HashMap<>();
temp.put("age", "19");
jedis.hset("b", temp);
jedis.hgetAll("b").forEach((key, value) -> {
System.out.println(key + " : " + value);
});
}
}
举例3: 通过jedis设置并查询List数据
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
jedis.lpush("c", "123", "456", "789");
jedis.lrange("c", 0, -1).forEach(System.out::println);
}
}
举例4: 通过jedis设置并查询Set数据
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
jedis.sadd("d", "123", "123", "456", "789");
jedis.smembers("d").forEach(System.out::println);
}
}
举例5: 通过jedis设置并查询SortedSet数据
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
jedis.zadd("e", 5, "abc");
jedis.zadd("e", 10, "def");
jedis.zadd("e", 2, "hij");
jedis.zrange("e", 0, -1).forEach(System.out::println);
}
}
2. 在SpringBoot中使用Redis
步骤1: 在SpringBoot工程的pom文件中加入redis和lombok依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
步骤2: 在application.yml中配置redis相关信息
spring:
redis:
host: localhost
port: 6379
database: 0
步骤3: 注入Redis的模版,测试连接、 设置并查询String数据
@Autowired
private StringRedisTemplate template;
@Test
void test1() {
template.opsForValue().set("a", "lisi");
System.out.println(template.opsForValue().get("a"));
}
在SpringBoot中对Redis的初始化模版有两个Bean(RedisAutoConfiguration.java)
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
3. 在SpringBoot中使用Redis事务管理器
SpringBoot中没有专门的Redis事务管理器,所以只能借助于JDBC提供的
步骤1: 在SpringBoot工程的pom文件中除了加入redis和lombok依赖外,还需要加入jdbc依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
步骤2: 在application.yml中除了配置redis相关信息外,还需要加入MySQL数据库的连接信息
spring:
redis:
host: localhost
port: 6379
database: 0
datasource:
url: jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password:
步骤3: 创建一个RedisSerivce类,并开启Redis事务。
@Service
public class RedisService {
@Autowired
private StringRedisTemplate template;
@PostConstruct
public void init() {
template.setEnableTransactionSupport(true);
}
@Transactional
public void test() {
template.multi();
template.opsForValue().set("a", "wangwu");
template.exec();
}
}
步骤4: 调用RedisService类的test方法
@Autowired
RedisService redisService;
@Test
void test1() {
redisService.test();
}
4. 在SpringBoot中Redis序列化
步骤1: 在SpringBoot工程的pom文件中加入redis和lombok依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
步骤2: 在application.yml中配置redis相关信息
spring:
redis:
host: localhost
port: 6379
database: 0
步骤3: 创建Demo实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Demo implements Serializable {
String id;
String name;
}
步骤4: 注入Redis的模版,测试连接、 设置并查询String数据
@Autowired
private RedisTemplate<Object, Object> template;
@Test
void test1() {
StringRedisSerializer StringRedisSerializer=new StringRedisSerializer();
template.setKeySerializer(StringRedisSerializer);
Demo demo = new Demo("1", "zhangsan");
template.opsForValue().set("e", demo);
}
通过Jackson实现序列化器:
步骤1: 在SpringBoot工程的pom文件中除了加入redis和lombok依赖外,在加入jackson依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
步骤2: 在application.yml中配置redis相关信息
spring:
redis:
host: localhost
port: 6379
database: 0
步骤3: 创建Demo实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Demo implements Serializable {
String id;
String name;
}
步骤4: 注入Redis的模版,测试连接、 设置并查询String数据
@Autowired
private RedisTemplate<Object, Object> template;
@Test
void test1() {
StringRedisSerializer StringRedisSerializer=new StringRedisSerializer();
template.setKeySerializer(StringRedisSerializer);
template.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
Demo demo = new Demo("1", "zhangsan");
template.opsForValue().set("e", demo);
}
主从复制
主从复制是指将一台Redis服务器的数据,复制到其它Redis服务器上。前者称为主节点(Master),后者称为从节点(Slave),数据的复制是单向的,只能是由主节点到从节点。
主从复制的优势:
- 实现了读写分离,提高性能。
- 在写少读多的场景下,可以安排多个从节点。这样能够大幅度的分担压力。
主从复制可以进行级联,即主从节点的从节点也可以作为其它节点的主节点。
级联主从的优点: 分担一主多从时,主节点的压力。
级联主从的缺点: 当某一个级联从节点出现问题后,那么后续级联的节点都会出现问题。
1. 主从搭建
由于以单台Windows环境搭建,在搭建时需要考虑端口冲突问题, 所以需要调整Redis默认端口
主节点Master端口: 6301; 从节点Slave端口: 6401
方式1: 通过命令行进行配置
步骤1: 根据实际情况判断,是否需要调整Redis默认6379端口号,进入Redis配置文件,找到port进行调整
# 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
步骤2: 启动两台Redis服务器
# linux启动命令
service redis start
# windows启动命令
redis-server.exe redis.windows.conf
步骤3: 登录Redis客户端,查看两台服务器当前节点信息
info replication
绑定前主节点信息如下图所示:
绑定前从节点信息如下图所示:
步骤4: 选取其中一台作为主节点, 通过redis-cli连接另一台Redis服务,使用replicaof
命令(老版本:slaveof
)绑定主从关系
replicaof 主节点Reids的ip地址 主节点Redis的端口
步骤5: 登录Redis客户端,查看两台服务器绑定主机节点后的信息
info replication
绑定后主节点信息如下图所示:
绑定后从节点信息如下图所示:
至此,主从复制搭建完成。
主从复制搭建完成之后,主节点是可以进行读写操作,但是从节点只能进行读操作。
方式2: 通过Redis配置文件进行配置
步骤1: 将replicaof
命令参数加入到从节点的Redis配置文件中
replicaof 主节点Reids的ip地址 主节点Redis的端口
从节点Redis配置文件加入replicaof配置信息:
步骤2: 重新启动Redis主从节点的服务
# linux启动命令
service redis start
# windows启动命令
redis-server.exe redis.windows.conf
2. 偏移量参数
搭建完成之后,查看主服务器节点信息,会发现存在一个slave0信息,该行中有一个字段offset。
offset: 复制偏移量,是反映从节点同步的情况。主服务器每次会向从服务器中传递N个字节后,主服务器会加上N,从服务器再接收到主服务器的N个字节数据后,也会将复制的偏移量加上N,通过主从服务器的偏移量对比,可以查看数据是否一致。
3. 主从撤销
主节点Master端口: 6301; 从节点Slave端口: 6401
如果主从节点不在使用,需要进行分离操作,则就需要撤销主从节点绑定的关系
步骤1: 登录Redis客户端,查看当前主从节点绑定状态
info replication
绑定后主节点信息如下图所示:
绑定后从节点信息如下图所示:
步骤2: 登录从节点客户端,执行解绑命令
replicaof no one
步骤3: 登录Redis客户端,查看解绑后的状态
info replication
解绑后主节点信息如下图所示:
解绑后从节点信息如下图所示:
4. 同步流程
- 从节点执行
replicaof ip port
命令后,从节点会保存主节点相关的地址信息。 - 从节点通过每秒运行的定时任务发现新的主节点后,会尝试与该节点建立网络连接,专门用于接收主节点发送的复制命令。
- 连接成功后,第一次会将主节点的数据进行全量复制,之后采用增量复制,持续将新来的命令同步给从节点。
哨兵模式
主从模式下如果主节点出现了问题,那么主从系统无法将写入操作,所以为了避免这种情况发生,需要采取哨兵模式来监控。
哨兵模式,类似于Nacos和Eureka服务治理,所有Redis服务都会被实时监控,只要被监控的服务出现问题,那么哨兵会及时发现,及时采取响应措施。
哨兵会对所有Redis节点进行监控,如果主节点出现问题,那么会在从节点中进行投票,选举一个新的主节点出来,这样就会避免因为原本主节点故障导致整个系统不可写的问题。
哨兵模式下的Redis服务至少是一主一从。
1. 单机搭建
单个哨兵,作为监控者,监控Redis的主从复制服务。
主从复制的三个节点信息: 主节点Master端口: 6301; 从节点Slave端口: 6401、 6501
步骤1: 搭建好主从复制环境
略,参照主从复制的搭建步骤。
步骤2: 在Redis配置文件统计目录下,创建一个sentinel.conf配置文件,并添加sentinel的配置信息
sentinel monitor sentinel的名称 当前主节点的IP地址 当前主节点的端口 触发哨兵选举的个数
步骤3: 启动Redis的哨兵节点服务
# linux启动命令
redis-server sentinel.conf的路径 --sentinel
# windows启动命令
redis-server.exe sentinel.conf --sentinel
启动完成之后,哨兵的sentinel.conf的配置信息如下所示(默认端口号: 26379):
至此,单机哨兵搭建完成。
测试: 停止当前主从复制的主节点,查看哨兵日志是否会在从节点中选举出新的节点作为主节点,除了原本的主节点外,查看其它节点日志是否恢复正常。
哨兵监控发现、 选举并切换的日志如下所示:
原本的从节点晋升为主节点的日志如下图所示:
晋升为主节点失败的从节点的日志如下图所示:
2. 哨兵选举规则
- 根据Redis配置文件中的replica-priority配置项的配置值进行选举(默认值: 100),该值越小优先级越高 。
- 如果replica-priority配置项的配置值相同,查看当前存活节点中复制偏移量,偏移量越大优先级越高。
- 如果replica-priority配置项的配置值相同且存活节点中复制偏移量的值也相同,查看当前Redis启动时run_id的值,run_id值越小优先级越高。
run_id查看方法: 登录到对应的Redis客户端,输入info server
命令进行查看。
3. 集群搭建
为了防止单个哨兵出现问题,导致整个Redis无法再次选举,所以进行哨兵的集群搭建,和单机搭建哨兵类似。
集群哨兵,作为监控者,监控Redis的主从复制服务。关注触发哨兵选举的值和当前Redis主节点信息。
主从复制的三个节点信息: 主节点Master端口: 6301; 从节点Slave端口: 6401、 6501
步骤1: 搭建好主从复制环境
略,参照主从复制的搭建步骤。
步骤2: 进入每个哨兵节点的配置文件统计目录下,创建一个sentinel.conf配置文件,并添加sentinel的配置信息
port sentinel的端口
sentinel monitor sentinel的名称 当前主节点的IP地址 当前主节点的端口 触发哨兵选举的个数
步骤3: 启动Redis的每个哨兵节点服务
# linux启动命令
redis-server sentinel.conf的路径 --sentinel
# windows启动命令
redis-server.exe sentinel.conf --sentinel
至此,集群哨兵搭建完成。
4. 哨兵与Maven应用程序交互
步骤1: 创建一个Maven工程在pom文件中引入Jedis依赖
<!--Jedis - Redis的依赖库-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.0.0</version>
</dependency>
<!--slf4j - 解决Jedis运行时提示的问题org.slf4j.impl.StaticLoggerBinder-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.9</version>
</dependency>
步骤2: 创建一个main方法,测试连接、 测试并查看结果
public static void main(String[] args) {
try (JedisSentinelPool pool = new JedisSentinelPool("sentinel的名称",
new HashSet<>(Arrays.asList("sentinel的IP地址:sentinel的端口", "sentinel的IP地址:sentinel的端口")))) {
Jedis jedis = pool.getResource();
jedis.set("a", "1");
System.out.println(jedis.get("a"));
}
}
集群搭建
集群搭建主要是为了解决内存不足问题,实现的横向水平扩容。
集群搭建的环境数据存储位置: 一个Reids集群包含16384个插槽。集群中的每个Redis示例负责一部分插槽及其插槽所映射的键值数据。默认均分16384个插槽数。
Redis中Key的路由计算公式: slot = CRC16(Key) \% 16384。
1. 搭建
具备三对主从复制节点,主节点端口号依次为: 6301、 6401、 6501; 从节点端口号依次为:6302、 6402、 6502。其中主节点搭建集群。
三对主从复制节点的关联方式有所不同,具体参照下述步骤。
步骤1: 调整所有主从节点的配置信息,设置cluster-enabled开启集群,根据实际情况调整port端口参数
################################## NETWORK #####################################
port 端口号
################################ REDIS CLUSTER ###############################
cluster-enabled yes
步骤2: 启动所有主从节点Redis服务
# linux启动命令
service redis start
# windows启动命令
redis-server.exe redis.windows.conf
步骤3: 通过redis-cli设置集群节点关联信息
# linux设置集群节点信息
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:6301 127.0.0.1:6401 127.0.0.1:6501 127.0.0.1:6302 127.0.0.1:6402 127.0.0.1:6502
# windows设置集群节点信息
redis-cli.exe --cluster create --cluster-replicas 1 127.0.0.1:6301 127.0.0.1:6401 127.0.0.1:6501 127.0.0.1:6302 127.0.0.1:6402 127.0.0.1:6502
配置提示插槽分配及主从节点关系信息:
步骤4: 接收分配情况,在命令提示符窗口下输入yes
至此,集群搭建完成
2. 测试
连接客户端时,默认方式,需要注意值映射的插槽位置。如果与当前连接的位置不匹配,则会提示错误,错误的类型如下图所示:
解决上述问题: 在连接客户端时加入**-c**参数
# linux连接客户端
redis-cli -p 端口 -c
# windows连接客户端
redis-cli.exe -p 端口 -c
查看集群所有节点信息: cluster nodes
集群中的主从节点,如果某个主节点出现问题之后,那么从节点将会替代当前主节点,成为该分片的主节点。等原本主节点恢复之后,称为当前该分片的从节点。
3. 集群与Maven应用程序交互
步骤1: 创建一个Maven工程在pom文件中引入Jedis依赖
<!--Jedis - Redis的依赖库-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.0.0</version>
</dependency>
<!--slf4j - 解决Jedis运行时提示的问题org.slf4j.impl.StaticLoggerBinder-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.9</version>
</dependency>
步骤2: 创建一个main方法,测试连接、 测试并查看结果
public static void main(String[] args) {
try (JedisCluster cluster = new JedisCluster(new HostAndPort("127.0.0.1", 6301))) {
System.out.println("集群实例个数:" + cluster.getClusterNodes().size());
cluster.set("a", "11143");
System.out.println(cluster.get("a"));
}
}
4. 集群与SpringBoot应用程序交互
步骤1: 创建SpringBoot工程,找到pom文件,引入SpringBoot依赖、 JDBC依赖、 MyBatis-Plus依赖、 Redis依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
步骤2: 在MySQL数据库中创建db1数据库,并创建一张数据t_account数据库表
# 创建db1数据库
CREATE DATABASE `db1`;
# 创建t_account数据库表
CREATE TABLE `t_account` (
`id` bigint NOT NULL COMMENT '主键',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`money` int DEFAULT NULL COMMENT '金额'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='账户信息'
步骤3: 在SpringBoot工程,找到resource目录,在其目录下创建application.yml文件,并配置MySQL数据库和Redis信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
username: root
url: jdbc:mysql://127.0.0.1:3306/db1?useUnicode=true&characterEncoding=utf-8
password: ZYMzym111
redis:
cluster:
nodes: 127.0.0.1:6301,127.0.0.1:6401,127.0.0.1:6501,127.0.0.1:6302,127.0.0.1:6402,127.0.0.1:6502
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations:
- classpath:mapper/*.xml
- classpath*:com/**/mapper/*.xml
步骤4: 在SpringBoot工程中,找到java目录下的包,在包下创建utils工具文件夹,该utils工具文件夹中创建MyBatisPlusConfig.java实体类
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("org.example.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//如果配置多个插件,切记分页最后添加
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
//interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
步骤5: 在SpringBoot工程中,找到java目录下的包,在包下创建entity实体类文件夹,该entity实体类文件夹中创建Account.java实体类
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("t_account")
public class Account implements Serializable {
private int id;
private String name;
private int money;
}
步骤6: 在SpringBoot工程中,找到java目录下的包,在包下创建mapper映射接口文件夹,该mapper映射类文件夹中创建AccountMapper.java映射接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.example.entity.Account;
import org.springframework.stereotype.Repository;
@Mapper
public interface AccountMapper extends BaseMapper<Account> {
}
步骤7: 在SpringBoot工程中,找到resource目录,在目录下创建mapper映射文件夹,该mapper映射文件夹中创建AccountMapper.xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--这个名称空间是Mapper接口的路径,记得修改-->
<mapper namespace="org.example.mapper.AccountMapper">
</mapper>
步骤8: 在SpringBoot工程中,找到java目录下的包,在包下创建service服务文件夹,该service服务文件夹中创建AccountService.java服务接口
import com.baomidou.mybatisplus.extension.service.IService;
import org.example.entity.Account;
import java.util.List;
public interface AccountService extends IService<Account> {
List<Account> listAccount();
}
步骤9: 在SpringBoot工程中,找到service服务文件夹,在该文件下创建impl实现文件夹,该impl实现文件夹中创建AccountServiceImpl.java服务接口
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.example.entity.Account;
import org.example.mapper.AccountMapper;
import org.example.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
@Cacheable(value= "account", key = "'account_list'")
public List<Account> listAccount() {
return accountMapper.selectList(null);
}
}
步骤10: 在SpringBoot工程中,找到java目录下的包,在包下创建controller控制层文件夹,该controller控制层文件夹中创建AccountController.java服务接口
import org.example.entity.Account;
import org.example.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class AccountController {
@Autowired
private AccountService accountService;
@RequestMapping("/listAccount")
public List<Account> listAccount() {
return accountService.listAccount();
}
@Autowired
private RedisTemplate<String, String> redisTemplate;
@RequestMapping("/redisTemplate")
public Object redisTemplate() {
redisTemplate.opsForValue().set("a", "aabbcc");
return redisTemplate.opsForValue().get("a");
}
}
步骤10: 在SpringBoot工程中,找到java目录下的包,在包下Application.java程序启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
步骤11: 启动程序,并访问程序接口
接口1: http://localhost:8080/redisTemplate
说明: 访问该地址,在redis中设置并查看a的值
接口2: http://localhost:8080/listAccount
说明: 访问该地址,如果redis中没有数据,则进行数据库查询,之后都在redis中查询数据结果并返回
至此,集群与SpringBoot应用程序交互完成。
Redis部分注解说明:
@EnableCaching注解: 启动SpringBoot的缓存机制。
@Cacheable(value="", key="",condition="")注解:如果该"value::key"中存在数据,那么就直接从缓存中取数据;如果该"value::key"中没有数据,那么就直接从数据库中查询,结果往缓存中存储一份后,在返回数据,Key值可以不填,默认"SimpleKey []"。condition通过条件筛选是否从缓存中查询结果,可以通过#号将传递的值传入注解内。
@CachePut注解: 无论缓存中是否有数据,都把缓存覆盖依次,也可称为更新缓存,参数与@Cacheacle一致。
@CacheEvict注解: 每次执行方法都会把指定的缓存清空。
缓存查询结果的方式示例:
@Autowired
private RedisTemplate<String, User> redisTemplate;
public User getUserById(String userId) {
// 先从 Redis 中查询缓存
User user = redisTemplate.opsForValue().get(userId);
// 如果缓存中不存在,则从数据库中查询,并将结果存入缓存
if (user == null) {
user = userDao.getUserById(userId);
if (user != null) {
redisTemplate.opsForValue().set(userId, user);
}
}
return user;
}
Redis配置类,方便使用RedisTemplate不需要指定类型:
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;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
评论区