首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >优惠券系统设计

优惠券系统设计

原创
作者头像
榴莲其实还可以
修改2020-06-17 15:47:10
4.3K0
修改2020-06-17 15:47:10
举报

前言

对于一个电商系统,一般都会有很多的促销手段,包括优惠券,拼团,砍价,老带新等等。我们在线教育的产品(腾讯课堂,企鹅辅导等)作为一个电商系统(商品比较单一,主要是卖课),自然也少不了会接入这些促销系统来提升我们的活跃用户与流水,就腾讯课堂而言,优惠券是众多促销手段中使用频次最高,优惠金额最多的一种手段了。

对于一个优惠券系统,其中最核心的操作就三个发券--->领券----->使用

发券

谁来发?

一般来说有商户可以发,平台也可以发。商户发的优惠券只能用于商户自身的商品,平台发的优惠券适用的范围就非常广了。

优惠券基本属性

  • 优惠秋的类型:立减券,满减券,折扣券等
  • 优惠券基本描述:比如活动名称等
  • 优惠券发行方:
  • 优惠券的发行方式:
  • 优惠券的有效期:一般有两种,固定起止时间的有效期,领取后一定时间内过期
  • 优惠券面额:
  • 优惠券的满减条件:
  • 优惠券的发行量:

领券

领取限制

  • 谁能领:一张优惠券是所有用户都可以领取还是只能指定的用户可领取
  • 领取上限:一个优惠券最多能领取多少张?
  • 领取方式:用户主动领取 还是 自动发放被动领取

使用

使用规则是可以非常多样的,比如

  • 适用于某个商品
  • 适用于多个商品
  • 适用于某种类别的商品
  • 全平台适用等等

使用规则这里是可以搞出很多玩法来的,就课堂与辅导两个产品而言,可以优惠券适用于某些课程(包),某些类别的课程,某些机构的课程,某些年级,某些班型,某些学年学季,全平台通用等等。。。

流程交互

那么对于一个优惠券系统,一般的流程交互如下:

交互流程
交互流程

需要解决的问题

那么对于一个优惠券系统,需要解决的问题主要有两点

安全性:

  1. 优惠券超: 高并发的情况下优惠券领取的数量超过了发行量
  2. 一券多用:一张优惠券购买多个商品

扩展性:

扩展性有很多种,比如商品的扩展(新增一种商品服务是否需要改动),服务的扩展性(其它的产品想接入我们的优惠券系统,需要哪些条件与步骤),规则的扩展性(不管是领取,还是使用,都有很多规则条件,如何在新增规则的时候,做最简单的改动去兼容新的玩法,而不是用if else 去堆积?)

我们的解决方案

安全性问题的处理

1,超发问题的处理

领券是一个c侧的操作,所以为了并发考虑,不可能直接操作db,这里有两种处理办法 1)以优惠券为维度,使用redis分布式锁, 2)使用lua脚本将取库存与扣库存等操作打包。 这里我们选择第二种方式,因为第一种方式需要一直自旋拿锁,浪费资源,而且引入锁,又会带来一些分布式锁的问题(如加锁后释放的问题)。当redis扣了库存之后,我们会抛出一条消息去异步更新mysql中的库存,以及写入一条领取的记录

2,一券多用

这里主要是将优惠券的使用分成了两个步骤,1)锁定,2)使用。在使用之前必须先锁定,锁定只是一个状态问题,不能重复锁定,可以做到是一个原子性的操作。

3,其它的问题

其实超发和一券多用问题往往还不是最严重的,业界一般暴露出来的优惠券刷羊毛问题,也不是超发和一券多用的问题。比如之前拼多多的平台无门槛使用券,抢了之后充话费,想追都追不回来。这种问题此处提供两种思路,1)权限控制:在发布优惠券的时候做严格的权限控制(只能指定某些人发券),发完之后做二次审核上线。再发布之前就做好预防。2)成本控制。这种主要是平台的成本控制,对于没一张平台券,进行一个成本预算,设定一个阈值比如每天100W,当快达到阈值之前,通过告警通知相关人员,当达到阈值的时候,直接熔断,不让领取和使用。

扩展性问题

扩展性这里我们主要探讨一下规则玩法的扩展性问题,就是说对于单一优惠券系统而言,当我们规则玩法变更后,我们需要做哪些改动来兼容这类规则。

说到规则,可能很容易让人想到规则引擎,像业界开源的一些常用的规则引擎如drools,easyrules 等但是这些东西引入到我们的优惠券系统无疑太重了,虽然说规则可以无穷无尽,但是实际上对于我们自身的业务来说,规则可能就那么十来个,甚至不到,引入一个规则引擎代价还是太大。

通过我们对业务的分析,我们对一般的规则进行了分类,可能存在的规则主要是两类

1, 计算比较类,如 商品价格是否满足满减条件, 商品是否在优惠券的商品使用列表里面等等,这些都可以抽象成 ">" ,"<" "==" "!=" “In“ 等操作

2,调用查询类,这类条件就是说优惠券自身是不可能知道的,需要通过调用其它服务查询到,比如规定某个优惠券只能老用户领取,对于优惠券服务自身来说,肯定是不知道什么叫老用户的,必然是通过查询其它服务。像这种类型一般需要被调方定义一套标准的接口。

对于规则的处理,我们的核心思想就是操作符的重载对于计算类的,就是对">","<"等操作符做重载,写一个对应的比较函数,对于查询类的,我们也可以搞一个类似的"rpc" 操作符重载。规则肯定是可配置的,我们的规则是以json的方式配置举个例子:

{
    "rules":[
        {
            "rule_id":1,
            "rule_represent":{
                "p1":"OrgPrice",
                "p2":"Price",
                "p3":"",
                "op":">"
            }
        },
        {
            "rule_id":100,
            "rule_represent":{
                "p1":"http://ke.qq.com/xxxxx",
                "p2":"",
                "p3":"",
                "op":"Rpc"
            }
        }]
}

在这个例子中,我们配置了两个规则,规则id为1和100,rule_id=1的重载了">"操作符,对应的两个操作数是商品原价和优惠券的价格,它表示商品原价应该大于优惠券的价格。rule_id=1的重载了"rpc"操作符,p1是http回调的地址,比如我们上述提到的新老用户的判断,当我们需要判断一个用户是否新老用户的时候,我们可以通过http请求去查询。

对于“rpc”操作符重载有两点需要说明的。第一,可支持的回调类型是多样,比如对于我们的很多业务,这里可以配置对应的l5地址,以及请求对应服务的命令字。第二,对于这种回调的接口,调用方应该要定制统一的接口,然后让被调方实现该接口。

当我们新增一种规则的时候,如果是已存在的操作符,我们只需要配置一个规则,然后再优惠券发布的时候,将对应的规则id与优惠券进行关联,然后领取使用的时候取出对应的规则进行判断即可,这里是可以做到不写任何代码的。如果是不存在的操作符,需要再此基础上多写一个操作符的重载函数。

其实因为我们的优惠券业务是比较孤立的,目前进行众多的条件规则过滤后,只有可以领取或者可以使用这两个动作,对于一个再复杂一点的系统,我们是可以将规则与行为进行组合的。比如满足规则 1&&2&&3 ----> 产生行为 a,bc 这种。 这种同样也是可以做到成可配置的。

后记

本文主要讨论了一个优惠券系统设计时候该考虑的一些问题,除了优惠券的一些属性细节之外,重点讨论了下一个优惠券系统再高并发时候的安全性 和可扩展性。后面我们将具体讨论,我们的产品(腾讯课堂)在优惠券重构过程中遇到的一些问题和解决方案。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 发券
    • 谁来发?
      • 优惠券基本属性
      • 领券
        • 领取限制
        • 使用
        • 流程交互
        • 需要解决的问题
          • 安全性:
            • 扩展性:
            • 我们的解决方案
              • 安全性问题的处理
                • 1,超发问题的处理
                • 2,一券多用
                • 3,其它的问题
              • 扩展性问题
              • 后记
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档