192. Pengenalan Redis & Driver go-redis/v9 di Golang
Memahami arsitektur In-Memory Data Store Redis dan menghubungkan Golang dengan package redis/go-redis/v9.
Apa itu Redis?
Redis (Remote Dictionary Server) adalah database In-Memory dengan struktur data Key-Value yang super cepat. Redis umumnya digunakan untuk Caching, Session Management, Pub/Sub Messaging, dan Rate Limiting.
Redis menyimpan seluruh data di dalam RAM (Random Access Memory), sehingga latency pembacaan dan penulisan data berada di tingkat sub-milidetik (< 1ms).
Instalasi Client `go-redis`:
go get github.com/redis/go-redis/v9
Driver lama github.com/go-redis/redis/v8 sudah tidak dipelihara. Sejak v9 repositori pindah ke organisasi redis, sehingga import path berubah menjadi github.com/redis/go-redis/v9. Perbedaan utama saat migrasi: seluruh perintah kini wajib menerima context.Context sebagai argumen pertama, dan opsi koneksi memakai redis.Options dengan dukungan RESP3 secara default.
Contoh program
package main
import (
"fmt"
)
// Simulasi Client Redis In-Memory
type RedisClient struct {
storage map[string]string
}
func NewRedisClient() *RedisClient {
return &RedisClient{storage: make(map[string]string)}
}
func main() {
fmt.Println("=== CONNECTING TO REDIS SERVER ===")
rdb := NewRedisClient()
fmt.Println("Ping Redis Server: PONG (Connection Latency: 0.2ms)")
_ = rdb
}
193. String Operations (Set, Get, SetEX, MGet, MSet, Del)
Mengoperasikan tipe data paling dasar di Redis yaitu String key-value beserta waktu kadaluarsa (TTL).
Operasi String Dasar pada Redis
Tipe data String di Redis dapat menyimpan teks, angka, maupun data biner (JSON/Gambar) hingga ukuran 512MB.
ctx := context.Background()
// 1. Menyimpan data dengan TTL 10 Menit
err := rdb.Set(ctx, "user:101:name", "Eko", 10*time.Minute).Err()
// 2. Mengambil data
val, err := rdb.Get(ctx, "user:101:name").Result()
if err == redis.Nil {
fmt.Println("Key tidak ditemukan atau sudah kadaluarsa (Cache Miss)")
}
// 3. Menghapus Key
rdb.Del(ctx, "user:101:name")
Contoh program
package main
import (
"fmt"
)
type RedisStore map[string]string
func main() {
store := make(RedisStore)
// SET
store["session:token_abc"] = "user_id_992"
// GET
val, exists := store["session:token_abc"]
fmt.Println("=== REDIS STRING GET / SET DEMO ===")
if exists {
fmt.Println("Redis GET 'session:token_abc' => Value:", val)
}
}
194. Expiration & TTL Management (Expire, TTL, Persist)
Mengatur dan memantau masa berlaku (Time-To-Live) dari sebuah Key di Redis.
Manajemen Time-To-Live (TTL)
Salah satu fitur terbaik Redis adalah pembersihan memori otomatis berdasarkan expiration time (TTL). Ketika durasi TTL habis, Redis akan menghapus key tersebut secara otomatis.
rdb.Expire(ctx, key, 30*time.Second): Memperbarui TTL key menjadi 30 detik.rdb.TTL(ctx, key).Result(): Memeriksa sisa durasi hidup key.rdb.Persist(ctx, key): Menghapus expiration time (membuat key permanen).
Contoh program
package main
import (
"fmt"
"time"
)
func main() {
ttl := 60 * time.Second
fmt.Println("=== REDIS TTL MANAGEMENT DEMO ===")
fmt.Println("Key 'otp:user_88' disimpan dengan TTL:", ttl)
fmt.Println("Sisa waktu kadaluarsa (rdb.TTL): 58.4 detik")
}
195. Hash Data Structure (HSet, HGet, HGetAll, HDel)
Menyimpan dan mengelola objek struct dalam bentuk struktur data Hash di Redis.
Struktur Data Redis Hash
Redis Hash adalah pemetaan field-value di dalam satu Key (sangat mirip dengan objek/map di dalam Redis). Hash sangat efisien memori untuk menyimpan objek seperti Profile User atau Shopping Cart.
// Set individual fields dalam Hash rdb.HSet(ctx, "user:100", "name", "Budi", "age", 25, "email", "[email protected]") // Get satu field name, _ := rdb.HGet(ctx, "user:100", "name").Result() // Get seluruh field sebagai map[string]string userMap, _ := rdb.HGetAll(ctx, "user:100").Result()
Contoh program
package main
import (
"fmt"
)
func main() {
userHash := map[string]string{
"username": "eko_khannedy",
"role": "admin",
"level": "gold",
}
fmt.Println("=== REDIS HGETALL DEMO ===")
fmt.Println("Hasil rdb.HGetAll('user_hash:99'):")
for k, v := range userHash {
fmt.Printf(" Field '%-10s' => %s\n", k, v)
}
}
196. List & Set Data Structures (LPush, RPop, SAdd, SMembers)
Mengimplementasikan Message Queue dengan List dan koleksi unik tanpa duplikat dengan Set.
Redis List & Redis Set
- List (Queue/Stack): Urutan elemen string berdasarkan waktu penambahan. Digunakan untuk background job queue. (
LPush,RPop,BLPop). - Set (Unique Collection): Kumpulan string unik tanpa urutan tertentu. Mencegah duplikasi data. (
SAdd,SMembers,SIsMember).
rdb.LPush(ctx, "queue:emails", "[email protected]") // Producer menambahkan job job, _ := rdb.RPop(ctx, "queue:emails").Result() // Consumer mengambil job terlama (FIFO)
Contoh program
package main
import (
"fmt"
)
func main() {
// Simulasi FIFO Queue dengan Slice
var queue []string
// LPUSH (Push to head)
queue = append([]string{"job_email_1"}, queue...)
queue = append([]string{"job_email_2"}, queue...)
fmt.Println("=== REDIS LIST FIFO QUEUE SIMULATION ===")
fmt.Println("Current Queue Contents:", queue)
// RPOP (Pop from tail)
lastJob := queue[len(queue)-1]
queue = queue[:len(queue)-1]
fmt.Println("Processed Job (RPop):", lastJob)
fmt.Println("Remaining Queue:", queue)
}
197. Sorted Set (ZAdd, ZRange, ZScore) & Leaderboard System
Membangun sistem papan peringkat (Leaderboard) realtime berkecepatan tinggi menggunakan Redis Sorted Set.
Sistem Papan Peringkat (Leaderboard) dengan Sorted Set
Redis Sorted Set (ZSet) mirip dengan Set biasa, namun setiap elemen diasosiasikan dengan sebuah Score (skor numerik). Redis akan otomatis mengurutkan elemen berdasarkan skor tersebut.
// Menambahkan member beserta score
rdb.ZAdd(ctx, "leaderboard:game", &redis.Z{Score: 1500, Member: "Player1"})
rdb.ZAdd(ctx, "leaderboard:game", &redis.Z{Score: 2300, Member: "Player2"})
// Mengambil Top 10 Player Skor Tertinggi (Rank 0-9)
topPlayers, _ := rdb.ZRevRangeWithScores(ctx, "leaderboard:game", 0, 9).Result()
Contoh program
package main
import (
"fmt"
"sort"
)
type PlayerScore struct {
Name string
Score float64
}
func main() {
players := []PlayerScore{
{Name: "GamerPro", Score: 850},
{Name: "ShadowNinja", Score: 1200},
{Name: "CyberKing", Score: 990},
}
sort.Slice(players, func(i, j int) bool {
return players[i].Score > players[j].Score
})
fmt.Println("=== REDIS SORTED SET LEADERBOARD (TOP 3) ===")
for i, p := range players {
fmt.Printf("Rank #%d | Player: %-12s | Score: %.0f\n", i+1, p.Name, p.Score)
}
}
198. Redis Pub/Sub (Publish, Subscribe, Channel)
Membangun sistem perpesanan terdistribusi berarsitektur Event-Driven (Pub/Sub) menggunakan Redis Channels.
Arsitektur Event-Driven dengan Redis Pub/Sub
Redis Pub/Sub memungkinkan aplikasi mengirimkan pesan (*Publish*) ke suatu channel, dan aplikasi lain yang mendengarkan (*Subscribe*) channel tersebut akan langsung menerima pesan secara **realtime**.
// Publisher: Mengirim pesan
rdb.Publish(ctx, "channel:notifications", "Order #992 Paid")
// Subscriber: Mendengarkan pesan
pubsub := rdb.Subscribe(ctx, "channel:notifications")
ch := pubsub.Channel()
for msg := range ch {
fmt.Println("Received Event Message:", msg.Payload)
}
Contoh program
package main
import (
"fmt"
)
func main() {
channel := "notifications:events"
payload := `{"event": "USER_REGISTERED", "user_id": 8812}`
fmt.Println("=== REDIS PUB/SUB SIMULATION ===")
fmt.Printf("[PUBLISHER] Sending message to channel '%s'...
", channel)
fmt.Printf("[SUBSCRIBER] Event Received on '%s' => Payload: %s
", channel, payload)
}
199. Redis Pipelines & Transactions (Multi/Exec)
Meningkatkan performa throughput dengan Redis Pipeline (batching) dan menjamin atomisitas dengan Multi/Exec.
Redis Pipeline & Transaction
Setiap kali kita memanggil `rdb.Get()` atau `rdb.Set()`, ada waktu tunggu bolak-balik (Round-Trip Time / RTT) di jaringan. **Pipeline** mengirimkan banyak perintah sekaligus dalam 1 paket jaringan untuk menghemat latency.
pipe := rdb.Pipeline() incr := pipe.Incr(ctx, "counter") pipe.Expire(ctx, "counter", time.Hour) // Eksekusi seluruh batch command dalam 1 kali round-trip! _, err := pipe.Exec(ctx)
Contoh program
package main
import (
"fmt"
)
func main() {
fmt.Println("=== REDIS PIPELINE BATCH EXECUTION ===")
fmt.Println("Sending 50 Commands in 1 Network Round-Trip Pipeline...")
fmt.Println("Latency tanpa Pipeline: ~50ms (50 x 1ms)")
fmt.Println("Latency DENGAN Pipeline: ~1.2ms (Batching Execution Successful!)")
}
200. Studi Kasus: High Performance HTTP Cache Strategy & Rate Limiter dengan Redis
Menggabungkan strategi Cache-Aside dan Sliding Window Rate Limiting untuk melindungi endpoint API Golang.
Studi Kasus Integrasi Redis di Production
Dua penggunaan Redis paling populer di backend API adalah:
- Cache-Aside Pattern:
- Cek Redis Cache (`rdb.Get`). Jika ada (Cache Hit), kembalikan data langsung.
- Jika tidak ada (Cache Miss), query ke SQL Database, simpan hasilnya ke Redis dengan TTL, lalu kembalikan data.
- Rate Limiting (Sliding Window / Fixed Window): Membatasi pengguna maksimal 100 request/menit untuk mencegah serangan DDoS atau spamming.
Contoh program
package main
import (
"fmt"
)
type ProductCache struct {
redisData map[string]string
dbData map[string]string
}
func (pc *ProductCache) GetProduct(id string) (string, string) {
// 1. Cek Redis
if val, ok := pc.redisData[id]; ok {
return val, "CACHE_HIT (From Redis RAM)"
}
// 2. Query SQL DB
val := pc.dbData[id]
// 3. Populate Redis
pc.redisData[id] = val
return val, "CACHE_MISS (Query SQL & Saved to Redis)"
}
func main() {
cache := ProductCache{
redisData: make(map[string]string),
dbData: map[string]string{"prod_1": "Laptop Gaming ASUS"},
}
fmt.Println("=== DEMO CACHE-ASIDE PATTERN ===")
// Request 1: Cache Miss
val1, source1 := cache.GetProduct("prod_1")
fmt.Printf("Req #1 => Value: %s | Source: %s\n", val1, source1)
// Request 2: Cache Hit
val2, source2 := cache.GetProduct("prod_1")
fmt.Printf("Req #2 => Value: %s | Source: %s\n", val2, source2)
}