Spring Cloud微服务实战 打造企业级优惠券系统 6-6 阶段总结 【优惠券系统业务思想与架构总结】

时间:2024-02-22 13:17:35
0    课程地址

https://coding.imooc.com/lesson/380.html#mid=28597

 

1    浓缩精华
1.1

 

2    个人关注
2.1

 

3    课程内容

 

这一章完整的介绍了优惠券系统的业务思想,包含三个功能微服务:模板微服务、分发微服务和结算微服务。之后对存储方面的设计进行了介绍,包含 MySQL 和 Redis 缓存的设计思想。最后,对系统的整体架构进行了介绍。优惠券系统的架构分为两类:SpringCloud 组件架构和功能微服务架构。

优惠券系统业务思想

模板微服务

先由运营人员创建优惠券模板,之后再去生成对应数量的优惠券,最后用户才可以去领取优惠券。这个模块(或者微服务)的核心功能都是围绕优惠券模板的。运营人员设定好条件(名称、logo、分类、产品线、数量、规则等等),后台异步创建优惠券模板。之所以是异步过程,是因为创建优惠券模板的过程是比较耗时的,HTTP接口不返回是一种不好的用户体验。

生成优惠券码需要考虑两个方面:

  • 不可以重复
  • 有一定的识别性

最终,我把优惠券码设定为18位,由三个部分组成:

  • 前四位:产品线和类型
  • 中间六位:日期随机
  • 后八位:0 ~ 9 之间的随机数

业务思想如下图所示
根据运营人员设定的条件构造优惠券模板

模板创建的一个关键步骤是异步的生成对应的优惠券码(前面已经介绍了它是怎样构成的),并保存到 Redis 中。需要注意的地方:

  • 提高异步线程池的效率,自定义线程池实现
  • 静态单实例生成优惠券码

业务思想如下图所示
给优惠券模板生成“优惠券码”保存到Redis中

运营人员创建的优惠券模板不可能是一直有效的(模板一旦过期,它所对应的优惠券则不能再分发给用户。但是,已经分发给用户的,可以是不过期的),所以,需要有一个过期机制能够让过期的优惠券不返回给用户展示。我在这里设计了两种实现策略:

  • 优惠券模板模块中实现一个定时任务,例如每个小时运行一次,定时清理过期的优惠券模板
  • 其他模块从模板模块获取优惠券模板时,自己去判断是否已经过期。之所以需要这样做,是因为定时任务总会存在一个定时间隔的延迟,并不能保证实时的过期

分发微服务

优惠券分发模块主要涉及四个核心的功能点。

根据用户id和优惠券状态查找用户优惠券记录

  • 首先,由于我们的系统暂时没有接入用户系统,所以,关于用户相关的创建、校验等功能是没有的,这些会在代码中进行简单的fake,或者叫做mock数据;这其实也很常见,我们在实际的企业级开发中,也会通过这样的方式去完成应用和对应用可用性的验证工作

  • 第二,我这里把属于用户的优惠券状态(注意,这里所说的优惠券是用户相关的,需要与优惠券模板区分开)定义为三类。可用的和已使用的都是字面意思,过期的指的是超出了优惠券的有效使用期,但是仍未被使用的

  • 第三,为了提升系统的响应速度,把用户的数据存储于Redis中,也就是与用户相关的优惠券信息都存储于Redis中;可以想象,在将来,展示用户数据的时候,将直接从Redis中读取

  • 第四,第二条中说到优惠券存在过期的状态,那么,什么时候确定优惠券过期了呢?这里也会使用延迟处理的策略。也就是当用户查看自己优惠券的时候,判断是否存在过期的但是没有被标记的优惠券。如果存在,除了展示用户优惠券信息外,再做额外的过期处理

业务思想如下图所示
给优惠券模板生成“优惠券码”保存到Redis中

根据用户id查找当前可以领取的优惠券模板

  • 第一,优惠券模板是一个独立的服务,所以,分发模块需要通过微服务调用去获取模板数据。但是访问任何一个微服务都存在不确定性,所以,这里要有熔断兜底的策略

  • 第二,从模板服务中获取到的优惠券模板,并不一定都是可领取的,需要去比对优惠券模板的相关限制。例如,有一张优惠券模板A,限制用户只能领取一张可用。那么,如果之前用户已经领取过了,且状态仍是可用状态,则这次就不能再次领取了

业务思想如下图所示
给优惠券模板生成“优惠券码”保存到Redis中

用户领取优惠券

  • 第一,优惠券模板是一个独立的服务,所以,分发模块需要通过微服务调用去获取模板数据。但是访问任何一个微服务都存在不确定性,所以,这里要有熔断兜底的策略

  • 第二,从模板服务中获取到的优惠券模板,并不一定都是可领取的,需要去比对优惠券模板的相关限制。例如,有一张优惠券模板A,限制用户只能领取一张可用。那么,如果之前用户已经领取过了,且状态仍是可用状态,则这次就不能再次领取了

  • 第三,由于每一张优惠券模板都要求它们所对应的优惠券要有优惠券码,且在生成的时候,直接放入到Redis中。所以,这里需要尝试从Redis中获取优惠券码

  • 第四,通过了验证,即优惠券模板是可以领取的,且成功获取到了优惠券码,就可以将优惠券写入MySQL和Redis了

业务思想如下图所示
给优惠券模板生成“优惠券码”保存到Redis中

结算(核销)优惠券

  • 第一,无论是结算还是核销,都需要对前端/客户端传递的参数值进行校验,判断当前用户想要使用的优惠券是否是合法的,合法的标准是属于当前用户且优惠券的状态是可用

  • 第二,由于我们的分发微服务直接面向用户,而结算这样的功能实际只与优惠券的相关,更细致的说,是只与优惠券模板定义的规则相关。所以,结算功能不放在分发微服务中,而是由优惠券系统中的第三个功能微服务负责,即结算微服务

  • 第三,需要知道,结算和核销是两个不同的概念。结算是计算利用优惠券可以优惠的金额,但并不是使用。这种场景发生在我们付款之前,付款之前,优惠券并未使用,但是,也会显示使用优惠券之后优惠的金额和实际需要结算的金额。而核销则是使用优惠券。所以,对于核销这种情况,需要把数据回写到数据库中

业务思想如下图所示
给优惠券模板生成“优惠券码”保存到Redis中

结算微服务

结算微服务只提供一个功能:根据优惠券类型结算优惠券

  • 第一,我们在设计优惠券的时候,会对优惠券设置不同的分类,例如:满减类、折扣类,大家也可以自行扩展更多的分类

  • 第二,由于优惠券种类的不同,自然会有不同的结算方式,或者说结算的算法。例如,满减券是根据满多少金额减去多少金额,而折扣券是直接打一定的折扣等等。另外,更复杂的情况是优惠券之间可以组合。例如满减和折扣组合,先去满减,再去打一定的折扣。需要注意,由于优惠券种类比较多,如果枚举出所有的组合,将会有巨大的工作量。所以,我在课程中,给出了一个组合优惠券的结算过程,其他的组合方式,大家可以按照我的实现方式自行修改,这个过程也并不会很复杂

业务思想如下图所示
给优惠券模板生成“优惠券码”保存到Redis中


存储设计

MySQL 表设计

系统中一共有两张 MySQL 表:

  • 优惠券模板表:优惠券模板是与用户无关的,是对一类优惠券的描述。运营人员通过设定模板,来描述优惠券的各种信息。
    优惠券模板表

  • 用户优惠券表:优惠券模板是用来描述优惠券的,而优惠券表则是记录用户用户优惠券信息。这张表比较简单,除了主键之外,只有5个字段。
    用户优惠券表

Redis 缓存设计

对于缓存,也是有两类,且都是使用Redis来实现。

  • 优惠券码缓存
    • 使用Redis实现,KV类型的缓存
    • Key是需要有意义的,即最好能够根据Key来识别它对应的是什么数据。且需要注意,Redis这类基础工具往往是通用的,不要与其他的Key有冲突
    • 由于优惠券码需要一直保持在系统中,等待分发(即等待用户的领取),所以,并不设置过期时间。

总结下来,为了保证优惠券码的Key不冲突,以前缀+主键的形式构成;且使用list类型(当然,使用set也是可以的)来保存优惠券码。

  • 用户优惠券信息缓存
    • 使用Redis实现,KV类型的缓存
    • Key是需要有意义的,即最好能够根据Key来识别它对应的是什么数据。且需要注意,Redis这类基础工具往往是通用的,不要与其他的Key有冲突
    • 由于优惠券分为3类,为了更加高效的检索,我这里的实现也会使用到三个缓存去实现。且由于每一类优惠券都可能是很多个,这里我选择使用Redis的hash类型
    • 由于用户数据量比较大,且在MySQL中保存有完整的用户信息。所以,不在Redis中长时间保留用户优惠券信息。需要设置一个过期时间

用户优惠券信息缓存的key是前缀+用户id的形式;value是hash类型,hash的key是优惠券id,hash的value是优惠券信息。


架构设计

SpringCloud微服务组件架构

这里主要是两个组件:Eureka和Zuul。客户端的请求入口是Zuul,也就是整个系统的网关服务。网关服务的最核心功能是能够根据请求做分发。把不同的请求分发到对应的微服务上去。Eureka Server是整个系统的注册中心,是SpringCloud服务治理的基础。不论是网关还是功能微服务,都需要把自己注册到Eureka Server上。各自在需要系统元信息的时候,再去询问Eureka Server去主动获取。
SpringCloud微服务组件架构

功能微服务架构设计

结算服务是比较独立的。目前只是我们的优惠券分发服务在做结算时会使用到。但是,对于结算,可以设计的更加通用,不只是优惠券的结算,还可以扩展成商品的结算等等。所以,在实现上,我会把结算服务单独的作为一个微服务。模板服务和结算服务不依赖于其他的服务,而分发服务则会依赖它们两个。实现上,需要考虑调用方式和熔断降级策略。
功能微服务架构设计

 

4    代码演练
4.1