GoでWebSocketを使用した通知システムを作成する：[Redis連携]

## はじめに

前回の記事では、GoでWebSocketを使った基本的なリアルタイム通信を実装しました。  
今回は、さらに応用的なWebSocketの活用方法として、通知システムの作成およびRedisを使ったスケーラブルなWebSocketサーバーの構築を解説します。

**対象読者**
- WebSocketを活用して通知システムを実装したい方
- Redisを使ってWebSocketサーバーをスケールアウトしたい方
- 複数のWebSocketクライアントを効率的に管理したい方

**目次**
1. WebSocketを活用した通知システムの作り方
   - 通知システムの仕組み
   - WebSocketサーバーの実装
   - クライアント側の実装（JavaScript）
2. Redisを使ったWebSocketサーバーの構築方法
   - Redis Pub/Subの仕組み
   - GoでのRedis連携（`go-redis`）
   - マルチインスタンス対応のWebSocketサーバー

---

## 1. WebSocketを活用した通知システムの作り方

#### **1.1 通知システムの仕組み**

WebSocketを活用すると、サーバーからリアルタイムでクライアントに通知を送信できます。

**例：通知システムの流れ**
1. クライアントがWebSocketに接続する
2. サーバーが特定のイベント（新しい投稿、メッセージなど）を検知
3. サーバーが該当ユーザーにリアルタイム通知を送信

#### **1.2 WebSocketサーバーの実装**

まず、GoでWebSocketの通知サーバーを実装します。

**server.go**
```go
package main

import (
    "fmt"
    "net/http"
    "sync"
    "github.com/gorilla/websocket"
)

type Client struct {
    conn *websocket.Conn
}

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool { return true },
}
var clients = make(map[*Client]bool)
var mu sync.Mutex

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        fmt.Println("WebSocket connection failed:", err)
        return
    }
    client := &Client{conn: conn}
    mu.Lock()
    clients[client] = true
    mu.Unlock()

    defer func() {
        mu.Lock()
        delete(clients, client)
        mu.Unlock()
        conn.Close()
    }()

    for {
        _, _, err := conn.ReadMessage()
        if err != nil {
            break
        }
    }
}

func sendNotification(message string) {
    mu.Lock()
    defer mu.Unlock()
    for client := range clients {
        client.conn.WriteMessage(websocket.TextMessage, []byte(message))
    }
}

func main() {
    http.HandleFunc("/ws", handleWebSocket)
    fmt.Println("WebSocket server running on ws://localhost:8080/ws")
    http.ListenAndServe(":8080", nil)
}
```

- `clients`マップでWebSocketに接続しているクライアントを管理
- `sendNotification()`で接続中のすべてのクライアントにメッセージを送信

#### **1.3 クライアント側の実装（JavaScript）**

**index.html**
```html
<script>
    const ws = new WebSocket("ws://localhost:8080/ws");

    ws.onmessage = function(event) {
        alert("通知: " + event.data);
    };
</script>
```

- WebSocketからのメッセージを受け取るとポップアップ通知を表示

---

## 2. Redisを使ったWebSocketサーバーの構築方法

#### **2.1 Redis Pub/Subの仕組み**

Redis Pub/Sub（パブリッシュ・サブスクライブ）を使うと、異なるWebSocketサーバー間で通知を共有できます。

#### **2.2 GoでのRedis連携（`go-redis`）**

**Redisをインストール**
```sh
docker run --name redis -d -p 6379:6379 redis
```

**Goライブラリをインストール**
```sh
go get github.com/go-redis/redis/v8
```

**server_redis.go**
```go
package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
    "net/http"
    "github.com/gorilla/websocket"
)

var ctx = context.Background()
var rdb = redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
})

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, _ := upgrader.Upgrade(w, r, nil)
    defer conn.Close()
    sub := rdb.Subscribe(ctx, "notifications")
    defer sub.Close()
    
    ch := sub.Channel()
    for msg := range ch {
        conn.WriteMessage(websocket.TextMessage, []byte(msg.Payload))
    }
}

func sendNotification(message string) {
    rdb.Publish(ctx, "notifications", message)
}

func main() {
    http.HandleFunc("/ws", handleWebSocket)
    fmt.Println("WebSocket server with Redis running on ws://localhost:8080/ws")
    http.ListenAndServe(":8080", nil)
}
```

- `rdb.Publish()`を使って通知をRedisに送信
- `rdb.Subscribe()` でRedisから通知を受信し、WebSocketでクライアントに配信

---

## まとめ

| 項目 | 説明 |
|------|------|
| **WebSocket 通知システム** | Goでリアルタイム通知を実装する方法 |
| **Redis Pub/Sub** | 異なるWebSocketサーバー間で通知を共有する方法 |
| **Redis によるスケールアウト** | 複数サーバーでも統一的に通知を送信 |

本記事では、WebSocketを活用した通知システムと、Redisを用いたWebSocketサーバーの構築方法を解説しました。  
次も発展系を勉強していこうと思います!