服务器可用性

  • 可用性概念
  • 服务器可用性
  • 进程容错
  • 进程容灾
  • 系统容灾
  • 数据容灾

可用性概念

什么是可用性

可用性指系统在面对异常时可以提供正常服务的能力,异常包括手写代码带入的bug、硬件故障、天灾人祸等, 正常服务要跟生存系统业务的概念有所区分。

4933701-49e3b2ff5097ddd3.png
正常业务 VS 生存系统业务

可用性并不等于易用性,易用性更多指代的是用户UI、用户习惯上的设计 。可用性指的是你所用的服务在你想用的时候它是不是照样可用。

可用性度量

计算机系统的可靠性用平均无故障时间(MTFF)来度量,也就是说计算机平均能够正常运行多长时间,才发生一次故障。系统的可靠性越高,平均无故障时间越长。

可维护性可以用平均维修时间(MTTR)来度量,即系统发生故障后维修和重新恢复正常运行平均花费的时间。系统的可维护性越好,平均修复时间越短。

计算机系统的可用性定义可以定义为

MTTF / (MTTF + MTTR) * 100%

互联网服务的服务定义为

服务总时长 / (服务总时长 + 服务总中断时长) * 100%  

可用性级别

4933701-c780a3c194c0ab1e.png
可用性级别

可用性的重要性

经济损失、运营风险、信任建立

服务器可用性

游戏服务器需要什么样的可用性目标呢?你在玩一款新推出的网游,忽然中断了1分钟,第一直觉会认为“刚才网络把我卡掉线了”...

  • 中断1分钟:“刚才网络把我卡掉了”
  • 中断5分钟:“刚才服务器卡了,我多试几次又好了”
  • 中断30分钟:“刚才游戏里恰好是xxx活动,我错过了,要求补偿。”
  • 中断数小时:玩家愤怒,打爆客服电话,全服公告补偿,影响收入。

影响业务可用性的因素

4933701-cffff7b7a455c959.png
影响业务可用性的因素

灾难问题一般由运维人员关注,但需要软件开发人员提供解决方案。

典型错误类型和影响范围

4933701-3ed1f745280cbe4b.png
典型错误类型和影响范围

功能异常通过几轮测试还是蛮容易发现的,但服务器宕机一般都是非常边界的问题引起的,比如空指针的访问、数组的越界、内存的OOM、死循环...

OOM指Out Of Memory,Linux系统自己在发现内存不足的时候,会挑选一些内存占用比较高的进程给杀掉。系统里跑太多耗内存的进程的时候,会产生的一个现象。

软件开发人员为什么要关注硬件错误呢?

硬件故障率

4933701-48dded3848bf4afe.png
硬件故障率

单台机器的小概率事件随着规模的扩大变成必然事件,要把机器故障当作软件容灾设计中的常规问题。

可用性建设

错误监控、错误容忍、错误恢复

4933701-ca76ce54d155e097.png
可用性建设

容灾容错

容灾容错分级

  • 进程容错:关注进程内部功能运行的稳定性,主要机制包括功能屏蔽和错误隔离等。
  • 进程容灾:关注单个进程或服务整体运行的稳定性,主要机制包括重启、resume、冷热备等。
  • 系统容灾:关注多个进程不可用情况下的可用性,主要机制包括冷热备、去单点设计等。
  • 数据容灾:关注数据的安全性和可用性,主要机制包括备份、流水日志等。

进程容错

进程容错思路

4933701-0c5251c9404a8ae6.png
错误隔离

在系统拓扑图中,绿点表示一个功能模块或一个进程, 它在不同层级其实概念上是一样的。此时,如果其中的一个进程出现了故障 (红点)。简单的解决思路就是能不能把它先给屏蔽掉先不提供服务,让别的模块提供正常的服务。

错误隔离原理

# server 主循环
server_proc()
{
  while(1)
  {
    # 处理外界发来的各种请求
    while(have_request(request))
      hanle_request(request);
    # 服务器上的定时器
    while(have_timer(timer))
      handle_timer(timer);
  }
}

服务器的两类驱动源:请求和定时器,对逻辑错误的隔离可转化为对引起的错误的请求和定时器的隔离。

错误隔离系统设计

  • 逻辑错误:基于动态开关框架对模块添加的动态开关,可随时关闭开启相关逻辑。
  • 数据错误:由于游戏服务器大多将对象数据保存于共享内存中,可见导致一场的驱动源实例或者内存对象通过接口进行隔离。

错误隔离框架

4933701-b6afbf77a52dbd33.png
错误隔离框架

错误隔离实现示例

# 对用户请求的隔离
int OnPlayerRequest(int player_id, Request &request)
{
  if(!request_allowed(request.op_type))
    return ERR_OPERATION_NOT_ALLOWED;
  else
    return request_handler(player_id, request.data);
}
# 对定时器的隔离
void OnTimeout(Timer &timer)
{
  if(!timer_allowed(timer.type))
    return;
  else
    timer_handler(timer);
}
# 内存池的访问接口
void *object_get(Object_Handler *handler)
{
  if(!object_allowed(handle))
    return NULL;
  return _obj_get_impl(hanle);
}

记录列表

OP List:
- OP_TYPE_TRADE (item_id)
- OP_TYPE_AUCTION
Timer List:
- TIMER_TYPE_A
- TIMER_TYPE_B
Object List:
- object_handler_1
- object_handler_2

进程容灾

引起进程宕机的原因

  • 代码bug:空指针、越界...
  • 系统限制:堆栈溢出、内存溢出...
  • 人为因素:运维误操作、死循环强杀...

是否杀掉进程后,将进程重启下就OK呢?这个就涉及到进程的有状态和无状态。

例如:有一个叫做GET_SEQ_SRV的服务器,它的作用就是每次调用GET_SEQ_REQ请求的时候返回SEQ_NUMBER,然后这个SEQ_NUMBER不断加一。另外一个服务器叫做ADD_ONE_SVR,它的功能就是给它一个数字COUNT然后返回N+1的结果。这两类服务器的区别在哪里呢?区别在于GET_SEQ_SRV服务器上的SEQ_NUMBER是要始终保持在服务器端的。如果服务器挂掉了,这个SEQ_NUMBER就没有了。而ADD_ONE_SVR服务器,它所有计算的上下文信息都是客户端自己带来的,服务器只是起到了一个计算的作用并吐回一个结果,它是不需要保存 任何的上下文。

4933701-7157109da846ac7f.png
有状态和无状态的服务器
  • 有状态的服务器:是需要服务器保存请求处理的上下文信息的
  • 无状态的服务器:是请求自己携带处理所需的上下文信息

那么,游戏服务器适合用那种模型呢?其实两类都有。

一种朴素的状态维护方式

4933701-92480aa422ee885d.png
一种朴素的状态维护方式

手游使用较多,它的数据完全是放在可靠存储里面,服务器相当于每次收到客户端请求的时候,它先去可靠存储中把数据读出来,计算完毕返回结果,然后再把数据给存回去。

其优点是健壮性、维护和扩容更加容易。因为把可靠性完全托管给后边的存储模块,服务器自己也就没有所谓的可靠或不可靠了。

其缺点是所有操作需要和存储进行异步交互,编码复杂访 问存储需要额外通信时间,降低了响应速度设计多个数据对象时需要加锁,影响吞吐。

此种方式不适合应用在强交互类的游戏上,只适用于简单的手游上。交互类的游戏不适合这种把可靠性完全往后端存储下沉的方案。

使用共享内存维护状态

共享内存技术是利用Linux的共享内存,Linux系统中有一个叫做shmget的系统调用,它会在kernal内核中分配一块共享内存。然后通过shmat接口可以把这块内存映射到进程的内存空间里。如果有多个进程的话,它是可以同时采取这块内存,也就是说,它的物理存在只是在kernal中有一份,但它在多个进程的地址空间中会各有一份。 更多的情况下,它是拿来做进程间通信用的。

4933701-1ea422e3157a5e87.png
Linux共享内存技术

如果不调用shmdt,进程退出后shm仍然保存在内核中。进程重新启动后重新shmat这块内存的技术我们称之为resume。

4933701-dc78e3dc8be3fe23.png
Linux共享内存技术

共享内存数据的组织形式

  • 游戏服务器内常见的状态数据:角色信息、队伍信息、帮盟信息、活动信息...
  • 数据特点:大小固定、数量固定

共享内存内部数据能怎么组织呢?是使用内存池还是对象池呢?内存池相当于Linux里面的一个堆的管理 ,因为事先不知道所需内存块的大小。要去管理不同大小的内存块的分配,还要去管理分配过程中产生的碎片。对象池的概念事先已经有了大小固定的对象,数量也是固定的,一开始其实就可以分配好。

4933701-a1f0d3f0bc1b825f.png
对象池实现示例

Mempool就是整个共享内存块的一个映射,它里面会根据内存块的类型会分成不同的池子unitpool,每个unitpool里面会记录一些内存块的信息,包括未分配的空闲列表、unit使用数量、单个unit的size...,剩下的空闲内存块以链表的形式,以freelistnode链表的形式给串联起来。

是不是已经万无一失呢?如果resume失败?如果进程所在硬件故障呢?如果进程还是单点呢?

系统容灾

单点

单点是指系统中只存在唯一实例的节点,是获取或维护某些值(决议)的权威方,单点可分为关键路径单点和非关键路径单点。关键路径单点是说少了这个节点的话,整个业务就无法正常地运行了。非关键路径单点是 说少了这个节点无所谓,大不了这部分功能不用了。

4933701-5fe8054d517394da.png
哪些是单点哪些是关键路径单点呢

单点是只存在唯一实例的节点,世界之上的都是属于单点。 世界属于关键路径节点,是必不可少的节点。

针对单点问题的应对策略

  • 硬件策略:可用性层级分类、同级部署、冗余部署...
  • 软件策略:服务失效(隔离)、主从或冷热备...
4933701-45770c5b2070049c.png
硬件分类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值