# Redis

## Redis基本定义

Redis是一个基于**内存**的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的**存储中间件**。

Redis是用C语言开发的一个开源的**高性能键值对(key-value)数据库**，官方提供的数据是可以达到100000+的QPS（每秒内查询次数）。它存储的value类型比较丰富，也被称为结构化的**NoSql数据库**(非关系型)。

**典型场景**：

- 热点数据缓存（商品/资讯/秒杀）
- 会话管理（Session）
- 排行榜/计数器
- 消息队列（Stream）

**NoSQL数据库**：

- 键值型（Redis）

- 文档型（MongoDB）
- 列存储（HBase）
- 图数据库（Neo4j）



## 下载与使用

Redis安装包分为windows版和Linux版：

- Windows版下载地址：https://github.com/microsoftarchive/redis/releases
- Linux版下载地址： https://download.redis.io/releases/ 

|     **角色**     |                           **作用**                           |             **典型示例**             |
| :--------------: | :----------------------------------------------------------: | :----------------------------------: |
| **Redis 服务端** | 数据存储的核心，负责接收/执行命令、管理内存、持久化数据等。需先启动服务端才能使用。 | `redis-server`（默认监听 6379 端口） |
| **Redis 客户端** | 连接服务端并发送命令（如 `GET/SET`），获取返回结果。可以是命令行工具或代码库。 |    `redis-cli`、Java 的 Jedis 库     |



**windows下服务启动/停止：**

启动：

```bash
redis-server.exe redis.windows.conf
```

![image-20250421172130160](https://pic.bitday.top/i/2025/04/21/sgvnti-0.png)

这种方式关闭命令行后Redis服务又停止了！

解决方法：**安装为 Windows 服务**

```
redis-server --service-install redis.windows.conf --service-name Redis
redis-server --service-start
```

停止：

```bash
ctrl+c
```



**客户端连接：**

直接运行 `redis-cli.exe` 时，它会尝试连接 **本机（127.0.0.1）的 Redis 服务端**，并使用默认端口 **6379**。
等价于手动指定参数：

```bash
redis-cli -h 127.0.0.1 -p 6379
```

指定连接：

```bash
redis-cli -h <IP> -p <端口> -a <密码>
redis-cli -h 192.168.1.100 -p 6379 -a yourpassword
```

退出连接：exit



**修改Redis配置文件**

设置Redis服务密码，修改redis.windows.conf(windows)  redis.conf(linux)

```bash
requirepass 123456
```

修改redis服务端口

```bash
port 6379
```



**Redis客户端图形工具**

默认提供的客户端连接工具界面不太友好，可以使用Another Redis Desktop Manager.exe ，类似Navicat连mysql。



## Redis数据类型

<img src="https://pic.bitday.top/i/2025/03/19/u7ckos-2.png" alt="image-20221130190150749" style="zoom:50%;" />

Redis存储的是key-value结构的数据，其中key是字符串类型，value有5种常用的数据类型：

- 字符串(string)：普通字符串，Redis中最简单的数据类型
- 哈希(hash)：也叫散列，类似于Java中的HashMap结构。（套娃）
- 列表(list)：按照插入顺序排序，可以有重复元素，类似于Java中的LinkedList
- 集合(set)：无序集合，没有重复元素，类似于Java中的HashSet（求交集）
- 有序集合(sorted set/zset)：集合中每个元素关联一个分数(score)，根据分数升序排序，没有重复元素（排行榜）



## Redis常用命令

### 通用命令

Redis的通用命令是不分数据类型的，都可以使用的命令：

- KEYS pattern 		**查找**所有符合给定模式( pattern)的 key 

  - **`pattern`**：匹配模式，支持通配符：

    - `*`：匹配任意多个字符（包括空字符）
    - `?`：匹配单个字符
    - `[abc]`：匹配 `a`、`b` 或 `c` 中的任意一个字符
    - `[a-z]`：匹配 `a` 到 `z` 之间的任意一个字符

  - ```bash
    KEYS user:*    #可以返回 形如  "user:1"    "user:2" 的 key
    ```

  - ```bash
    KEYS *         #查找所有key
    ```

- EXISTS key 		检查给定 key 是否存在

- TYPE key 		返回 key 所储存的值的类型

- DEL key 		该命令用于在 key 存在是删除 key



### 字符串

Redis 中字符串类型常用命令：

- **SET** key value 					         设置指定key的值
- **GET** key                                        获取指定key的值
- **SETEX** key seconds value         设置指定key的值，并将 key 的过期时间设为 seconds 秒（验证码）
- **SETNX** key value                        只有在 key不存在时设置 key 的值



### 哈希操作

Redis hash 是一个string类型的 field 和 value 的映射表，hash特别适合用于存储对象，常用命令：

- **HSET** key field value             将哈希表 key 中的字段 field 的值设为 value
- **HGET** key field                       获取存储在哈希表中指定字段的值
- **HDEL** key field                       删除存储在哈希表中的指定字段
- **HKEYS** key                              获取哈希表中所有字段
- **HVALS** key                              获取哈希表中所有值

<img src="https://pic.bitday.top/i/2025/03/19/u7cyeg-2.png" alt="image-20221130193121969" style="zoom:67%;" />



### 列表操作

Redis 列表是简单的**字符串列表**，按照插入顺序排序，常用命令：

- **LPUSH** key value1         将一个值插入到列表头部

- **RPUSH** key value1 [value2]         将一个或多个值插入到列表尾部

  - ```bash
    RPUSH mylist "world" "redis" "rpush" #多个值插入
    ```

- **LRANGE** key start stop                获取列表指定范围内的元素（这里L代表List 不是Left）

  - ```bash
    LRANGE mylist 0 -1  #获取整个列表
    ```

- **LLEN** key                                        获取列表长度

- **RPOP** key                                       移除并获取列表最后一个元素

- **BRPOP** key1 [key2 ] timeout       移出并获取列表的最后一个元素， 如果列表没有元素会阻塞列表直到等待超    时或发现可弹出元素为止

<img src="https://pic.bitday.top/i/2025/03/19/u7cqgp-2.png" alt="image-20221130193332666" style="zoom:67%;" />



### 集合操作

Redis set 是**string类型的无序集合**。集合成员是唯一的，这就意味着集合中不能出现重复的数据，常用命令：

- **SADD** key member1 [member2]            向集合添加一个或多个成员

  ```bash
  # 添加单个成员
  SADD fruits "apple"
  
  # 添加多个成员（自动去重）
  SADD fruits "banana" "orange" "apple"  # "apple" 已存在，不会重复添加
  ```
- **SMEMBERS** key                                         返回集合中的所有成员
- **SCARD** key                                                  获取集合的成员数(基数**Cardinality**)
- **SINTER** key1 [key2]                                   返回给定所有集合的交集
- **SUNION** key1 [key2]                                 返回所有给定集合的并集
- **SREM** key member1 [member2]            移除集合中一个或多个成员

<img src="https://pic.bitday.top/i/2025/03/19/u7d7ff-2.png" alt="image-20221130193532735" style="zoom:67%;" />



### 有序集合

Redis有序集合是string类型元素的集合，且不允许有重复成员。每个元素都会关联一个double类型的分数。常用命令：

常用命令：

- **ZADD** key score1 member1 [score2 member2]     向有序集合添加一个或多个成员（score代表分数）
- **ZRANGE** key start stop [WITHSCORES]                     通过索引区间返回有序集合中指定区间内的成员
- **ZINCRBY** key increment member                              有序集合中对指定成员的分数加上**增量** increment
- **ZREM** key member [member ...]                                移除有序集合中的一个或多个成员

<img src="https://pic.bitday.top/i/2025/03/19/u7c9il-2.png" alt="image-20221130193951036" style="zoom:67%;" />



## Java中操作Redis

Spring Data Redis 是 Spring 的一部分，提供了在 Spring 应用中通过简单的配置就可以访问 Redis 服务，就如同我们使用JDBC操作MySQL数据库一样。

网址：https://spring.io/projects/spring-data-redis

### 环境搭建

进入到sky-server模块

**1). 导入Spring Data Redis的maven坐标(已完成)**

```java
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
```

**2). 配置Redis数据源**

在application-dev.yml中添加

```java
sky:
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 10
```

**解释说明：**

database:指定使用Redis的哪个数据库，Redis服务启动后默认有16个数据库，编号分别是从0到15。

可以通过修改Redis配置文件来指定数据库的数量。

在application.yml中添加读取application-dev.yml中的相关Redis配置

```java
spring:
  profiles:
    active: dev
  redis:
    host: ${sky.redis.host}
    port: ${sky.redis.port}
    password: ${sky.redis.password}
    database: ${sky.redis.database}
```

**3). 编写配置类，创建RedisTemplate对象**

```java
@Configuration
@Slf4j
public class RedisConfiguration {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        log.info("开始创建redis模板对象...");
        RedisTemplate redisTemplate = new RedisTemplate();
        //设置redis的连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置redis key的序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}
```

**解释说明：**

当前配置类不是必须的，因为 Spring Boot 框架会自动装配 RedisTemplate 对象，但是默认的key序列化器为

`JdkSerializationRedisSerializer`，存到 Redis 里会是二进制格式，这里把 key 的序列化器指定为 `StringRedisSerializer`，就会把所有的 key 都以 UTF‑8 字符串形式写入 Redis，方便观测和调试



### 功能测试

**通过RedisTemplate对象操作Redis**，注意要Another Redis Desktop中刷新一下数据库，才能看到数据。

**字符串测试**

```java
@DataRedisTest
@Import(com.sky.config.RedisConfiguration.class)
public class SpringDataRedisTest {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void testRedisTemplate(){
        System.out.println(redisTemplate);
    }
    @Test
    public void testString(){
        // SET city "北京"
        redisTemplate.opsForValue().set("city", "北京");

        // GET city
        String city = (String) redisTemplate.opsForValue().get("city");
        System.out.println(city);

        // SETEX code 180 "1234"    （3 分钟 = 180 秒）
        redisTemplate.opsForValue().set("code", "1234", 3, TimeUnit.MINUTES);

        // SETNX lock "1"
        redisTemplate.opsForValue().setIfAbsent("lock", "1");

        // (由于上一步 lock 已存在，这里相当于不执行)
        // SETNX lock "2"
        redisTemplate.opsForValue().setIfAbsent("lock", "2");
    }
}
```

**对于非String类型：**

**写入时序列化**

- 调用 `opsForValue().set(key, value)` 时，`value`（比如 `List<DishVO>`）会先走一遍 **ValueSerializer**。
- 默认是 `JdkSerializationRedisSerializer`，它用 Java 的 `ObjectOutputStream` 把整个对象图打包成一个 byte[]。
- 这个 byte[] 直接就存成了 Redis 的 String 值。

**读取时反序列化**

- 调用 `opsForValue().get(key)` 时，RedisTemplate 拿回那段 byte[]，再用同一个 JDK 序列化器（`ObjectInputStream`）把它变回 `List<DishVO>`。



**哈希测试**

```java
@Test
public void testHash(){
    HashOperations hashOperations = redisTemplate.opsForHash();

    // HSET 100 "name" "tom"
    hashOperations.put("100", "name", "tom");

    // HSET 100 "age" "20"
    hashOperations.put("100", "age", "20");

    // HGET 100 "name"
    String name = (String) hashOperations.get("100", "name");
    System.out.println(name);

    // HKEYS 100
    Set keys = hashOperations.keys("100");
    System.out.println(keys);

    // HVALS 100
    List values = hashOperations.values("100");
    System.out.println(values);

    // HDEL 100 "age"
    hashOperations.delete("100", "age");
}
```

get获得的是Object类型，keys获得的是set类型，values获得的是List



**3). 操作列表类型数据**

```java
@Test
public void testList(){
    ListOperations listOperations = redisTemplate.opsForList();

    // LPUSH mylist "a" "b" "c"
    listOperations.leftPushAll("mylist", "a", "b", "c");

    // LPUSH mylist "d"
    listOperations.leftPush("mylist", "d");

    // LRANGE mylist 0 -1
    List mylist = listOperations.range("mylist", 0, -1);
    System.out.println(mylist);

    // RPOP mylist
    listOperations.rightPop("mylist");

    // LLEN mylist
    Long size = listOperations.size("mylist");
    System.out.println(size);
}
```



**4). 操作集合类型数据**

```java
@Test
public void testSet(){
    SetOperations setOperations = redisTemplate.opsForSet();

    // SADD set1 "a" "b" "c" "d"
    setOperations.add("set1", "a", "b", "c", "d");

    // SADD set2 "a" "b" "x" "y"
    setOperations.add("set2", "a", "b", "x", "y");

    // SMEMBERS set1
    Set members = setOperations.members("set1");
    System.out.println(members);

    // SCARD set1
    Long size = setOperations.size("set1");
    System.out.println(size);

    // SINTER set1 set2
    Set intersect = setOperations.intersect("set1", "set2");
    System.out.println(intersect);

    // SUNION set1 set2
    Set union = setOperations.union("set1", "set2");
    System.out.println(union);

    // SREM set1 "a" "b"
    setOperations.remove("set1", "a", "b");
}
```



**5). 操作有序集合类型数据**

```java
@Test
public void testZset(){
    ZSetOperations zSetOperations = redisTemplate.opsForZSet();

    // ZADD zset1 10 "a"
    zSetOperations.add("zset1", "a", 10);

    // ZADD zset1 12 "b"
    zSetOperations.add("zset1", "b", 12);

    // ZADD zset1 9 "c"
    zSetOperations.add("zset1", "c", 9);

    // ZRANGE zset1 0 -1
    Set zset1 = zSetOperations.range("zset1", 0, -1);
    System.out.println(zset1);

    // ZINCRBY zset1 10 "c"
    zSetOperations.incrementScore("zset1", "c", 10);

    // ZREM zset1 "a" "b"
    zSetOperations.remove("zset1", "a", "b");
}
```

**6). 通用命令操作**

- `*` 匹配零个或多个字符。
- `?` 匹配任何单个字符。
- `[abc]` 匹配方括号内的任一字符（本例中为 'a'、'b' 或 'c'）。
- `[^abc]` 或 `[!abc]` 匹配任何不在方括号中的单个字符。

```java
@Test
public void testCommon(){
    // KEYS *
    Set keys = redisTemplate.keys("*");
    System.out.println(keys);

    // EXISTS name
    Boolean existsName = redisTemplate.hasKey("name");
    System.out.println("name exists? " + existsName);

    // EXISTS set1
    Boolean existsSet1 = redisTemplate.hasKey("set1");
    System.out.println("set1 exists? " + existsSet1);

    for (Object key : keys) {
        // TYPE <key>
        DataType type = redisTemplate.type(key);
        System.out.println(key + " -> " + type.name());
    }

    // DEL mylist
    redisTemplate.delete("mylist");
}
```



## Redisson

### 快速入门

#### 1. 引入依赖
```xml
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.26.0</version>
</dependency>
```

#### 2. 在 application.yml 中配置 Redis 连接（Redisson 会自动读取）
```yaml
spring:
  redis:
    host: localhost
    port: 6379
    password: # 如无密码则留空
    database: 0
```

#### 3. 可选：自定义 RedissonClient 配置（无需也可不写）
```java
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        org.redisson.config.Config config = new org.redisson.config.Config();
        config.useSingleServer()
              .setAddress("redis://localhost:6379")
              .setDatabase(0);
        return org.redisson.Redisson.create(config);
    }
}
```

#### 4. 示例：使用分布式锁防止超卖的 Service
```java
@Service
public class InventoryService {

    private static final String STOCK_KEY_PREFIX = "stock:";
    private static final String STOCK_LOCK_PREFIX = "lock:stock:";

    private final RedissonClient redissonClient;

    public InventoryService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    /**
     * 尝试购买一个单位商品，防止超卖
     * @param productId 商品 ID
     * @return 是否扣减成功
     */
    public boolean purchase(String productId) {
        String lockKey = STOCK_LOCK_PREFIX + productId;
        RLock lock = redissonClient.getLock(lockKey);
        boolean success = false;
        try {
            // 最多等待 5 秒获取锁，获取后锁自动过期时间 10 秒
            if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
                String stockKey = STOCK_KEY_PREFIX + productId;
                // 假设库存存储在 Redis 中
                Integer stock = (Integer) redissonClient.getBucket(stockKey).get();
                if (stock != null && stock > 0) {
                    redissonClient.getBucket(stockKey).set(stock - 1);
                    success = true;
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return success;
    }
}
```

#### 5. 调用示例
```java
@RestController
@RequestMapping("/api/order")
public class OrderController {

    private final InventoryService inventoryService;

    public OrderController(InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }

    @PostMapping("/buy/{productId}")
    public ResponseEntity<String> buy(@PathVariable String productId) {
        boolean ok = inventoryService.purchase(productId);
        if (ok) {
            return ResponseEntity.ok("购买成功");
        } else {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("库存不足");
        }
    }
}
```

