一、整体架构图
二、核心业务系统介绍
2.1.接入层统一异常处理逻辑
2.2.邀请服务逻辑
2.3.权益发放服务
2.4.排行榜服务
2.4.1.榜单服务数据结构
数据结构分为两块:
- 配置中心数据,因为排行榜没有后台配置平台,只能将配置数据放到配置中心,具备实时更改配置的能力
- 数据表,主要是排行榜核心数据结构
-- 配置中心信息
榜单配置信息数据:
{"rank_code": {"name": "榜单名称","rank_type": "用于榜单归类","rank_default": {"rank_alias": "default","rank_biz_id": "默认榜单筛选条件:能量值","rank_sub_biz_id": "none,对应过滤值"},"rank_children": [{"rank_alias": "出行能量值:consumption","rank_sub_biz_id": "consumption"},{"rank_alias": "邀请能量值:invite","rank_sub_biz_id": "invite"}],"rand_period": [{"periodType": "day"},{"periodType": "week"},{"periodType": "month"},{"periodType": "year"},{"periodType": "range"}],"beginTime": "开始时间","endTime": "结束时间"}
}
榜单周期id:
day:当前日期yyyy-MM-dd;
week:w-当前周第一天;
month:yyyy-MM;
year:yyyy;
rang: 开始时间-结束时间,
-- 数据库表结构
CREATE TABLE `activity_rank_data_source_detail` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',`request_id` varchar(128) NOT NULL DEFAULT '' COMMENT '唯一键',`entity_id` varchar(64) NOT NULL DEFAULT '' COMMENT '实体id',`entity_type` smallint(6) NOT NULLactivity_rank_period_count DEFAULT '0' COMMENT '实体类型,1:用户id',`entity_data_real_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '该数据发生的真实时间',`entity_data_biz_id` varchar(128) NOT NULL DEFAULT '' COMMENT '业务线,1:能量值',`entity_data_sub_biz_id` varchar(128) NOT NULL DEFAULT '' COMMENT '二级业务:能耗、助力',`entity_data_num` int(11) NOT NULL DEFAULT '0' COMMENT '数据大小',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_request_id` (`request_id`),KEY `idx_biz_id` (`entity_data_biz_id`,`entity_data_sub_biz_id`,`entity_id`,`entity_data_real_time`,`entity_data_num`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动榜单-榜单数据更新明细';CREATE TABLE `activity_rank_period_count` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',`rank_biz_id` varchar(128) NOT NULL DEFAULT '' COMMENT '榜单业务id',`rank_sub_biz_id` varchar(128) NOT NULL DEFAULT '' COMMENT '榜单业务id',`rank_code` varchar(128) NOT NULL DEFAULT '' COMMENT '榜单code',`rank_name` varchar(128) NOT NULL DEFAULT '' COMMENT '榜单名称',--待定`entity_id` varchar(64) NOT NULL DEFAULT '' COMMENT '实体id',`entity_type` smallint(6) NOT NULL DEFAULT '0' COMMENT '实体类型,1:用户id',`period_type` smallint(6) NOT NULL DEFAULT '0' COMMENT '周期类型,1:日、2:周、3:月,4:年、5:固定区间',`period_id` varchar(64) NOT NULL DEFAULT '' COMMENT '周期唯一code',`period_begin_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '周期开始时间,所有的周期开始-结束时间为左闭右开',`period_count` int(11) NOT NULL DEFAULT '0' COMMENT '周期统计数值',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_period_entity_id` (`rank_biz_id`,`period_id`,`entity_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动榜单-榜单周期数据统计';
2.4.2.榜单服务更新流程
说明:
-
redis中会缓存一份topN的数据
-
更新时机
- 用户数据变更时,实时更新
- 定时从DB中加载对应的topN数据
- topN数据只能根据统计后的用户-周期数据进行更新。
-
redis内存数据结构:
rank_source_biz_id(榜单类型)_day(周期类型)_period_code: zset rank_top_biz_id(榜单类型)_day(周期类型)_period_code(当前): List<String> ,user_id,能量值,排名
数据注意事项:
- rank_source_xxx避免元素数量过多(目前暂时忽略,元素个数在1w以内)
-
rank_top_xxx
- 并列排名的问题,topN实际元素可能是N+n (是否要做限制)
2.4.3.多维度榜单设计
zset value:
long 类型 最大值为: 111111111111111111111111111111111111111111111111111111111111111 63位
value需要包括: 排行榜值count 和 时间戳
所以我们可以根据我们业务的值来选择 使用多少bit位来表示排行榜值,和时间戳
假如我们业务内,count取值范围在0-10000, 时间戳在 2025-01-01 00:00:00~ 2026-01-01 00:00:00
那么我们可以这样设计
10000的二进制表示为: 10011100010000 共14位
2025-01-01 00:00:00 日期表示
毫秒级: 11001010000011101011100100000100000000000 41 位
秒: 1100111011101000001010100000000 31 位
2026-01-01 00:00:00 日期表示
毫秒级: 11001101101110101001000110011010000000000 41 位
秒: 1101001010101010100100010000000 31 位
所以说如果表示这一年时间,我们最多可以使用41位 ,假设我们在榜单上配置一个基准时间,例如,就是2025-01-01 00:00:00 的毫秒,
那么存储时间,就可以设置为该基准时间的相对时间,
2026-01-01 00:00:00 和2025-01-01 00:00:00 之间最大时间差为:
11101010111101100010010110000000000 共 35位
如果只精确到秒:1111000010011001110000000 共 25位
这是我们就可以在一个用一个long类型数据表示这两部分数据
高位用15位表示 count ,低位用40位表示时间.
以 count = 1000,time = 2025-06-01 10:12:30 基准时间为:2025-01-01 00:00:00 ,精确秒来计算:
合并计算:
//偏移位数
int timeShift = 45;
Date date1 = DateUtil.parse("2025-01-01 00:00:00", DatePattern.NORM_DATETIME_FORMAT);
Date date2 = DateUtil.parse("2025-06-01 10:12:30", DatePattern.NORM_DATETIME_FORMAT);
long diff =Long.valueOf(( date2.getTime() -date1.getTime()) /1000);
int count = 1000;
long result = 0L;
result = (result | count) << timeShift;
result = result | diff;
System.out.println("001:"+Long.toBinaryString(result));
//反解析
int count1 = (int)(result >> 45);
long diff1 = result^((count1 | 0L)<<45);
System.out.println("002:"+Long.toBinaryString(count1));
System.out.println("022:"+Long.toBinaryString((count1 | 0L) << timeShift));
System.out.println("003:"+ Long.toBinaryString(diff));
System.out.println("004:"+ Long.toBinaryString(diff1));
三、服务治理