---
# System prepended metadata

title: Redis

---

# Redis

如有錯誤歡迎糾正，小弟正在學習中，透過此文章你可以獲取的知識有


- 基本概念與用途
- 數據結構與優點
- Redis 的環境安裝
- 主要數據結構操作
   - **String**：如何新增、刪除、查詢、設置過期時間。
   - **List**：如何插入、讀取、刪除元素，以及保留指定範圍的元素。
   - **Set**：如何新增、查詢、刪除集合中的元素，以及進行交集、聯集、差集操作。
   - **SortedSet**：如何使用 ZADD 新增元素，使用 ZRANGE 查詢，使用 ZSCORE 查看分數，使用 ZRANK 和 ZREVRANK 查詢排名。
   - **Hash**：如何新增、查詢、刪除哈希中的鍵值對，以及查看哈希的所有鍵值對和長度。
   - **訂閱發布模式**：如何使用 SUBSCRIBE 和 PUBLISH 進行消息的訂閱和發布。

## 介紹
Redis(全名 Remote Dictionary Server)，為NoSQL資料庫，基於記憶體儲存系統，常見運用場景有
- DB數據緩存
- MQ消息隊列

在早期大多企業使用MySQL進行資料讀寫，但由於時代演進，資料庫的負擔隨著量大而導致效能瓶頸，且硬碟IO讀寫操作相較記憶體是非常慢的，後來演進成將一部分常被讀取的數據放入記憶體中，降低資料庫負擔以及增加效能查詢，因此就有了Redis的出現
![image](https://hackmd.io/_uploads/HysNQ_0tA.png)
https://x.com/bytebytego/status/1578259927490969600
## 數據結構與優點
Redis 支援多種數據結構以及高階結構
- String 字串
- List 列表
- Set 集合
- SortedSet 有序排列
- Hash 哈希
- Stream 流
- Geospatial 地理空間
- HyperLogLog 
- Bitmap 位圖
- Bitfield 位域

特點
- 性能極高
- 數據結構支援豐富
- 支援各種程式語言
- 支持多種模式(資料持久化、主從設置等高效可靠特性)
> 💡Redis鍵與值都是由二進制儲存的，因此不支援中文
## 操作方式
操作方式可用
- CLI
- API
- GUI

## Redis環境安裝
個人喜歡用Docker所以只用docker

docker compose 
``` dockerfile
version: "3.3"
services:
  redis:
    image: redis:7.2.5
    container_name: redis
    restart: always
    volumes:
      - redis_volume_data:/data
    ports:
      - 6379:6379
  redis_insight:
    image: redislabs/redisinsight:1.14.0
    container_name: redis_insight
    restart: always
    ports:
      - 8001:8001
    volumes:
      - redis_insight_volume_data:/db

volumes:
  redis_volume_data:
  redis_insight_volume_data:
```
這邊要注意Host 為你的docker compose services name，這邊是redis
![image](https://hackmd.io/_uploads/ry19oI0Y0.png)

成功連上
![image](https://hackmd.io/_uploads/Hkuqj8CKR.png)

![image](https://hackmd.io/_uploads/ry19oI0Y0.png)


# Redis 資料結構與操作

## String

### SET(新增)
透過SET來建立鍵值

範例
``` redis
Connecting...

Pinging Redis server on redis:6379...
Connected.
Ready to execute commands.
>> set Jed Good

"OK"
>> get Jed

"Good"
```
另外前面所提Redis是由二進制儲存，不支援儲存中文的，不過可以透過 --raw 以原始格式（raw format）顯示 Redis 命令的輸出
```
redis-cli --raw 
```


``` redis
>> SET jed 是個大帥哥

"OK"
>> get jed

"\xe6\x98\xaf\xe5\x80\x8b\xe5\xa4\xa7\xe5\xb8\xa5\xe5\x93\xa5"
```

這邊需要注意Redis默認為String，透過以下範例可以驗證取得的型態是String

範例
``` redis
>> set year 2024

"OK"
>> get year

"2024"
```
### DEL(刪除)
如果要刪除可以使用DEL

``` redis
>> del jed

(integer) 0
>> get jed

(nil)
```

### EXISTS(是否存在)
也能透過 EXISTS來查詢鍵值是否存在，1代表存在，0代表不存在

``` redis
>> exists jed

(integer) 0

>> exists year

(integer) 1
```
### KEYS(查詢鍵)
查詢所有存在的鍵
``` redis
>> KEYS *

1) "year"
2) "Jed"
```

還能透過*表示like來查鍵
``` redis
>> KEYS ye*
1) "year"
```


### FLUSHALL(刪除所有鍵)
如果要刪除全部鍵可以使用FLUSHALL，注意謹慎使用此語法，並免快取雪崩

``` redis
>> FLUSHALL

"OK"
>> KEYS *

(empty list or set)
```

### TTL(設置過期時間)
TTL全名 Time To Live，可設置過期時間，在後端開發上會依據需求而定過期時間，-1表示為時間永久
``` redis
>> TTL jed

(integer) -1
```

#### EXPIRE、SETEX、SETNX(設置過期時間)
也可以透過EXPIRE設置時間

``` redis
>> EXPIRE jed 180

(integer) 1
>> TTL jed

(integer) 175
```

也能透過SETEX設置鍵值並給予過期時間

``` redis
>> SETEX jed 180 666

"OK"
>> get jed

"666"
>> ttl jed

(integer) 170
```

如果要針對不存在的鍵在更新可以使用SETNX，如果存在鍵則不做任何動作

jed-girl-firendQQ為不存在的鍵，可看到建立成功傳回1, ttl也設置為-1，

``` redis
>> SETNX jed-girl-firendQQ -1

(integer) 1
>> get jed-girl-firendQQ

"-1"
```

jed為存在的鍵，可以看到結果為0也沒更新為777

``` redis
>> SETNX jed 777

(integer) 0
>> set jed
>> get jed

"666"
```

## List
列表，

### LPUSH、RPUSH(插入列表)
從左至右 新增element 至list


``` redis
>> LPUSH queue a

(integer) 1

```
也能一次push多個element 

``` redis
>> LPUSH queue p p l e

(integer) 5
>> LRANGE queue 0 -1

1) "e"
2) "l"
3) "p"
4) "p"
5) "a"
```

可以發現順序是相反的從左至右，所以一開始輸入的a為index 4

同理由右至左就使用RPUSH



``` redis
>> RPUSH newqueue a

(integer) 1
>> RPUSH newqueue p p l e

(integer) 7
>> LRANGE newqueue 0 -1

1) "a"
2) "p"
3) "p"
4) "l"
5) "e"

```


### LRANGE(讀取列表範圍) 
讀取list 從index 0 到 -1(最後)

``` redis
>> LRANGE queue 0 -1

1) "a"
```

### LPOP、RPOP(刪除列表元素)
刪除list中的element，LPOP由左，R由右，來回顧目前list的狀態

> 💡注意 POP刪除多個element 只支援redis 6.2以上

| 0 | 1 | 2 | 3 | 4 |
| -------- | -------- | -------- | -------- | -------- |
| a     | p     | p     | l     | e     |


接著使用RPOP 刪除右邊兩個element
``` redis
>> RPUSH queue a p p l e

(integer) 5

>> LRANGE queue 0 -1

1) "a"
2) "p"
3) "p"
4) "l"
5) "e"
>> RPOP queue 2
1) "e"
2) "l"
>> LPOP queue 2

```
刪除list左邊前兩個element，所以是e l

| 0 | 1 | 2 
| -------- | -------- | -------- | 
| a     | p     | p     |


然後再使用LPOP一樣刪除兩個element
```


1) "a"
2) "p"
>> LRANGE queue 0 -1

1) "p"

```

刪除list左邊前兩個element，所以是a p，最終只會留下 p

| 0 | 
| -------- |
| p     |


### LTRIM(保留指定範圍刪除全部元素)
保留指定範圍的element，並刪除以外所有element

我們新增一個 a p p l e與 b a r 的 list，接著把b a r刪除

``` redis
>> RPUSH newqueue a p p l e b a r

(integer) 8
>> LRANGE newqueue 0 -1

1) "a"
2) "p"
3) "p"
4) "l"
5) "e"
6) "b"
7) "a"
8) "r"
>> LTRIM newqueue 0 4

"OK"
>> LRANGE newqueue 0 -1

1) "a"
2) "p"
3) "p"
4) "l"
5) "e"
```


### LLEN(查看列表長度)
由上面例子可透過LLEN來查看目前list中的長度

``` redis
>> LLEN queue

(integer) 1
```

## SET
集合，特性為無序的且不能有重複的element

### SADD(集合新增元素)
建立SET與新增element，當有重複element會return 0代表操作失敗

``` redis
>> SADD box apple

(integer) 1
>> SMEMBERS box

1) "apple"
>> SADD box apple
(integer) 0
```

### SMEMBERS(列出所有集合元素)
列出所有SET element
``` redis
>> SMEMBERS box

1) "apple"
```

### SISMEMBER(是否存在在集合)
查詢element是否在SET裡面


``` redis
>> SISMEMBER box apple

(integer) 1
>> SISMEMBER box bar

(integer) 0
```

### SREM(刪除集和元素)
刪除SET中element

``` redis
>> SREM box apple

(integer) 1
>> SMEMBERS box

(empty list or set)

```

### SINTER、SUNION、SDIFF(交集、聯集、差集)
交集、聯集、差集，透過以下兩個SET操作




set1

| 0 | 1 | 2  |
|----|----|----|
| a  | b  | c  |

set2

| 0 | 1 | 2  |
|----|----|----|
| b  | c  | d  |

建立SET
``` resis
>> SADD set1 a b c

(integer) 3
>> SADD set2 b c d
```
SINTER 集合
```
(integer) 3
>> SINTER set1 set2
1) "b"
2) "c"
```
SUNION 聯集

```
>> SUNION set1 set2
1) "a"
2) "b"
3) "c"
4) "d"
```

SDIFF 差集
``` redis
>> SDIFF set1 set2

1) "a"

```

## SortedSets
有序SET一樣不可重複，且每個element都會對應一個double score，score是可以重複的，當分數重複時會取字符字串順序

### 字符串排序規則

當多個字符串具有相同的分數時，可以按詞典順序（字典順序 lexicographical order））對這些字符串進行排序。詞典順序類似於字典中的單詞排序規則，即先比較字符串的第一個字符，如果相同則比較第二個字符，以此類推。


假設我們有以下字符串及其分數：

- `"apple"`: 5 分
- `"banana"`: 3 分
- `"cherry"`: 5 分
- `"date"`: 3 分
- `"elderberry"`: 5 分



1. **按分數排序**：
   先按分數從低到高進行排序：
   - `"banana"`: 3 分
   - `"date"`: 3 分
   - `"apple"`: 5 分
   - `"cherry"`: 5 分
   - `"elderberry"`: 5 分

> 💡SortedSets排序是由小到大

2. **按詞典順序排序**：
   在分數相同的情況下，按詞典順序進行排序：
   - `"apple"`: 5 分
   - `"cherry"`: 5 分
   - `"elderberry"`: 5 分


### ZADD
常見情境為排行榜，舉成績單排行榜例子，先新增所有學生成績
``` redis
>> ZADD transcript 100 Jed 95 Jack 99 Maggie 100 Jason 94 Tom

(integer) 5

```

### ZRANGE 
查看element
``` redis
>> ZRANGE transcript 0 -1

1) "Tom"
2) "Jack"
3) "Maggie"
4) "Jason"
5) "Jed"
```

如果要看分數可以加上 WITHSCORES
``` redis
>> ZRANGE transcript 0 -1 WITHSCORES

 1) "Tom"
 2) "94"
 3) "Jack"
 4) "95"
 5) "Maggie"
 6) "99"
 7) "Jason"
 8) "100"
 9) "Jed"
10) "100"
```

### ZSCORE 
查看element 分數
``` redis 
>> ZSCORE transcript Jed

"100"
```


### ZRANK、ZREVRANK
查看element 排名，分別由小到大與由大到小
``` redis 
>> ZRANK transcript Jed

(integer) 4
>> ZREVRANK transcript Jed

(integer) 0

```

## Hash
鍵與值的集合


### HSET
新增鍵與值 name 與 age

``` redis
>> HSET person name Jed

(integer) 1
>> HSET person age 26

```
### HGET
獲取鍵的值
``` redis
(integer) 1
>> HGET person name

"Jed"
>> HGET person age

"26"

```
### HGETALL
獲取所有Hash key value
``` redis


>> HGETALL person

1) "name"
2) "Jed"
3) "age"
4) "26"
```


### HDEL
刪除key value
``` redis
>> HDEL person age

(integer) 1
>> HGETALL person

1) "name"
2) "Jed"
```

### HEXISTS
查看key是否存在
``` redis
>> HEXISTS person age

(integer) 0
```

### HKEYS
列出所有key 
``` redis 
>> HKEYS person

1) "name"

```

### HLEN
查看Hash長度
``` redis
>> HLEN person

(integer) 1
```



# 訂閱發布模式
有使用MQTT的人一定很熟悉 Subscribe與Publish，其實不難理解

以youtube為例，你訂閱了Youtube官方頻道就相對於Subscrib，當官方頻道上傳新影片時你會收到消息，官方上傳並發布訊息給你就相對於Publish


### SUBSCRIBE、PUBLISH

``` redis
SUBSCRIBE mychannel
```

``` redis
publish mychannel 海嘯警報
```

![image](https://hackmd.io/_uploads/B1_cgUz90.png)
