当前位置: 首页 > news >正文

基于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

 

 

 

  

 

 

  

  

 

谋胆并重

相关文章:

  • [52PJ] Java面向对象笔记(转自52 1510988116)
  • 团队项目第二阶段个人进展——Day6
  • 转载:[译] 内容加速黑科技趣谈
  • Hbase中checkAndPut操作
  • Cognos11第三方权限认证之OpenDJ
  • Django级联删除的选项
  • QQ浏览器x5内核的兼容性问题
  • 部分保守派呼吁抵制 Mozilla
  • Java(第十一章 )
  • linux经常使用命令
  • 基于maven+dubbo+spring+zookeeper的简单项目搭建
  • C++组合通信
  • 阿里数据库内核月报:2017年02月
  • Facebook 首席安全官建议对 Flash 设定死亡日期
  • 项目期复习:JS操作符,弹窗与调试,凝视,数据类型转换
  • @jsonView过滤属性
  • [原]深入对比数据科学工具箱:Python和R 非结构化数据的结构化
  • 3.7、@ResponseBody 和 @RestController
  • C++类的相互关联
  • iOS 颜色设置看我就够了
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • Python利用正则抓取网页内容保存到本地
  • 分享几个不错的工具
  • 浅谈Golang中select的用法
  • 入门级的git使用指北
  • 使用docker-compose进行多节点部署
  • 原生 js 实现移动端 Touch 滑动反弹
  • ​如何在iOS手机上查看应用日志
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • $().each和$.each的区别
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (4)(4.6) Triducer
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (区间dp) (经典例题) 石子合并
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (算法二)滑动窗口
  • (算法设计与分析)第一章算法概述-习题
  • (一)Neo4j下载安装以及初次使用
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (一)SpringBoot3---尚硅谷总结
  • (正则)提取页面里的img标签
  • (转)Linux整合apache和tomcat构建Web服务器
  • (转)菜鸟学数据库(三)——存储过程
  • .Net FrameWork总结
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .NET 读取 JSON格式的数据
  • .Net 垃圾回收机制原理(二)
  • .NET应用架构设计:原则、模式与实践 目录预览
  • @WebServiceClient注解,wsdlLocation 可配置
  • [ CTF ]【天格】战队WriteUp- 2022年第三届“网鼎杯”网络安全大赛(青龙组)
  • [2008][note]腔内级联拉曼发射的,二极管泵浦多频调Q laser——
  • [20180129]bash显示path环境变量.txt
  • [AI]ChatGPT4 与 ChatGPT3.5 区别有多大