基于Go的websocket消息服务

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

  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

 

 

 

  

 

 

  

  

 

谋胆并重
相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
|
2月前
|
运维 网络协议 安全
长连接网关技术专题(十):百度基于Go的千万级统一长连接服务架构实践
本文将介绍百度基于golang实现的统一长连接服务,从统一长连接功能实现和性能优化等角度,描述了其在设计、开发和维护过程中面临的问题和挑战,并重点介绍了解决相关问题和挑战的方案和实践经验。
86 1
|
2月前
|
负载均衡 Java 中间件
使用Go语言构建高性能Web服务
Go语言作为一种快速、高效的编程语言,其在构建高性能Web服务方面具有独特优势。本文将探讨如何利用Go语言开发和优化Web服务,以实现更高的性能和可伸缩性。
|
5天前
|
网络协议 Java Go
【Go语言专栏】Go语言中的WebSocket实时通信应用
【4月更文挑战第30天】Go语言(Golang)是Google开发的编程语言,适用于云计算、微服务等领域。本文介绍了WebSocket,一种实现浏览器与服务器全双工通信的协议,其特点是实时性、全双工和轻量级。在Go中实现WebSocket,可以使用gorilla/websocket库。示例展示了如何创建服务器端和客户端,实现消息的收发。WebSocket广泛应用于聊天、游戏、通知推送和实时数据同步等场景。学习Go语言中的WebSocket对于开发实时通信应用至关重要。
|
1天前
|
监控 算法 Go
Golang深入浅出之-Go语言中的服务熔断、降级与限流策略
【5月更文挑战第4天】本文探讨了分布式系统中保障稳定性的重要策略:服务熔断、降级和限流。服务熔断通过快速失败和暂停故障服务调用来保护系统;服务降级在压力大时提供有限功能以保持整体可用性;限流控制访问频率,防止过载。文中列举了常见问题、解决方案,并提供了Go语言实现示例。合理应用这些策略能增强系统韧性和可用性。
13 0
|
1天前
|
负载均衡 算法 Go
Golang深入浅出之-Go语言中的服务注册与发现机制
【5月更文挑战第4天】本文探讨了Go语言中服务注册与发现的关键原理和实践,包括服务注册、心跳机制、一致性问题和负载均衡策略。示例代码演示了使用Consul进行服务注册和客户端发现服务的实现。在实际应用中,需要解决心跳失效、注册信息一致性和服务负载均衡等问题,以确保微服务架构的稳定性和效率。
11 3
|
5天前
|
Go 微服务
4. 参考 go 代码——服务注册与发现
4. 参考 go 代码——服务注册与发现
|
5天前
|
存储 负载均衡 监控
【Go 语言专栏】构建高可靠性的 Go 语言服务架构
【4月更文挑战第30天】本文探讨了如何利用Go语言构建高可靠性的服务架构。Go语言凭借其高效、简洁和并发性能,在构建服务架构中备受青睐。关键要素包括负载均衡、容错机制、监控预警、数据存储和服务治理。文章详细阐述了实现这些要素的具体步骤,通过实际案例分析和应对挑战的策略,强调了Go语言在构建稳定服务中的作用,旨在为开发者提供指导。
|
5天前
|
缓存 监控 前端开发
【Go 语言专栏】Go 语言中的 WebSocket 与 Socket.IO 集成
【4月更文挑战第30天】本文介绍了在 Go 语言中集成 WebSocket 与 Socket.IO 的相关技术,WebSocket 是一种高效的双向通信协议,Socket.IO 是一个实时通信库,提供丰富的事件处理。集成两者能实现更强大的实时通信功能。文章讨论了 Go 中 WebSocket 的实现,Socket.IO 与 WebSocket 的关系,集成的意义及步骤,并提醒注意协议兼容性、消息格式等问题。此外,还提到了性能优化策略和应用案例,如实时聊天、数据监控和在线协作工具。通过集成,开发者可以构建出满足多样化需求的实时通信应用。
|
5天前
|
缓存 监控 前端开发
【Go 语言专栏】Go 语言中的 WebSocket 实时通信应用
【4月更文挑战第30天】本文探讨了Go语言在WebSocket实时通信中的应用。WebSocket作为全双工通信协议,允许持续的双向通信。Go语言凭借其高效和并发特性,适合构建实时应用。文中概述了在Go中实现WebSocket的基本步骤,包括服务器和客户端的建立与通信,并列举了实时聊天、数据监控和在线协作等应用案例。同时,强调了消息格式、并发处理、错误处理和安全性的注意事项。通过数据压缩、缓存管理和连接管理等策略可优化性能。Go语言还能与数据库和前端框架结合,提升用户体验。总之,Go语言为WebSocket实时通信提供了强大支持,有望在更多领域发挥作用。
|
5天前
|
缓存 监控 测试技术
【Go语言专栏】使用Go语言构建高性能Web服务
【4月更文挑战第30天】本文探讨了使用Go语言构建高性能Web服务的策略,包括Go语言在并发处理和内存管理上的优势、基本原则(如保持简单、缓存和并发控制)、标准库与第三方框架的选择、编写高效的HTTP处理器、数据库优化以及性能测试和监控。通过遵循最佳实践,开发者可以充分利用Go语言的特性,构建出高性能的Web服务。