访问控制

  1. package 内没有任何访问控制
  2. package 间的访问控制只由大小写区分

问题1我们可以通过合理拆分 package,来避免代码维护变得越来越困难,为了避免循环依赖,这里有一些实践

问题2则更难处理一些,因此 Go语言的reflect这类基础设施也受此影响,有时候你为了对象能够序列化或者 DeepCopy,就必须将其字段大写,也就对所有的package都暴露了实现。也就是说,Go 语言的基础设施(如reflect)也受此访问控制的限制(reflect本质上也是个package)。

阅读全文 »

现状&契机&目标

DevOps 是分工细化下的产物,用于提高开发和运维的协作效率,实现快速交付。这里主要从开发的角度谈谈游戏服务器中的DevOps,以及我们最近做的一些尝试。

1. 现状

我们现在是不同的项目使用不同的语言和框架,上线前,运维了解熟悉各个项目组的配置方案,部署流程,数据管理等,同时开发也需要熟悉运维的检测工具,接入监控报警机制。这通常需要一周甚至更多的时间,并且线上部署和本地部署可能是两套完全不同的流程,由于项目组相关性太强,运维通常也是专职维护指定项目,效率很低。上线后,如果出了BUG 需要紧急修复,通常是开发人员直接ssh到正式环境的主机上,进行日志查看,状态调试,甚至代码修改,对操作人员的要求比较高并且风险大,没有充分发挥运维的作用。

阅读全文 »

本文聊聊游戏服务器中常见的通信模型和对比,讨论下常见的实现方案,最后分享下我们当前的实践。

常见的交互语义

在实践中,节点交互中常用的点对点通信方式,对应用层而言,可以大概分为以下几种:

  • 同步RPC
  • 同步请求
  • 异步消息
  • 异步请求
  • 发布订阅

在本文中,我将围绕一个简单的例子,来聊聊对这几种交互语义的个人理解。

该例子为: 作为client端的A需要执行(x+y)*z操作,但其中的x+y,是由server端B实现的,A需要请求B得到x+y的结果,再将其*z完成业务逻辑。

同步RPC

RPC库通常有现成的轮子,比如gRPC、thrift等,以golang gRPC为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// A 线程
func handle (x,y,z int) int {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req := &AddReq{X: x, Y: y}
ack, err := grpcClient.Add(ctx, req)
return ack.Result * z
}

// B 线程
func (b *B) Add(ctx context.Context, req *AddReq) (*AddAck, error) {
return &AddAck{Result: req.X+req.Y}, nil
}

代码简洁明了,远程请求和本地函数调用一样方便,这种写法应该是开发者最喜欢的方式。诸如超时,错误传递,甚至负载均衡等,gRPC都已经处理好了。同步RPC的缺点在后面讨论同步异步以及具体RPC框架的时候会讨论。

阅读全文 »

简单聊聊Web前端(主要是JS)中的几种异步编程机制和范式,由于JS是单线程的(事实上,几乎所有的前端或GUI框架都是单线程的,如Unity,WPF等),因此要提高效率,要么新建线程(如Web Worker),要么就只能异步。由于UI框架的大部分数据都不是线程安全的,如JS中的DOM对象便不支持并发访问,因此新建线程能分担的事情比较有限(如CPU密集运算或IO),因此单线程异步编程模型成为了JS中的核心编程模型。下面来聊聊JS中异步编程模型演进史。

Callback

Callback 在 JS 中无处不在,Ajax,XMLHttpRequest 等很多前端技术都围绕回调展开,比如创建一个 Button: <button onclick="myFunction()">Click me</button>。回调的优点是简单易于理解和实现,其最大的缺点是调层数过深时,代码会变得非常难维护(所谓回调地狱,Callback Hell):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 为了便于测试,通过setTimeout模拟  fs.readFile(file, cb) 读取文件操作
function MyReadFile(file, cb) {
return setTimeout(()=>{
cb(null, "filecontent: "+file) // 将err设为null,模拟读取到文件内容为 filecontent + filename
}, 100) // 模拟读取文件需要100ms
}

MyReadFile("abc.txt", (err1, filedata1) => {
console.log(filedata1);
MyReadFile("xyz.txt", (err2, filedata2) => {
console.log(filedata2)
// MyReadFile( ... )
})
})

使用Callback需要注意的一个问题是闭包引用可变上下文的问题: 当执行异步回调(闭包)时,闭包引用的外部局部变量可能已经失效了(典型地,比如对应的对象在容器中已经被删除了),此时闭包会读写无效的数据,产生非预期的结果,且较难调试:

阅读全文 »

Go的package和goroutine,前者组织Go程序的静态结构,后者形成Go程序的动态结构,这里谈谈对这两者的一些理解和实践。

一. package 管理

1. package 布局

分包的目的是划分系统,将系统划分为一个个更小的模块,更易于理解,测试和维护。如何组织Go项目的包结构,是大多数Go程序员都遇到过的问题,各个开源项目的实践可能也并不相同。以下是几种常见分包方案。

单一Package

适用于小型应用程序,无需考虑循环依赖等问题。但在Go中,同一个Package下的类和变量是没有隐私可言的,C++/Java可以在同一个文件中通过Class实现访问控制,但是Go不可以。因此随着项目代码规模增长(超过10K SLOC,代码维护和隔离将变得非常困难。

阅读全文 »

如果给你一个服务器框架,你如何评估这个框架?不同的人可能有不同的维度和优先级,比如性能,可维护性,可扩展性,可用性,可靠性等等等等,目前已经有相对成熟的几种软件质量评估方案。其中McCall软件评估模型(1977)比较有意思:

产品修正: 开发视角,反映系统应对变更的能力
产品运行: 用户视角,产品稳定性,效率,易用性等
产品转移: 运维视角,可移植性,组件复用性等

阅读全文 »

现在的游戏服务器通常使用NoSQL作为DB以满足Model设计上的灵活性,特别是即将到来的MongoDB 4.0将提供多文档事务支持,这意味着,SQL转向NoSQL的最后障碍已经被消除。

理想的MongoDB使用场景是将单个对象映射为单个文档,比如玩家数据,公会数据等,但一方面MongoDB对单文档大小硬限制为16M,另一方面,我们需要根据对象更新频度,固有特性等进一步优化,提高DB性能。这里简单谈谈那些GS对象落地的方方面面。

阅读全文 »

Hash算法

Hash算法本质是将一个值域(也称定义域,通常更大)映射到另一个值域(通常更小),比如SHA-2,MD5等。Hash算法有一些共有特性,比如确定性,不可逆性。Hash算法被广泛应用于加密,Hash表,文件校验等领域。

分布式系统中常用Hash算法来进行任务分配,比如我们要设计一个分布式存储系统,通过Hash算法能够有序均匀地将N个任务分配到M个节点(Hash槽)上:

阅读全文 »

React实际上只是View层的一套解决方案,它将View层组件化,并约定组件如何交互,数据如何在组件内流通等,但实际的Web App除了View层外,还包括Model层,界面响应,服务器请求等,Flux则是Facebook为此给出一套非常简洁的方案,用于管理Web应用程序数据流。与其说Flux是一套框架,不如说其是一套设计模式,因为其核心代码只有几百行,它主要表述的是一种Web应用设计理念和模式。

阅读全文 »

预备知识

ES6: Javascript 的新标准,主要包括引入class,箭头函数,let, const 新关键字等。

JSX: JSX 是JavaScript 语法扩展,让在 js 中写HTML像模板语言一样方便,最终会编译为js。

React 特性

1. 组件

React的核心思想便是将UI切分成一些的独立的、可复用的组件,这样你就只需专注于构建每一个单独的部件,达到非常灵活的组件级别的解耦和复用。

阅读全文 »