计数系统设计

在营销的场景里有三要素

  1. 用户
  2. 商品
  3. 优惠

在这三个要素里,再加一些如时间,数量,频次等变量,会演化出各种组合,使得业务变得非常灵活。各业务线为了满足业务,一般都会各自实现,且多数情况下都会重复实现,而且实现起来各地方都会产生交叉配置,交叉互斥的问题。在观察到这些问题后,总结并尝试用一个计数频次中间件形态的系统统一解决。一是解决了复杂度、重复建设;二是统一处理后,数据层面上将整体打通,对于用户监控、风控、特征、行为上提供完整数据。

在电商的系统中,有商品、用户、优惠三种要素。这三种要素分别在数量,时间,频次上又有多维度的分类。

如商品限量100份(数量)对用户每天(时间)每人购买限量2份(频次),按这种规律下,用户使用优惠也同样遵循这种规则,即

优惠券总数100张(数量),用户每场(时间)可使用1张(频次)

抽奖优惠也一样,即

优惠总数100张,用户每天(时间)可领取1张(频次)

发放优惠也一样,即

优惠总额100元,用户每天(时间)可优惠1次(频次)

分类法

  1. 按主体,维度分类
主体\维度 数量 时间 频次
商品 总数 指定周期,天,小时,周 不限,1份,多份
用户 总数 指定周期,天,小时,周 不限,1份,多份
优惠 总数 指定周期,天,小时,周 不限,1份,多份
活动 总数 指定周期,天,小时,周 不限,1份,多份

按上面这个分类,主体会变,维度会变,先设计领域模型,分别为实体(entity);维度(dimensions);实体维度关系(entityDimensionsRel),关系领域将表示,主体将拥有多个维度,并在多个维度中设定维度值,如商品每天限购1份,那么将在entityDimensionsRel中的limit_value中设定1,time_interval设定为1天

领域模型

数据模型

数据库表模型

  1. 主体表 (Entities)
  • EntityID: 主键,唯一标识某个主体(如商品、优惠、活动等)
  • EntityType: 主体类型(如商品、优惠、抽奖等)
  • EntityName: 主体名称
sql 复制代码
CREATE TABLE Entities (
    EntityID INT PRIMARY KEY,
    EntityType VARCHAR(50),
    EntityName VARCHAR(100)
);
  1. 维度定义表 (Dimensions)
  • DimensionID: 主键,唯一标识某个维度(如数量、时间、频次)
  • DimensionName: 维度名称(如"数量"、"时间"、"频次")
  • DimensionType: 维度类型(如整数、日期、时间区间等)
sql 复制代码
CREATE TABLE Dimensions (
    DimensionID INT PRIMARY KEY,
    DimensionName VARCHAR(50),
    DimensionType VARCHAR(50)
);
  1. 主体-维度关系表 (EntityDimensions)
  • EntityDimensionID: 主键,唯一标识某个主体-维度关系
  • EntityID: 外键,关联到Entities表
  • DimensionID: 外键,关联到Dimensions表
  • LimitValue: 该维度的限制值(如限量、限次等)
  • TimeInterval: 时间间隔(如每天,每场活动等),适用于时间维度
sql 复制代码
CREATE TABLE EntityDimensions (
    EntityDimensionID INT PRIMARY KEY,
    EntityID INT,
    DimensionID INT,
    LimitValue INT,
    TimeInterval VARCHAR(50),
    FOREIGN KEY (EntityID) REFERENCES Entities(EntityID),
    FOREIGN KEY (DimensionID) REFERENCES Dimensions(DimensionID)
);

上面这些表可以完整记录主体在各维度的限制值。还需要一张实时记录主体数据在各维度的过程数据

  1. 实例数据记录表 (EntityDimensionRecords)
  • RecordID: 主键,唯一标识某条记录
  • EntityID: 外键,关联到Entities表
  • DimensionID: 外键,关联到Dimensions表
  • UserID: 外键,关联到用户表(适用时)
  • Value: 在该维度上记录的数值
  • RecordDate: 记录的日期或时间。
sql 复制代码
CREATE TABLE EntityDimensionRecords (
    RecordID INT PRIMARY KEY,
    EntityID INT,
    DimensionID INT,
    UserID INT,
    Value INT,
    RecordDate DATETIME,
    FOREIGN KEY (EntityID) REFERENCES Entities(EntityID),
    FOREIGN KEY (DimensionID) REFERENCES Dimensions(DimensionID)
);

数据流转

样例1

用户参与大转盘活动,活动周期内只能抽一次奖

主体:大转盘活动

维度:时间,计数

关系:限制值(一次);限制时间(活动周期);

简单填入数据,业务就可以满足了。

现在又有新的业务需求,即这个活动周期为1个月,用户每天都要能参与一次,现有的表模型就不支持了!

关系表(EntityDimensions)的时间字段TimeInterval是个varchar,表示1天、1小时等枚举值是可以,如果表示时间段,每天等带频次的复杂时间是不够的,所以,最好的办法之一就是将时间维度拆出一张表

sql 复制代码
CREATE TABLE TimePeriods (
    TimePeriodID INT PRIMARY KEY,
    PeriodName VARCHAR(50),       
    TimeInterval VARCHAR(50),     -- 时间周期(每天、每小时、每半小时、每分钟)
    StartTime TIME,               -- 时间段开始时间
    EndTime TIME                  -- 时间段结束时间
);

有了这张表后,就可以表示周期时间段的计数关系了。

有了这张表后,业务在查询EntityDimensionRecords,就知道这条记录的周期,开始时间结束时间。

如果时间频次TimeInterval是天,当前时间为2022-12-12时,EntityDimensionRecords.RecordDate值为2022-12-11时,那么EntityDimensionRecords需要重新生成一条记录,并且RecordDate值为2022-12-12;

越往深入,能够枚举出的维度是有限的,在大的方向上,每一个维度也可以拆分出一个领域也是可行的。因为维度值不一样,单纯用维度一个变量有点满足不了需求,如时间维度和数量维度,维度值用一个变量就表示不了,时间范围最少需要2个字段。所以在不同的业务发展时期,只有当时最合适的设计,没有永远对的设计。在考虑扩展性的同时,也需要考虑研发成本。

如果以后有需求对计数的方式有变化,比如一次计数消耗2次机会,那相应的,再在EntityDimensions.LimitValue字段上做一个新表来表示计数的复杂方式。万变不离其宗,好的设计一定是支持业务慢慢成长起来的。糟糕的设计是不停的妥协设计,随着时间推移开发和维护将会越来越困难!