跳至正文

2015年终总结

  • 2015-12-28

终于又到了年末自我审查的时间了……今年就不填表了,我觉得我已经不需要这种东西来督促我学习了,毕竟目标很明确了。

今年在游戏公司算是扎下了根,带了一小段时间的后端团队(虽然只有一个成员),学到了怎么给他人做职业规划,教学,发展;也学到了不少职场上的事情。Golang也算是能信手拈来的程度了,但是对于高性能系统还是有些不知所措,比如内存池规划,TCP连接优化。架构上至少接触了点分布式的东西,但是还没有上手的感觉,毕竟需求并不是那么强烈,对于CAP、Paxos都只是停留在大致知道上,明年务必要实现过一次Paxos和raft才行。算法/数据结构上,今年简直就是LRU年,之前在Game
Server里有用到,写了一个,然后跑到新团队发现,又缺一个,想着LRU反正不复杂,又写了一个……LeetCode重新捡了起来,刷了40多道而已,简单地学了些图论的东西,但是边上班边生活(其实都是些琐事,比如小区换出入证什么的)真的业余时间几乎就没有了,加上还要学车,简直快要把我给累垮了。幸好女友经常安慰,疏导我,其实对我来说,能有个人说说今天的烦恼,舒缓一下压力就已经足够,然而她给予我的更多,真的要感谢她。

今年只看了一本书--《网络游戏核心技术与实战》,讲得真得很细,对于架构上也是面面俱到,可惜,大裁员时顺手把书送给了同事,要不然,还得继续精读。

东西还是造了不少,可惜没有时间整理、总结:

  • 一个基于Onehop的去中心、分布式的KV数据库
  • 花了两周业余写了这个博客程序 bla
  • ucloud sdk
  • justrpc --jsonrpc v1 的python 实现

参加了不少分享会,不过学到的并不多,以后看来得参加更加高端点的分享会。或者报个算法班来系统补补课?

#年终总结

Bla — another static blog app

  • 2015-11-23

After 2 weeks working on my pet project, I’m glad to say that Bla is ready at
the stage of MVP(minimum viable product).

  • Fully static file serving
  • WYSIWYG (No markdown or offline editing)
  • Tag/Relation finding
  • Golang blog style template
  • Easy deploy with no php, no Mysql (only golang)

Other highlights:

  • automatically reload on content/template/config change
  • support TLS

Simple Tutorial

go get -u github.com/mengzhuo/bla/cmd/bla

bla new

edit your configure file “bla.config.json” and just run bla

Github repo:

github.com/mengzhuo/bla

某项目上线及事故总结

  • 2015-10-19

EW4终于在中国iOS顺利上线了,虽然我已经被调到其他项目组,但是也经历了上线加班调试到4点的情况。所用的技术栈其实在招聘页面都有,所以不算泄密。

事故总是由看似无关紧要的小错误累积而成的。

整个系统架构上是一般的二代网游架构,大问题没啥,但是登入服务赶新潮地依赖了DynamoDB这个SLA号称在线率三个9的玩意。可是偏偏在正式上线前三天us
east1的数据中心写故障!这时IT团队试图从us east1迁到us
east2,顺利的迁移完了,结果再次中了彩票,east2也故障了!这简直是可以买彩票的节奏!!反正数据还没迁完,IT团队就再次修改了配置指向east1,由于此前迁移没有进行过演练,所有配置的更改都是手动操作的,所以有台负责处理连接的节点没有更新,埋下了祸根:这个节点不停地对登入节点上报错误的地址,但由于ELB的关系,这个错误在人工测试的阶段都没有察觉到。
这时,悄然开服的消息已经传遍了骨灰玩家圈,玩家纷纷开始登入注册。

大概是下午开始,客服陆续收到玩家投诉说丢档了!但错误收集sentry并没有报告什么异常,顿时我就纳闷了,难道是DynamoDB又出了问题?但,这次,并不是,只好继续排查代码上的问题。一波未平一波又起,之前给各个安卓运营商上线之后一直正常,但是现在某运营商那边的玩家投诉无法登入,美国上线的负责人也表示自己无法登入,这时整个团队都非常沮丧,因为错误收集系统压根就没有错误报告。IT团队开始一遍又一遍地切换DynamoDB的数据库、登入服务的配置来查错,这时已经是凌晨4点了,估计因为在线人数下降了,ELB自动分配到了一台机子上,所有人又能正常登入了。王总提出让一部分人休息,明天好继续排错,所以我回家休息了。
结果第二天来时,美国团队的人通过debug
客户端,发现本来该指向US的节点,竟然给出了中国区的服务器地址!大家顿时恍然大悟,有台机子在上报错误的地址!!于是IT团队用昨晚写好的部署程序,重新部署了所有大区服务,登入问题就都解决了。
接下来就是无法下载的问题,客户端的同事发现是更新用的服务下载到了不同的数据更新包导致的,不过所幸只是一个小国的部分用户……
这次事故,其实说实话是完全可以避免的。

  • DynamoDB出故障是意外,但是我们服务器团队没有跟IT部门交代清楚配置,导致上报的密钥还是开发时的TEST_KEY是根本原因
  • 服务器并没有完整的校验机制、机器人、最后线上bug还得手动测试,浪费了很多宝贵的时间
  • 对于代码,各种需求变更过快、没有足够的人手做完UT,导致排查代码也花费了大量的时间
  • IT团队也没有及时地做好自动化部属工作、加上2日的操劳、同事们出错也是在所难免
  • 客户端团队对于数据更新包的需求也没有弄清楚就急忙开发,导致下载到了不同的数据更新包。

p.s.通过这次排查代码,我发现系统里还有不少因为加新功能而加的补丁,重构看来也是不可避免了。

如何使用Wireshark对远程服务器数据进行分析

  • 2015-06-05

抓包对于后端工程师来说就是家常便饭的技巧,包分析软件里最牛B的是Wireshark,可是Wireshark只能在有X环境的机子上运行,当我们想知道服务器上的情况,又想用Wireshark该怎么办呢?
@MatheMatrix指出用管道就好了

ssh [email protected] "sudo tcpdump -s 0 -U -n -i eth0 not port 22 -w -" | wireshark -k -i -

-–原文章—-

1. 在本地运行 mkfifo /tmp/remote 这样就创建了一个先进先出的文件

2. 在本地运行

ssh [email protected] "sudo tcpdump -s 0 -U -n -w - -i eth0 not port 22" > /tmp/remote

将远端服务器的数据通过管道输送到刚才我们建立的文件中 tcpdump的规则里将22端口去掉了,因为我们传输的方法是ssh

3. 在本地运行 wireshark -k -i /tmp/remote 开启wireshark!

如何让Go程序更快

  • 2015-05-25

本文为部分翻译、整理。 原文为Go的开发者之一的Dave Cheney所做的 Five things that make go
fast

清晰赋值类型

例如,有一个绝对不会超过uint32的数值,就不要用int var gocon uint32 = 2015 这样gocon这个值只会占用4个字节
为啥呢? 因为如下图,CPU的处理速度已经远超内存的总线速度了 Gocon-2014-10 所以数值能用小的用小的,尽量让数值留在CPU
cache,而不是速度更慢的内存里

函数调用有overhead,为了内联,尽量消除编译器无法侦测的dead code

当函数调用时,始终是由overhead(额外开销)的,比如保存调用栈,CPU切出。
因此编译器会尝试进行内联,将小函数直接复制并编译。
举个栗子:

func Max(a,b int) int {
if a > b {
return a
}
return b
}
func DoubleMax(a, b) int {
return 2 * Max(a,b)
}

-m 查看内联状态 $go build -gcflags=-m main.go # utils src/utils/max.go:4: can inline Max src/utils/max.go:11: inlining call to Max
这样做的代价是可执行的二进制文件更大了,但由于内联,并不是函数调用,性能自然是更好了。 但有些函数,是不能内联的,比如下面这个

func Test() bool {return False}
func Expensive() {
if test(){ //接下来的Expensive没办法内联
// Expensive....
}
}

改成下面这样就可以内联了

const TEST = False
func Expensive() {
if TEST{
// Expensive....
}
}

逃逸检查

首先,要理解一个概念,stack 和 heap Gocon-2014-24
stack作用域是本地的(locals),在函数执行完之后会自动收回,CPU控制,效率高 而heap则需要由程序来管理,效率低 具体有篇文章讲这个:
Memory stack vs
heap

因此,就算有GC,也应该把不需要传出的参数尽量控制在函数内。 例如下图的程序
Gocon-2014-26 因为numbers 只在
Sum中,编译器也会自动分配100个int空间在stack中,而不是heap中。 正因为在stack 中,所以不需要GC参与,自动收回。
但这不意味着不能用指针引用,见第二个栗子: Gocon-2014-27
尽管变量c是通过new函数生成的,但是因为在center外没有c的引用,所以c也会被存储在stack上。 逃逸检查实例:
Gocon-2014-28

Goroutine

第四个,我觉得是提示吧: Goroutine是比进程、线程都小的执行单元 Goroutine中的会被调度器(scheduler)切出的操作:

  1. chan 收发
  2. go 语句调用函数
  3. 阻塞的syscall
  4. gc

一图胜千言,下图表示了调度器是如何在goroutine之间切换的。 Gocon-2014-35
第五个算是对Go1.3以后的stack分配机制的总结,我就不翻译了,有兴趣的同学自己可以看看原文:)

总结

  1. 我想补充的是: 逃逸对于channel也是成立的,因此,在channel之间,最好传递的也是对象,而不是引用。这个问题上我栽过一次了哈哈哈
  2. 之前我并不知道内联是啥,Golang、pypy这样的JIT、或者cpython真的是减轻了我的心智负担,当然,了解一下也是很不错的。
  3. 清晰的赋值个人感觉不是很必要,因为Go的int类型最大是2**31-1,性能调优的时候再认真地梳理其实也来得及

如何压缩Golang 编译出的可执行文件大小

  • 2015-05-05

先给结论:可以减少到原来的29%

最近在写一个TLScat小工具
Github.com/mengzhuo/tlscat
源文件仅仅2KB不到,但是用 go build tlscat.go 编译出来的有4.6MB!

屏幕快照 2015-05-04 23.39.08

贴纸

后来发现这个Golang的1.5才会解决的问题 Issue #6853 all: binaries too big and
growing
可是,我就不信这个邪,于是搜索到了go
build的一些用法 go build -ldflags "-s -w" ‘-s’ 相当于strip掉符号表,
但是以后就没办法在gdb里查看行号和文件了。 ‘-w’ flag to the linker to omit the debug information
告知连接器放弃所有debug信息 这样一来就只有3MB了

贴纸

然后发现在Mac平台下,还有upx这样神一般的存在。

UPX achieves an excellent compression ratio and offers very fast
decompression.

简而言之,upx就是对可执行文件进行压缩,然后可以已极快的速度解压并运行

可以用brew快速安装upx brew install upx upx 可执行文件 屏幕快照 2015-05-0523.41.40

参加某开发比赛后记

  • 2015-05-04

前些日子参加了某云的开发大赛,结果出来了~ 屏幕快照 2015-05-04 16.19.27 仅仅拿了一个三等奖,略桑心。
参赛过程具体见《[如何一下午写3000行?记某云的Golang API
SDK生产过程](/%e5%a6%82%e4%bd%95%e4%b8%80%e4%b8%8b%e5%8d%88%e5%86%993000%e8%a1%8c%ef%bc%9f%e8%ae%b0%e6%9f%90%e4%ba%91%e7%9a
%84golang-api-sdk%e7%94%9f%e4%ba%a7%e8%bf%87%e7%a8%8b)》

不过伤心之余,我理性一把,看了看前4的分别都是啥: 特等奖是移动客户端的解决方案:mu
MU (Mobile UCloud) 是一个基于 UCloud API 的手机版管理工具……
成熟度已经和产品可以媲美了,也解决了在移动端大潮之下,官方对移动端支持几乎是空白的痛点,所以只需要签个协议就可以用的产品,拿奖不奇怪。
而三个一等奖里,有另一个产品: ESS_For_Ucloud
这货就相当于AWS里的ELB,同样是改改就能投入线上使用的产品。 解决了……呃……官方有LB,但是需要人工调整这样"不云"的尴尬。 第二个一等奖是:
Ucloud CLI命令行接口
回想我司的运维、SA们用来控制AWS的工具,不是web控制端,不是各种写好的SDK,而是AWS CLI,因为他们最熟悉的就是各种脚本。
这也正是比赛举办方所缺少的! 虽然命令覆盖得并不是很完善,但也算是补上了个自动化运维的坑。 最后一个一等奖是: Python
SDK

在我吐槽官方的SDK用了**params这样的动态语言特性时,聪明的人自然会想到,举办方自然希望修补官方的SDK也是比赛的侧重方向了嘛~

虽然,我粗浅地看下去,这个SDK也仅仅是修补了官方SDK调用时不严谨的尴尬,而且由于API这么多,自然覆盖得不全,但是也算抓住了举办方的心了。
公布之后在交流群里,不少人也吐槽自己UT覆盖率比得上面的都高,为啥只能拿二等甚至是三等。反过来想,真的算是程序员典型的思维了:自己做的程序、工具很"牛B",但是为啥没有人用,甚至是不受欢迎。
都是因为: 没有抓住痛点!没有抓住痛点!!没有抓住痛点!!! 所以,下次做东西的时候,得多问问自己:

需求是啥!?

如何一下午写3000行?记某云的Golang API SDK生产过程

  • 2015-04-06

近日参加了某云的SDK编写比赛,官方给的样例是个Python版,请求直接用**params这样方法构造请求体

暴走漫画-你他妈的在逗我

因为尼玛Golang是静态语言,我们严谨!没有可变参数!还没有默认值!!

所以我苦逼地写了一个多小时类似这样的代码:

func (c *Client) GetHostInstance(id string, option string, count int) {
// bla....
}

每个API一遍遍地重复,真是苦不堪言,啥时候是个头啊,人生苦短啊,早知道还用Python了…… 于是我去睡觉了,嗯 梦中Rob
Pike托梦
醒来,突然想到,几天前学习的Go
reflect库,可以反射出调用的type,这样我只要构造struct,然后遍历一下每一项,不就可以省了很多时间了么? 于是,我开始构造各种struct
type GetHostInstance struct{ id string option string count int }
从json库中直接拉了些代码来遍历NumField,顿时感觉自己棒棒的!! 但是,写了三个以后我突然发现API里有些参数是optional的!!!

20130422212028-23861660

幸好,Rob大大已经替我等想好了,那就是struct的TagField,于是我TM也构造了一个自己的解析器

type GetHostInstance struct{
id string `cloud:"optional"`
option string
count int }
// .....解析器部分代码
// .....获取tag tag := typ.Field(i).Tag.Get("cloud")

取出tag值之后,依靠检查struct这项是否为nil来确定是否传不传参数!
终于可变参数这么蛋疼的事情都让我解决了,紧接着,我又写了3个struct,累得不行了……年纪轻轻就体力不支了
但是我决定写完去,因为这么漂亮的解决方案不用来拿个马克杯做奖品怎么行?!
于是又写了3个API,我发现我就是在复制粘贴官方的API文档,于是我祭出我的复制粘帖大法+Vim宏
顿时快了很多,变成5分钟一个API,我看了看文档里……TM的一共50多个接口!! 5*50 = 250 分钟 = 4个多小时 =
我都可以拿来看《黑鹰坠落》+ 5集《GTA 5搞笑视频》了!!
于是我开始无聊地点着官方API文档,我突然发现,官方的API文档是用Sphinx写的,只是套了层自己的css而已 –>
因为我的请求体都只是struct,官方还把请求的类型都已经标注好了,这时耳边响起了:人生苦短,我用Python 的标语
我可以用python写个程序,将官方文档直接转成SDK 代码啊!!! 于是,说干就干!! 找了requests等库,人生顿时快乐了很多有没有~
大法 壮观地生成,连注释都有( ̄▽ ̄) 哈哈哈

生成器代码:https://gist.github.com/mengzhuo/f1b07decb69eea6e7dab

生成好的SDK:https://github.com/mengzhuo/ucloud-go-sdk

涠洲岛之旅

  • 2015-03-19

春节去了趟涠洲岛,今天才整理照片,发现除了发朋友圈,还是有些可以拿出来分享的。

涠洲岛火山岩海滩
火山岩海滩

涠洲岛 教堂 教堂

码头 码头

灯塔 灯塔

海滩日落 海滩日落

渔船船队 渔船船队

无名海滩 无名海滩

如何用ZeroMQ实现UDP组播Pub-Sub(基于Golang)

  • 2015-01-19

ZeroMQ有一对Pub/Sub socket 类型,但是网上的教程一般侧重于使用TCP版本的……
虽然TCP版本的也能组网,但是略显麻烦,今天我来给大家介绍一下基于PGM协议的ZeroMQ Pub/Sub模型 首先要编译安装OpenPGM brew install libpgm 接着是zmq brew install zmq --with-pgm 这样就准备好了环境了,
这里需要了解一下PGM网络的原理,很简单,如下图 Udp_encapsulated_ports
绿色的就是发送方,向目的组播地址239.192.0.1 端口3055(黑色粗线)发送数据,然后所有监听此端口的接收者(Receiver)都收到了。
就这么简单。 然后就是写代码咯:

soc = zmq.NewSocket(zmq.PUB)
soc.Connect("epgm://192.168.1.100;239.192.0.1:3055")
soc.SendMessage("Hi")

呃……192.168.1.100? 这个是pgm的特点,你需要指定发送组播包的网卡名,一般人记不住网卡名……所以用此网卡所持有的IP来标示。 p.s.

  1. PGM有个特点,就是发送方进行流量控制,zmq中使用的是setrate,切记在Connect之前使用
  2. zmq会整理包,所以再散的数据,都会组合成一个message发送出来(不愧是智能网络)
  3. 实际测试时……16Mbps的流量根本不是问题
  4. 需要debug PGM时export PGM_MIN_LOG_LEVEL=TRACE