How it Works#

Monitoring Data Store#

我们使用 DynamoDB 作为监控数据的存储后端. 无论我们要采集什么监控数据, 它本质上都是一个时间序列. 它有以下三个属性:

  • series_id: 时间序列的 ID, 一般是 server_id 和 use_case_id 的组合. 例如你想要监控 sbx-blue 这台服务器上的游戏的在线人数, 那么 series_id 就是 sbx-blue-online-players. 这是 DynamoDB 的 hash key.

  • create_at: 数据采集的时间. 数据一旦被写入就不会被修改了.

  • expire_at: 数据的过期时间, 我们启用了 DynamoDB 的 TTL 功能, 一旦数据过期后, 过一段时间就会被自动删除. 关于 TTL 的详情可以参考我写的这篇博文 DynamoDB Time to Live (TTL).

对于不同的 use case, 我们会额外有不同的 attribute. 由于 DynamoDB 是 NoSQL 是 schemaless 的, 所以我们可以根据情况任意添加 attribute.

下面是 DynamoDB 的表结构源码:

dynamodb.py
 1# -*- coding: utf-8 -*-
 2
 3"""
 4Monitoring data store DynamoDB table schema.
 5"""
 6
 7import enum
 8import pynamodb_mate.api as pm
 9
10
11def make_dynamodb_table_name(env_name: str) -> str:  # pragma: no cover
12    return f"wserver-{env_name}-server-monitoring"
13
14
15class UseCaseEnum(str, enum.Enum):
16    worldserver_status = "worldserver_status"
17    ec2_rds_status = "ec2_rds_status"
18
19
20class Measurement(pm.Model):
21    """
22    Acore server monitoring time series measurement data store.
23
24    :param series_id: the time series id, usually it is in format of
25        ``{server_id}-{use_case_id}``, for example ``sbx-blue-worldserver_status``,
26        ``sbx-blue-ec2_rds_status``.
27    :param create_at: the time when the measurement data is created.
28    :param expire_at: the time when the measurement data is expired. DynamoDB
29        will automatically delete the expired data in a few days.
30    """
31
32    series_id: pm.REQUIRED_STR = pm.UnicodeAttribute(hash_key=True)
33    create_at: pm.REQUIRED_DATETIME = pm.UTCDateTimeAttribute(range_key=True)
34    expire_at: pm.REQUIRED_DATETIME = pm.TTLAttribute()
35
36
37class WorldServerStatusMeasurement(Measurement):
38    """
39    World server status measurement data.
40
41    :param is_ec2_exists: is EC2 exists.
42    :param is_rds_exists: is RDS exists.
43    :param is_ec2_running: is EC2 running.
44    :param is_rds_running: is RDS running.
45    :param ec2_status: EC2 status in string.
46    :param rds_status: RDS status in string.
47    :param connected_players: connected players.
48    :param characters_in_world: characters in world.
49    :param server_uptime: server uptime in seconds.
50    :param cpu_usage: 0 ~ 100, float. 50.0 means 50%.
51    :param memory_usage: 0 ~ 100, float. 50.0 means 50%.
52    :param total_memory: total memory in MB.
53    :param available_memory: available memory in MB.
54    """
55
56    is_ec2_exists: pm.OPTIONAL_BOOL = pm.BooleanAttribute(default=None, null=True)
57    is_rds_exists: pm.OPTIONAL_BOOL = pm.BooleanAttribute(default=None, null=True)
58    is_ec2_running: pm.OPTIONAL_BOOL = pm.BooleanAttribute(default=None, null=True)
59    is_rds_running: pm.OPTIONAL_BOOL = pm.BooleanAttribute(default=None, null=True)
60    ec2_status: pm.OPTIONAL_STR = pm.UnicodeAttribute(default=None, null=True)
61    rds_status: pm.OPTIONAL_STR = pm.UnicodeAttribute(default=None, null=True)
62    connected_players: pm.OPTIONAL_INT = pm.NumberAttribute(default=None, null=True)
63    characters_in_world: pm.OPTIONAL_INT = pm.NumberAttribute(default=None, null=True)
64    server_uptime: pm.OPTIONAL_INT = pm.NumberAttribute(default=None, null=True)
65    cpu_usage: pm.OPTIONAL_FLOAT = pm.NumberAttribute(default=None, null=True)
66    memory_usage: pm.OPTIONAL_FLOAT = pm.NumberAttribute(default=None, null=True)
67    total_memory: pm.OPTIONAL_INT = pm.NumberAttribute(default=None, null=True)
68    available_memory: pm.OPTIONAL_INT = pm.NumberAttribute(default=None, null=True)
69
70
71class Ec2RdsStatusMeasurement(Measurement):
72    """
73    EC2 and RDS status measurement data.
74
75    :param is_ec2_exists: is EC2 exists.
76    :param is_rds_exists: is RDS exists.
77    :param is_ec2_running: is EC2 running.
78    :param is_rds_running: is RDS running.
79    :param ec2_status: EC2 status in string.
80    :param rds_status: RDS status in string.
81    """
82
83    is_ec2_exists: pm.OPTIONAL_BOOL = pm.BooleanAttribute(default=None, null=True)
84    is_rds_exists: pm.OPTIONAL_BOOL = pm.BooleanAttribute(default=None, null=True)
85    is_ec2_running: pm.OPTIONAL_BOOL = pm.BooleanAttribute(default=None, null=True)
86    is_rds_running: pm.OPTIONAL_BOOL = pm.BooleanAttribute(default=None, null=True)
87    ec2_status: pm.OPTIONAL_STR = pm.UnicodeAttribute(default=None, null=True)
88    rds_status: pm.OPTIONAL_STR = pm.UnicodeAttribute(default=None, null=True)

Capture Data#

我们主要是要采集两类数据:

  1. EC2 和 RDS 的状态信息, 比如是否在线, CPU 和 内存使用情况等. 这一功能主要由 acore_server_metadata 来提供.

  2. worldserver 的统计数据, 比如在线人数, 服务器在线时间等. 这一功能主要由 acore_soap 来提供.

Telemetry#

对于 EC2 和 RDS 的状态, 我们使用遥测的方式. 一般是用一个 Lambda Function 来调 AWS API 来获取游戏服务器的状态. 这里我们显然不能用 EC2 本身来测量, 因为 EC2 本身可能会挂掉.

Localmetry#

对于 worldserver 的统计数据, 由于我们必须要通过跟 SOAP API 通信来获取数据. 如果是在 worldserver 所在的 EC2 上跟 SOAP 通信, 一般几百毫秒就完成了. 而如果我们使用 SSM run remote command, 那么就需要花 3-5 秒甚至更长的时间. 如果我们使用的是 Lambda Function 来运行遥测代码, 那么等待的时间也是要花钱的. 而如果放在 EC2 上跑, 内存开销可以忽略不计, 而且速度会更快, 并且从逻辑上如果 EC2 挂掉了, 那么游戏服务器也挂掉了, 你自然也采集不到任何数据. 所以在 EC2 上跑一段程序来采集 worldserver 的统计数据是最佳选择.

Analyze Data#

todo