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#
我们主要是要采集两类数据:
EC2 和 RDS 的状态信息, 比如是否在线, CPU 和 内存使用情况等. 这一功能主要由 acore_server_metadata 来提供.
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