Kodeloka
  1. Kodeloka
  2. Kelas Golang
  3. Materi

Domain 19 · 9 bab

Golang Redis Client

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.

💡 Kenapa Redis Sangat Cepat?

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
⚠️ Jangan pakai v8 lagi

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
}

Praktik dan kuis bab ini di kelas interaktif →

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.

📌 Kode Standar go-redis:
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)
	}
}

Praktik dan kuis bab ini di kelas interaktif →

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.

💡 Fungsi Manajemen TTL:
  • 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")
}

Praktik dan kuis bab ini di kelas interaktif →

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.

📌 Kode Redis Hash:
// 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)
	}
}

Praktik dan kuis bab ini di kelas interaktif →

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).
💡 Contoh Job Queue dengan List:
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)
}

Praktik dan kuis bab ini di kelas interaktif →

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.

📌 Kode Leaderboard Redis:
// 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)
	}
}

Praktik dan kuis bab ini di kelas interaktif →

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**.

💡 Alur Pub/Sub:
// 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)
}

Praktik dan kuis bab ini di kelas interaktif →

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.

📌 Kode Redis Pipeline:
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!)")
}

Praktik dan kuis bab ini di kelas interaktif →

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:

  1. 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.
  2. 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)
}

Praktik dan kuis bab ini di kelas interaktif →