游戏编程


  • 首页

  • 分类

  • 归档

  • 标签

案例分析 - 2018/03/25跨服活动异常导致进程宕机

发表于 2018-04-03 | 分类于 案例分析

事故表现

跨服活动在比赛开启的时候服务进程(Go语言开发)异常宕机,重启进程之后比赛正常进行。该服务已经运行多年,第一次出现此问题。

问题分析

进程宕机时收集到的堆栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x8 pc=0x4b6901]
goroutine 743264 [running]:
runtime.panic(0x819240, 0xb37813)
/usr/local/go/src/pkg/runtime/panic.c:279 +0xf5
container/list.(*List).insertValue(0xc20847efc0, 0x86ee40, 0xc208f90cc0, 0xc20b0de660, 0x7fc1581e3c50)
/usr/local/go/src/pkg/container/list/list.go:105 +0x71
container/list.(*List).PushBack(0xc20847efc0, 0x86ee40, 0xc208f90cc0, 0x1)
/usr/local/go/src/pkg/container/list/list.go:139 +0x52
main.(*Room).Sort(0xc2084b63c0, 0xc208f90cc0)
/home/sf/M_00009/server/tags/release_4.2.0/src/master/room.go:308 +0x8bb
main.(*Room).doSort(0xc2084b63c0)
/home/sf/M_00009/server/tags/release_4.2.0/src/master/room.go:243 +0x58d
created by main.(*Room).Run
/home/sf/M_00009/server/tags/release_4.2.0/src/master/room.go:105 +0x2b5
  • 首先, 宕机的信息显示panic的原因是invalid memory address or nil pointer dereference,使用空指针导致异常宕机的想法立刻浮现,通过查看go语言源码:

    1
    2
    3
    4
    // insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
    func (l *List) insertValue(v interface{}, at *Element) *Element {
    return l.insert(&Element{Value: v}, at)
    }

    宕机信息中container/list/list.go:105对应源码return l.insert(&Element{Value: v}, at),如果是因为使用了空指针,根据代码不难确定只有List为空才会发生宕机。按照List为空这个思路查下去,却发现List在使用之前明确的被初始化过,不可能为Nil。习惯害死人,即使明确List不可能为Nil,还是固执地认为可能存在隐藏问题导致List为Nil,因为这个惯性思维耽误了不少时间。期间也发现了一个不确定的信息是:panic堆栈(goroutine 743264)与堆栈(goroutine 743272,如下 )相似且Room相同(0xc2084b63c0):

    1
    2
    3
    4
    5
    goroutine 743272 [select]:
    main.(*Room).doSort(0xc2084b63c0)
    /home/sf/M_00009/server/tags/release_4.2.0/src/master/room.go:214 +0x5b6
    created by main.(*Room).Run
    /home/sf/M_00009/server/tags/release_4.2.0/src/master/room.go:105 +0x2b5

    但是这个信息没有引起重视,完全迷失在List被重置为Nil的幻觉中。。。

  • 然后,就在百思不得其解时,查看了一下进程宕机时段的日志,摘要如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    [2018/03/25 20:30:01.930863] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.931084] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 1 top1 boot room
    [2018/03/25 20:30:01.931115] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.931472] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 2 top1 boot room
    [2018/03/25 20:30:01.931528] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.931763] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 3 top1 boot room
    [2018/03/25 20:30:01.931794] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.932042] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 4 top1 boot room
    [2018/03/25 20:30:01.932077] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.932479] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 5 top1 boot room
    [2018/03/25 20:30:01.932505] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.932974] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 6 top1 boot room
    [2018/03/25 20:30:00.579081] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 3
    [2018/03/25 20:30:00.580716] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 3
    [2018/03/25 20:30:00.582094] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 3
    [2018/03/25 20:30:00.583440] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 3
    [2018/03/25 20:30:00.584705] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 3
    [2018/03/25 20:30:00.585861] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 3
    [2018/03/25 20:30:01.577092] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.577349] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 1 top1 boot room
    [2018/03/25 20:30:01.577424] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.577717] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 2 top1 boot room
    [2018/03/25 20:30:01.577761] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.578035] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 3 top1 boot room
    [2018/03/25 20:30:01.578185] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.578475] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 4 top1 boot room
    [2018/03/25 20:30:01.578561] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.578803] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 5 top1 boot room
    [2018/03/25 20:30:01.578834] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 4
    [2018/03/25 20:30:01.579156] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 6 top1 boot room

    起先因为代码是四年前写的(对代码记忆已经模糊),日志中并未发现错误信息(都是INFO类型),反而更坚定了List为Nil的假设。但是仔细查看日志发现:Room在极短的时间被boot 两次,进而发现进程的时间居然被重置过:

    1
    2
    [2018/03/25 20:30:01.932974] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:338) CrossPK: 6 top1 boot room
    [2018/03/25 20:30:00.579081] [INFO] (/home/sf/M_00009/server/tags/release_4.2.0/src/master/state.go:386) CrossPK: save type: 3

    下一条日志的时间(20:30:00.579081)早于上面一条日志(20:30:01.932974)(日志同步写入,正常不可能出现时间倒转问题),豁然开朗!!!进程时间重置导致比赛再次被启动,每次启动都会创建一个相应的Goruntine,List被两个Goruntine同时访问产生了数据竞争问题,进而引发宕机。这也解释了上文中panic日志有两个相似的堆栈,一切也都能解释的通了。

  • 到此,我们知道引发这一系列问题的真实原因是时间被重置。那又是谁修改了进程时间?

    1
    */6 * * * * /usr/sbin/ntpdate ntp-jh.uuzu.com >> /var/log/uptime.log 2>&1 || /usr/sbin/ntpdate ntp-yd.uuzu.com >> /var/log/uptime.log 2>&1;/sbin/hwclock -w

    crontab中包含上面这段操作,用来同步服务器时间。查看uptime.log中包含25 Mar 20:30:00 ntpdate[12587]: step time server 10.0.0.6 offset -2.353620 sec,于是乎就明朗了,游戏进程中使用的时间是wall clock而不是monotonic clock,服务器脚本定时使用NTP机制同步系统时间进而影响游戏时间。

结论

  1. 惯性思维害人;
  2. 查BUG就像破案,蛛丝马迹,认真仔细,全面分析,冷静思考;

防范措施

  1. 代码中增加判断步骤,防止时间重置引发多次启动;
  2. 游戏中时间wall clock考虑改为monotonic clock;
  3. NTP脚本执行时间错开活动开启时间(治标不治本)。

行为树

发表于 2017-05-20 | 分类于 游戏编程基础

简介

游戏开发中,怪物的AI系统主要实现方法有状态机,行为树等。本文主要介绍行为树的概念,以及基于behaviac中行为树的源码分析。

行为树是一种由根节点,控制节点以及执行节点组成的树状结构,也可以理解为一种图形化的模型语言。节点之间的连接用父子节点的形式表示。在behaviac中节点从左到右,从上到下依次执行。每个节点执行完成后将自身状态返回给它的父节点,父节点根据自身类型,综合子节点执行结果决定它的后续子节点的执行或者它自身的执行结果。

节点执行结果有三种状态,分别是:

  • RUNNING:代表节点还没有执行完成;
  • SUCCESS:代表节点执行成功;
  • FAILURE:代表节点执行失败;

节点的分类

由上可知,行为树有三种类型的节点,分别是:

  • 根节点:没有父节点,只有一个子节点,它是起始节点。
  • 控制节点:有一个父节点,至少一个子节点。它有两种类型节点:
    • 组合节点,常见的组合节点包括:
      • 序列节点,依次执行所有子节点,如果某个子节点返回失败,序列节点结束后续子节点的执行并返回给它的父节点失败状态。如果所有子节点都返回成功,序列节点返回给它的父节点成功状态。类似编程中&&语法。
      • 选择节点,依次执行所有子节点,如果某个子节点返回成功,序列节点结束后续子节点的执行并返回给它的父节点成功状态。如果所有子节点都返回失败,序列节点返回给它的父节点失败状态。类似编程中||语法。
      • 并行节点,同时执行所有子节点,根据所有子节点的返回结果决定本身结果。
    • 装饰器节点,顾名思义,它对子节点的处理结果进行额外处理,并将处理后的结果返回给它的父节点,常见节点包括:
      • 非节点,对子节点的返回值取反。类似编程中!语法。
      • 循环节点,循环执行子节点指定的次数。类似编程中while语法。
  • 执行节点:有一个父节点,没有子节点。执行节点也可以称为叶节点,只有叶节点需要特别定制。它也包括两类节点:
    • 条件节点,根据条件的比较结果,返回成功或失败。
    • 行为节点,根据动作结果返回成功,失败,或运行。

简单的怪物AI

这张图表示一个简单的怪物AI行为树,绿色节点是根节点,蓝色节点是控制节点,其他的是执行节点。


behaviac源码分析

下文是针对behaviac库中行为树源码的简单分析,并梳理其核心的结构与过程。

基础数据结构

每种节点都是由BehaviorNode(或者它的子类)与BehaviorTask(或者它的子类)两部分组成,它们一一对应,通过BehaviorNode::CreateTask生成BehaviorTask,BehaviorTask::GetNode获得属于它的BehaviorNode。

  • BehaviorNode,代表节点的配置信息,它是各类节点的配置所对应结构体的基类。如,
    • DecoratorNode
    • BehaviorTree
    • Action
  • BehaviorTask,代表节点的运行时结构,它是各类节点运行时结构体的基类。如,
    • CompositeTask
    • BehaviorTreeTask
    • ActionTask

BehaviorTask执行流程

每个节点的执行是通过BehaviorTask::exec实现的,BehaviorTask::exec过程简化如下:

  1. 首先,BehaviorTask::onenter_action,在进入节点之前执行,例如一些前置判断。virtual函数 BehaviorTask::onenter,子类的差异化通过此函数实现。
  2. 其次,virtual函数 BehaviorTask::update_current,节点自身的执行过程,子类的差异化通过它与BehaviorTask::update两个函数实现。
  3. 最后,BehaviorTask::onexit_action,在退出节点之后执行,例如一些后置判断。virtual函数 BehaviorTask::onexit,子类的差异化通过此函数实现。

常见节点分析

从上可知,每种类型的节点都有两部组成,详细的代码不一一罗列,简单说明如下:

  • 行为节点(action.h action.cpp)
    • struct Action - 配置信息
    • struct ActionTask - 运行时信息
  • 序列节点(sequence.h sequence.cpp)
    • struct Sequence - 配置信息
    • struct SequenceTask - 运行时信息
  • 选择节点(selector.h selector.cpp)
    • struct Selector - 配置信息
    • struct SelectorTask - 运行时信息
  • 并行节点(parallor.h parallor.cpp)
    • struct Parallor - 配置信息
    • struct ParallorTask - 运行时信息

自定义类型信息加载

behaviac系统中自定义的类型信息会生成单独的cpp文件,运行时自动加载(通过static变量的初始化)到系统中,过程如下:

  1. AgentMeta::SetBehaviorLoader,被自定义类型信息生产的代码(behaviac_agent_meta.cpp)中调用。
  2. TryStart
  3. BaseStart
  4. AgentMeta::Register
  5. BehaviorLoaderImplement::load

行为树执行流程

首先介绍一下BehaviorTree,BehaviorTreeTask两个结构体,

  • BehaviorTree,它是BehaviorNode的子类,代表一颗完整行为树配置信息,在Workspace::Load中生成。
  • BehaviorTreeTask,它是BehaviorTask的子类,在Workspace::CreateBehaviorTreeTask中生成,对于BehaviorTree运行时结构体。

行为树执行过程就是BehaviorTreeTask执行过程,如下:

  1. Agent::btexec,Agent中包含BehaviorTreeTask
  2. BehaviorTreeTask::exec
  3. BehaviorTask::exec
    • onenter_action
    • update_current
    • onexit_action
  4. BranchTask::SetCurrentTask,它涉及子节点RUNNING状态的处理,详见代码。

以上就是简单的行为树源码分析。这里也说明一下我个人阅读一个代码库方法:

  1. 对于库的作用有一个基本的了解,通过阅读相关文档,在这里,例如:Behavior Tree(wiki)),behaviac-腾讯开源项目
  2. 从入口函数开始浏览整个代码,比如Agent::btexec。
  3. 找到核心点仔细阅读,比如BehaviorNode与BehaviorTask。
  4. 库中不理解的知识点,有针对性的阅读相关代码,例如并行节点。
  5. 多调试。

游戏服务器框架简述

发表于 2017-03-29 | 分类于 游戏编程基础

框架

目前游戏类型千差万别(卡牌,arpg,mmo,moba等),每种类型游戏设计的侧重点不相同导致框架上的差异,不过即使相同类型游戏,不同设计师实现的框架也不经相同。这里我们不会针对各种纷杂的框架一一介绍,本着求同存异的态度,只是从分层架构设计的角度来阐述这些零零种种框架的本质。

依据业务上的差异,基本上可以把游戏服务器框架映射到一个四层架构模型上,如图:

游戏服务器分层模型

  • Gateway Service

    负责客户端网络接入,收敛网络入口。gateway的业务逻辑相对比较简单,主要处理网路消息的转化(客户端发送到后端服务器,后端服务器发送到客户端)与过滤。从它负责的业务角度上可以看出一个好的gateway service其实就是一个高性能的网络服务。针对高性能网络服务的实现有很多方式,常见模型有:

    • 多线程模型,如果对网络编程不熟悉,可以选择开源实现,比如muduo, asio,netty等。
    • 纤程,这一类的开源实现也有很多,语言级别的golang,erlang,单独库形式的libco,skynet。

    在我们游戏里面使用的方案是基于linux系统下的多线程模型。具体的实现是,one loop per thread,loop是基于反应器的设计模式实现。系统层面也会做相应的优化,比如cpu绑定,网卡的硬中断,软中断的优化。在这些完成之后,一个高性能的网络服务器基本上就成型了。在我们实际运行过程,单Gateway最高同时在线达到15w,机器整体的负载也不高。

  • Logic Service

    游戏逻辑主要在这个服务上处理。常见的实现,多IO线程加单独的业务线程。线程之间通过消息队列进行通信,消息队列简单可以用一个加锁的队列实现,也可以选择无锁队列(建议使用成熟的开源方案)。网络消息汇总到同一个线程中处理,也就是说游戏业务处理是单线程的,这种设计的优势在于避免业务开发中数据竞争。如果单线程实在不能满足性能要求,比如一些mmo游戏中,可以采用多进程的模式,也就是有多个logic service,配合可能也需要一些协调logic service的服务。

  • Record Service

    数据库的代理层,负责与数据库交互,解耦业务层的数据处理,适应数据库的改变,业务逻辑也相对简单。实现类似于Logic Service,它也可以合并到logic service里,但是独立开来,整体框架更加清晰。

  • Database

    顾名思义,保存玩家数据的地方。常见的游戏数据库包括: mysql,redis,mongodb等。我们游戏数据库选择是RedisLV,它解决了redis备份时候可能导致内存使用过高的问题。它使用的方法是为redis引入基于leveldb的数据落地方案。leveldb是google开源的kv数据库,使用lsm tree算法,数据写入的性能比较高。修改redis的源码,在redis中与写入操作相关的命令处理中,将改变同时写入leveldb,数据库的备份只针对leveldb做备份。

服务驱动

这一节主要针对Logic Service,也就是游戏核心业务的驱动。游戏的业务主要是响应外部请求和处理内部状态改变,包括以下两种驱动方式:

  • 消息驱动,针对各种网络消息做出响应。比如,玩家的点击反馈等。常见实现方式是通过命令模式,也就是消息封装成一个对象,不同的消息参数化。
  • 时间驱动,一些业务需要基于时间而做出改变。比如,定时活动的开启,玩家数据定时的保存等。常见的实现方案是定时器,定时器采用的算法可以是时间轮,红黑树等。

实现这两种驱动之后,游戏服务器可以响应外部和内部的改变,基本上服务就可以正常的运转。游戏业务与其他所有后端业务都一样,本身就是数据结构的组织。下面介绍一些常用的设计模式和算法帮助游戏业务的实现。

常用设计模式

  • 单例模式
  • 工厂模式
  • 命令模式
  • 订阅者模式
  • 状态模式
  • 策略模式

常用算法

  • 二分查找
  • 快排
  • 红黑树
  • 跳表
  • 优先级队列
  • A*
  • 行为树

案例分析 - 2017/03/19数据库回档失败

发表于 2017-03-23 | 分类于 案例分析

事故表现

​ 游戏服务器机器异常重启之后, 数据库回档(游戏服务器数据存放在redis并同时写入leveldb,回档操作是通过leveldb中的数据恢复到redis)完成之后发现玩家数据丢失严重,连续使用10天内备份的数据进行回档操作依然失败。

问题分析

  • 首先,可能的问题是: 磁盘故障导致数据写入leveldb失败,进而导致数据回档失败。但是通过对服务器日志分析发现并没有写入失败记录,基本上排除是磁盘故障这种可能。

  • 其次,发现leveldb备份文件(20M)明显小于同期其他游戏区服leveldb备份文件(60M),该服务器异常之前,玩家访问正常,随之想到的可能性就是: 仅有leveldb数据在十天以前被异常操作删除,导致后续备份数据不完整。能够导致leveldb数据被操作删除的最大可能性就是,通过GM后台访问删除。由于GM后台操作没有日志可以查询,这种可能性不好验证,暂且放下,不深入考证。

  • 最后,在查看数据库日志的时候发现这台机器只有2017/01/07开始的日志,但是游戏服务器是2016/12/开服的,明显缺失日志。通过跟运维沟通,2017/01/07由于服务器机器异常数据进行了一次迁移。进一步分析ssdb(ssdb数据本质上是存放在leveldb中)日志,发现ssdb在7号14:47:50第一次启动成功后,15:01:05进行了一次数据库备份,时长在20毫秒左右,基本上可以判断此时的leveldb中保存数据很少,更可能的此时的启动的ssdb是空的。

    到此,我们基本上可以断定数据库备份回档失败的原因是:作为redis备份的leveldb,它丢失了数据迁移之前的leveldb数据,后续备份文件中保存的仅是增量数据,所有通过备份文件回档会丢失大量玩家数据。

    在我们明确事故产生的原因之后,但是还有一个疑惑: 既然redis数据是通过leveldb恢复的,为什么redis数据在01/07没有缺少,再次查看ssdb,redis日志发现在第一次ssdb启动成功后,ssdb进程没有关闭的情况下,14:58:03再次进行启动操作,由于监听tcp port已经存在,导致第二次启动失败。同时也伴随两次redis的启动,但是不同的地方,redis在第二次启动之前关闭了之前运行的redis。现在我们可以做出一个假设(后面查看1/7迁移的另一组服务器部分操作日志验证了这种假设),操作如下:

    1. 运维启动了一个空的ssdb和redis;
    2. 运维关闭了redis,进行正常的数据恢复;
      • 清空leveldb数据存储目录下文件;
      • 移动数据库备份文件到leveldb数据存储目录;
      • 通过leveldb文件恢复到redis中。
    3. 恢复完成之后,再次启动ssdb,由于端口冲突,启动失败,但不影响redis的正常使用。

    这样,产生问题的操作我们基本上就还原出来了。这里有一个比较有趣的技术点是,leveldb使用文件锁来保证同一个数据存储目录下只能启动一个leveldb,那么为什么在第一个leveldb没有关闭的情况下,还能再次启动leveldb恢复redis数据?仔细查看运维操作过程可以发现问题,运维在恢复redis数据之前第一步就是清空了数据存储目录中的文件(包括leveldb的文件锁使用的LOCK文件),所以可以再次启动leveldb恢复redis数据。

    在查找问题的过程中,使用ltrace跟踪进程时发现mmap使用的参数对不上源码,最后发现是leveldb版本的不一致,ssdb使用的是leveldb1.14.0,我看的是leveldb1.18.0源码。也同时加深了对leveldb中文件组织方式和保存的认识,即通过VersionEdit记录leveldb中文件的增加和删除,每次改动信息增量写入MANIFEST文件中,并且在服务器每次启动的时候合并增量,生成一个新的文件组织快照并写入MANIFEST文件。

防范措施

  • 回档操作之前要确保ssdb是关闭状态;
  • 定期检查回档文件,确定其完整性。

游戏服务器编程资料

发表于 2016-08-18 | 分类于 游戏编程基础

Linux基础

  • 常用工具
    • bash awk sed grep find wc sort uniq
  • 参考图书
    • 鸟哥的Linux私房菜基础学习篇

编程语言

  • c++ 参考图书
    • C++ Primer
    • Effective C++
    • Effective Modern C++
    • 深度探索C++对象模型
  • golang 参考图书
    • Go程序设计语言
  • python 参考图书
    • Python核心编程
  • lua 参考图书
    • Lua程序设计
  • php 内部GM后台开发语言
  • erlang 参考图书
    • Erlang程序设计
    • Erlang/OTP并发编程实战

数据库

  • mysql
  • postgresql
  • redis 建议学习源码
  • redisLV 增加redis内存数据实时写入leveldb的机制
  • leveldb 建议学习源码
  • rocksdb leveldb加强版
  • redisLV 增加redis内存数据实时写入leveldb的机制
  • hbase 目前用来保存玩家行为日志,后续会被内部基于rocksdb开发的组件替代
  • tidb
  • 参考图书
    • 高性能MySQL
    • Redis设计与实现

网络编程与网络库

  • libevent 建议学习源码
  • muduo 建议学习源码
  • zeromq 缅怀作者 Pieter Hintjens
  • netty Java开发,功能完善
  • libuv NodeJS网络库
  • 参考图书
    • TCP/IP详解 卷一
    • C++网络编程 卷一 卷二
    • 面向模式的软件体系结构 卷二
    • Linux多线程服务器端编程
    • Netty 实战

日志系统

  • 日志收集软件 kafka flume
  • 日志分析
    • 实时分析 spark 自定义工具
    • 离线分析 map reduce hive
  • 存储 hdfs hbase mysql timedb(内部使用,基于Rocksdb)
  • 参考图书
    • Apache Kafka 源码剖析

Linux系统分析

  • CPU 常用工具 top mpstat dstat
  • 内存 常用工具 free
  • 文件系统 常用工具 sar
  • 磁盘 常用工具 iostat iotop dstat
  • 网络 常用工具 tcpdump wireshark sar ss netstat lsof mtr traceroute dstat
  • 参考图书
    • 深入理解计算机系统
    • Linux/Unix系统编程手册
    • Linux内核设计与实现
    • Wireshark网络分析就这么简单
    • Wireshark网络分析的艺术
    • 性能之巅

游戏开发

  • 参考图书
    • 游戏编程精粹 1-7
    • 游戏编程模式

代码质量

  • 参考图书
    • 设计模式 可复用面向对象软件的基础
    • 重构 改善既有代码的设计
    • 编程珠玑
    • 代码大全
    • UNIX编写艺术

杂谈

  • 参考图书
    • 松本行弘的程序世界
    • 代码的未来
    • 程序员的自我修养
    • Software Design 杂志

怀旧

  • 参考图书
    • C程序设计语言
    • 深入浅出MFC
    • C专家编程

linux performance tools

发表于 2016-08-18 | 分类于 linux

linux errno 含义

发表于 2016-08-04 | 分类于 linux

Linux errno 注解分布在两个文件中,罗列于此,方便查阅。

/usr/include/asm-generic/errno-base.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#endif

/usr/include/asm-generic/errno.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#ifndef _ASM_GENERIC_ERRNO_H
#define _ASM_GENERIC_ERRNO_H
#include <asm-generic/errno-base.h>
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Function not implemented */
#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
#define EL2HLT 51 /* Level 2 halted */
#define EBADE 52 /* Invalid exchange */
#define EBADR 53 /* Invalid request descriptor */
#define EXFULL 54 /* Exchange full */
#define ENOANO 55 /* No anode */
#define EBADRQC 56 /* Invalid request code */
#define EBADSLT 57 /* Invalid slot */
#define EDEADLOCK EDEADLK
#define EBFONT 59 /* Bad font file format */
#define ENOSTR 60 /* Device not a stream */
#define ENODATA 61 /* No data available */
#define ETIME 62 /* Timer expired */
#define ENOSR 63 /* Out of streams resources */
#define ENONET 64 /* Machine is not on the network */
#define ENOPKG 65 /* Package not installed */
#define EREMOTE 66 /* Object is remote */
#define ENOLINK 67 /* Link has been severed */
#define EADV 68 /* Advertise error */
#define ESRMNT 69 /* Srmount error */
#define ECOMM 70 /* Communication error on send */
#define EPROTO 71 /* Protocol error */
#define EMULTIHOP 72 /* Multihop attempted */
#define EDOTDOT 73 /* RFS specific error */
#define EBADMSG 74 /* Not a data message */
#define EOVERFLOW 75 /* Value too large for defined data type */
#define ENOTUNIQ 76 /* Name not unique on network */
#define EBADFD 77 /* File descriptor in bad state */
#define EREMCHG 78 /* Remote address changed */
#define ELIBACC 79 /* Can not access a needed shared library */
#define ELIBBAD 80 /* Accessing a corrupted shared library */
#define ELIBSCN 81 /* .lib section in a.out corrupted */
#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
#define ELIBEXEC 83 /* Cannot exec a shared library directly */
#define EILSEQ 84 /* Illegal byte sequence */
#define ERESTART 85 /* Interrupted system call should be restarted */
#define ESTRPIPE 86 /* Streams pipe error */
#define EUSERS 87 /* Too many users */
#define ENOTSOCK 88 /* Socket operation on non-socket */
#define EDESTADDRREQ 89 /* Destination address required */
#define EMSGSIZE 90 /* Message too long */
#define EPROTOTYPE 91 /* Protocol wrong type for socket */
#define ENOPROTOOPT 92 /* Protocol not available */
#define EPROTONOSUPPORT 93 /* Protocol not supported */
#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT 96 /* Protocol family not supported */
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET 102 /* Network dropped connection because of reset */
#define ECONNABORTED 103 /* Software caused connection abort */
#define ECONNRESET 104 /* Connection reset by peer */
#define ENOBUFS 105 /* No buffer space available */
#define EISCONN 106 /* Transport endpoint is already connected */
#define ENOTCONN 107 /* Transport endpoint is not connected */
#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS 109 /* Too many references: cannot splice */
#define ETIMEDOUT 110 /* Connection timed out */
#define ECONNREFUSED 111 /* Connection refused */
#define EHOSTDOWN 112 /* Host is down */
#define EHOSTUNREACH 113 /* No route to host */
#define EALREADY 114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTALE 116 /* Stale NFS file handle */
#define EUCLEAN 117 /* Structure needs cleaning */
#define ENOTNAM 118 /* Not a XENIX named type file */
#define ENAVAIL 119 /* No XENIX semaphores available */
#define EISNAM 120 /* Is a named type file */
#define EREMOTEIO 121 /* Remote I/O error */
#define EDQUOT 122 /* Quota exceeded */
#define ENOMEDIUM 123 /* No medium found */
#define EMEDIUMTYPE 124 /* Wrong medium type */
#define ECANCELED 125 /* Operation Canceled */
#define ENOKEY 126 /* Required key not available */
#define EKEYEXPIRED 127 /* Key has expired */
#define EKEYREVOKED 128 /* Key has been revoked */
#define EKEYREJECTED 129 /* Key was rejected by service */
/* for robust mutexes */
#define EOWNERDEAD 130 /* Owner died */
#define ENOTRECOVERABLE 131 /* State not recoverable */
#define ERFKILL 132 /* Operation not possible due to RF-kill */
#define EHWPOISON 133 /* Memory page has hardware error */
#endif

游戏服务器编程概述

发表于 2016-07-30 | 分类于 游戏编程基础

规范 - golang


  • 函数注释
1
2
3
4
5
6
7
8
// fn意图
// @param p1 - 第一个参数(p1含义)
// @param p2 - 第二个参数(p2含义)
// @return r1(or TYPE) - 第一个返回值(如果函数返回值没有定义名称标注类型)
// @return r2(or TYPE) - 第二个返回值
func fn(p1 TYPE,p2 TYPE)(r1 TYPE, r2 TYPE){
...
}
  • import顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import (
标准包
程序内部包
第三方包
)
import (
"flag"
"net/http"
"common"
"common/zebra"
l4g "github.com/ivanabc/log4go"
)
  • 全局const 和 var位置
    • 全局const,var变量放在文件最上面(import下面)或者保存在单独的const和var文件中。
  • 命名

    • 禁止程序中使用缩写或者有歧义的名称。
    • 采用驼峰式命名。
    • 形参和局部变量采用驼峰结构命名,首字母必须小写,不得出现下划线。
    • 常量统一采用大写字母,单词之间使用下划线进行分隔。如果是包可见的常量,可在其名字前加上 “k_” 作为前缀。可导出常量与不可导出常量应该分开声明,不得出现在同一常量声明块内。 例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 常量命名方式
    const (
    k_CONST_A = 1
    )
    const (
    CONST_A = 1
    )
  • Slice初始化

    • 已知slice长度,使用make([]type, len)。
    • 已知slice容量,使用make([]type, 0, cap)。
    • 直接使用字面值形式[]type{A,B,C}声明和赋值,取消没必要的append操作。
  • Set类型
    • 使用map[Type]struct{}表示set类型。

设计


基础原则

  1. 模块原则: 使用简洁的接口拼合简单的部件
  2. 清晰原则: 清晰胜于机巧
  3. 组合原则: 设计时考虑拼接组合
  4. 分离原则: 策略同机制分离,接口同引擎分离
  5. 简洁原则: 设计要简洁,复杂度能低则低
  6. 吝啬原则: 除非确无它法,不要编写庞大的程序
  7. 透明性原则: 设计要可见,以便审查和调试
  8. 健壮原则: 健壮源于透明与简洁
  9. 表示原则: 把知识叠入数据以求逻辑质朴而健壮
  10. 通俗原则: 接口设计避免标新立异
  11. 缄默原则: 如果一个程序没什么好说的,就保持沉默
  12. 补救原则: 出现异常时,马上退出并给出足量错误信息
  13. 经济原则: 宁花机器一分,不花程序员一秒
  14. 生成原则: 避免手工hack,尽量编写程序去生成程序
  15. 优化原则: 雕琢前先得有原则,跑之前先学会走
  16. 多样原则: 决不相信所谓不二法门的断言
  17. 扩展原则: 设计着眼未来,未来总比预想快

面向对象设计

基本元素

  • 封装
  • 继承
  • 多态

基本原则(S.O.L.I.D) )

  1. 单一责任
  2. 开放封闭
  3. 里氏替换
  4. 接口隔离
  5. 依赖倒置

设计模式

项目原则 参考


1
2
3
4
5
1. Passes the tests
2. Reveals intention
3. No duplication
4. Fewest elements
5. The rules are in priority order, so "passes the tests" takes priority over "reveals intention"
  1. 测试,测试再测试
  2. 减少繁琐的继承层次设计
  3. 函数功能单一,避免一个函数实现多种功能
  4. 提取重复代码,避免一点改动多处修改(不只限于函数,还包括类和package)
  5. 注意扩展性和接口设计
  6. 清楚简单的算法的复杂度

重构


目的

使软件更加容易被修改和理解。

方法

项目

  • 少三
    1
    2
    3
    4
    5
    6
    7
    8
    原则
    新代码设计时考虑重构,为后续添加和修改功能留下空间。
    代码设计待改进的地方(随后会有相应的案例分析)
    1. 事件机制
    2. 商店重复实现
    3. 各种重置的统一处理
    4. 微服务化

资料


  • UNIX编程艺术
  • 设计模式
  • 重构-改善既有代码的设计
  • 代码大全
  • 编写可读代码的艺术
ivan

ivan

8 日志
3 分类
8 标签
github weibo
© 2019 ivan
由 Hexo 强力驱动
主题 - NexT.Pisces