请教一个系统设计题

2024-04-02 10:47:23 +08:00
 zdking08135
一个系统,客户端会上报统计点,上报本身不去重,报一次记一次:
uid ,地域(城市省份国家),打点时间(时间戳)。

这里定位服务保证地域唯一。

要求实现如下能力:

- 统计指定地域(最小城市维度,省份,国家也可以)与时间范围(天维度)内的单 uid 单日平均打点数
(结果是按天输出,每天一个指定地域平均数)。

- 查询支持实时 & 支持输入。

- 时间范围,最长 1 年。

- 地域支持多地域 or 查询,但是多地域出现的 uid 需要合并统计。(比如,用户同一天在 A,B 两地上报,如果查询条件为 A or B ,那么这个用户的打点数要合并计算)

指标:用户量 10 亿,单 uid 单日打点数 10~50 之间,城市范围覆盖到全球。
4077 次点击
所在节点    程序员
22 条回复
wei2629
2024-04-02 11:19:47 +08:00
找个时序数据库
lsk569937453
2024-04-02 11:29:25 +08:00
## 假设
- 存储每天 10 亿*50(次)*100byte=5TB(存储量太大,上 hbase 吧)
- 打点接口最多每秒访问次数:10 亿次
- 打点接口最多每秒占用的网络带宽:10 亿*100byte=100GB(万兆网卡可能不够用了)
- 单中心情况下网络延迟:地球上两点间最长距离为 20000 公里/光速(299792458m/s)=0.066s ,即单次请求的网络延时为 0.066*2=1.3(s)

## 系统设计
- 打点接口收到数据直接异步写入 kafka 集群,假设接口单次处理时间为 0.5ms,则单机 QPS 为 2000,处理 10 亿条数据需要的机器数量为:10 亿/2000=5w 台。
- 同时我们开线程从 kafka 集群读取数据,格式化后写入 HBase 集群。

### 数据库设计


Hbase 的 rowkey 设计为:地域+时间戳+uuid
- 统计指定地域:直接地域+时间范围全部查出来即可
- 地域支持多地域 or 查询:根据查询条件查询出来,将所有的数据写入到 kafka ,然后由 storm/spark/flink 做实时的统计,然后将结果写入到数据库中。
llsquaer
2024-04-02 11:38:39 +08:00
给你们老板说下,别来不来就 10 亿,先 10 万的开始
thedinosaurmail
2024-04-02 11:50:14 +08:00
clickhouse , 10 亿还好 ,按天分区就行
zdking08135
2024-04-02 11:54:40 +08:00
@lsk569937453
感谢老哥,这里不是每秒 10 亿次,保证一天能抗住 10 亿 * 50 次上报就行了,大约是 60w 的 qps ,这个不是重点。
重点是怎么支持查询。

--------------------

"根据查询条件查询出来,将所有的数据写入到 kafka ,然后由 storm/spark/flink 做实时的统计,然后将结果写入到数据库中。"

这里,如果想查比如上海+苏州范围,两地一共 2kw 用户,10 亿条记录
需要把上海和苏州的用户记录数据全部读出来,再写 Kakfa 做统计?
zdking08135
2024-04-02 11:58:39 +08:00
@llsquaer 系统设计题啦,不是实际业务,实际肯定会取舍。
yjhatfdu2
2024-04-02 12:00:23 +08:00
这个量,clickhouse 集群,做好表的设计,选好排序键、字段编码和压缩,按天分区,这个量写入和查询问题应该都不是很大。顺便,兄弟你这是在做天网么
zdking08135
2024-04-02 12:19:08 +08:00
@yjhatfdu2
@thedinosaurmail

用 clickhouse 的话,具体一点呢?表怎么设计?有哪些字段?
查询要怎么写?效率如何,可以实时吗?
thedinosaurmail
2024-04-02 12:25:49 +08:00
直接写 clickhouse 就行 ,不需要怎么设计设计
uid ,country ,province ,create_at

主要是要判断好按什么排序就行
thedinosaurmail
2024-04-02 12:25:57 +08:00
在使用 ClickHouse 进行表的设计时,针对您的需求,我们需要考虑如何优化存储和查询效率,尤其是面对大规模数据和复杂查询(如跨地域合并统计)。以下是一个基于您需求的示例表结构,包括了用户 ID 、打点时间、地域信息和打点数。

首先,考虑到数据量和查询需求,建议使用 MergeTree 系列引擎,它适用于大数据量的存储和分析,支持高效的数据插入和实时查询。

表结构设计
sql
Copy code
CREATE TABLE user_events
(
`event_date` Date,
`user_id` UInt64,
`city_id` UInt32,
`country_id` UInt32,
`event_count` UInt32,
`event_datetime` DateTime
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, country_id, city_id, user_id)
SAMPLE BY user_id
SETTINGS index_granularity = 8192;
字段解释:
event_date: 打点发生的日期,用于分区和快速过滤。
user_id: 用户的唯一标识符。
city_id: 城市的唯一标识符,需要有一个额外的映射表来解释每个城市 ID 对应的实际城市。
country_id: 国家的唯一标识符,同样需要一个映射表来详细说明。
event_count: 该用户在该日的打点数,考虑到您的业务场景,可能需要在数据插入前进行聚合计算。
event_datetime: 打点的具体时间点,支持精确到秒的时间戳,可用于进一步的时间序分析。
注意事项:
分区策略:根据 event_date 进行分区,可以有效地管理数据的存储和查询,尤其是对历史数据的分析。
排序键:通过(event_date, country_id, city_id, user_id)进行排序,优化查询性能,特别是当进行地域和时间范围的查询时。
采样:通过 SAMPLE BY user_id 支持对数据进行采样查询,适用于需要估算或快速分析的场景。
索引粒度:index_granularity 设置为 8192 ,这是一个平衡查询速度和存储效率的配置。根据实际数据量和查询模式,这个值可能需要调整。
多地域查询设计思路:
对于跨地域的统计分析,可以在查询时通过 GROUP BY 语句实现。例如,如果需要合并计算用户在同一天内不同城市(或国家)的打点数,可以通过将 user_id 和 event_date 作为聚合的关键字,然后对 event_count 求和。
dlmy
2024-04-02 13:13:59 +08:00
Log -> Kafka -> Flink ↓
--> ODS -> DWD -> DWM -> DWS -> ADS ↓
--> ClickHouse -> API ↓
--> Visualization Panel

看得懂这个,你就知道怎么做了
yjhatfdu2
2024-04-02 14:55:08 +08:00
clickhouse 造一天数据试试看,单机 64 核 epyc 256G ram
建表,目前试下来效率最高的表结构
create table test4
(
time datetime CODEC (DoubleDelta, LZ4),
country UInt8 CODEC (DoubleDelta, LZ4),
province UInt8 CODEC (DoubleDelta, LZ4),
city UInt16 CODEC (DoubleDelta, LZ4),
uid UInt32
) engine = MergeTree() partition by toYYYYMMDD(time)
order by (time, country, province, city) settings index_granularity = 65536;

先造 10 亿数据,分布在一天内
insert into test4
select dateAdd(second, number / 1000000000, toDateTime('2024-04-02'))
, rand32() % 200
, rand32(1) % 250
, rand32(2) % 100
, number + 1
from numbers(1000000000);
-- 然后扩增到 32 倍
insert into test4 select * from test4;
insert into test4 select * from test4;
insert into test4 select * from test4;
insert into test4 select * from test4;
insert into test4 select * from test4;

SELECT count(*)
FROM test4

Query id: a4a01197-a22b-4a0d-9747-26555229ff58

┌─────count()─┐
│ 32000000000 │
└─────────────┘

1 row in set. Elapsed: 0.004 sec.
一共 320 亿
等后台 merge 完才 14.28 GiB 磁盘占用
楼主要的查询
WITH r AS
(
SELECT count() AS c
FROM test4
WHERE country = 100
GROUP BY uid
)
SELECT avg(c)
FROM r

Query id: c634e7a7-13fa-4d40-9f30-e6e43105ffe9

┌─avg(c)─┐
│ 32 │
└────────┘

1 row in set. Elapsed: 0.168 sec. Processed 160.30 million rows, 801.18 MB (954.12 million rows/s., 4.77 GB/s.)
0.168 秒完成

这样看起来,一年的数据单机也问题不大
注意,不同的建表语句尤其是 CODEC 非常影响存储空间和性能
siaronwang
2024-04-02 17:01:07 +08:00
apache drios
MoYi123
2024-04-02 17:51:40 +08:00
只要想办法把 Euler Tour Tree 存数据库里就行了.
hefish
2024-04-02 18:17:21 +08:00
现在就要准备毕业设计啦。。。这么早啊。。。
1018ji
2024-04-02 18:35:25 +08:00
有时间范围只能现算,又不能预聚合,ck doris 之类试试吧
wu00
2024-04-02 18:44:47 +08:00
牛批,学习一下
zdking08135
2024-04-02 22:32:10 +08:00
@yjhatfdu2

NB 了,感谢,看来要多研究一下这个软件。
话说,可以顺便尝试复杂查询?

比如(city = 100 or city = 101) and date < '2024-04-02' and date > '2024-03-31'
yjhatfdu2
2024-04-03 10:55:57 +08:00
@zdking08135 当然可以,不过按照这个编码形式,肯定要指定 country
zzmark06
2024-04-03 12:42:46 +08:00
就这么点数据,想这么多,又这又那的
这点量都摸不到 doris/ck 单机瓶颈

拿 ck 来说,上面给出 ck 表结构的兄弟,@yjhatfdu2 表排序键有问题,排序优先遵循业务必选条件,再根据基数由低到高。建议调整排序顺序为 country,province,city,time 编码方式里,时间去掉 doubledelta ,追求压缩率平衡不用 lz4 ,改用 zstd(1)差不多就这样了。
你这第一个就是高基数,压缩比会很低,速度上不来

对列存来说,整分区 count 都是 O(1)消耗的元数据查询,看不出性能

至于表分区键选用按日还是按月,需要考虑业务平常查询到底按什么的多些。经常跨度大的就改为按月,反之按日。若是业务有按国家为租户的习惯,那分区把国家带上再按月也合理。
若是还有一些大范围时间内区域统计需求,上 projection 来预计算

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1029046

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX