在之前的博客中几次简单提及过给GS做测试,关于测试的必要性不用再多说,但在实际实践过程中,却往往会因为如下原因导致想要推进测试规范困难重重:

-. Q1: 写测试代码困难: 代码耦合重,各种相互依赖,全局依赖,导致写测试代码”牵一发而动全身”,举步维艰
-. Q2: 测试时效性低: 需求变更快,数值变更频繁,可能导致今天写好的测试代码,明天就”过时”了
-. Q3: 开发进度紧: 不想浪费过多时间来写测试代码,直接开发感觉开发效率更高

要想推进测试规范,上面的三个问题是必须解决的。这里简单聊聊我们在Golang游戏后端中的测试实践和解决方案。我们在GS中尝试的测试方案主要分为四种: 单元测试,集成测试,压力测试,以及模拟测试。

阅读全文 »

用XMind Zen 2020画了一张思维导图玩,主要是关于编程语言学习的,XMind导出的Markdown不包含链接,不知道是不是试用版的原因,由于对应链接在博客里面都找得到。就没有再做一份Markdown了。

之前被同事安利了很多次Rust,周末没事去Rust官方文档学习了下,记录一些对Rust语言粗浅理解。

一. 所有权系统

要说Rust最有别于其它语言的特性,应该就是它的所有权系统了。要谈所有权系统,从GC谈起是个不错的切入点,我们众所周知的程序语言GC只要包含两种: 手动GC和自动GC,它们各有利弊,总的来说是运行时效率和开发效率之前的权衡取舍,由于现代硬件设施发展速度很快,运行时效率越来越不是问题,因此自动GC逐渐成为新语言的标配。而Rust的GC,按照我的理解,可以将其看做半自动GC,即开发者在代码中通过所有权约束来明确变量的生命周期,这样Rust在编译器就已经知道内存应该何时释放,也就不需要运行时通过复杂的GC算法去解析变量的引用关系,对运行时几乎零负担,这也是Rust敢号称系统级编程语言,运行时效率叫板C/C++的底气来源。

阅读全文 »

一. Go GC 要点

先来回顾一下GC的几个重要的阶段:

Mark Prepare - STW

做标记阶段的准备工作,需要停止所有正在运行的goroutine(即STW),标记根对象,启用内存屏障,内存屏障有点像内存读写钩子,它用于在后续并发标记的过程中,维护三色标记的完备性(三色不变性),这个过程通常很快,大概在10-30微秒。

Marking - Concurrent

标记阶段会将大概25%(gcBackgroundUtilization)的P用于标记对象,逐个扫描所有G的堆栈,执行三色标记,在这个过程中,所有新分配的对象都是黑色,被扫描的G会被暂停,扫描完成后恢复,这部分工作叫后台标记(gcBgMarkWorker)。这会降低系统大概25%的吞吐量,比如MAXPROCS=6,那么GC P期望使用率为6*0.25=1.5,这150%P会通过专职(Dedicated)/兼职(Fractional)/懒散(Idle)三种工作模式的Worker共同来完成。

阅读全文 »

最近做的优化比较多,整理下和Go内存相关的一些东西。

一. 不要过早优化

虽是老生常谈,但确实需要在做性能优化的时候铭记在心,说说我的理解:

  1. first make it work, then measure, then optimize
  2. 二八原则
  3. 需求变更快
  4. 对性能的主观直觉不靠谱
阅读全文 »

简单谈谈我们最近是如何给GS做压测的。

1. 压测机器人

压测机器人需要满足如下几个条件:

  1. 异步请求: 异步才能模拟真实的客户端请求和压力
  2. 数据同步: 像客户端一样缓存和处理服务器响应数据,这样才能做好有效请求和可重入
  3. 可重入: 机器人应该可以在任何时候关闭/重启,而不应该假设初始状态(比如只有注册的时候能跑)
  4. 随机性: 机器人行为尽可能随机分布,并且每次重启重新初始化随机种子
阅读全文 »

这段时间学习OOP对语言和编程范式有一些新的理解,之前系统整理过函数式编程,因此先从OOP谈起。我们先回顾下面向对象(OOP)的核心思想:

  1. 将数据及其相关操作(方法)封装起来,以对象的方式暴露出来,对象与对象之间通过方法调用(或者说是发消息)进行通信。
  2. 对象可以有自己的私有字段,只有对象的方法可以访问这些字段。
  3. 每个对象都是一个类(Class)的实例,类定义了对象的行为(内部数据和方法实现)。

与函数式的”一切皆函数”一样,OOP也有一个宏大的目标”一切皆对象”。

阅读全文 »

本文主要谈谈CPU Cache的设计,内存屏障的原理和用法,最后简单聊聊内存一致性。

我们都知道存储器是分层级的,从CPU寄存器到硬盘,越靠近CPU的存储器越小越快,离CPU越远的存储器越大但越慢,即所谓存储器层级(Memory Hierarchy)。以下是计算机内各种存储器的容量和访问速度的典型值。

存储器类型 容量 特性 速度
CPU寄存器 几十到几百Bytes 数据电路触发器,断电丢失数据 一纳秒甚至更低
Cache 分不同层级,几十KB到几MB SRAM,断电丢失数据 几纳秒到几十纳秒
内存 几百M到几十G DRAM,断电丢失数据 几百纳秒
固态硬盘(SDD) 几十到几百G SSD,断电不丢失数据 几十微秒
机械硬盘(HDD) 上百G 磁性介质和磁头,断电不丢失数据 几毫秒

从广义的概念上来说,所有的存储器都是其下一级存储器的Cache,CPU Cache缓存的是内存数据,内存缓存的是硬盘数据,而硬盘缓存的则是网络中的数据。本文只谈CPU Cache,一个简单的CPU Cache示意图如下:

图中忽略了一些细节,现代的CPU Cache通常分为三层,分别叫L1,L2,L3 Cache, 其中L1,L2 Cache为每个CPU核特有,L3为所有CPU核共有,L1还分为缓存指令的i-cache(只读)和缓存程序数据的d-cache,L2 L3 Cache则不区分指令和程序数据,称为统一缓存(unified cache)。本文主要讨论缓存命中和缓存一致性的问题,因此我们只关注L1 Cache,不区分指令缓存和程序数据缓存。

阅读全文 »

Perf(Performance Event)是Linux 2.6.31后内置的性能分析工具,它相较其它Prof工具最大的优势在于与Linux Kernel紧密结合,可以进行内核甚至硬件级的性能分析。我之前只零散地用一些ptrace,strace之类的小工具,与Perf比起来,确实小巫见大巫。也赶紧花了点时间简单了解和试用一下,添加到工具箱,以备不时之需。

阅读全文 »

游戏服务器难吗?不就是处理和保存一些玩家数据么?本文谈谈我对这个问题的一些理解,或者是说,如果说游戏服务器开发很难,那么它究竟难在哪里?

一. 状态性

游戏服务器是后端,做后端的,每天耳濡目染横向扩展,自动伸缩等炫酷的特性,要说放在以前,这些特性还是巨头的”专利”,我们想要自己实现这些东西挑战性是比较大的,但近几年有了容器生态如k8s的加持,只要你实现了一个无状态应用,你几乎马上就可以得到一个可伸缩的集群,享受无状态本身带来的各种好处,机器挂了自动重启,性能不够了就自动扩展等等。而作为一名游戏服务器开发者,自然也想充分享受容器时代的红利,所以我们来捋捋无状态游戏服务器的可行性。

阅读全文 »