基于Go的websocket消息服务
3个月没写PHP了,这是我的第一个中小型go的websocket微服务。那么问题来了,github上那么多轮子,我为什么要自己造轮子呢?
Why 造轮子?
因为这样不仅能锻炼自己的技术能力,而且能帮助深入了解其中的实现原理。
直接上流程图:

其实其中有些难点并没有反映出来,比如历史消息数据的存储结构、病发时遇到的一些坑等。
历史消息的存储结构 :

即广播、组播可拆解成单播,那么代码就可以变得简单。
但是,但是,但是,有看到 "ref"? ref表示,用户的历史消息,是否是一个引用, 类似于c/cpp的指针、地址。想一想,如果广播给1w用户,那么是不是要把一个msg push到每一个用户呢?
答案至少有2:
其一:push msg给everyone,优点:读取数据时很方便, 缺点:数据大量冗余,且push一瞬间io量过大,效率低;
其二:push msg时,分别存储:广播表、组播表、单播表, 优点:分别查询性能高,无冗余 , 缺点:综合查询用户的所有历史消息时,性能差,而且redis的网络io次数较多,还有时间等排序的问题。
综合考虑,选用第1种方案。
问题又来了, 这个项目开发顺利不,遇到坑没?
废话,技术的活,哪有不带坑的!
坑1:panic中断既出 ,真tmd不是我想要的, 解决方式是:recovery ( : P
坑2:环境变量向内包的传递,试了几种办法不行,最后用一个包作代理,封装工厂和单例, 最好的解决了。
var instance *env
func NewEnv()*env {
env := env{}
env.init()
env.parameters = make(map[string]interface{})
return &env
}
func SingleEnv()*env{
if nil == instance {
instance = NewEnv()
}
return instance
}
//...
坑3:websocket跨域问题,解决方法至少有2:可以修改默认设定
// 临时忽略websocket跨域
ws := websocket.Upgrader{
}
if model.SingleConfig().Http.EnableCrossSite {
ws.CheckOrigin = func(r *http.Request) bool { //mock and stub
return true
}
}
或者是在nginx上加这些,相当于在同一个域,推荐用这:
nginx conf:
upstream push {
ip_hash;
server 127.0.0.1:9999 ;
keepalive 60;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name dev.push.pub.sina.com.cn;
location /push {
proxy_http_version 1.1;
proxy_redirect off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
proxy_pass http://push;
fastcgi_keep_conn on;
include fastcgi_params;
}
}
坑4:go map不内建支持并发安全,这是最大的问题。解决稍有点麻烦,需要用到RWMutex锁。 我参考beego写的:
package lib
import "sync"
type RWLocker struct {
mtx sync.RWMutex
}
func NewRWLock()*RWLocker{
return &RWLocker{}
}
func (l *RWLocker)RLockDo(callback func()){
l.mtx.RLock()
defer l.mtx.RUnlock()
callback()
}
func (l *RWLocker)RWLockDo(callback func()){
l.mtx.Lock()
defer l.mtx.Unlock()
callback()
}
type Locker struct {
mtx sync.Mutex
}
func NewLock()*Locker{
return &Locker{}
}
func (l *Locker)LockDo(callback func()){
l.mtx.Lock()
defer l.mtx.Unlock()
callback()
}
type MutexMap struct{
m map[interface{}]interface{}
lock *sync.RWMutex
}
func NewMutexMap() *MutexMap {
return &MutexMap{
lock: new(sync.RWMutex),
m: make(map[interface{}]interface{}),
}
}
func (m *MutexMap) Size() int{
return len(m.m)
}
func (m *MutexMap) Raw() map[interface{}]interface{} {
return m.m
}
//Get from maps return the k's value
func (m *MutexMap) Get(k interface{}) interface{} {
m.lock.RLock()
defer m.lock.RUnlock()
if val, ok := m.m[k]; ok {
return val
}
return nil
}
// Maps the given key and value. Returns false
// if the key is already in the map and changes nothing.
func (m *MutexMap) Set(k interface{}, v interface{}) bool {
m.lock.Lock()
defer m.lock.Unlock()
if val, ok := m.m[k]; !ok {
m.m[k] = v
} else if val != v {
m.m[k] = v
} else {
return false
}
return true
}
// Returns true if k is exist in the map.
func (m *MutexMap) Check(k interface{}) bool {
m.lock.RLock()
defer m.lock.RUnlock()
if _, ok := m.m[k]; !ok {
return false
}
return true
}
func (m *MutexMap) Keys(ignoreNil bool, keys ...interface{}) []interface{}{
m.lock.RLock()
defer m.lock.RUnlock()
vals := []interface{}{}
for _,k := range keys {
if v,ok := m.m[k]; ok {
vals = append(vals, v)
}else{
if !ignoreNil {
vals = append(vals, nil)
}
}
}
return vals
}
func (m *MutexMap) Delete(k interface{}) {
m.lock.Lock()
defer m.lock.Unlock()
delete(m.m, k)
}
基本的坑就是这些了,上线部署当然是jenkins+salt+rsync:


最后,谈下,维护性、调试性。
首先维护性:目前只遇到几次go会异常崩溃的情况,一般都是不细心或并发安全没做好,这个根据日志、race tool、strace/gdb可以搞定。
另外,调试性的话,介于php, cpp之间,和java类似,一般能检查出问题,并打出日志,包括数组下标越界等,另外 还有pprof/strace/gdb等神器能用上,还是不错的。
哈哈,今天就写这么多了, 要哄妹子了-----------我闺女。
:P
基于Go的websocket消息服务的更多相关文章
- Go语言【项目】 websocket消息服务
websocket消息服务 目的:搭建websocket服务,用浏览器与服务进行消息交互(写的第一个Go程序) 代码目录结构: 前端html页面: <!DOCTYPE html> < ...
- Spring Boot 集成 WebSocket 实现服务端推送消息到客户端
假设有这样一个场景:服务端的资源经常在更新,客户端需要尽量及时地了解到这些更新发生后展示给用户,如果是 HTTP 1.1,通常会开启 ajax 请求询问服务端是否有更新,通过定时器反复轮询服务端响应的 ...
- 拾人牙慧篇之——基于HTML5中websocket来实现消息推送功能
一.写在前面 要求做一个,后台发布信息,前台能即时得到通知的消息推送功能.网上搜了也有很多方式,ajax的定时询问,Comet方式,Server-Sent方式,以及websocket.表示除了定时询问 ...
- .NET Core微服务之基于EasyNetQ使用RabbitMQ消息队列
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.消息队列与RabbitMQ 1.1 消息队列 “消息”是在两台计算机间传送的数据单位.消息可以非常简单,例如只包含文本字符串:也可以更 ...
- 滴滴出行基于RocketMQ构建企业级消息队列服务的实践
小结: 1. https://mp.weixin.qq.com/s/v6NM3UgX-qTI7yO1QPCJrw 滴滴出行基于RocketMQ构建企业级消息队列服务的实践 原创: 江海挺 阿里巴巴中间 ...
- 模拟websocket推送消息服务mock工具二
模拟websocket推送消息服务mock工具二 在上一篇博文中有提到<使用electron开发一个h5的客户端应用创建http服务模拟后端接口mock>使用electron创建一个模拟后 ...
- spring集成webSocket实现服务端向前端推送消息
原文:https://blog.csdn.net/ya_nuo/article/details/79612158 spring集成webSocket实现服务端向前端推送消息 1.前端连接webso ...
- .net平台 基于 XMPP协议的即时消息服务端简单实现
.net平台 基于 XMPP协议的即时消息服务端简单实现 昨天抽空学习了一下XMPP,在网上找了好久,中文的资料太少了所以做这个简单的例子,今天才完成.公司也正在准备开发基于XMPP协议的即时通讯工具 ...
- 搭建websocket消息推送服务,必须要考虑的几个问题
近年,不论是正在快速增长的直播,远程教育以及IM聊天场景,还是在常规企业级系统中用到的系统提醒,对websocket的需求越来越大,对websocket的要求也越来越高.从早期对websocket的应 ...
随机推荐
- Android服务器——使用TomCat实现软件的版本检测,升级,以及下载更新进度!
Android服务器--使用TomCat实现软件的版本检测,升级,以及下载更新进度! 算下来,TomCat服务器已经写了很长一段时间了,一直说拿他来搞点事 情,也一直没做,今天刚好有空,交流群还有人请 ...
- 机房收费系统之导出Excel
刚开始接触机房收费的时候,连上数据库,配置ODBC,登陆进去,那窗体叫一个多,不由地有种害怕的感觉,但是有人说,每天努力一点点,就会进步一点点,不会的就会少一点点,会的就会多一点点.. ...
- HBase replication使用
hbase-0.90.0的一个重要改进是引入了replication机制,使它的数据完整性得到了进一步的保障.虽然这一功能还不太完善,但是今后必然会变得更加重要. hbase的replication机 ...
- 如何将windows格式的图标作为os x应用程序的图标
刚由windows转为os x的同学可能知道,在os x下我们想改变一个app的图标异常简单,直接打开该app的简介,然后将图标文件拖入简介窗口左上角图标方框即可.但是很多童鞋下载了一些图标文件后发现 ...
- Django之跨域请求
同源策略 首先基于安全的原因,浏览器是存在同源策略这个机制的,同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性. 而如果我们要跳过这个策略,也就是说非要跨域请求,那么就需要通过J ...
- 用 Javascript 实现的“Dual listbox”(双向选择器)
这是我用 Javascript 制作的"Dual listbox"(双向选择器)的一个应用示例,是从我的代码中抠出来的.在网页编程中经常会用到. 也许我的实现太烦琐了,希望大家有更 ...
- 使用swagger管理接口
swagger 配置 1.pom 增加jar包依赖 <dependency> <groupId>io.springfox</groupId> <artifac ...
- JavaScript中对象数组,如何给对象添加一个新属性
var a =[{name: 'Tom',age:20},{name: 'Tom2',age:22}] 现在给a数组中的第一个对象添加性别属性 a[0]['gender']='women' a[0][ ...
- Day4_装饰器
装饰器: #模板def auth(func): def wrapper(*args,**kwargs): res=func(*args,**kwargs) return res return wrap ...
- Linux服务器安全审计工具与流程完全指南
http://Linux.chinaitlab.com/server/860516.html 当今许多linux服务器都不是刚刚部署完毕的新机器,有专业的Linux系统管理员进行定期维护,IT技术人员 ...