聊聊GS引入MQ的一些实践
在目前这套项目架构诞生初期,基于当时的游戏类型和项目需求,架构做得相对简单,设计上尽可能通过goroutine而不是节点来并发,节点间用ETCD做服务发现,用gRPC做节点通信,在单向依赖,弱藕合的情况下,基本能够满足需求。对于个别强耦合的节点交互,使用gRPC Stream来建立双工连接。
随着游戏类型和业务需求的变更,跨服功能增多,节点划分越来越细,藕合越来越重,网络拓扑也越来越复杂。gRPC Stream不再能很好地胜任。
因此我们考虑用一套新的节点交互方案,大概有两个思路:
- 写一套完备的TCP网络库(包含服务发现,自动重连,编解码,心跳,流控等),用于统一节点间甚至Gateway与Client间的网络交互
- 使用MQ解耦集群内节点交互,将网状网络化为星形网络,简化网络拓扑
Go 泛型特性速览
2022.1.22 更新: 最近一年,Go泛型已经从草案,过渡到提案,并开始实现。Go1.18实现了初版泛型,最终方案相较之前的泛型草案,将类型列表约束(type list in constraint)进一步丰富完善为类型集约束(type sets of constraints)的概念,本文内容已随最新文档更新。
之前我在编程范式游记中介绍了OOP中的子类化(subtype,也叫子类型多态subtype polymorphism)和泛型(generics,也叫参数多态parametric polymorphism或类型参数type parameters),关于两者的区别和比较可以参考那篇文章,在其中我吐槽了Go目前对泛型支持的匮乏,Go泛型最初在Go 2中讨论,目前已经在Go1.18中正式实现,随着Go泛型设计和实现的细节也越来越清晰,我们从最新的Go泛型文档来了解下Go泛型设计上有哪些考量和取舍。
PS. 虽然Go官方文档仍然沿用社区惯用的”泛型(generics)”术语,由于主流语言都有自己不同的泛型支持,如Java基于编译期类型擦除的泛型(伪泛型),C++的图灵完备的模板机制(支持模板元编程的真泛型)等,为了避免概念混淆,将Go泛型理解为类型参数(type parameters)更精确,它不支持C++那样灵活的模板元编程,但比Java这种运行时擦除类型信息的补丁实现更优,另外,关于运算符泛型,Go也提出了一套新的解决方案。
1. 最简原型
先从最简单的泛型定义开始:
1 | // Define |
语法上和其它语言泛型大同小异,泛型的本质是将类型参数化,Go中用函数名后的[]
定义类型参数。以上声明对C++开发者来说非常亲切的(只是换了一种语法形式),实际上这在Go中是错误的泛型函数声明,因为它没有指明类型参数约束(constraints)。
GS 测试规范实践
在之前的博客中几次简单提及过给GS做测试,关于测试的必要性不用再多说,但在实际实践过程中,却往往会因为如下原因导致想要推进测试规范困难重重:
-. Q1: 写测试代码困难: 代码耦合重,各种相互依赖,全局依赖,导致写测试代码”牵一发而动全身”,举步维艰
-. Q2: 测试时效性低: 需求变更快,数值变更频繁,可能导致今天写好的测试代码,明天就”过时”了
-. Q3: 开发进度紧: 不想浪费过多时间来写测试代码,直接开发感觉开发效率更高
要想推进测试规范,上面的三个问题是必须解决的。这里简单聊聊我们在Golang游戏后端中的测试实践和解决方案。我们在GS中尝试的测试方案主要分为四种: 单元测试,集成测试,压力测试,以及模拟测试。
编程语言杂记
初识 Rust
之前被同事安利了很多次Rust,周末没事去Rust官方文档学习了下,记录一些对Rust语言粗浅理解。
一. 所有权系统
要说Rust语言的核心优势,应该就是运行效率+内存安全了,这两者都与其独树一帜的所有权系统有关。要谈所有权系统,GC是个不错的切入点,众所周知,编程语言GC主要包含两种: 手动GC和自动GC,它们各有利弊,总的来说是运行效率和内存安全之间的权衡取舍。而Rust则尝试两者兼顾,Rust的GC,我将其理解为半自动GC或编译期GC,即开发者配合编译器通过所有权约束来明确变量的生命周期,这样Rust在编译期就已经知道内存应该何时释放,不需要运行时通过复杂的GC算法去解析变量的引用关系,也无需像C/C++让开发者对各种内存泄露、越界访问等问题如履薄冰。这也是Rust敢号称可靠的系统级编程语言,运行时效率叫板C/C++的底气来源。
Golang GC核心要点和度量方法
一. 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共同来完成。