侧边栏壁纸
博主头像
叩钉壹刻博主等级

7分技术,3分管理,2分运气

  • 累计撰写 28 篇文章
  • 累计创建 13 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

Redis非关系型数据库

鹿心肺语
2023-11-22 / 0 评论 / 0 点赞 / 44 阅读 / 47791 字

整体介绍

Redis 是一种非关系型数据库(NoSQL数据库),主要用作数据库、 缓存、 流式处理引擎和消息代理的开源内存的数据存储。

Redis数据库是一个开源的键值存储数据库,所有的数据全部存放在内存中,它的性能大大高于磁盘的IO,并且它可以支持数据库的持久化,支持横向扩展,主从复制等。

NoSQL介绍

1. 特点及优势

NoSQL(Not Only SQL)是一种非关系型数据库。

NoSQL相较于传统的关系型数据库,具有的特点如下:

  • 不保证关系数据库的ACID特性。
  • 不遵循SQL的标准。
  • 不需要清除数据之前的关联特性。

NoSQL相较于传统的关系型数据库,具有的优势如下:

  • 性能远超于传统关系型数据库。
  • 易于扩展。
  • 灵活的数据模型。
  • 高可用。

关系型数据库的ACID特性:

  • 原子性(Atomicity),不可分割性。
  • 一致性(Consistency)。
  • 隔离性(Isolation),独立性。
  • 持久性(Durabilit).

2. 分类

NoSQL数据库的分类:

  1. 键值存储数据库: 所有数据都是通过键值方式存储,类似于HashMap。使用非常简单方便,性能也非常高。
  2. 列存储数据库: 通常用来应对分布式存储的海量数据,键仍然存在,但是他们的特点是指向了多个列。
  3. 文档型数据库: 以特定的文档格式存储数据,比如JSON格式。在处理网页等复杂的数据时,文档型数据库比传统的键值数据库的查询效率更高。
  4. 图形数据库: 利用类似于图的数据结构存储数据,结合图相关算法,高速访问。

安装和部署

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-serverRedis服务器
redis-cliRedis命令行客户端
redis-benchmarkRedis性能测试
redis-check-aofAOF文件修复工具
redis-check-rdbRDB文件修复工具
redis-sentinelSentienl服务器(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文件配置信息,检查EXECCLIEXECCONF的路径是否正确并调整

# 查看文件配置信息
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连接方式

方式1Redis远程连接

步骤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服务, 需要修改停止参数。

方式2Redis客户端连接

# 通过-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安装包

image-20231007143740163

2.2 Redis文件介绍

双击redis-server.exe应用程序即可启动Redis服务。

模块名称模块功能
redis-server.exeRedis服务器启动的应用程序
redis-cli.exeRedis命令行客户端启动的应用程序
redis-benchmark.exeRedis性能测试的应用程序
redis-check-aof.exeAOF文件修复工具
redis-check-rdb.exeRDB文件修复工具
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. 直接保存当前已经存储的数据,相当于复制内存中的数据到磁盘上,需要恢复数据时直接读取即可。
  2. 保存存放数据的所有过程,只需要将整个过程完整地重演一遍,就可以保证与之前数据内容一致。

1. RDB模式

RDB模式是保存已经存储的数据

RDB保存数据的两种方式:

  • save: 在Redis主进程中开启保存事物操作。
  • bgsave: 单独会开一个子进程后台执行保存事物操作。

savebgsave 在执行后会产生一个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

绑定前主节点信息如下图所示:

image-20231008093854625

绑定前从节点信息如下图所示:

image-20231008093924221

步骤4: 选取其中一台作为主节点, 通过redis-cli连接另一台Redis服务,使用replicaof命令(老版本:slaveof)绑定主从关系

replicaof 主节点Reids的ip地址 主节点Redis的端口

步骤5: 登录Redis客户端,查看两台服务器绑定主机节点后的信息

info replication

绑定后主节点信息如下图所示:

image-20231008094135523

绑定后从节点信息如下图所示:

image-20231008094111453

至此,主从复制搭建完成。

主从复制搭建完成之后,主节点是可以进行读写操作,但是从节点只能进行读操作。

方式2通过Redis配置文件进行配置

步骤1: 将replicaof命令参数加入到从节点的Redis配置文件中

replicaof 主节点Reids的ip地址 主节点Redis的端口

从节点Redis配置文件加入replicaof配置信息:

image-20231008110607185

步骤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

绑定后主节点信息如下图所示:

image-20231008094135523

绑定后从节点信息如下图所示:

image-20231008094111453

步骤2: 登录从节点客户端,执行解绑命令

replicaof no one  

步骤3: 登录Redis客户端,查看解绑后的状态

info replication

解绑后主节点信息如下图所示:

image-20231008100552560

解绑后从节点信息如下图所示:

image-20231008100518624

4. 同步流程

  1. 从节点执行replicaof ip port命令后,从节点会保存主节点相关的地址信息。
  2. 从节点通过每秒运行的定时任务发现新的主节点后,会尝试与该节点建立网络连接,专门用于接收主节点发送的复制命令。
  3. 连接成功后,第一次会将主节点的数据进行全量复制,之后采用增量复制,持续将新来的命令同步给从节点。

哨兵模式

主从模式下如果主节点出现了问题,那么主从系统无法将写入操作,所以为了避免这种情况发生,需要采取哨兵模式来监控。

哨兵模式,类似于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):

image-20231008154618992

至此,单机哨兵搭建完成。

测试: 停止当前主从复制的主节点,查看哨兵日志是否会在从节点中选举出新的节点作为主节点,除了原本的主节点外,查看其它节点日志是否恢复正常。

哨兵监控发现、 选举并切换的日志如下所示:

image-20231008155220053

原本的从节点晋升为主节点的日志如下图所示:

image-20231008155741456

晋升为主节点失败的从节点的日志如下图所示:

image-20231008160024539

2. 哨兵选举规则

  1. 根据Redis配置文件中的replica-priority配置项的配置值进行选举(默认值: 100),该值越小优先级越高 。
  2. 如果replica-priority配置项的配置值相同,查看当前存活节点中复制偏移量,偏移量越大优先级越高。
  3. 如果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

配置提示插槽分配及主从节点关系信息:

image-20231009102130185

步骤4: 接收分配情况,在命令提示符窗口下输入yes

image-20231009102314487

至此,集群搭建完成

2. 测试

连接客户端时,默认方式,需要注意值映射的插槽位置。如果与当前连接的位置不匹配,则会提示错误,错误的类型如下图所示:

image-20231009102658437

解决上述问题: 在连接客户端时加入**-c**参数

# linux连接客户端
redis-cli -p 端口 -c

# windows连接客户端
redis-cli.exe -p 端口 -c

查看集群所有节点信息cluster nodes

image-20231009103140988

集群中的主从节点,如果某个主节点出现问题之后,那么从节点将会替代当前主节点,成为该分片的主节点。等原本主节点恢复之后,称为当前该分片的从节点。

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;
    }
}
0

评论区