TIGERB

TIGERB 查看完整档案

北京编辑天津师范大学  |  电信 编辑Xiaomi  |  Web开发 编辑 tigerb.cn 编辑
编辑

// Trying to be the person you want to be.

// 时刻夯实基础
// 时刻对新技术保持热忱

// 个人博客 http://TIGERB.cn
// 轻量级PHP框架EasyPHP 作者 http://easy-php.tigerb.cn
// 电商设计手册|SkrShop 作者 https://github.com/skr-shop/m...

// 新的目标成为一名优秀的 Gopher

个人动态

TIGERB 分享了头条 · 2020-12-21

今天要分享的是主要内容是Go语言Map底层实现,目的让大家快速了解Go语言Map底层大致的实现原理。

赞 1 收藏 3 评论 0

TIGERB 分享了头条 · 2020-11-05

嗯,Go设计模式实战系列,一个设计模式业务真实使用的golang系列。

赞 1 收藏 1 评论 0

TIGERB 分享了头条 · 2020-10-27

千呼万唤始出来😁 SkrShop《订单中心》第1篇 🎉🎉🎉~ 尽力为你解开「电商订单系统」的面纱🤷‍♀️

赞 2 收藏 0 评论 0

TIGERB 关注了专栏 · 2020-07-09

人云思云

码农一只

关注 207

TIGERB 发布了文章 · 2020-07-01

你想知道的优惠券业务,SkrShop来告诉你

经过两年的更新「SkrShop」已经构成了下面的架构:

图中紫色的内容就是本编文章的主要内容:营销体系的基础服务「优惠券服务」。但是呢,首先要说的是关于不断被催更的事。

关于催更?

我给出了如下解释:人逢假日懒🤷‍♀️(我没错😭)、工作紧、需要保证质量,就酱。但是我一定能保证的是一直会更新下去,希望得到大家理解。

关于下期内容?

之前在Github上的Issues大家一致想看关于订单相关的内容,所以更新完本期「优惠券」之后就开始了订单之旅

Issues如下:

1. https://github.com/skr-shop/manuals/issues/25
2. https://github.com/skr-shop/manuals/issues/18

进入正题,营销体系的基础服务「优惠券服务」。通过如下问题来介绍优惠券:

  • 优惠券有哪些类型
  • 优惠券有哪些适用范围
  • 优惠券有哪些常见的场景
  • 优惠券服务要有哪些服务能力
  • 优惠券服务的风控怎么做?

优惠券有哪些类型?

对于获取优惠券的用户而言:关注的是优惠券的优惠能力,所以按优惠能力维度优惠券主要分为下面三类:

优惠能力维度描述
满减券满多少金额(不含邮费)可以减多少金额
现金券抵扣多少现金(无门槛)
抵扣券抵扣某Sku全部金额(一个数量)
折扣券打折

对于发放优惠券的运营人员而言:

一种是「固定有效期」,优惠券的生效时间戳和过期时间戳,在创建优惠券的时候已经确定。用户在任意时间领取该券,该券的有效时间都是之前设置的有效时间的开始结束时间。

另一种是「动态有效期」,创建优惠券设置的是有效时间段,比如7天有效时间、12小时有效时间等。这类优惠券以用户领取优惠券的时间为优惠券的有效时间的开始时间,以以用户领取优惠券的时间+有效时间为有效时间的结束时间。

有效期维度优惠券类型优惠券生效时间优惠券失效时间描述
固定固定有效期优惠券类型被创建时已确定优惠券类型被创建时已确定无论用户什么时间领取该优惠券,优惠券生效的时间都是设置好的统一时间
动态动态有效期用户领取优惠券时,当前时间戳用户领取优惠券时,当前时间戳 + N*24*60*60优惠券类型被创建时,只确定了该优惠券的有效,例如6小时、7天、一个月

小结如下:

优惠券有哪些适用范围?

运营策略

运营策略描述
(非)指定SkuSku券
(非)指定SpuSpu券
(非)指定类别类别券
指定店铺店铺券
全场通用平台券

适用终端

适用终端(复选框)描述
Android安卓端
iOSiOS端
PC网页电脑端
Mobile网页手机端
Wechat微信端
微信小程序微信小程序
All以上所有

适用人群

适用人群描述
白名单测试用户
会员会员专属

小结如下:

优惠券有哪些常见的场景?

领取优惠券场景

领取优惠券场景描述
活动页面大促、节假日活动页面展示获取优惠券的按钮
游戏页面通过游戏获取优惠券
店铺首页店铺首页展示领券入口
商品详情商品详情页面展示领券入口
积分中心积分兑换优惠券

展示优惠券场景

展示优惠券场景描述
活动页面大促、节假日活动页面展示可以领取的优惠券
商品详情商品详情页面展示可以领取、可以使用的优惠券列表
个人中心-我的优惠券我的优惠券列表
订单结算页面结算页面,适用该订单的优惠券列表以及推荐
积分中心展示可以兑换的优惠券详情

选择优惠券场景

选择优惠券场景描述
商品详情商品详情页面展示该用户已有的,且适用于该商品的优惠券
订单结算页面-优惠券列表选择可用优惠券结算
订单结算页面-输入优惠码输入优惠码结算

返还优惠券场景

返还优惠券场景描述
未支付订单取消未支付的订单,用户主动取消返还优惠券,或超时关单返还优惠券
已支付订单全款取消已支付的订单,订单部分退款不返还,当整个订单全部退款返还优惠券

场景示例

场景示例描述
活动页领券大促、节假日活动页面展示获取优惠券的按钮
游戏发券游戏奖励
商品页领券-
店铺页领券-
购物返券购买某个Sku,订单妥投后发放优惠券
新用户发券新用户注册发放优惠券
积分兑券积分换取优惠券

小结如下:

优惠券服务要有哪些服务能力?

服务能力1: 发放优惠券

发放方式描述
同步发放适用于用户点击领券等实时性要求较高的获取券场景
异步发放适用于实时性要求不高的发放券场景,比如新用户注册发券等场景
发放能力描述
单张发放指定一个优惠券类型ID,且指定一个UID只发一张该券
批量发放指定一个优惠券类型ID,且指定一批UID,每个UID只发一张该券
发放类型描述
优惠券类型标识通过该优惠券类型的身份标识发放,比如创建一个优惠券类型时会生成一个16位标识码,用户通过16位标识码领取优惠券;这里不使用自增ID(避免对外泄露历史创建了的优惠券数量),
优惠码code创建一个优惠券类型时,运营人员会给该券填写一个6位左右的Ascall码,比如SKR6a6,用户通过该码领取优惠券

服务能力2: 撤销优惠券

撤销能力描述
单张撤销指定一个优惠券类型ID,且指定一个UID只撤销一张该券
批量撤销指定一个优惠券类型ID,且指定一批UID,每个UID撤销一张该券

服务能力3: 查询优惠券

用户优惠券列表子类描述
全部-查询该用户所有的优惠券
可以使用全部查询该用户所有可以使用的优惠券
-适用于某个spu或sku查询该用户适用于某个spu或sku可以使用的优惠券
-适用于某个类别查询该用户适用于某个类别可以使用的优惠券
-适用于某个店铺查询该用户适用于某个店铺可以使用的优惠券
无效全部查询该用户所有无效的优惠券
-过期查询该用户所有过期的优惠券
-失效查询该用户所有失效的优惠券

服务能力4: 结算页优惠券推荐

订单结算页面推荐一张最适合该订单的优惠券

小结如下:

优惠券服务的风控怎么做?

一旦有发生风险的可能则触发风控:

  • 对用户,提示稍后再试或联系客服
  • 对内部,报警提示,核查校验报警是否存在问题

频率限制

领取描述
设备ID每天领取某优惠券的个数限制
UID每天领取某优惠券的个数限制
IP每天领取某优惠券的个数限制
使用描述
设备ID每天使用某优惠券的个数限制
UID每天使用某优惠券的个数限制
IP每天使用某优惠券的个数限制
手机号每天使用某优惠券的个数限制
邮编比如注重邮编的海外地区,每天使用某优惠券的个数限制

用户风险等级

依托用户历史订单数据,得到用户成功完成交易(比如成功妥投15天+)的比率,根据此比率对用户进行等级划分,高等级进入通行Unblock名单,低等级进入Block名单,根据不同用户级别设置限制策略。等其他大数据分析手段。

阈值

  • 发券预算
  • 实际使用券预算

根据预算值设置发券总数阈值,当触发阈值时阻断并报警。

优惠券不要支持虚拟商品

优惠券尽量不要支持虚拟商品以防止可能被利用的不法活动。


SkrShop历史分享:https://github.com/skr-shop/m...

3911642037-d2bb08d8702e7c91_articlex.jpg

查看原文

赞 2 收藏 2 评论 0

TIGERB 发布了文章 · 2020-06-02

客户决策 | Go语言设计模式实战

嗯,我的代码没有else系列,一个设计模式业务真实使用的golang系列。

我的代码没有else系列.jpg

前言

本系列主要分享,如何在我们的真实业务场景中使用设计模式。

本系列文章主要采用如下结构:

  • 什么是「XX设计模式」?
  • 什么真实业务场景可以使用「XX设计模式」?
  • 怎么用「XX设计模式」?

本文主要介绍「策略模式」如何在真实业务场景中使用。

什么是「策略模式」?

「策略模式」比较简单,大家平常工作中应该经常使用到,所以本文作为复习,帮助大家温故知新。我们先来看下定义:

不同的算法按照统一的标准封装,客户端根据不同的场景,决策使用何种算法。

上面的概念的关键词:

  • 算法:就是行为
  • 标准:就是interface
  • 客户端:客户端是相对的,谁调用谁就是客户端
  • 场景:判断条件
  • 决策:判断的过程

概念很容易理解,不多说。

「策略模式」的优势:

  • 典型的高内聚:算法和算法之间完全独立、互不干扰
  • 典型的松耦合:客户端依赖的是接口的抽象方法
  • 沉淀:每一个封装好的算法都是这个技术团队的财富,且未来可以被轻易的修改、复用

什么真实业务场景可以用「策略模式」?

每一行代码下面的十字路口

当代码的下一步面临选择的时候都可以使用「策略模式」,我们把不同选择的算法按照统一的标准封装,得到一类算法集的过程,就是实现「策略模式」的过程。

我们有哪些真实业务场景可以用「策略模式」呢?

比如:

  • 缓存: 使用什么样的nosql
  • 存储: 使用什么样的DB
  • 支付: 使用什么样的支付方式
  • 等等...

本文以支付接口举例,说明「策略模式」的具体使用。

怎么用「策略模式」?

关于怎么用,完全可以生搬硬套我总结的使用设计模式的四个步骤:

  • 业务梳理
  • 业务流程图
  • 代码建模
  • 代码demo

业务梳理

我们以某团的订单支付页面为例,页面上的每一个支付选项都是一个支付策略。如下:

用户可以使用:

  • 美团支付(策略)
  • 微信支付(策略)
  • 支付宝支付(策略)

用户决定使用美团支付下的银行卡支付方式的参数

用户决定使用支付宝网页版支付方式的参数

注:不一定完全准确。

业务流程图

我们通过梳理的文本业务流程得到了如下的业务流程图:

注:流程不一定完全准确。

代码建模

「策略模式」的核心是接口:

  • PaymentInterface

    • Pay(ctx *Context) error 当前支付方式的支付逻辑
    • Refund(ctx *Context) error 当前支付方式的退款逻辑

伪代码如下:

// 定义一个支付接口
- `PaymentInterface`
    + 抽象方法`Pay(ctx *Context) error`: 当前支付方式的支付逻辑
    + 抽象方法`Refund(ctx *Context) error`: 当前支付方式的退款逻辑

// 定义具体的支付方式 实现接口`PaymentInterface`

- 具体的微信支付方式`WechatPay`
    +  实现方法`Pay`: 支付逻辑
    +  实现方法`Refund`: 支付逻辑
- 具体的支付宝支付网页版方式`AliPayWap`
    +  实现方法`Pay`: 支付逻辑
    +  实现方法`Refund`: 支付逻辑
- 具体的支付宝支付网页版方式`BankPay`
    +  实现方法`Pay`: 支付逻辑
    +  实现方法`Refund`: 支付逻辑

// 客户端代码
通过接口参数pay_type的值判断是哪种支付方式策略

同时得到了我们的UML图:

代码demo

package main

import (
    "fmt"
    "runtime"
)

//------------------------------------------------------------
//我的代码没有`else`系列
//策略模式
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------

const (
    // ConstWechatPay 微信支付
    ConstWechatPay = "wechat_pay"
    // ConstAliPayWap 支付宝支付 网页版
    ConstAliPayWap = "AliPayWapwap"
    // ConstBankPay 银行卡支付
    ConstBankPay = "quickbank"
)

// Context 上下文
type Context struct {
    // 用户选择的支付方式
    PayType string `json:"pay_type"`
}

// PaymentInterface 支付方式接口
type PaymentInterface interface {
    Pay(ctx *Context) error    // 支付
    Refund(ctx *Context) error // 退款
}

// WechatPay 微信支付
type WechatPay struct {
}

// Pay 当前支付方式的支付逻辑
func (p *WechatPay) Pay(ctx *Context) (err error) {
    // 当前策略的业务逻辑写这
    fmt.Println(runFuncName(), "使用微信支付...")
    return
}

// Refund 当前支付方式的支付逻辑
func (p *WechatPay) Refund(ctx *Context) (err error) {
    // 当前策略的业务逻辑写这
    fmt.Println(runFuncName(), "使用微信退款...")
    return
}

// AliPayWap 支付宝网页版
type AliPayWap struct {
}

// Pay 当前支付方式的支付逻辑
func (p *AliPayWap) Pay(ctx *Context) (err error) {
    // 当前策略的业务逻辑写这
    fmt.Println(runFuncName(), "使用支付宝网页版支付...")
    return
}

// Refund 当前支付方式的支付逻辑
func (p *AliPayWap) Refund(ctx *Context) (err error) {
    // 当前策略的业务逻辑写这
    fmt.Println(runFuncName(), "使用支付宝网页版退款...")
    return
}

// BankPay 银行卡支付
type BankPay struct {
}

// Pay 当前支付方式的支付逻辑
func (p *BankPay) Pay(ctx *Context) (err error) {
    // 当前策略的业务逻辑写这
    fmt.Println(runFuncName(), "使用银行卡支付...")
    return
}

// Refund 当前支付方式的支付逻辑
func (p *BankPay) Refund(ctx *Context) (err error) {
    // 当前策略的业务逻辑写这
    fmt.Println(runFuncName(), "使用银行卡退款...")
    return
}

// 获取正在运行的函数名
func runFuncName() string {
    pc := make([]uintptr, 1)
    runtime.Callers(2, pc)
    f := runtime.FuncForPC(pc[0])
    return f.Name()
}

func main() {
    // 相对于被调用的支付策略 这里就是支付策略的客户端

    // 业务上下文
    ctx := &Context{
        PayType: "wechat_pay",
    }

    // 获取支付方式
    var instance PaymentInterface
    switch ctx.PayType {
    case ConstWechatPay:
        instance = &WechatPay{}
    case ConstAliPayWap:
        instance = &AliPayWap{}
    case ConstBankPay:
        instance = &BankPay{}
    default:
        panic("无效的支付方式")
    }

    // 支付
    instance.Pay(ctx)
}

代码运行结果:

[Running] go run "../easy-tips/go/src/patterns/strategy/strategy.go"
main.(*WechatPay).Pay 使用微信支付...

结语

最后总结下,「策略模式」抽象过程的核心是:

每一行代码下面的十字路口

  • 声明标准:定义interface
  • 封装算法:按照标准interface封装分支代码,得到每一个具体策略
  • 构建算法集:每一个具体策略构成策略池子 -> 这就是沉淀的过程
特别说明:
1. 我的代码没有`else`,只是一个在代码合理设计的情况下自然而然无限接近或者达到的结果,并不是一个硬性的目标,务必较真。
2. 本系列的一些设计模式的概念可能和原概念存在差异,因为会结合实际使用,取其精华,适当改变,灵活使用。

文章列表

我的代码没有else系列 更多文章 点击此处查看

3911642037-d2bb08d8702e7c91_articlex.jpg

查看原文

赞 12 收藏 11 评论 0

TIGERB 发布了文章 · 2020-06-02

状态变换 | Go语言设计模式实战

嗯,我的代码没有else系列,一个设计模式业务真实使用的golang系列。

我的代码没有else系列.jpg

前言

本系列主要分享,如何在我们的真实业务场景中使用设计模式。

本系列文章主要采用如下结构:

  • 什么是「XX设计模式」?
  • 什么真实业务场景可以使用「XX设计模式」?
  • 怎么用「XX设计模式」?

本文主要介绍「状态模式」如何在真实业务场景中使用。

「状态模式」比较简单,就是算法的选取取决于于自己的内部状态。相较于「策略模式」算法的选取由用户决策变成内部状态决策,「策略模式」是用户(客户端)选择具体的算法,「状态模式」只是通过内部不同的状态选择具体的算法。

什么是「状态模式」?

不同的算法按照统一的标准封装,根据不同的内部状态,决策使用何种算法

「状态模式」和「策略模式」的区别

  • 策略模式:依靠客户决策
  • 状态模式:依靠内部状态决策

什么真实业务场景可以用「状态模式」?

具体算法的选取是由内部状态决定的
  • 首先,内部存在多种状态
  • 其次,不同的状态的业务逻辑各不相同
我们有哪些真实业务场景可以用「状态模式」呢?

比如,发送短信接口、限流等等。

  • 短信接口

    • 服务内部根据最优算法,实时推举出最优的短信服务商,并修改使用何种短信服务商的状态
  • 限流

    • 服务内部根据当前的实时流量,选择不同的限流算法,并修改使用何种限流算法的状态

怎么用「状态模式」?

关于怎么用,完全可以生搬硬套我总结的使用设计模式的四个步骤:

  • 业务梳理
  • 业务流程图
  • 代码建模
  • 代码demo

业务梳理

先来看看一个短信验证码登录的界面。

可以得到:

  • 发送短信,用户只需要输入手机号即可
  • 至于短信服务使用何种短信服务商,是由短信服务自身的当前短信服务商实例的状态决定
  • 当前短信服务商实例的状态又是由服务自身的算法修改

业务流程图

我们通过梳理的文本业务流程得到了如下的业务流程图:

代码建模

「状态模式」的核心是:

  • 一个接口:

    • 短信服务接口SmsServiceInterface
  • 一个实体类:

    • 状态管理实体类StateManager

伪代码如下:

// 定义一个短信服务接口
- 接口`SmsServiceInterface`
    + 抽象方法`Send(ctx *Context) error`发送短信的抽象方法

// 定义具体的短信服务实体类 实现接口`SmsServiceInterface`

- 实体类`ServiceProviderAliyun`
    + 成员方法`Send(ctx *Context) error`具体的发送短信逻辑
- 实体类`ServiceProviderTencent`
    + 成员方法`Send(ctx *Context) error`具体的发送短信逻辑
- 实体类`ServiceProviderYunpian`
    + 成员方法`Send(ctx *Context) error`具体的发送短信逻辑

// 定义状态管理实体类`StateManager`
- 成员属性
    + `currentProviderType ProviderType`当前使用的服务提供商类型
    + `currentProvider SmsServiceInterface`当前使用的服务提供商实例
    + `setStateDuration time.Duration`更新状态时间间隔
- 成员方法
    + `initState(duration time.Duration)`初始化状态
    + `setState(t time.Time)`设置状态

同时得到了我们的UML图:

代码demo

package main

//------------------------------------------------------------
//我的代码没有`else`系列
//状态模式
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------

import (
    "fmt"
    "math/rand"
    "runtime"
    "time"
)

// Context 上下文
type Context struct {
    Tel        string // 手机号
    Text       string // 短信内容
    TemplateID string // 短信模板ID
}

// SmsServiceInterface 短信服务接口
type SmsServiceInterface interface {
    Send(ctx *Context) error
}

// ServiceProviderAliyun 阿里云
type ServiceProviderAliyun struct {
}

// Send Send
func (s *ServiceProviderAliyun) Send(ctx *Context) error {
    fmt.Println(runFuncName(), "【阿里云】短信发送成功,手机号:"+ctx.Tel)
    return nil
}

// ServiceProviderTencent 腾讯云
type ServiceProviderTencent struct {
}

// Send Send
func (s *ServiceProviderTencent) Send(ctx *Context) error {
    fmt.Println(runFuncName(), "【腾讯云】短信发送成功,手机号:"+ctx.Tel)
    return nil
}

// ServiceProviderYunpian 云片
type ServiceProviderYunpian struct {
}

// Send Send
func (s *ServiceProviderYunpian) Send(ctx *Context) error {
    fmt.Println(runFuncName(), "【云片】短信发送成功,手机号:"+ctx.Tel)
    return nil
}

// 获取正在运行的函数名
func runFuncName() string {
    pc := make([]uintptr, 1)
    runtime.Callers(2, pc)
    f := runtime.FuncForPC(pc[0])
    return f.Name()
}

// ProviderType 短信服务提供商类型
type ProviderType string

const (
    // ProviderTypeAliyun 阿里云
    ProviderTypeAliyun ProviderType = "aliyun"
    // ProviderTypeTencent 腾讯云
    ProviderTypeTencent ProviderType = "tencent"
    // ProviderTypeYunpian 云片
    ProviderTypeYunpian ProviderType = "yunpian"
)

var (
    // stateManagerInstance 当前使用的服务提供商实例
    // 默认aliyun
    stateManagerInstance *StateManager
)

// StateManager 状态管理
type StateManager struct {
    // CurrentProviderType 当前使用的服务提供商类型
    // 默认aliyun
    currentProviderType ProviderType

    // CurrentProvider 当前使用的服务提供商实例
    // 默认aliyun
    currentProvider SmsServiceInterface

    // 更新状态时间间隔
    setStateDuration time.Duration
}

// initState 初始化状态
func (m *StateManager) initState(duration time.Duration) {
    // 初始化
    m.setStateDuration = duration
    m.setState(time.Now())

    // 定时器更新状态
    go func() {
        for {
            // 每一段时间后根据回调的发送成功率 计算得到当前应该使用的 厂商
            select {
            case t := <-time.NewTicker(m.setStateDuration).C:
                m.setState(t)
            }
        }
    }()
}

// setState 设置状态
// 根据短信云商回调的短信发送成功率 得到下阶段发送短信使用哪个厂商的服务
func (m *StateManager) setState(t time.Time) {
    // 这里用随机模拟
    ProviderTypeArray := [3]ProviderType{
        ProviderTypeAliyun,
        ProviderTypeTencent,
        ProviderTypeYunpian,
    }
    m.currentProviderType = ProviderTypeArray[rand.Intn(len(ProviderTypeArray))]

    switch m.currentProviderType {
    case ProviderTypeAliyun:
        m.currentProvider = &ServiceProviderAliyun{}
    case ProviderTypeTencent:
        m.currentProvider = &ServiceProviderTencent{}
    case ProviderTypeYunpian:
        m.currentProvider = &ServiceProviderYunpian{}
    default:
        panic("无效的短信服务商")
    }
    fmt.Printf("时间:%s| 变更短信发送厂商为: %s \n", t.Format("2006-01-02 15:04:05"), m.currentProviderType)
}

// getState 获取当前状态
func (m *StateManager) getState() SmsServiceInterface {
    return m.currentProvider
}

// GetState 获取当前状态
func GetState() SmsServiceInterface {
    return stateManagerInstance.getState()
}

func main() {

    // 初始化状态管理
    stateManagerInstance = &StateManager{}
    stateManagerInstance.initState(300 * time.Millisecond)

    // 模拟发送短信的接口
    sendSms := func() {
        // 发送短信
        GetState().Send(&Context{
            Tel:        "+8613666666666",
            Text:       "3232",
            TemplateID: "TYSHK_01",
        })
    }

    // 模拟用户调用发送短信的接口
    sendSms()
    time.Sleep(1 * time.Second)
    sendSms()
    time.Sleep(1 * time.Second)
    sendSms()
    time.Sleep(1 * time.Second)
    sendSms()
    time.Sleep(1 * time.Second)
    sendSms()
}

代码运行结果:

[Running] go run "./easy-tips/go/src/patterns/state/state.go"
时间:2020-05-30 18:02:37| 变更短信发送厂商为: yunpian 
main.(*ServiceProviderYunpian).Send 【云片】短信发送成功,手机号:+8613666666666
时间:2020-05-30 18:02:37| 变更短信发送厂商为: aliyun 
时间:2020-05-30 18:02:38| 变更短信发送厂商为: yunpian 
时间:2020-05-30 18:02:38| 变更短信发送厂商为: yunpian 
main.(*ServiceProviderYunpian).Send 【云片】短信发送成功,手机号:+8613666666666
时间:2020-05-30 18:02:38| 变更短信发送厂商为: tencent 
时间:2020-05-30 18:02:39| 变更短信发送厂商为: aliyun 
时间:2020-05-30 18:02:39| 变更短信发送厂商为: tencent 
main.(*ServiceProviderTencent).Send 【腾讯云】短信发送成功,手机号:+8613666666666
时间:2020-05-30 18:02:39| 变更短信发送厂商为: yunpian 
时间:2020-05-30 18:02:40| 变更短信发送厂商为: tencent 
时间:2020-05-30 18:02:40| 变更短信发送厂商为: aliyun 
main.(*ServiceProviderAliyun).Send 【阿里云】短信发送成功,手机号:+8613666666666
时间:2020-05-30 18:02:40| 变更短信发送厂商为: yunpian 
时间:2020-05-30 18:02:40| 变更短信发送厂商为: tencent 
时间:2020-05-30 18:02:41| 变更短信发送厂商为: aliyun 
时间:2020-05-30 18:02:41| 变更短信发送厂商为: yunpian 
main.(*ServiceProviderYunpian).Send 【云片】短信发送成功,手机号:+8613666666666

结语

最后总结下,「状态模式」抽象过程的核心是:

  • 每一个状态映射对应行为
  • 行为实现同一个接口interface
  • 行为是内部的一个状态
  • 状态是不断变化的
特别说明:
1. 我的代码没有`else`,只是一个在代码合理设计的情况下自然而然无限接近或者达到的结果,并不是一个硬性的目标,务必较真。
2. 本系列的一些设计模式的概念可能和原概念存在差异,因为会结合实际使用,取其精华,适当改变,灵活使用。

文章列表

我的代码没有else系列 更多文章 点击此处查看

3911642037-d2bb08d8702e7c91_articlex.jpg

查看原文

赞 1 收藏 1 评论 0

TIGERB 分享了头条 · 2020-05-07

5张图送你5种秒杀系统,再加点骚操作,再顺带些点心里话🤷‍♀️。

赞 0 收藏 3 评论 0

TIGERB 发布了文章 · 2020-05-07

什么,秒杀系统也有这么多种!

前言

本文结构很简单:

5张图送你5种秒杀系统,再加点骚操作,再顺带些点心里话🤷‍♀️。

一个简单的秒杀系统

实现原理: 通过redis原子操作减库存

图一

优点缺点
简单好用考验redis服务能力
是否公平
公平
先到先得

我们称这类秒杀系统为:

简单秒杀系统

如果刚开始QPS并不高,redis完全抗的下来的情况,完全可以依赖这个「简单秒杀系统」。

一个够用的秒杀系统

实现原理: 服务内存限流算法 + redis原子操作减库存

图二

优点缺点
简单好用-
是否公平
不是很公平
相对的先到先得

我们称这类秒杀系统为:

够用秒杀系统

性能再好点的秒杀系统

实现原理: 服务本地内存原子操作减库存

服务本地内存的库存怎么来的?

活动开始前分配好每台机器的库存,推送到机器上。

图三

优点缺点
高性能不支持动态伸缩容(活动进行期间),因为库存是活动开始前分配好的
释放redis压力-
是否公平
不是很公平
不是绝对的先到先得

我们称这类秒杀系统为:

预备库存秒杀系统

支持动态伸缩容的秒杀系统

实现原理: 服务本地协程Coroutine定时redis原子操作减部分库存到本地内存 + 服务本地内存原子操作减库存

图四

优点缺点
高性能支持动态伸缩容(活动进行期间)
释放redis压力-
具备通用性-
是否公平
不是很公平,但是好了点
几乎先到先得

我们称这类秒杀系统为:

实时预备库存秒杀系统

公平的秒杀系统

实现原理: 服务本地Goroutine定时同步是否售罄到本地内存 + 队列 + 排队成功轮训(或主动Push)结果

图五

优点缺点
高性能开发成本高(需主动通知或轮训排队结果)
真公平-
具备通用性-
是否公平
很公平
绝对的先到先得

我们称这类秒杀系统为:

公平排队秒杀系统

骚操作

上面的秒杀系统还不够完美吗?

答案:是的。

还有什么优化的空间?

答案:静态化获取秒杀活动信息的接口。

静态化是什么意思?

答案:比如获取秒杀活动信息是通过接口 https://seckill.skrshop.tech/v1/acticity/get 获取的。现在呢,我们需要通过https://static-api.skrshop.tech/seckill/v1/acticity/get 这个接口获取。有什么区别呢?看下面:

服务名接口数据存储位置
秒杀服务https://seckill.skrshop.tech/...秒杀服务内存或redis等
接口静态化服务https://static-api.skrshop.te...CDN、本地文件

以前是这样

变成了这样

结果:可以通过接口https://static-api.skrshop.tech/seckill/v1/acticity/get就获取到了秒杀活动信息,流量都分摊到了cdn,秒杀服务自身没了这部分的负载。

小声点说:“秒杀结果我也敢推CDN😏😏😏。”
备注:
之后我们会分享`如何用Golang设计一个好用的「接口静态化服务」`。

总结

上面我们得到了如下几类秒杀系统

秒杀系统
简单秒杀系统
够用秒杀系统
预备库存秒杀系统
实时预备库存秒杀系统
公平排队秒杀系统

我想说的是里面没有最好的方案,也没有最坏的方案,只有适合你的。

先到先得来说,一定要看你们的产品对外宣传,切勿上来就追逐绝对的先到先得。其实你看所有的方案,相对而言都是“先到先得”,比如,活动开始一个小时了你再来抢,那相对于准时的用户自然抢不过,对吧。

又如预备库存秒杀系统,虽然不支持动态伸缩容。但是如果你的环境满足如下任意条件,就完全够用了。

  • 秒杀场景结束时间之快,通常几秒就结束了,真实活动可能会发生如下情况:

    • 服务压力大还没挂:根本就来不及动态伸缩容
    • 服务压力大已经挂了:可以先暂停活动,服务起来&扩容结束,用剩余库存重新推送
  • 运维自身不具备动态伸缩容的能力

所以:

合适好用就行,切勿过度设计。

最后

这次算是把老本都吐露出来了,真是慌得一匹。


SkrShop历史分享:https://github.com/skr-shop/m...

3911642037-d2bb08d8702e7c91_articlex.jpg

查看原文

赞 59 收藏 39 评论 2

认证与成就

  • SegmentFault 讲师
  • 获得 1465 次点赞
  • 获得 137 枚徽章 获得 5 枚金徽章, 获得 52 枚银徽章, 获得 80 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • easy-php

    A Faster Lightweight Full-Stack PHP Framework

  • easy-tips

    A little tips in my code career with PHP

  • easy-vue

    An easy example using the vue to implement easy web

  • naruto

    An object-oriented multi process manager for PHP

  • 电商设计手册

    Do design No code | 只设计不码码

注册于 2016-01-08
个人主页被 16k 人浏览