文档章节

golang实践-异步系统的无锁

我是大班生
 我是大班生
发布于 2017/06/11 18:09
字数 1708
阅读 44
收藏 0
点赞 0
评论 0

背景

这段时间,重构了一些服务的基础工具库,主要是解耦pub-sub改为异步系统[eventbus],简单调整了定时器[clock]。本来以为已经大幅简化了业务没问题了,结果5月份,其中一个服务因为广播事件,导致死锁。分析后,发现是一个非常基础的问题导致,值得捋一捋。

问题原因大致是这样:

有一个服务对象,通过RPC,对外提供多个公共服务,并可以反向推送消息给客户端。其中,

  1. 有多个rpc方法,被客户端调用,有的方法需要数据保护,调用了锁。
  2. 该服务能收到服务端的网络断开命令,会调用名为ServerNetReset方法能够随时重置网络代理这个私有属性,也调用了锁。
  3. 该服务对象订阅了一些事件,一旦触发就会向客户端主动调用Notify方法来推送消息。为了避免该操作时,网络代理被ServerNetReset方法清空,因此Notify调用的私有方法push也调用了锁。

结果某一次业务重构,有一个方法Foo在使用过程中某种情况下调用了Notify推送消息,同时因为Foo有对象私有数据维护,直接使用了锁。于是,就出现了死锁状态。

相信类似于这种问题的场景,还会很多,只是我们没有发现。

最终,我们改进方案是:网络重置、消息推送这两种操作,通过消息方式串行执行,不再用锁。

使用场景

学习go的时候,很多资料都提到:“多用通道(chan),少用锁”。对于长期习惯同步编程,方法之间直接调用,对其中的理解并不深入,很多人更多把chan作为信号传递。因为异步调用涉及到事件定义、订阅发布系统、延时返回,远远没有直接调用方法来的简便。因此,一个上万行代码的项目,会使用大量的锁来保护对象属性。

如果要采用通道,不用锁,就不得不在“开发效率”、“运行效率”、“资源占用”这三个方面权衡。简单来看:

基本工具库对象,单向引用,建议用锁。

通过锁进行对象内部属性的保护,同步直接调用对象提供的公共方法,是运行效率最高的。如果该对象运行示例不需要与其他实例“相互关联”,而仅仅是被引用,则用锁是完全没问题,也是最简单的。

比如,我们做一个支持并发的计数器,不存在对第三方对象的引用,这时候,只需要用锁即可。比如:


//Counter counter is a multi-thread safe counters
type Counter struct {
	mut     sync.Mutex
	currNum int64 //当前数量
	maxNum  int64 //最大数量
}

//AddOne 在原内部计数基础上,+1。
func (c *Counter) AddOne() int {

	new := atomic.AddInt64(&c.currNum, 1)
	c.mut.Lock()

	if c.maxNum < new {
		c.maxNum = new
	}
	c.mut.Unlock()

	return int(c.currNum)
}

//DecOne 在原内部计数基础上,-1。
func (c *Counter) DecOne() int {

	return int(atomic.AddInt64(&c.currNum, -1))

}

//Current 获取当前内部计数结果。
func (c *Counter) Current() int {
	return int(atomic.LoadInt64(&c.currNum))
}

//MaxNum 计数器生存周期内,最大的计数。
func (c *Counter) MaxNum() int {
	return int(atomic.LoadInt64(&c.maxNum))
}

//NewCounter counter constructor
func NewCounter() *Counter {
	return &Counter{}
}

业务对象,尤其是DDD中提到的聚集之间,优先考虑用消息框架解耦。

由于对象间引用非常复杂,最容易理解的就是魔兽世界的战斗场景:双方多个玩家相互配合,不断施展攻击、辅助技能,过程中有的英雄使用了道具,有的被攻击导致死亡等。如果采用同步调用,对象A调用对象B,B调用C,C执行完成后,某条件下需要再告知A,那就非常复杂。这时候,我们考虑到的是ECS框架,基本的就是pub-sub系统支持。

对于pubsub的使用,不同系统接口略有不同,大家也比较熟悉,这里就不举例。

在复杂的业务对象建议用异步消息

如果一个实例存在多个公共方法+私有方法,类似于前面问题背景描述的那样,既有外部UI带来的命令驱动,又有内部的消息框架驱动。考虑到并发,不得不引入锁的时候,则建议采用串行异步方式。所有业务方法不对外,对象只有创建、接受消息、销毁三个对外的公共方法。所有消息只有一个入口,这样,就可以不用锁了。代码结构非常简单:

type Message1 struct {
}
type Message2 struct {
}
type A struct {
	close  int32            //对象是否关闭的标志
	msgbuf chan interface{} //消息缓冲

}

func NewA() *A {
	a := &A{
		msgbuf: make(chan interface{}, 10),
	}
	go a.receive()
	return a
}
func (a *A) Post(message interface{}) {
	if atomic.LoadInt32(&a.close) == 1 {
		a.msgbuf <- message
	}
}
func (a *A) receive() {
	//通过defer实现简单的故障隔离
	defer func() {
		if err := recover(); err != nil {
			log.Println(err)
		}
	}()
	//执行消息处理
	for message := range a.msgbuf {
		switch msg := message.(type) {
		case Message1:
			a.foo1(msg)
		case Message2:
			a.foo2(msg)
		}
	}
}

func (a *A) foo1(message Message1) {

}
func (a *A) foo2(message Message2) {
}
func (a *A) Close() {
	if atomic.CompareAndSwapInt32(&a.close, 0, 1) {
		//	do other thing
	}
}

//...

特别强调,即使作为Consumer,在pub-sub系统中订阅事件,也只传递Post作为事件响应的函数句柄,这样即使复杂系统也不会出现因为多方法执行操作内部属性,需要加锁保护,而带来的负面问题。

当然,异步消息对象的使用需要基本功,并且有额外的工作:

  • 清楚架构。比如rpc服务的对象是一个,但其中被调用的方法是并行。原因是 rpc/server.go Line481 用了协程:
	go service.call(server, sending, mtype, req, argv, replyv, codec)
  • 前期构架不如同步调用直观,构建缓慢,效益要在维护时才能体现。
  • 有的消息回复会很麻烦,需要Future、Promise、Callback这些模式,而go标准库没有原生支持。

对于Future、Promise、Callback模式,其实很简单,本质上就是异步框架提供的,用于业务对象间的值对象。go原生的waitgroup、cond、chan已经提供了很好的原生支持,自己很容易扩展。

此外,从理论到实践长达40年的actor模型也非常不错,相对于scala、java等,go的内存优势非常明显,也非常不错。因为这两部分涉及到更多设计模式的使用,内容不少,有时间另开帖说说。

© 著作权归作者所有

共有 人打赏支持
我是大班生
粉丝 0
博文 2
码字总数 1777
作品 0
渝北
架构师
PHP的异步并行网络扩展Swoole已发布1.7.5版本

PHP的异步并行网络扩展Swoole今天发布了最新的1.7.5版本。 项目主页:http://www.swoole.com/ 文档页面:http://wiki.swoole.com/ 源代码:https://github.com/swoole/swoole-src 1.7.4版本:...

matyhtf ⋅ 2014/09/10 ⋅ 31

Zan-Group/zanphp

基于 PHP 协程的网络服务框架,提供最简单的方式开发面向 C10K+ 的高并发SOA服务和RPC服务。 每天为2,000+个服务提供300,000,000+次访问量支持,广泛应用于有赞各项业务。 核心特性 基于 实现...

Zan-Group ⋅ 2017/06/21 ⋅ 0

基于 PHP 协程的网络服务框架--Zan PHP Framework

Zan PHP Framework 是有赞开源的基于 PHP 协程的网络服务框架,提供最简单的方式开发面向 C10K+ 的高并发SOA服务和RPC服务。 该项目每天为2,000+个服务提供300,000,000+次访问量支持,广泛应...

匿名 ⋅ 2017/06/21 ⋅ 6

Stackful 协程库--libgo

libgo 是一个使用 C++ 编写的协作式调度的stackful协程库, 同时也是一个强大的并行编程库。 设计之初是为高并发分布式Linux服务端程序开发提供底层框架支持,可以让链接进程序的同步的第三方...

Li_Mr ⋅ 2016/01/25 ⋅ 6

zan 正式开源,异步+非阻塞的PHP框架

Zan PHP Framework Zan PHP是基于PHP协程的网络服务框架,提供最简单的方式开发面向C10K+的高并发HTTP服务或SOA服务。 核心特效 基于 yield 实现了独立堆栈的协程 类似于 Golang 的并发编程模...

demon666 ⋅ 2016/07/27 ⋅ 28

阿弟@postgresql/pgclusteradmin

pgclusteradmin Pgclusteradmin是一款基于Go开发的PostgreSQL集群管理工具,当前主要功能有“节点资料集中管理”、“运行参数在线配置,参数文件多版本管理,参数文件模板管理”、“服务管理...

阿弟@postgresql ⋅ 2017/03/10 ⋅ 0

PHP的异步并行扩展Swoole发布1.7版本

Swoole 1.7.0 发布了,该版本主要改进内容包括: reactor线程与writer线程合并 对send优化,加入out_buffer机制 增加AIO异步读写文件的API 增加DNS异步查询函数 swooleclient在php-fpm或apa...

matyhtf ⋅ 2014/04/17 ⋅ 39

年终盘点!2017年超有价值的Golang文章

年终盘点!2017年超有价值的Golang文章 鸟窝2017-12-281 阅读 Go 马上就要进入2018年了,作为年终的盘点,本文列出了一些2017年的关于Go编程的一些文章,并加上简短的介绍。 文章排名部分先后...

鸟窝 ⋅ 2017/12/28 ⋅ 0

Gopher China 上海大会上的PPT

Gopher China 上海大会上的PPT gofmt 的文化演变 by Robert Griesemer@Google ,Go语言作者之一 http://airjd.com/view/i93yszn60004azt Go语言游戏项目应用情况汇报 by 达达@真有趣-神仙道 ...

Kris_zcl ⋅ 2015/05/04 ⋅ 1

【掘金小报】第十期 带你空手撸一个 ofo 微信小程序

掘金日报主打分享优质深度技术内容,技术内容分:前端、后端、Android、iOS、产品设计、工具资源和一些有趣的视频。 前端 给 ofo 共享单车撸一个微信小程序 想学一下微信小程序,发现文档这东...

膜法小编 ⋅ 2017/05/09 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Netweaver和SAP云平台的quota管理

Netweaver 以需要为一个用户上下文(User Context)能够在SAP extended memory区域中分配内存尺寸创建quota为例。 对于Dialog工作进程,使用事务码修改参数 ztta/roll_extension_dia. 对于非D...

JerryWang_SAP ⋅ 28分钟前 ⋅ 0

IDEA提示编码速度

焦点移动 将焦点冲代码编辑窗口移动到菜单栏:Alt+菜单栏带下划线字母 将焦点从工具窗口移动到代码编辑窗口 Esc或Shift+Esc 将焦点从代码编辑移动到最近使用的工具窗口 F12 模板提示 Ctrl+J...

bithup ⋅ 37分钟前 ⋅ 0

180623-SpringBoot之logback配置文件

SpringBoot配置logback 项目的日志配置属于比较常见的case了,之前接触和使用的都是Spring结合xml的方式,引入几个依赖,然后写个 logback.xml 配置文件即可,那么在SpringBoot中可以怎么做?...

小灰灰Blog ⋅ 今天 ⋅ 0

冒泡排序

原理:比较两个相邻的元素,将值大的元素交换至右端。 思路:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第...

人觉非常君 ⋅ 今天 ⋅ 0

Vagrant setup

安装软件 brew cask install virtualboxbrew cask install vagrant 创建project mkdir -p mst/vmcd mst/vmvagrant init hashicorp/precise64vagrant up hashicorp/precise64是一个box......

遥借东风 ⋅ 今天 ⋅ 0

python3.6 安装pyhook_3

我的是在win下的,忙了半天老是安装不了, pip install 也不行。 那么可以看出自己的版本是32bit 一脸懵逼 没办法 只好下载32版本的来安装 我一直以为 是 对应32 位的 。 下面是 小例子 http...

之渊 ⋅ 今天 ⋅ 0

004、location正则表达式

1、location的作用 location指令的作用是根据用户请求的URI来执行不同的应用,也就是根据用户请求的网站URL进行匹配,匹配成功即进行相关的操作。 2、location的语法 = 开头表示精确匹配 ^~...

北岩 ⋅ 今天 ⋅ 0

CentOS7 静默安装 Oracle 12c

环境 CentOS7.5 最小安装 数据库软件 linuxx64_12201_database.zip 操作系统配置 关闭 SELinux sed -i '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config 关闭防火墙 systemctl disable ......

Colben ⋅ 今天 ⋅ 0

Yii2中findAll()的正确使用姿势/返回为空的处理办法

从一次错误的操作开始 $buildingObject = Building::findAll("status=1"); 1 这个调用看着没有任何毛病,但是在使用时返回的结果却是一个空数组。再回过头来看看数据表中: 按照套路来讲,查...

dragon_tech ⋅ 今天 ⋅ 0

如何优雅的编程——C语言界面的一点小建议

我们鼓励在编程时应有清晰的哲学思维,而不是给予硬性规则。我并不希望你们能认可所有的东西,因为它们只是观点,观点会随着时间的变化而变化。可是,如果不是直到现在把它们写在纸上,长久以...

柳猫 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部