在项目实际运行过程中,随着集群服务器数量的指数级别增长,需要自研一套维护集群内所有服务器server-client架构的系统,主要功能是采集客户端服务器各项指标,并对异常指标进行告警,达到监控服务器和及时处理异常的作用,保证集群内各项服务的稳定正常运行。
由于系统有很强的实时通信的需求,因此决定采用websocket方式实现客户端和服务端的通信。websocket通信比http的好处主要是支持长连接,全双工通信,相比http传输数据量小。
Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理能力,非常好的支持中间件和 json。Gin 的功能不只是简单输出 Json 数据。它同时是一个轻量级的 WEB 框架,支持 RestFull 风格 API,支持 GET,POST,PUT,PATCH,DELETE,OPTIONS 等 http 方法,支持文件上传,分组路由,Multipart/Urlencoded FORM,以及支持 JsonP,参数处理等等功能,这些都和 web 紧密相关,通过提供这些功能,使开发人员更方便地处理 web 业务。作为Go语言下优秀的 Web 框架,通过Gin框架可以轻松实现websocket通信功能。
Demo代码如下:
服务端:
package main
import (
"log"
"g i t h u b .c o m /gin-gonic/gin"
"g i t h u b .c o m/gorilla/websocket"
)
var upgrader = websocket.Upgrader{}
func main() {
// 使用gin框架,和普通的http协议的服务器基本一致
s := gin.Default()
s.GET("/echo", echo)
_ = s.Run("127.0.0.1:8090")
}
func echo(c *gin.Context) {
//服务升级,对于来到的http连接进行服务升级,升级到ws
cn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
defer cn.Close()
if err != nil {
panic(err)
}
for {
mt, message, err := cn.ReadMessage()
if err != nil {
log.Println("server read:", err)
break
}
log.Printf("server recv msg: %s", message)
msg := string(message)
if msg == "woshi client1" {
message = []byte("client1 去服务端了一趟")
} else if msg == "woshi client2" {
message = []byte("client2 去服务端了一趟")
}
err = cn.WriteMessage(mt, message)
if err != nil {
log.Println(" server write err:", err)
break
}
}
}
客户端:
package main
import (
"log"
"net/url"
"os"
"os/signal"
"time"
"g i t h u b . c o m /gorilla/websocket"
)
func main() {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u := url.URL{Scheme: "ws", Host: "127.0.0.1:8090", Path: "/echo"}
log.Printf("client1 connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial server:", err)
}
defer c.Close()
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("client read err:", err)
return
}
log.Printf("client recv msg: %s", message)
}
}()
//ticker := time.NewTicker(time.Second)
//defer ticker.Stop()
for {
select {
// if the goroutine is done , all are out
case <-done:
return
case <-time.Tick(time.Second * 5):
err := c.WriteMessage(websocket.TextMessage, []byte("woshi client2"))
if err != nil {
log.Println("client write:", err)
return
}
case <-interrupt:
log.Println("client interrupt")
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) for the server to close the connection.
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("client1 write close:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
在实战中,每台服务器需要安装客户端和服务端进行通信。对于上百上千台服务器通信基本是不会有拥塞现象的。但如果达到上万级别,服务器端这么集中接收这么多客户端的消息肯定还是会有性能瓶颈,所以技术层面还需要通道channel去接收消息;架构层面需要分流去接收消息。而对于直播等百万级别服务,还是需要研究新的架构。