String 是最單純的型別,一個 key 對一個值。
SET key value
GET key
MGET key ...(一次取多個)
SETEX key TTL value(存的同時設定過期時間,預設秒)
TTL key(查看剩餘時間,-1 永久保存,-2 key不存在,已過期)
SETNX key value(0 是已存在,不覆蓋,1 是儲存成功)
INCR key(原子 + 1)
INCRBY key num(一次增加指定的數值 num)
DECR key(原子 - 1)
DECRBY key num(一次減少指定的數值 num)
SETNX 「已經存在就不動」的特性,之後做分散式鎖會用到。
Day 2 留了一個坑:同樣的程式碼換成 RedisTemplate,在 redis-cli 看到的會是這種東西。
"\xac\xed\x00\x05t\x00\x0bhello-redis"
因為 RedisTemplate 預設用 Java 序列化,存進去是二進位,只有 Java 讀得懂。
解法很單純:不要用它,自己把物件轉成 JSON 字串,繼續用 Day 2 的 StringRedisTemplate 就好。
最常被拿來做快取,先查 Redis,沒有才去資料庫撈,撈完順手寫回快取。
@GetMapping("/{id}")
public Map<String, Object> get(@PathVariable Long id) throws JsonProcessingException {
String key = "product:" + id;
String json = redis.opsForValue().get(key);
if (json != null) {
return result(mapper.readValue(json, Product.class), "HIT");
}
Product product = loadFromDb(id); // sleep 100 毫秒,模擬查詢成本
redis.opsForValue().set(key, mapper.writeValueAsString(product), TTL); // 給 TTL
return result(product, "MISS");
}
mapper 就是 Spring Boot 準備好的 ObjectMapper,直接注入,不用自己設定。
curl http://localhost:8080/product/1001
curl http://localhost:8080/product/1001
108 毫秒變成 2 毫秒。有快取到真的變超快,每個快取 key 都要設 TTL,不然那筆資料會一直佔著記憶體!
Redis 沒有數字型別,計數器就是存數字的 String。
Day 3 說單執行緒讓每個指令天然是原子的,實際打一次看看。
private int javaCount = 0; // 故意不用 AtomicInteger
@GetMapping("/java")
public int java() {
return ++javaCount;
}
@GetMapping("/redis")
public Long redis() {
return redis.opsForValue().increment("counter:redis");
}
用 k6 開 50 個虛擬使用者,兩邊各打 5000 次:
export const options = {
vus: 50, // 50 個虛擬使用者同時打
iterations: 5000, // 總共各打 5000 次
};
export default function () {
http.get('http://localhost:8080/counter/java');
http.get('http://localhost:8080/counter/redis');
}

Redis 準準的 5000,Java 少了 47 次。
因為 ++ 其實是「讀 → 加 → 寫回」三個動作,兩條執行緒同時讀到 100,就會各自寫回 101,少加一次。這叫 lost update。
Java 要修得靠 AtomicInteger 或 synchronized,Redis 什麼都不用做。
相關範例程式碼可以參考 https://github.com/gary880306/redis-30days/tree/dev
商品快取是整包存、整包讀。明天換成購物車,只改其中一件的數量試試看!