背景
接上文 https://my.oschina.net/tuxpy/blog/1631953
之前通过grpc实现了一个双向流的方式来实现聊天室. 所有的stream都是存在一个sync.Map
里。如果这时候聊天室的压力大了,如何做扩展? 说得简单点就是怎么样来做负载均衡。之前在https://segmentfault.com/a/1190000008672912 看到过一篇借助etcd来实现负载均衡的文章,就借来实现了哈.
问题
- grpc的服务发现如何做?
- 某一个client发送的聊天内容,如何让分布在不同的节点上的client都收到
- 当某些client连接的service节点down了,如何让client重新发现并连接健康的节点? 如果所有的rpc方法都是request <-> response模式 倒没什么问题,大不了有可能会丢次当前正在处理的请求. 但是像聊天室这类应用,使用双向流模式, 一旦一个节点down了,后续服务端的stream就会得到
codes.Unavailable
错误
解决
- 参考https://segmentfault.com/a/1190000008672912 。服务端注册key, 定期keepalive, 客户端watch
- 利用etcd的put + watch实现一个remote channel,所有服务节点监听某一个key的PUT行为,根据value内容调用相应的方法(如广播消息给连接到自己的所有客户端). 之前还想过,每一个service同时还是一个grpc client,每次自己有广播行为时, 远程调用其他节点广播操作。天呐,还要维护一份所有节点的清单,杀了我吧.
- 将身份验证操作和聊天操作分离出来,先进行身份验证,取出token, 后续聊天的rpc调用时带上. token验证借助etcd实现的session, 每次断线重连,只影响聊天的stream, 不会重复登录.
实现
service
/*
*
* Author : tuxpy
* Email : q8886888@qq.com.com
* Create time : 3/7/18 9:18 AM
* Filename : service.go
* Description :
*
*
*/
package main
import (
"bytes"
"context"
"crypto/rand"
"encoding/gob"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
grpclb "grpclb/etcdv3"
pb "grpclb/helloword"
"io"
"log"
"net"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
"utils"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/mvcc/mvccpb"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)
type Service struct{}
type ConnectPool struct {
sync.Map
}
type RemoteCommand struct {
Command string
Args map[string]string
}
type RemoteChannel struct {
In chan RemoteCommand
Out chan RemoteCommand
cli *clientv3.Client
}
type SessionManager struct {
cli *clientv3.Client
}
type Session struct {
Name string
Token string
}
var connect_pool *ConnectPool
var remote_channel *RemoteChannel
var session_manger *SessionManager
// 将消息广播给其他service. 因为做了负载均衡,一个client stream有可能落在不同节点,需要将行为广播给所有的节点
func ReadyBroadCast(from, message string) {
remote_channel.Out &