# 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。 ![bloom filter](https://hackmd.io/_uploads/HkWfUws1gg.png) ## ## 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 ![](https://hackmd.io/_uploads/HJzT8Ztygg.png) ### 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) ```