有没有做php项目实战的网站,织梦网站转移服务器,哈尔滨网站建设吕新松,太仓住房和城乡建设局网站Go实现WebSocket#xff08;千万级别弹幕系统架构设计#xff09;
1 websocket简介(基于HTTP协议的长连接) 使用WebSocket可以轻松的维持服务器端长连接#xff0c;其次WebSocket是架构在HTTP协议之上的#xff0c;并且也可以使用HTTPS方式#xff0c;因此WebSocket是可靠…Go实现WebSocket千万级别弹幕系统架构设计
1 websocket简介(基于HTTP协议的长连接) 使用WebSocket可以轻松的维持服务器端长连接其次WebSocket是架构在HTTP协议之上的并且也可以使用HTTPS方式因此WebSocket是可靠传输并且不需要开发者关注底层细节。 websocket具体细节 ①Upgrade浏览器告知服务器升级为WebSocket协议 ②Switch服务器升级成功后会返回101状态码 ③Communicate浏览器和服务器就可以以WebSocket格式发送数据 还有一种推送数据的方式是SSE ①SSE(Server Send Event)服务器单项推送消息text/event-stream它是一种流可以返回多次数据 ②使用场景CI/CDChatGPT回答问题 详细文章推送数据— —WebSocket与SSE
2 弹幕业务的技术选择推、拉模式
2.1 客户端拉服务器压力过大类似DDoS 如果是客户端拉取服务器端数据那么将会存在以下几个问题 直播在线人数多就意味着消息数据更新频率高拉取消息意味着弹幕无法满足时效性如果很多客户端同时拉取那么服务器端的压力无异于DDOS一个弹幕系统应该是通用的因此对于直播间弹幕较少的场景意味着消息数据拉取请求都是无效的 2.2 服务端推服务端需要维护大量长连接 推送模式当数据发生更新的时候服务器端主动推送到客户端这样可以有效减少客户端的请求次数。 如果需要实现消息推送那么就意味着服务器端维护大量的长连接。 3 技术实现go
完整代码
go实现websocket https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-websocket/1-simplego实现简易弹幕系统https://github.com/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-websocket/2-boardcast
其他教程Java实现简易聊天室
3.1 前端页面
index.html
!DOCTYPE html
html
headtitlego websocket/titlemeta charsetutf-8/
/head
body
script typetext/javascriptvar wsUri ws://127.0.0.1:7777/ws;var output;function init() {output document.getElementById(output);testWebSocket();}function testWebSocket() {websocket new WebSocket(wsUri);websocket.onopen function (evt) {onOpen(evt)};websocket.onclose function (evt) {onClose(evt)};websocket.onmessage function (evt) {onMessage(evt)};websocket.onerror function (evt) {onError(evt)};}function onOpen(evt) {// writeToScreen(CONNECTED);// doSend(WebSocket rocks);}function onClose(evt) {writeToScreen(DISCONNECTED);}function onMessage(evt) {var message evt.data;if (message.startsWith(CONNECTED )) {var connectionId message.substring(CONNECTED .length);writeToScreen(CONNECTED: connectionId);} else {writeToScreen(span stylecolor: blue;RESPONSE: message /span);}}// function onMessage(evt) {// writeToScreen(span stylecolor: blue;RESPONSE: evt.data/span);// // websocket.close();// }function onError(evt) {writeToScreen(span stylecolor: red;ERROR:/span evt.data);}function doSend(message) {// writeToScreen(SENT: message);websocket.send(message);}function writeToScreen(message) {var pre document.createElement(p);pre.style.wordWrap break-word;pre.innerHTML message;output.appendChild(pre);}window.addEventListener(load, init, false);function sendBtnClick() {var msg document.getElementById(input).value;doSend(msg);document.getElementById(input).value ;}function closeBtnClick() {websocket.close();}
/script
h2WebSocket Test/h2
input typetext idinput/input
button onclicksendBtnClick()send/button
button onclickcloseBtnClick()close/button
div idoutput/div/body
/html3.2 go-websocket实现
1. model/connection.go封装websocket连接 整体思路 1. 封装websocket连接为connection ①维护连接的读写channel ②分别启两个协程for循环一个用于读一个用于写 //中间多了一层Channel保证了线程安全 readLoop - inChannel - c.ReadMessage拿到data - c.WriteMessage(data) - outChannel - writeLoop从outChannel中拿到data写回同样的数据到对端 2. conn_mgr实现connection的管理一旦有消息发送过来便广播给其他连接实现弹幕效果 package modelimport (errorsgithub.com/google/uuidgithub.com/gorilla/websocketsync
)/*整体思路1. 维护连接的读写channel2. 分别启两个协程for循环一个用于读一个用于写//中间多了一层Channel保证了线程安全readLoop - inChannel - c.ReadMessage拿到data - c.WriteMessage(data) - outChannel - writeLoop
*/type Connection struct {ConnID stringConn *websocket.Conn// 读消息队列inChannel chan []byte//写消息队列outChannel chan []byte// 监听Channel是否关闭closeChan chan byte// 标识isClosed boollock sync.Mutex
}// InitConnection 初始化封装的conn
func InitConnection(conn *websocket.Conn) (c *Connection, err error) {connId, err : uuid.NewUUID()if err ! nil {return nil, err}c Connection{ConnID: connId.String(),Conn: conn,inChannel: make(chan []byte, 1000),outChannel: make(chan []byte, 1000),closeChan: make(chan byte),isClosed: false,}//启动协程读取消息go c.readLoop()go c.writeLoop()return c, nil
}// ReadMessage 读取消息从inChannel中读取数据channel保证线程安全阻塞读取
func (c *Connection) ReadMessage() (data []byte, err error) {//从inChannel读取数据for {select {case data -c.inChannel:return data, nil//监听连接关闭信号避免一直阻塞读取数据case -c.closeChan:return nil, errors.New(conn is closed)}}
}// WriteMessage 写消息将数据写入outChannelchannel保证线程安全,等待write loop从outChannel中获取数据写回连接
func (c *Connection) WriteMessage(data []byte) (err error) {for {select {case c.outChannel - data:return nilcase -c.closeChan:return errors.New(conn is closed)}}
}// 从连接中不断读取数据写入inChannel
func (c *Connection) readLoop() {var (data []byteerr error)for {if _, data, err c.Conn.ReadMessage(); err ! nil {//读取数据失败关闭连接c.Close()return}select {//读取到数据写到inChannelcase c.inChannel - data:case -c.closeChan:c.Close()}}
}// 从outChannel中不断读取数据并发送数据写回对端
func (c *Connection) writeLoop() {var (data []byteerr error)for {select {case data -c.outChannel:if err c.Conn.WriteMessage(websocket.TextMessage, data); err ! nil {c.Close()return}case -c.closeChan:c.Close()return}}
}func (c *Connection) Close() {c.Conn.Close()c.lock.Lock()if !c.isClosed {close(c.closeChan)c.isClosed true}WebSocketMgr.RemoveConnection(c)c.lock.Unlock()
}2. model/conn_mgr.go
package modelimport (fmtsync
)type connectionMgr struct {connections map[string]*Connectionlock sync.RWMutex
}var WebSocketMgr connectionMgr{connections: make(map[string]*Connection),lock: sync.RWMutex{},
}func (cm *connectionMgr) AddConnection(conn *Connection) {cm.lock.Lock()defer cm.lock.Unlock()cm.connections[conn.ConnID] connfmt.Printf(connection %s added\n, conn.ConnID)return
}func (cm *connectionMgr) RemoveConnection(conn *Connection) {cm.lock.Lock()defer cm.lock.Unlock()delete(cm.connections, conn.ConnID)fmt.Printf(connection %s removed\n, conn.ConnID)return
}func (cm *connectionMgr) GetConnection(connID string) (conn *Connection, err error) {cm.lock.RLock()defer cm.lock.RUnlock()conn, ok : cm.connections[connID]if !ok {err fmt.Errorf(connection not found)return}return
}func (cm *connectionMgr) Boardcast(data []byte) {cm.lock.RLock()defer cm.lock.RUnlock()for _, conn : range cm.connections {if err : conn.WriteMessage(data); err ! nil {//if err : conn.WriteMessage([]byte(fmt.Sprintf([%s] %s, conn.ConnID, string(data)))); err ! nil {//TODO 补救或者日志记录或者忽略return}}
}3. main.go
package mainimport (github.com/gorilla/websocketlogmyTest/demo_home/go-demo/go-websocket/2-boardcast/modelnet/http
)var (upgrader websocket.Upgrader{//允许跨域CheckOrigin: func(r *http.Request) bool {return true}}
)func main() {//模拟简易弹幕系统注意为了逻辑简洁并没有做过多的封装部分代码设计以及安全监测并不合理http.HandleFunc(/ws, wsHandler)http.ListenAndServe(:7777, nil)
}func wsHandler(w http.ResponseWriter, r *http.Request) {var (conn *websocket.Connconnection *model.Connectionerr errordata []byte)if conn, err upgrader.Upgrade(w, r, nil); err ! nil {return}//初始化连接if connection, err model.InitConnection(conn); err ! nil {return}//注册连接model.WebSocketMgr.AddConnection(connection)// 发送连接ID给前端if err : conn.WriteMessage(websocket.TextMessage, []byte(CONNECTED connection.ConnID)); err ! nil {log.Println(Error sending connection ID:, err)return}go func(c *model.Connection) {for {if data, err connection.ReadMessage(); err ! nil {return}//广播消息model.WebSocketMgr.Boardcast(data)}}(connection)
}3.3 效果
1. 启动websocket服务端
2. 分别用chrome、Firefox、edge打开页面建立websocket连接 CONNECTIONID用于标识不同的websocket长连接 chrome Firefox edge
3. 不同浏览器相当于不同用户chrome用户发起一个弹幕点击send发送弹幕 4. 观察其他用户是否接受到弹幕 自己也会收到自己发的弹幕
5. edge、firefox用户分别发一个弹幕观察效果 模拟其他用户发送弹幕 6. chrome退出直播间其他用户发送弹幕它接受不到 chrome用户退出直播间edge、firefox发送弹幕chrome用户应该接受不到 edge、firefox用户发送弹幕 后端系统日志
4 千万级别弹幕系统设计
4.1 难点(内核、锁、CPU瓶颈) 内核瓶颈 推送量大100w在线用户*10条/s 1000w条/slinux内核发送TCP的极限包频约为100w条/s 锁瓶颈 推模式需要维护一个存储了100w条数据的集合比如map推送消息遍历整个集合顺序发送消息耗时极长推送期间客户端仍可以正常上/下线所以集合需要加锁 CPU瓶颈 浏览器与服务端采用json格式通讯json编码非常耗cpu向100w在线用户推送1次就需要100w次的json encode 4.2 解决方案多小包合为一个大包 内核瓶颈 减少网络小包的发送将同一秒内的N条消息合并为1条合并后每秒推送次数只等于在线连接数 锁瓶颈 将连接分散到多个集合中每个集合都有自己的锁多线程并发推送多个集合避免锁竞争读写锁取代互斥锁多个推送任务可以遍历相同集合 cpu瓶颈 减少重复计算json编码前置1次消息编码100w次推送 4.3 分布式架构 如果是单机架构的话 维护海量的连接必然会耗费很多内存消息推送的瞬间也会消耗大量CPU资源消息推送瞬间带宽可能高达400-600MB4-6Gbits主要瓶颈即需要万兆网卡 因此我们需要分布式架构 网关集群维护websocket长连接逻辑集群基于HTTP/2向gateway网关集群分发消息rpc与其他服务的交互等业务方 业务方-逻辑集群-网关集群 参考https://learnku.com/articles/48418 文章转载自: http://www.morning.sgwr.cn.gov.cn.sgwr.cn http://www.morning.nqypf.cn.gov.cn.nqypf.cn http://www.morning.rbgwj.cn.gov.cn.rbgwj.cn http://www.morning.ktmpw.cn.gov.cn.ktmpw.cn http://www.morning.yxnkr.cn.gov.cn.yxnkr.cn http://www.morning.ptmgq.cn.gov.cn.ptmgq.cn http://www.morning.lbcbq.cn.gov.cn.lbcbq.cn http://www.morning.cfccp.cn.gov.cn.cfccp.cn http://www.morning.srbfp.cn.gov.cn.srbfp.cn http://www.morning.ftync.cn.gov.cn.ftync.cn http://www.morning.zxfdq.cn.gov.cn.zxfdq.cn http://www.morning.hlzpb.cn.gov.cn.hlzpb.cn http://www.morning.ldnrf.cn.gov.cn.ldnrf.cn http://www.morning.bfcxf.cn.gov.cn.bfcxf.cn http://www.morning.nypgb.cn.gov.cn.nypgb.cn http://www.morning.lizimc.com.gov.cn.lizimc.com http://www.morning.btrfm.cn.gov.cn.btrfm.cn http://www.morning.syynx.cn.gov.cn.syynx.cn http://www.morning.qsfys.cn.gov.cn.qsfys.cn http://www.morning.rrgm.cn.gov.cn.rrgm.cn http://www.morning.yhwmg.cn.gov.cn.yhwmg.cn http://www.morning.xrqkm.cn.gov.cn.xrqkm.cn http://www.morning.rtbx.cn.gov.cn.rtbx.cn http://www.morning.kyjpg.cn.gov.cn.kyjpg.cn http://www.morning.cwqpl.cn.gov.cn.cwqpl.cn http://www.morning.mknxd.cn.gov.cn.mknxd.cn http://www.morning.tgfsr.cn.gov.cn.tgfsr.cn http://www.morning.kmqlf.cn.gov.cn.kmqlf.cn http://www.morning.wjzzh.cn.gov.cn.wjzzh.cn http://www.morning.mbpfk.cn.gov.cn.mbpfk.cn http://www.morning.ccyns.cn.gov.cn.ccyns.cn http://www.morning.zbqry.cn.gov.cn.zbqry.cn http://www.morning.nylbb.cn.gov.cn.nylbb.cn http://www.morning.qzbwmf.cn.gov.cn.qzbwmf.cn http://www.morning.fprll.cn.gov.cn.fprll.cn http://www.morning.xmhpq.cn.gov.cn.xmhpq.cn http://www.morning.cnkrd.cn.gov.cn.cnkrd.cn http://www.morning.zthln.cn.gov.cn.zthln.cn http://www.morning.pqypt.cn.gov.cn.pqypt.cn http://www.morning.lbzgt.cn.gov.cn.lbzgt.cn http://www.morning.sqgqh.cn.gov.cn.sqgqh.cn http://www.morning.jzklb.cn.gov.cn.jzklb.cn http://www.morning.qjlnh.cn.gov.cn.qjlnh.cn http://www.morning.nbgfk.cn.gov.cn.nbgfk.cn http://www.morning.lbhck.cn.gov.cn.lbhck.cn http://www.morning.ptdzm.cn.gov.cn.ptdzm.cn http://www.morning.cfpq.cn.gov.cn.cfpq.cn http://www.morning.bkwd.cn.gov.cn.bkwd.cn http://www.morning.gwdnl.cn.gov.cn.gwdnl.cn http://www.morning.gwkwt.cn.gov.cn.gwkwt.cn http://www.morning.fhtbk.cn.gov.cn.fhtbk.cn http://www.morning.c7627.cn.gov.cn.c7627.cn http://www.morning.rhkq.cn.gov.cn.rhkq.cn http://www.morning.kggxj.cn.gov.cn.kggxj.cn http://www.morning.trlhc.cn.gov.cn.trlhc.cn http://www.morning.qkqhr.cn.gov.cn.qkqhr.cn http://www.morning.rfbt.cn.gov.cn.rfbt.cn http://www.morning.wqmpd.cn.gov.cn.wqmpd.cn http://www.morning.txlxr.cn.gov.cn.txlxr.cn http://www.morning.wdply.cn.gov.cn.wdply.cn http://www.morning.trqzk.cn.gov.cn.trqzk.cn http://www.morning.lngyd.cn.gov.cn.lngyd.cn http://www.morning.wyctq.cn.gov.cn.wyctq.cn http://www.morning.zmwd.cn.gov.cn.zmwd.cn http://www.morning.rlhh.cn.gov.cn.rlhh.cn http://www.morning.pgmbl.cn.gov.cn.pgmbl.cn http://www.morning.hpxxq.cn.gov.cn.hpxxq.cn http://www.morning.tndhm.cn.gov.cn.tndhm.cn http://www.morning.rytps.cn.gov.cn.rytps.cn http://www.morning.tturfsoc.com.gov.cn.tturfsoc.com http://www.morning.nzfqw.cn.gov.cn.nzfqw.cn http://www.morning.qmwzr.cn.gov.cn.qmwzr.cn http://www.morning.qczpf.cn.gov.cn.qczpf.cn http://www.morning.tmbfz.cn.gov.cn.tmbfz.cn http://www.morning.wnrcj.cn.gov.cn.wnrcj.cn http://www.morning.kggxj.cn.gov.cn.kggxj.cn http://www.morning.tqhpt.cn.gov.cn.tqhpt.cn http://www.morning.pghgq.cn.gov.cn.pghgq.cn http://www.morning.xrpjr.cn.gov.cn.xrpjr.cn http://www.morning.rfrxt.cn.gov.cn.rfrxt.cn