短信服务(二):实现动态判定服务商运作状态(策略二)

一、背景

接上文,这篇文章分享 应对如果短信服务商真的寄了的策略二:动态判定服务商状态 。不同于上文的轮询 failover 策略,本文的策略的思路是 计算服务商是否还运作正常 。常用的判断标准有:(根据自己调用的服务商的特点来确定使用哪个)

1)超时情况:如连续 N 个超时响应【本文将依据这个标准】

2)错误率:如错误率超过 10%

3)响应时间增长率:如响应时间从 100ms 突然变成 1s

这里我用一个比较简单的算法:只要连续超过 N 个请求超时了,就直接切换。如果有多个服务商,那么就是在这些服务商之间逐个切换过去。

二、实现

golang 复制代码
package failover

import (
    "context"
    "refactor-webook/webook/internal/service/sms"
    "sync/atomic"
)

type TimeoutFailoverSmsService struct {
    svcs []sms.Service
    // 当前使用的服务商
    idx int32
    // 记录已经超时的个数
    cnt int32
    // 切换的阈值,只读(所以没有并发安全的问题)
    threshold int32
}

func (t *TimeoutFailoverSmsService) Send(ctx context.Context, tplId string, args []string, numbers ...string) error {
    length := len(t.svcs)
    // 原子操作保证拿到的是最新的 idx 和 cnt
    idx := atomic.LoadInt32(&t.idx)
    cnt := atomic.LoadInt32(&t.cnt)
    if cnt >= t.threshold {
       // 先计算下一个idx
       newIdx := (idx + 1) % (int32)(length)
       // note 注意此处的并发问题:可能有两个请求同时获得 newIdx。我们期望 idx 只因一个请求超过 threshold 而被赋值成 newIdx 即可,其他请求共享这个 idx
       // note 利用原子操作的 CAS 若返回 false,则说明 idx 已经因为其他请求而被修改成 newIdx;若返回 true,则说明是因为本请求修改的,要进一步将 cnt 置为 0
       if atomic.CompareAndSwapInt32(&t.idx, idx, newIdx) {
          atomic.StoreInt32(&t.cnt, 0)
       }
    }
    svc := t.svcs[t.idx]
    err := svc.Send(ctx, tplId, args, numbers...)
    switch err {
    case nil:
       // 请求没超时,重置 cnt
       atomic.StoreInt32(&t.cnt, 0)
    case context.DeadlineExceeded:
       // 请求超时,cnt++
       atomic.AddInt32(&t.cnt, 1)
    default:
       // 不是超时的错误
       // note 可以考虑若是 EOF 错误,可直接切换
    }
    return err
}

注意:并发场景下,为了平衡性能和同步效果,采用了 原子操作 。所以,我们实现的并不是严格的 "连续 N 个超时就切换"。

三、单元测试

这里面涉及到并发问题,所以很难测试。也就是,你没办法通过 mock 之类的东西来判断你的代码是不是并发安全的。你只能说,在没有并发问题的情况下,这个代码的运行结果是符合你的预期的。

注意:并发代码的测试,大部分时候只能有限度的测试。更大程度上是依赖于代码 review 来保证的

相关推荐
IT书架3 分钟前
golang面试题
开发语言·后端·golang
机器之心1 小时前
全球十亿级轨迹点驱动,首个轨迹基础大模型来了
人工智能·后端
潜洋2 小时前
Spring Boot教程之五:在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序
java·spring boot·后端
St_Ludwig3 小时前
C语言 蓝桥杯某例题解决方案(查找完数)
c语言·c++·后端·算法·游戏·蓝桥杯
vener_3 小时前
LuckySheet协同编辑后端示例(Django+Channel,Websocket通信)
javascript·后端·python·websocket·django·luckysheet
计算机毕设孵化场3 小时前
计算机毕设-基于springboot的多彩吉安红色旅游网站的设计与实现(附源码+lw+ppt+开题报告)
vue.js·spring boot·后端·计算机外设·课程设计·计算机毕设论文·多彩吉安红色旅游网站
爪哇学长3 小时前
解锁API的无限潜力:RESTful、SOAP、GraphQL和Webhooks的应用前景
java·开发语言·后端·restful·graphql
战神刘玉栋4 小时前
《SpringBoot、Vue 组装exe与套壳保姆级教学》
vue.js·spring boot·后端
码到成功>_<5 小时前
Spring Boot实现License生成和校验
数据库·spring boot·后端
Ztiddler6 小时前
【npm设置代理-解决npm网络连接error network失败问题】
前端·后端·npm·node.js·vue