说明

所有的业务系统中。

都需要哦对交易中的用户的交易金额+交易次数进行限制。

本质:一定的时间维度,对指定的用户的指定类别的信息进行统计。如果超过一定的次数,则进行拦截(处罚)。

基本的交易限额限次

基本流程

所有的交易分为事前+事后。

发起交易之前,首先调用风控系统判断是否存在风险。

交易完成之后,把交易最后的结果要通知风控。(需要统计成功的交易时,需要状态)

风控

事前请求响应的状态

调用风控的时候,会有 3 个结果:

  1. 成功

  2. 失败

  3. 超时


成功时,直接根据是否允许,进行处理即可。

失败时,一般为风控系统内部异常,此时要结合业务的风险指数,一般建议直接拦截,不再进行下去。

超时,一般是风控系统较慢,比如存在慢 SQL(数据量较大时)、系统 FULL-GC 等,导致响应较慢超时。此时一般也建议按照失败的方式处理。

事后请求响应的状态

需要把一次操作(一笔交易)的状态通知风控,便于更新状态。

调用风控的时候,会有 3 个结果:

  1. 成功

  2. 失败

  3. 超时


成功时,说明调用成功。

时候失败超时问题

失败+超时的时候,要结合具体的业务。

是忽略还是继续?

我们后续会聊一聊如何保证交易系统和风控系统的状态一致性?

对于超时请求的处理

一般提供 2 种方式:

1)异步回调

2)同步反查状态接口

如果要求非常精准,那就是类似于业务系统+清结算系统一样,需要保证一致性。


事后风控状态更新的时间差问题

我们一般计算用户的限额限次,看的是用户成功交易的信息。

比如我们看同一个用户的 2 笔交易:

序号 交易 A 交易 B
1 事前风控A —–
2 xxx(交易A处理) 事前风控 B
3 xxx(交易A处理) xxx(交易 B 处理)
4 事后风控 A xxx(交易 B 处理)
5 ——- 事后风控 B

一笔交易的完成存在一定的时间差问题,那么这段时间以内的怎么办?

比如在时间序号2,此时交易A实际上已经发起,此时事后还没有落库;直接统计交易 B 的时候,实际会导致统计不到交易 A 的数据。

我个人能想到的 2 个方法:

1)用户业务维度加锁

可以用户+交易场景作为 key 加锁,或者暴力一点,直接以用户维度加锁。

当然,这个一般是在业务端加锁的,但是作为一个独立的系统,我们首先要保证自己系统的精准性。

2)事前风控的时候,把交易状态置为 P。事后请求成功为 S、失败为 F

当每次累加的时候,要包含 S + P 这两种状态。

如何累加?

数据库的选型

对于数据库的选型可以有多种:mysql、mongodb、redis 都行。

如果只是一些业务类的限制,比如开户不能超过 3 次,那么用DLTP 型数据库这种就基本可以满足(比如 mysql)。

但是,如果是核心的交易链路,还是推荐使用性能较好的 OLAP 型数据库或者缓存(比如 redis)。

全量聚合 VS 单维度累加

全量聚合

传统的 mysql 可以把全量的数据存储起来,然后我们通过 sum count 进行金额和次数的统计。

当然,mysql 还存在一个问题,那就是拓展性不是很好,这里说的不是很好,是相对于 mongodb 可以基于 json 形式的文本型数据库而言。

比如业务系统加了一堆字段,比如一个分账串。

对于 mysql 而言,可能就需要额外创建一些附加表,然后去做各种关联。

对于 mongodb 这种,就会简单很多,直接一个大的 json 对象丢进去,后面加字段也不需要改表结构。

优点:可以存储全量的数据,如果想增加一个维度统计,那么是直接支持的。

缺点:一般性能较差。

单维度累加

还有一种方式,可以把统计的维度摊平。

形如下面:

限次 key: merId + 交易场景 + count

限额 key: merId + 交易场景 + amount

当然,还可以结合具体的业务有一定的拓展,比如日维度,月维度,和维度。

限次 key: merId + 交易场景 + count + 时间维度

限额 key: merId + 交易场景 + amount + 时间维度

优点:累加的非常简单,性能比较好。

缺点:增加新维度的时候比较麻烦,需要把历史数据初始化进去。如果有多个维度的统计,这个就会变得很麻烦。

比如用户发起一笔快捷交易,我们需要更新用户的 日限额+日限次 + 月限额/限次。

这里说的麻烦,不是说更新多个就麻烦,而是说 redis 实际上是不支持事务的。

如果我们把 key 分开,一部分成功,一部分失败怎么办?

当前,我们可以改成另外一种结构形式:

key: mer_id + 交易场景

value: {
    日限额:xxx
    日限次:xxx
    月限额:xxx
    月限次:xxx
    ......
}

这种会导致对应的 value 结构比较大,好处是可以一次设置成功/失败。

在代码处理的时候,可以转换为 map,然后处理。按照指定的规则,处理对应的 key。

产品化维度?

如何设计一套,适用于所有的业务系统?

1)比如交易系统的限额限次

2)比如业务系统的,单个用户一天只能发起短信多少次,短信只能多少次等。

对应的触发/预警操作。

配置维度

系统标识 + 业务维度

所有的系统支持申请(可自动化流程)

业务维度:支持配置

用户信息:某一个指定的用户,或者操作者。

次数

总金额

接口设计

事前

  • beforeRisk
appId: 唯一应用标识
traceId: 唯一跟踪号
requestTime: 请求时间
checksum: 签名      (每一个应用,都有 appSecret。通过这个统一处理)

orderId: 业务订单号
transAmt: 交易金额
transType: 交易类别
prodId: 产品号
merId: 商户号
transTime: 交易时间 yyyyMMddHHmmssSSS
transDate: 交易日期 yyyyMMdd

要求:orderId + appId 唯一。

事后

  • afterRisk
appId: 唯一应用标识
traceId: 唯一跟踪号
requestTime: 请求时间
checksum: 签名      (每一个应用,都有 appSecret。通过这个统一处理)

orderId: 业务订单号
transStatus: 交易状态

参考资料

多线程使用redis进行累加结果不对,不能保证原子性解决方案

redis 对单个key进行大数据量incr