redis常用知识汇总(基本命令, springboot整合, 哨兵、集合部署,分布式锁)

介绍

Redis 是一个开源的内存数据库,它支持多种数据结构,并且常用于高速缓存、会话管理、消息队列等场景。Redis 的全称是 Remote Dictionary Server,是一种 key-value(键值对)存储系统,能够以极高的性能处理大量数据的读写操作。

Redis 的主要特点:

  1. 基于内存:数据存储在内存中,具有极高的读写速度,适用于实时数据处理和快速响应需求的应用。
  2. 支持持久化:虽然是内存数据库,但 Redis 提供了将数据保存到磁盘的持久化功能,防止数据丢失。
  3. 丰富的数据结构
  4. 数据过期与自动删除:Redis 支持为每个键设置过期时间,到期后自动删除,常用于缓存系统。
  5. 发布/订阅机制:Redis 提供了消息队列功能,可以让客户端订阅和发布消息。
  6. 事务支持:Redis 支持简单的事务机制,允许多个命令作为一个原子操作执行。
  7. 高可用性与分布式:通过 Redis Cluster、Sentinel 机制支持高可用和自动故障转移,能够在分布式环境中使用。

1、Redis 基本配置

Redis 的配置文件通常命名为 redis.conf(windows的为redis.windows.conf),存放在 Redis 安装目录中。通过修改该文件,可以自定义 Redis 的各种行为。

以单例模式启动

  • 默认情况下,redis.conf 文件位于 Redis 安装目录中。您可以在启动 Redis 时指定该文件的位置,例如:

    redis-server /path/to/redis.conf
    

关键配置选项

1. 端口号

  • port:指定 Redis 服务器监听的端口号。默认端口是 6379

    port 6379
    

2. 绑定地址

  • bind:指定 Redis 只在特定的网络接口上监听。如果您希望 Redis 只接受来自本地的连接,可以将其设置为 127.0.0.1

    bind 127.0.0.1
    

3. 守护进程模式

  • daemonize:指定 Redis 是否以守护进程的方式运行。如果设置为 yes,Redis 将在后台运行。默认值是 no

    daemonize yes
    

4. 密码保护

  • requirepass:为 Redis 设置访问密码,只有提供正确密码的客户端才能连接到 Redis 服务器。

    requirepass yourpassword
    

5. RDB 快照文件

  • dbfilename:指定 RDB 快照文件的名称。默认文件名为 dump.rdb

    dbfilename dump.rdb
    
  • dir:指定 RDB 快照文件的存放目录。

    dir /var/lib/redis/
    

启动和使用 Redis 配置

  • 修改完 redis.conf 后,您可以通过以下命令启动 Redis:

    redis-server /path/to/redis.conf
    
  • 可以通过以下命令检查 Redis 是否正确加载了配置文件:

    redis-cli CONFIG GET *
    

    这会显示当前 Redis 实例的所有配置选项及其值。
    通过这些基本配置,我们可以根据需要自定义 Redis 的行为,以适应不同的应用场景。

  • 使用 AUTH 命令进行身份验证

    AUTH your_password
    

2、Redis 的通用命令

除了与数据结构相关的命令外,Redis 还提供了一些通用命令,用于管理 Redis 实例、数据操作和服务器配置。

  • 键管理

    • DEL key:删除键。
    • EXISTS key:检查键是否存在。
    • EXPIRE key seconds:为键设置过期时间。
    • KEYS pattern:查找所有符合给定模式的键。
    • TTL key:查看键的剩余生存时间。
    DEL mykey
    EXISTS mykey
    EXPIRE mykey 60
    KEYS *pattern*
    TTL mykey
    

3、不同数据的命令

Redis 是一个高性能的键值存储系统,支持多种复杂的数据结构。这些数据结构使得 Redis 不仅可以作为简单的缓存,还能满足更多样化的数据存储需求。以下是 Redis 支持的主要数据结构:

3.1 字符串(String )

String 是 Redis 中最基本的数据类型,每个键对应一个字符串类型的值(value)。

常见命令

  1. SET:添加或修改一个键值对。例如 SET key value
  2. GET:根据键获取对应的值。例如 GET key
  3. MSET:批量添加多个键值对。例如 MSET key1 value1 key2 value2
  4. MGET:根据多个键获取多个值。例如 MGET key1 key2
  5. INCR:让一个整型的键自增1。例如 INCR counter
  6. INCRBY:让一个整型的键自增指定步长。例如 INCRBY counter 5
  7. INCRBYFLOAT:让一个浮点数的键自增指定步长。例如 INCRBYFLOAT balance 0.5
  8. SETNX:添加一个键值对,前提是这个键不存在,否则不执行。例如 SETNX key value
  9. SETEX:添加一个带有过期时间的键值对。例如 SETEX key 60 value(60秒后过期)。

3.2 哈希(Hash )

Hash 是 Redis 中的一种用于存储键值对的集合。它类似于编程语言中的哈希表或字典。一个 Hash 里可以存储多个字段和值,因此非常适合存储对象。

常见命令

  1. HSET:向 Hash 中添加一个字段和值。例如 HSET key field value
  2. HGET:获取 Hash 中某个字段的值。例如 HGET key field
  3. HMSET:批量添加多个字段和值。例如 HMSET key field1 value1 field2 value2
  4. HMGET:批量获取多个字段的值。例如 HMGET key field1 field2
  5. HGETALL:获取 Hash 中所有的字段和值。例如 HGETALL key
  6. HKEYS:获取 Hash 中的所有字段名。例如 HKEYS key
  7. HVALS:获取 Hash 中的所有值。例如 HVALS key
  8. HINCRBY:让 Hash 中某个字段的值自增指定步长。例如 HINCRBY key field 2
  9. HSETNX:向 Hash 中添加一个字段和值,前提是这个字段不存在。例如 HSETNX key field value

3.3 列表(List)

列表是一个有序的字符串集合,可以在集合的头部或尾部插入、删除元素。适合用于实现消息队列等功能。

常见命令

  1. LPUSH:在列表头部插入一个元素。例如 LPUSH mylist "world"
  2. RPUSH:在列表尾部插入一个元素。例如 RPUSH mylist "hello"
  3. LPOP:移除并返回列表的第一个元素。例如 LPOP mylist
  4. RPOP:移除并返回列表的最后一个元素。例如 RPOP mylist
  5. LRANGE:获取列表的一个子集。例如 LRANGE mylist 0 -1

3.4 集合(Set)

集合是无序的字符串集合,不允许重复元素。适合用于存储需要唯一性的集合,如标签、用户角色等。

常见命令

  1. SADD:向集合添加一个元素。例如 SADD myset "apple"
  2. SREM:移除集合中的一个元素。例如 SREM myset "apple"
  3. SCARD:返回集合中元素的个数。例如 SCARD myset
  4. SMEMBERS:获取集合中的所有元素。例如 SMEMBERS myset
  5. SISMEMBER:判断元素是否在集合中。例如 SISMEMBER myset "apple"

3.5 有序集合(Sorted Set)

有序集合类似于集合,但每个元素都会关联一个分数,元素按分数进行排序。适合用于排名、评分系统等场景。

常见命令

  1. ZADD:向有序集合添加一个元素,并设置分数。例如 ZADD leaderboard 100 "player1"
  2. ZRANGE:按索引范围获取有序集合中的元素,可以选择同时返回分数。例如 ZRANGE leaderboard 0 -1 WITHSCORES
  3. ZSCORE:获取有序集合中指定元素的分数。例如 ZSCORE leaderboard "player1"
  4. ZREM:移除有序集合中的一个元素。例如 ZREM leaderboard "player1"

Jedis 是一个用于与 Redis 数据库交互的 Java 客户端库。通过 Jedis,你可以在 Java 应用程序中执行各种 Redis 操作,如增删改查等。Jedis 提供了一组简单易用的 API,使开发者可以轻松地将 Redis 集成到他们的 Java 应用程序中。

3.6 " : "的作用

在 Redis 中,冒号 (:) 用于创建键名的层级结构,以帮助组织和管理数据。例如,user:1000:name 表示用户 ID 为 1000 的名字,order:5000:status 表示订单 ID 为 5000 的状态。使用冒号可以让数据看起来像路径一样分层,便于分类和查询。

假设你在存储用户信息和订单信息:

  • user:1000:name -> "Alice"(用户 ID 为 1000 的名字)
  • user:1000:email -> "alice@example.com"(用户 ID 为 1000 的电子邮件)
  • order:2000:status -> "shipped"(订单 ID 为 2000 的状态)

这里,user:order: 是前缀,用于区分不同类型的数据,而冒号 : 用于分隔不同的层级。

4、jedis

Jedis 是一个用于与 Redis 数据库交互的 Java 客户端库。通过 Jedis,你可以在 Java 应用程序中执行各种 Redis 操作,如增删改查等。Jedis 提供了一组简单易用的 API,使开发者可以轻松地将 Redis 集成到他们的 Java 应用程序中。

Jedis 的基本用法

  1. 引入依赖
    Maven 官网获取 Jedis 依赖,并添加到 pom.xml 中:
    在这里插入图片描述

  2. 连接 Redis
    使用 Jedis 连接 Redis 非常简单。以下是一个基本的连接示例:

    import redis.clients.jedis.Jedis;
    
    public class RedisExample {
        public static void main(String[] args) {
            // 创建一个 Jedis 对象,并指定 Redis 服务器的地址和端口
            Jedis jedis = new Jedis("localhost", 6379);
            // 输入密码,如果没有修改配置文件修改密码的话,不用写这个
            jedis.auth(“123456”);
            // 设置 Redis 字符串数据
            jedis.set("mykey", "Hello, Redis!");
            // 获取并打印 Redis 字符串数据
            System.out.println("Redis 存储的字符串: " + jedis.get("mykey"));
            // 关闭 Jedis 连接
            jedis.close();
        }
    }
    
  3. 常见操作
    Jedis 支持 Redis 的所有基本数据结构和操作。(上面 3、Redis 的数据结构 中的命令 jedis 都有对应的方法)

    • 字符串(String)jedis.set(key, value)jedis.get(key)
    • 哈希(Hash)jedis.hset(key, field, value)jedis.hget(key, field)
    • 列表(List)jedis.lpush(key, value)jedis.lrange(key, start, end)
    • 集合(Set)jedis.sadd(key, member)jedis.smembers(key)
    • 有序集合(Sorted Set)jedis.zadd(key, score, member)jedis.zrange(key, start, end)
  4. 连接池

    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    public class JedisConnectionFactory {
        private static final JedisPool jedisPool;
    
        static {
            // 配置连接池
            JedisPoolConfig poolConfig = new JedisPoolConfig();
            poolConfig.setMaxTotal(8);         // 最大连接数
            poolConfig.setMaxIdle(8);          // 最大空闲连接数
            poolConfig.setMinIdle(0);          // 最小空闲连接数
            poolConfig.setMaxWaitMillis(1000); // 最大等待时间
    
            // 创建连接池对象
            jedisPool = new JedisPool(poolConfig, 
                                      "127.0.0.1", // Redis 主机地址
                                      6379,              // Redis 端口号
                                      1000,              // 超时时间
                                      "yourPassword");         // Redis 密码
        }
    
        public static Jedis getJedis() {	// 通过这个方法在pool中获取jedis
            return jedisPool.getResource();
        }
    }
    
    

    参数说明:

    • config: JedisPoolConfig 配置对象,用于配置连接池参数。
    • redisHost: Redis 服务器的主机地址。
    • redisPort: Redis 服务器的端口号。
    • 2000: 连接超时时间,单位为毫秒。
    • redisPassword: Redis 服务器的密码。

5、SpringDataRedis

Spring Data Redis 是 Spring Data 生态系统中的一个模块,提供与 Redis 的简便集成。Redis 是一个高性能的内存键值存储,Spring Data Redis 通过提供简单、一致和声明式的方式,简化了与 Redis 的交互,将低级别的 Redis 操作抽象为高级 API 和模板。

1、准备工作

  • 添加依赖

    • spring data redis
      在这里插入图片描述

    • commons pool

      在这里插入图片描述

  • 常见配置

    • application.yaml
    spring:
      data:
        redis:
          # Redis 服务器的主机地址
          host: localhost
    
          # Redis 服务器的端口
          port: 6379
    
          # 配置 Redis 连接池(Lettuce 使用的连接池)
          lettuce:
            pool:
              # 连接池中最大活动连接数
              max-active: 8
    
              # 连接池中最大空闲连接数
              max-idle: 8
    
              # 连接池中最小空闲连接数
              min-idle: 0
    
              # 连接池中最大等待时间
              max-wait: 100ms
    
          # Redis 数据库索引(默认为 0,Redis 默认提供 16 个数据库)
          database: 0
    
          # Redis 服务器的密码,用于身份验证
          password: yourpassword
    
    • 配置序列化器
      Spring Data Redis 中,序列化和反序列化是处理 Redis 数据的关键部分。序列化是将 Java 对象转换为字节流的过程,以便存储到 Redis 中;反序列化则是将存储在 Redis 中的字节流转换回 Java 对象的过程。Spring Data Redis 提供了多种序列化和反序列化策略,可以根据具体需求进行配置。

    配置效果

    • : 被序列化成字符串(例如,"myKey")。
    • : 被序列化成 JSON 字符串(例如,{"name":"John","age":30})。
    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.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
            // 创建RedisTemplate对象
            RedisTemplate<String, Object> template = new RedisTemplate<>();
    
            // 设置连接工厂
            template.setConnectionFactory(connectionFactory);
    
            // 使用 StringRedisSerializer 代替 GenericJackson2JsonRedisSerializer
            //  GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
    
            // 设置key的序列化
            template.setKeySerializer(stringRedisSerializer);
            template.setHashKeySerializer(stringRedisSerializer);
    
            template.setValueSerializer(genericJackson2JsonRedisSerializer);
            template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
            return template;
        }
    }
    

2、对数据的操作

Spring Data Redis 提供了一组 API 用于操控 Redis 数据库。以下是一些常用的 API 及其详细说明:

1. 字符串操作 (String)

  • 设置值

    redisTemplate.opsForValue().set("key", "value");
    
  • 获取值

    String value = (String) redisTemplate.opsForValue().get("key");
    
  • 设置值(带过期时间)

    redisTemplate.opsForValue().set("key", "value", Duration.ofMinutes(10));
    
  • 删除键

    redisTemplate.delete("key");
    

2. 哈希操作 (Hash)

  • 设置哈希字段的值

    redisTemplate.opsForHash().put("user:1000", "name", "Alice");
    redisTemplate.opsForHash().put("user:1000", "email", "alice@example.com");
    
  • 获取哈希字段的值

    String name = (String) redisTemplate.opsForHash().get("user:1000", "name");
    
  • 获取哈希的所有字段和值

    Map<Object, Object> userMap = redisTemplate.opsForHash().entries("user:1000");
    

3. 列表操作 (List)

  • 右侧推入列表

    redisTemplate.opsForList().rightPush("list", "value1");
    redisTemplate.opsForList().rightPush("list", "value2");
    
  • 左侧推入列表

    redisTemplate.opsForList().leftPush("list", "value0");
    
  • 获取列表的所有元素

    List<Object> list = redisTemplate.opsForList().range("list", 0, -1);
    
  • 弹出列表的右侧元素

    Object value = redisTemplate.opsForList().rightPop("list");
    

4. 集合操作 (Set)

  • 添加元素到集合

    redisTemplate.opsForSet().add("set", "value1", "value2");
    
  • 获取集合的所有元素

    Set<Object> set = redisTemplate.opsForSet().members("set");
    
  • 移除集合的元素

    redisTemplate.opsForSet().remove("set", "value1");
    

5. 有序集合操作 (Sorted Set)

  • 添加元素到有序集合

    redisTemplate.opsForZSet().add("zset", "value1", 1);
    redisTemplate.opsForZSet().add("zset", "value2", 2);
    
  • 获取有序集合的所有元素

    Set<Object> zset = redisTemplate.opsForZSet().range("zset", 0, -1);
    
  • 获取有序集合的元素及其分数

    Set<ZSetOperations.TypedTuple<Object>> zsetWithScores = redisTemplate.opsForZSet().rangeWithScores("zset", 0, -1);
    

3、RedisTemplate 和 StringRedisTemplate

1. RedisTemplate

RedisTemplate 是一个泛型类,能够处理不同类型的键和值。你可以指定键和值的序列化方式,以及 Redis 数据的类型(如 String, Object, Hash, List, Set, ZSet 等)。

主要特点:

  • 泛型支持: 它支持任意类型的键和值,不限于 String,可以是 ObjectInteger、自定义对象等。
  • 序列化方式灵活: 通过设置不同的序列化器,可以对键和值进行序列化和反序列化。

2. StringRedisTemplate

StringRedisTemplateRedisTemplate 的一种特化版本,它专门用于操作 String 类型的键和值。它默认使用 StringRedisSerializer 来对键和值进行序列化和反序列化,因此只处理 String 数据类型。

主要特点:

  • 简化操作: 专注于 String 类型的数据操作,不需要额外配置序列化器,适用于键和值都为 String 类型的场景。
  • 默认序列化方式: 内置 StringRedisSerializer,默认将键和值都序列化为 String

3. 区别总结

特性 RedisTemplate StringRedisTemplate
支持的键和值类型 支持任何类型(如 String, Object, Integer 等) 只支持 String 类型的键和值
序列化方式 需要手动配置序列化器(可以选择 JSON、JDK 序列化等) 默认使用 StringRedisSerializer,无需额外配置
适用场景 适用于复杂的数据类型,如 ObjectJSON 等。 适用于简单的 String 类型数据操作。
示例1:

图一是通过 RedisTemplate 存到 redis 中的 JSON 字符串,图二是通过 StringRedisTemplate 存到redis 中的 JSON 字符串,由于 RedisTemplate 能够实现 自动的反序列化,所以需要存储多余的 @class 信息,会造成内存的浪费。
在这里插入图片描述
在这里插入图片描述

示例2:

观察下图有 @Test 注解的代码

// Uesr类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    String name;
    Integer age;
}

// 测试
@Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
@Test
    void insertUser() {
        User user = new User("world", 1);

        redisTemplate.opsForValue().set("user:1", user);    // 自动将JAVA对象序列换成JSON格式的字符串
        User myuser = (User) redisTemplate.opsForValue().get("user:1"); // 自动反序列化,将JSON格式的字符串转换成JAVA对象
        System.out.println("myuser = " + myuser);
    }

@Test
    void insertUser2() {
        User user = new User("world", 2);
        String jsonUser = JSON.toJSONString(user);  // 手动序列化成JSON格式的字符串

        stringRedisTemplate.opsForValue().set("user:1", jsonUser);
        String str = stringRedisTemplate.opsForValue().get("user:1");
        System.out.println("str = " + str);

        User myuser = JSON.parseObject(str, User.class);    // 手动从JSON格式的字符串转换成JAVA对象
        System.out.println("myuser = " + myuser);
    }

下面两张图分别是第一个Test和第二个Test输出的结果
在这里插入图片描述

在这里插入图片描述
通过上面图可以清楚的看到 RedisTemplate 会自动系列化JSON格式字符串和反序列化成JAVA对象,但是 StringRedisTemplate 都需要手动处理

4、补充

上面用到的JSON处理库是 fastjson2, 可在 maven 官网 下载依赖
在这里插入图片描述
fastjson 常见用法

// 将 Java 对象序列化为 JSON 字符串
String jsonString = JSON.toJSONString(user);
        
// 将 JSON 字符串反序列化为 Java 对象
User deserializedUser = JSON.parseObject(jsonString, User.class);

// 创建一个 JSONObject
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "John");
// 转换成JSON字符串
jsonObject.toString()

// 解析 JSON 字符串
String jsonString = "{\"name\":\"ld\",\"age\":18,\"city\":\"ShangHai\"}";
JSONObject parsedObject = JSONObject.parseObject(jsonString);
// 获取value, 输出 ld
System.out.println("Name: " + parsedObject.getString("name"));

6、部署模式

上面讲到的是单机部署模式,下面讲一些其他的部署模式

Redis 支持多种部署模式,分别适用于不同场景:

  • 主从复制(Master-Slave):数据备份与读写分离。 (小型项目)
  • 哨兵模式(Sentinel):高可用性管理主从结构。 (中型项目)
  • 集群模式(Cluster):分布式存储和负载均衡。(大型项目)

6.1 主从复制(Master-Slave Replication)

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master),后者称为从节点(Slave),数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主。实现了读写分离,提高了性能

工作原理

  1. 初次同步
    从节点连接到主节点后,主节点会将当前数据库的快照(RDB 文件)发送给从节点,从节点加载到内存中。
  2. 命令传播
    主节点的所有写操作会以命令流的形式发送给从节点,从节点执行相同的命令保持同步。

搭建主从复制

这里以在本地启动两个不同端口的redis演示,复制redis文件夹,弄出两份redis

  1. 分别修改配置文件的端口
    port 6001
    port 6002
    # 1. 如果设置的有密码,即 requirepass 
    # 那么还需要在从节点的配置文件中添加 masterauth yourMasterPassword 或者是在redis控制台中输入 config set masterauth yourMasterPassword 
    
    # 2. 如果需要打开内网,需要设置 protected-modeno no 和 bind 0.0.0.0。(这里我们本地测试,只修改端口即可)
    
    
  2. 通过 .\redis-server.exe .\redis.windows.conf 启动这两个redis
  3. 进入redis控制台 .\redis-cli.exe -p 6001.\redis-cli.exe -p 6002
  4. 可以通过 info replication 查看信息
    在这里插入图片描述
  5. 在 6002 的控制台输入 replicaof 127.0.0.1 6001 ,即可把6002变成6001的从节点。
    也可以直接在从节点的配置文件中写上 replicaof 127.0.0.1 6001, 那样每次启动就不需要手动配置了(replicaof no one 用于将从节点重新变成单独的主节点)
    在这里插入图片描述

最后,我们除了可以把 6002 弄作 6001 的从节点,也可以给 6002 弄一个(6003)或多个从节点,60026003 都只是可读的,这样做可以减小 6001 主节点同步数据时的压力,把同步数据的部分任务给到从节点 6002


经过之前的学习,我们发现,实际上最关键的还是主节点,因为一旦主节点出现问题,那么整个主从系统将无法写入,因此,我们得想一个办法,处理一下主节点故障的情况。哨兵模式 就可以解决这个问题

6.2 哨兵模式(Sentinel Mode)

哨兵模式通过哨兵进程(Sentinel)监控主从节点的状态,自动完成主节点故障转移和通知客户端切换到新的主节点。适合需要高可用的场景。

在这里插入图片描述

在这里插入图片描述

工作原理

  1. 主节点监控
    哨兵进程周期性地通过 PING 检查主从节点的状态。
  2. 故障判定
    如果主节点不可用,哨兵会启动选举流程,从从节点中选出新的主节点。
  3. 主从切换
    • 选出的从节点被提升为主节点。
    • 其他从节点重新同步到新的主节点。
    • 当之前的主节点恢复时,会自动同步到新的主节点
  4. 通知客户端
    客户端通过哨兵获取新的主节点地址。

主从切换规则:
1.首先会根据优先级进行选择,可以在配置文件中进行配置,添加replica-priority配置项(默认是100),越小表示优先级越高。2.如果优先级一样,那就选择偏移量最大的
3.要是还选不出来,那就选择runid(启动时随机生成的)最小的。

哨兵可部署多台,这样不同担心哨兵节点也挂掉,下面演示 一主一从一哨兵 的部署

搭建哨兵模式

sentinel monitor mymaster <master_ip> <master_port> <quorum>
sentinel auth-pass mymaster <password>
  • mymaster 是主节点的名字。
  • <quorum> 是判断主节点失效的最少哨兵数。如果有多个哨兵,比如3个,那么只有当 quorum 个哨兵认为主节点挂掉的时候才会进行选举
  1. 在上面主从复制的基础上,新建一个新的节点 6003 在从节点 6003 的配置文件中添加 port 6003 sentinel monitor sentinel1 127.0.0.1 6001 1sentinel auth-pass sentinel1 yourpassword
  2. 启动哨兵 6003.\redis-server.exe .\redis.windows.conf --sentinel

在这里插入图片描述
此时我们尝试把主节点 6001 关掉
在这里插入图片描述
这时我们发现 6002 现在可以写入了,之前最为从节点不能写入数据
在这里插入图片描述
当我们再次启动 6001 时,可以看到, 6001 直接变成了 6002 的从节点。
(这里需要注意一点,如果之前的 6001 配置文件中没有写 masterauth yourmasterpassword 的话(上面讲到的主节点的密码),即使变成 6002 的从节点,也没有权限实现同步主节点的数据)

在这里插入图片描述

客户端的感知

客户端感知是指客户端应用程序可以自动感知到 Redis 主从节点的变化,从而在主节点发生故障转移(Failover)时,能够自动连接到新的主节点,而无需手动配置。

这里我们用到的客户端是 SpringDataRedis

spring:
  application:
    name: learnRedis

  data:
    redis:
    ## sentinel 配置,只需要在原有的配置中添加上这个 sentinel 的配置即可
      sentinel:
        master: sentinel1 # 启动哨兵时 自己定义的主节点id
        nodes: # 填写哨兵的定制
          - localhost:6003
        password: di135790 # 哨兵的密码,如果没有密码不写
        
      host: localhost # 原本主节点的地址
      port: 6001 # 原本主节点的端口
      lettuce: # 配置 Redis 连接池(Lettuce 使用的连接池)
        pool:
          max-active: 8 # 连接池中最大活动连接数
          max-idle: 8 # 连接池中最大空闲连接数
          min-idle: 0 # 连接池中最小空闲连接数
          max-wait: 100ms # 连接池中最大等待时间
      database: 0 # Redis 数据库索引(默认为 0,Redis 默认提供 16 个数据库)
      password: di135790 # Redis 服务器的密码,用于身份验证
server:
  port: 8001

这里我们做测试,当 6001 挂掉的时候,再次访问 /test 可以成功加入数据,读取数据

@RestController
public class TestController {

    @Autowired
    RedisTemplate redisTemplate;

    @RequestMapping("/test")
    public String test() {
        redisTemplate.opsForValue().set("test", "hello world");
        String test = (String) redisTemplate.opsForValue().get("test");
        return test;
    }
}

6.3 集群模式(Cluster Mode)

Redis 集群模式是分布式部署方式,多个节点协同存储数据,通过分片(Sharding)实现数据分布,提供高性能高扩展性

工作原理

  1. 分片存储
    数据通过一致性哈希分布到多个节点。Redis 使用 16384 个槽位(hash slot),每个键通过 CRC16 哈希算法映射到一个槽位,再分配到具体的节点。
  2. 节点角色
    • 主节点:负责存储数据和处理读写请求。
    • 从节点:同步主节点数据,作为主节点的备份。
  3. 容错机制
    • 每个主节点至少有一个从节点。
    • 主节点故障时,从节点自动升级为主节点。(当之前的主节点恢复时,成为新的主节点的从节点)

搭建集群模式

这里我们弄四个redis,6001,6002,6003为集群的主节点,7001,7002,7003分别是6001,6002,6003的从节点,这样可以保证当其中一个主节点挂掉的时候,整个集群还是可用的。
(当集群中的某个主节点挂掉的时候,该主节点的从节点会提升会新的主节点,当一个分支的主节点和从节点都挂掉的时候,整个集群就不可用了)

搭建集群最少是三个主节点

  1. 配置集群节点:

    # redis.conf
    cluster-enabled yes
    # 注意:不要手动在配置文件中指定 replicaof 127.0.0.1 6001 主从关系,我们等下启动的时候才可以指定
    
  2. 启动多个 Redis 实例:

    .\redis-server.exe .\redis.windows.conf
    # 启动这四个redis
    
  3. 创建集群:
    使用 redis-cli 工具:

    # --cluster-replicas 用来指定每个主节点的从节点个数
    redis-cli --cluster create 127.0.0.1:6001 127.0.0.1:6002 127.0.0.1:6003  127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 --cluster-replicas 1
    # redis-cli -p yourport flushall     如果节点中有数据,会导致创建集群失败,可以用这个命令清除一下
    # redis-cli -p yourport cluster reset hard     如果节点已经存在的集群配置,可以用这个命令重置
    

    在这里插入图片描述

这里我们插入了一个数据,可以发现出现了一个error,表明这个数据被插入到了 6002的分区中。
在这里插入图片描述
我们启动 redis-cli 时加上一个-c的参数可以在插入到别的分区时自动切换过去。redis-cli.exe -p 6001 -c
在这里插入图片描述cluster nodes 可以展示当前集群的信息
在这里插入图片描述
当我们把 6001 关掉后,会发现 7001 变成了主节点,而 6001 显示 fail
在这里插入图片描述
当我们再次启动 6001 时,发现 6001 变成了 7001 的从节点了
在这里插入图片描述

客户端配置

下面是在 application 中的配置

spring:
	data:
	    redis:
	      cluster:
	        nodes:
	          - localhost:6001
	          - localhost:6002
	          - localhost:6003
	          - localhost:7001
	          - localhost:7002
	          - localhost:7003

6.4 模式对比

模式 适用场景 优点 缺点
主从复制 数据备份、读写分离 部署简单,读性能提高 主节点故障需手动切换
哨兵模式 高可用性需求 自动故障转移,通知客户端 部署复杂,需要多个哨兵保证稳定
集群模式 大规模、高并发需求 数据分片,自动扩展,容错能力强 部署复杂,不支持多键事务

7、分布式锁

在我们的传统单体应用中,经常会用到锁机制,目的是为了防止多线程竞争导致的并发问题,但是现在我们在分布式环境下,可能一条链路上有很多的应用,它们都是独立运行的,这时我们就可以借助Redis来实现分布式锁。

为什么不能用java的锁?
因为 Java 的锁机制是进程内的,仅在单个 JVM 内有效。如果分布式系统的多个节点运行在不同的 JVM 中,这些锁无法相互感知。


使用 Redis 的 键值对(Key-Value)存储功能,将一个唯一的锁标识作为键(Key),通过特定的操作确保这个键在某一时间内只能由一个客户端持有。

7.1 基础实现

1. 加锁

使用 Redis 的 SET 命令:

SET lock_key unique_value NX PX 10000
  • lock_key:锁的键名,用于标识这把锁。
  • unique_value:锁的唯一标识,用于区分不同客户端(如使用 UUID 或线程标识)。
  • NX: Redis 的一个选项,常用在 SET 命令中,用于确保只有在键不存在时才会设置键值。key不存在时才能插入成功
  • PX 10000:设置键的过期时间为 10 秒,防止因客户端崩溃导致锁永远不释放。

返回值

  • 成功:OK(表示加锁成功)。
  • 失败:null(表示锁已经被其他客户端持有)。

2. 释放锁

释放锁时需要确保只释放自己加的锁,这通过检查 unique_value 是否匹配来实现。

  1. 检查锁的值是否匹配 unique_value
  2. 匹配成功:删除锁。
  3. 匹配失败:不操作,防止误删他人持有的锁。

7.2 加锁时的关键问题

1. 锁过期问题

如果客户端在持有锁期间崩溃,锁会因为设置的过期时间自动释放,其他客户端可以继续尝试获取锁。

2. 锁超时

如果业务逻辑执行时间超过锁的过期时间,可能导致锁过期后其他客户端误获得锁。这种情况下可以:

  • 延长锁时间:通过定期续约(如 Redisson 实现)。
  • 保证业务执行时间小于锁的过期时间:尽量避免锁超时问题。

3. 误删他人锁

  • 使用 unique_value 作为锁的标识,可以避免误删其他客户端的锁。
  • 仅在 unique_value 匹配的情况下,才删除锁。

实际上本质还是因为锁的超时时间不太好衡量,如果超时时间能够设定地比较恰当,那么就可以避免这种问题了。(Redisson实现了分布式锁)


8、Redisson

Redisson 是一个基于 Redis 的 Java 客户端,提供了丰富的分布式工具(如分布式锁、分布式集合、分布式缓存等)。相比于手动实现 Redis 操作,Redisson 封装了很多复杂逻辑(如锁的自动续期、分布式状态管理),使用简单且可靠。


Redisson 的常见使用场景

  1. 分布式锁(可重入锁、公平锁、读写锁等)。
  2. 分布式集合(Set、List、Map、Queue 等)。
  3. 分布式对象(AtomicLong、Semaphore、CountDownLatch 等)。
  4. 分布式缓存(基于 Redis 的缓存解决方案)。
  5. 消息队列(基于 Redis 的 Pub/Sub 或延迟队列)。

依赖引入

在 Maven 项目中添加 Redisson 依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.22.0</version> <!-- 使用最新版本 -->
</dependency>

Redisson 配置

Redisson 支持多种 Redis 部署模式:单节点模式、主从模式、哨兵模式、集群模式等。

1. 单节点模式

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);

2. 集群模式

Config config = new Config();
config.useClusterServers()
      .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001");
RedissonClient redisson = Redisson.create(config);

3. 哨兵模式

Config config = new Config();
config.useSentinelServers()
      .setMasterName("mymaster")
      .addSentinelAddress("redis://127.0.0.1:26379");
RedissonClient redisson = Redisson.create(config);

常用功能示例

1. 分布式锁

Redisson 提供多种分布式锁类型:普通锁、可重入锁、公平锁、读写锁等。这里我们可以使用 Redisson提供的分布式锁 来实现对共享资源的访问,我们不需要怎么控制解锁的时间,它会自动续期。

普通分布式锁
import org.redisson.api.RLock;

RLock lock = redisson.getLock("myLock");
lock.lock(); // 加锁

try {
    // 处理共享资源
} finally {
    lock.unlock(); // 解锁
}
尝试加锁(带超时)
boolean isLocked = lock.tryLock(5, 10, TimeUnit.SECONDS);
// 5 秒内尝试加锁,成功后保持 10 秒
if (isLocked) {
    try {
        // 处理共享资源
    } finally {
        lock.unlock();
    }
}

2. 读写锁

读写锁允许多个读操作并发执行,但写操作是独占的。

RReadWriteLock rwLock = redisson.getReadWriteLock("myRWLock");

// 获取读锁
RLock readLock = rwLock.readLock();
readLock.lock();
try {
    // 读操作
} finally {
    readLock.unlock();
}

// 获取写锁
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
    // 写操作
} finally {
    writeLock.unlock();
}

3. 分布式集合

Redisson 提供的集合支持 Redis 的分布式特性。

分布式 Map
RMap<String, String> map = redisson.getMap("myMap");
map.put("key1", "value1");
System.out.println(map.get("key1")); // 输出: value1
分布式 List
RList<String> list = redisson.getList("myList");
list.add("item1");
list.add("item2");
System.out.println(list.get(0)); // 输出: item1
分布式 Set
RSet<String> set = redisson.getSet("mySet");
set.add("value1");
set.add("value2");
System.out.println(set.contains("value1")); // 输出: true

Redisson 的分布式集合 vs Spring Data Redis

特性 Redisson 分布式集合 Spring Data Redis
易用性 提供面向对象的 API,更接近 Java 原生数据结构 操作相对底层,需要理解 Redis 数据结构
线程安全性 内部封装了分布式锁机制,确保并发安全 默认不支持线程安全,需要手动实现
分布式特性 天然支持多节点读写一致性和分布式环境 基于 Redis 的单节点数据存储,分布式需手动处理
数据结构功能 提供高级功能,例如 BlockingQueueScoredSortedSet 基本操作(增删改查),缺乏高级特性
一致性保障 支持数据一致性操作(分布式锁) 无一致性保障,需开发者自行处理

暂且更到这里 ... 后面再补

posted @ 2024-09-09 14:53  小程xy  阅读(130)  评论(0编辑  收藏  举报