# redis
## References
+ 🔗 [**菜鳥教程 - Redis**](https://www.runoob.com/redis/redis-tutorial.html)
+ 🎬 [**GeekHour - 一小时 Redis 教程**](https://youtu.be/WgpP7-XAI5Y)
+ 🎬 [**轩辕的编程宇宙 - 1. 我是Redis,MySQL大哥被我坑惨了!**](https://www.bilibili.com/video/BV1Fd4y1T7pD/?)
+ 🎬 [**轩辕的编程宇宙 - 2. Redis数据持久化AOF和RDB原理一次搞懂!**](https://www.bilibili.com/video/BV1sV4y147Jz/?)
+ 🎬 [**轩辕的编程宇宙 - 3. Redis的高可用是怎么实现的?哨兵是什么原理?**](https://www.bilibili.com/video/BV1MW4y187AH/?)
+ 🎬 [**轩辕的编程宇宙 - 4. Redis集群是如何工作的?**](https://www.bilibili.com/video/BV1ge411L7Sh/?)
## Brief
in-memory database,支援眾多 data structures,可利用它作為進程間通訊的中介
## Install
### Windows
可以用 `scoop` 安裝
### Docker
註:[RedisStack](https://redis.io/about/redis-stack/) 包含進階功能,如:RedisJSON, RediSearch, RedisTimeSeries, RedisBloom
| image | 包含內容 | 適用情境 |
| --- | --- | --- |
| `redis` | Redis | 簡單或 legacy 專案 |
| `redis/redis-stack-server` | Redis + Redis Stack | 部署到生產環境 |
| `redis/redis-stack` | Redis + Redis Stack + RedisInsight | 開發、測試 |
|☢️ <span class="warning">WARNING</span>|
|:---|
|要使用 `redis-om` 的 `find()` 功能,必須要使用含 Redis Stack 的 image|
1. 運行 container
```
docker run --name test-redis -p 6379:6379 -d redis
```
+ 上 DockerHub 抓 image (redis)
+ 創建一個 container 叫 `test-redis`
+ daemon 運行 container
+ `redis-server` 也會在 container 內運行
+ 資料會儲存在這裡 (in-memory)
+ listening on port 6379 (預設)
+ 將其 port mapping 到本機 port 6379,方便後續在本機操作
2. 進入 container
```
docker exec -it test-redis redis-cli
```
+ 進入 container,一進去就執行 `redis-cli`,讓我們 debug
+ `redis-cli` 可以選擇加一個 `--raw` options
+ 字串以 UTF-8 編碼存入,以 byte 格式顯示
+ `--raw` options 讓字串得以用 Unicode 格式顯示 (正常顯示非英文字串)
## Deletion Policies
記憶體空間注定是有限的,鍵值沒辦法永久留存在記憶體中,需要定期清理。
### Key Expiration
<mark>如何剔除已過期的鍵值</mark>
+ **主動刪除 active expiration**:<mark>隨機挑選剔除</mark>
每隔 100 ms 隨機挑選 20 個鍵值,然後刪除過期鍵值,若這 20 個鍵值中,有超過 25% 是過期的鍵值,則會重複這個動作,直到過期比例低於 25%。 (每秒做 10 次,每次挑 20 個鍵值)
+ **被動刪除 passive expiration**:<mark>查詢發現過期時剔除</mark>
當該鍵值被存取到時,若該鍵值已過期,則會先刪除該鍵值,而後回覆空值給應用程式。
### Memory Eviction
<mark>記憶體不夠用時,如何剔除鍵值</mark>
+ `noeviction`:返回錯誤,不會剔除任何鍵值。
+ `allkeys-lru`:針對所有鍵值,使用 LRU 算法剔除鍵值。
+ `allkeys-lfu`:針對所有鍵值,使用 LFU 算法剔除鍵值。
+ `allkeys-random`:針對所有鍵值,隨機剔除鍵值。
+ `volatile-lru`:在設有過期時間的鍵值中,使用 LRU 算法剔除鍵值。
+ `volatile-lfu`:在設有過期時間的鍵值中,使用 LRU 算法剔除鍵值。
+ `volatile-random`:在設有過期時間的鍵值中,隨機剔除鍵值。
+ `volatile-ttl`:在設有過期時間的鍵值中,剔除即將過期的鍵 (最短 TTL)。
## Cache Miss Problems
+ **快取擊穿 hotspot invalid**
+ 說明:<mark>某個超熱門鍵值過期</mark>,進而導致大量 request 打在資料庫臉上。
+ 解決方案:Ops Engineer 要進行流量分析,針對熱點 key 將其設置為永不過期。
+ **快取雪崩 cache avalanche**
+ 說明:在<mark>短時間內發生大批鍵值過期</mark>,進而導致大量 request 打在資料庫臉上。
+ 解決方案:加上一些 <mark>jitter</mark>。即給予每個鍵值的固定過期時間加上一些隨機偏移,去分散流量。
+ **快取穿透 cache penetration**
+ 說明:出現<mark>大量既不存在於 Redis 也不存在於資料庫的 GET request</mark>,這些 request 都會赤裸裸的打在資料庫臉上。
+ 解決方案:在 backend app 層實作 <mark>bloom filter</mark>,藉由其所耗空間超小及不會有偽陰性 (說你不存在就是不存在) 判定的特性,去大量刷掉這些 GET request。

##
## Commands
+ `FLUSHALL`:清除所有資料
### Exists
+ `1`:存在
+ `0`:不存在
### TTL
+ `-1`:存在但 forever
+ `-2`:不存在
### Data Structure
+ 列表 list (命令 `L` 開頭)
+ 集合 set (命令 `S` 開頭)
+ 單層字典 hash (命令 `H` 開頭)
+ 有序集合 sorted set (命令 `Z` 開頭)
+ key:value (value 為 floating point,依此排序)
### Subscribe & Publish

### Stream
(命令 `X` 開頭)
### Geo
(命令 `GEO` 開頭)
### HyberLogLog
(命令 `PF` 開頭)
### BitMap
字串就是 bit 的集合,所以結構上就是字串
### BitField
字串就是 bit 的集合,所以結構上就是字串
## Python
+ Third Party Library
+ `redis-py`
+ `redis-om` (將 Pydantic Model 物件存入 Redis)
+ 範例 1:同步
```py
import redis
REDIS_URL = "redis://localhost:6379"
redis_connection_pool = ConnectionPool.from_url(REDIS_URL, max_connections=10)
redis_connection = redis.Redis(connection_pool=redis_connection_pool)
value = "bar"
redis_connection.set("foo", value)
result = redis_connection.get("foo") # 回傳 bytes | None
redis_connection.close()
assert result.decode() == value
```
+ 範例 2:異步
```py
import redis.asyncio as redis
REDIS_URL = "redis://localhost:6379"
redis_connection = redis.Redis.from_url(REDIS_URL)
value = "bar_async"
await redis_connection.set("foo_async", value)
result = await redis_connection.get("foo_async")
await redis_connection.close()
assert result.decode() == value
```
+ 範例 3:redis-om,get 功能
```py
from pydantic import ConfigDict
from redis_om import Field, HashModel, get_redis_connection
# HashModel 來讓我們可以將 Object 存成 Hash
# JsonModel 來讓我們可以將 Object 存成 JSON
class UserReadCache(HashModel):
id: Annotated[int, Field(index=True)]
name: Annotated[str, Field(index=True)]
email: Annotated[str, Field(index=True)]
avatar: Annotated[str, Field()] | None = None
model_config = ConfigDict(database=get_redis_connection(url=REDIS_URL))
new_user = UserReadCache(
id=1,
name="json_user",
email="json_user@email.com",
avatar="image_url",
)
new_user.save()
pk = new_user.pk
result = UserReadCache.get(pk)
print(result)
assert result == new_user
```
+ 範例 4:redis-om,find 功能 (需有 Redis Stack)
```py
from pydantic import ConfigDict
from redis_om import Field, JsonModel, Migrator, get_redis_connection
class ProductCache(JsonModel):
name: Annotated[str, Field(index=True)]
price: Annotated[float, Field(index=True)]
in_stock: Annotated[bool, Field(index=True)]
model_config = ConfigDict(database=get_redis_connection(url=REDIS_URL))
Migrator().run() # 🔥 記得要有這行
# 建立產品 1
product1 = ProductCache(
id=1,
name="Apple iPhone 15",
price=999.99,
in_stock=True,
)
product1.save()
# 建立產品 2
product2 = ProductCache(
id=2,
name="Samsung Galaxy S24",
price=899.99,
in_stock=True,
)
product2.save()
# 建立產品 3
product3 = ProductCache(
id=3,
name="Google Pixel 8",
price=799.99,
in_stock=False,
)
product3.save()
# 建立產品 4
product4 = ProductCache(
id=4,
name="OnePlus 12",
price=749.99,
in_stock=True,
)
product4.save()
# 建立產品 5
product5 = ProductCache(
id=5,
name="Sony Xperia 5 V",
price=949.99,
in_stock=False,
)
product5.save()
products = ProductCache.find(ProductCache.price < 800).all()
print(products)
```