当前位置: 首页 > news >正文

微服务可用性设计

一、隔离

对系统或资源进行分割,实现当系统发生故障时能限定传播范围和影响范围。进一步的,通过隔离能够降低系统之间得耦合度,使得系统更容易维护和扩展。某些业务场景下合理使用隔离技巧也能提高整个业务的性能。我理解隔离本质就是一种解耦手段。

1.1 动静隔离

动静隔离本质上是根据资源的变化频率,对它们进行划分和隔离。本质上是加速/缓存变化频率小的数据,将它们与其它资源进行隔离,避免其它资源频繁变化影响到它们,从而提高性能

场景一: 将对象存储和业务服务隔离

对象存储费用低、存储量大,并且放入对象存储空间的资源变化频率也低。我们将对象存储作为一个单独的模块让所有业务服务共享,这样业务服务就无需考虑存储问题,比如空间不足、静态资源和业务数据在带宽上无法隔离造成业务卡顿等问题。
本质上是对静态资源(静态文件)和动态资源(动态业务数据)进行了隔离,将它们进行隔离提高了业务服务的带宽性能、每个业务服务也不需要考虑自身的存储空间以及数据之间的一致性问题,将业务服务变为一种无状态的服务,更方便扩缩容。

场景二:数据表字段的分离

针对一个业务设计了一张数据表,该表包括了一个对象的基本信息和统计信息。基本信息的变动频率往往很低(用户名、性别、状态、手机号、密码),而统计信息变化频率往往很高(点赞数、关注数、粉丝数、资产数)等等
我们可以将基本信息和统计信息进行分离,能够得到以下好处:

  1. 修改统计信息只会使得统计信息的缓存失效,不会使得基本信息的缓存失效
  2. 使得B+树叶子节点能够存放更多记录,从而降低树的高度,缓解单表数据量大导致读写操作慢的问题

场景三: 各种数据库的读写分离也借鉴了该思想

1.2 快慢隔离

把服务的吞吐想象成一个蓄水池,当突然洪流进来的时候,池子需要一定时间才能将其排放完,而这时候其它的小流量在池子里面待的时间取决于这个蓄水池的排放能力,为了提高排放能力(接口响应速度),我们可以对服务进行快慢隔离。

当我们请求一个接口时,它可能涉及到很多业务处理,有些业务轻处理快、有些重处理慢。在不影响用户体验的前提下,我们可以将这些重的业务特别是一些大的并发业务从上游解耦到消息队列的消费者端处理,提高整个api的响应速度,保证业务最终完成即可。

1.3 热点隔离

统计某个热点数据重访问频次最高TopKS数据进行缓存,或者将remoteCache升级为localCache,提升效率

1.4 物理隔离

  1. 线程池隔离
  2. 同一主机下使用cgroup隔离服务的cpu、内存
  3. 进程隔离、集群隔离等

二、超时控制

超时控制,通过限制请求在系统里停留的最长时间,避免请求长时间在系统中占用资源,降低系统的性能。(当用户发起一次请求过了2~3秒都没反应,用户往往会重试,上次的请求也就应该丢弃了,如果不设置超时时间它还会继续在系统中执行业务,消耗系统的资源)
超时控制应该具备传递功能,当上游服务已经返回超时错误时,下游服务不要再继续执行。Quota传递到下游服务中,应该继承超时策略
go-grpc框架中,会依赖gRPC Metadata Exchange,基于HTTP2的Headers传递grpc-timeout字段传递到下游,构建待timeout的context。
简单说,如果你调用grpc方法中传入的context携带timeout,那么grpc则会在http2的header frame中设置字段grpc-timeout为context.DeadLine(),而grpc server在接收请求时若发现grpc-timeout的存在,则会构造一个带timeout的context传给handler,从而实现grpc的超时传递

go-zero的超时控制实现

go-zero 中我们可以再BFF层配置Timeout字段设置接口的超时时间,它会帮我们完成超时传递

go-zero/core/fx/timeout.go:超时控制

package fximport ("context""fmt""runtime/debug""strings""time"
)var (// ErrCanceled is the error returned when the context is canceled.ErrCanceled = context.Canceled// ErrTimeout is the error returned when the context's deadline passes.ErrTimeout = context.DeadlineExceeded
)// DoOption defines the method to customize a DoWithTimeout call.
type DoOption func() context.Context// DoWithTimeout runs fn with timeout control.
func DoWithTimeout(fn func() error, timeout time.Duration, opts ...DoOption) error {parentCtx := context.Background()for _, opt := range opts {parentCtx = opt()}ctx, cancel := context.WithTimeout(parentCtx, timeout)defer cancel()// create channel with buffer size 1 to avoid goroutine leakdone := make(chan error, 1)panicChan := make(chan any, 1)go func() {defer func() {if p := recover(); p != nil {// attach call stack to avoid missing in different goroutinepanicChan <- fmt.Sprintf("%+v\n\n%s", p, strings.TrimSpace(string(debug.Stack())))}}()done <- fn()}()select {case p := <-panicChan:panic(p)case err := <-done:return errcase <-ctx.Done():return ctx.Err()}
}// WithContext customizes a DoWithTimeout call with given ctx.
func WithContext(ctx context.Context) DoOption {return func() context.Context {return ctx}
}

参考

小米技术团队:https://xiaomi-info.github.io/2019/12/30/grpc-deadline/

三、过载保护、限流

本质上是根据系统的情况限制请求接收的数量,将能力以外的请求快速响应失败,但是二者有一些区别
过载保护:在未达到阈值前尽可能的接收请求,当cpu达到阈值(一般为90%)时得到系统的最大负载,开始丢弃一些请求。但是由于这个时候已经接近满负载了并且丢弃请求本身也具有一丢丢的性能损耗,此时若请求仍大量进来还是会将服务打崩。
限流:限流算法从始至终都在限制请求接收的速率,弥补过载保护在极端条件的风险,使得服务更加稳健

过载保护

通过记录当前机器的各项性能指标,比如cpu、内存、响应延迟等信息判断是否过载,如果过载则开启过载保护,以过载的系统负载作为阈值,判断当前请求数量是否超过阈值。
负载统计:

 
const (// 250ms and 0.95 as beta will count the average cpu load for past 5 secondscpuRefreshInterval = time.Millisecond * 250allRefreshInterval = time.Minute// moving average beta hyperparameterbeta = 0.95
)func init() {go func() {cpuTicker := time.NewTicker(cpuRefreshInterval)defer cpuTicker.Stop()allTicker := time.NewTicker(allRefreshInterval)defer allTicker.Stop()for {select {case <-cpuTicker.C:threading.RunSafe(func() {curUsage := internal.RefreshCpu()prevUsage := atomic.LoadInt64(&cpuUsage)// cpu = cpuᵗ⁻¹ * beta + cpuᵗ * (1 - beta)//指数衰变算法 usage := int64(float64(prevUsage)*beta + float64(curUsage)*(1-beta))atomic.StoreInt64(&cpuUsage, usage)})case <-allTicker.C:printUsage()}}}()
}

过载保护:/core/load/adaptiveshedder.go

func (as *adaptiveShedder) shouldDrop() bool {if as.systemOverloaded() || as.stillHot() {if as.highThru() {flying := atomic.LoadInt64(&as.flying)as.avgFlyingLock.Lock()avgFlying := as.avgFlyingas.avgFlyingLock.Unlock()msg := fmt.Sprintf("dropreq, cpu: %d, maxPass: %d, minRt: %.2f, hot: %t, flying: %d, avgFlying: %.2f",stat.CpuUsage(), as.maxPass(), as.minRt(), as.stillHot(), flying, avgFlying)logx.Error(msg)stat.Report(msg)return true}}return false
}

分布式限流

滑动事件窗口计数器

统计一段时间内的计数器,采用滑动窗口使得计数粒度更细,更平滑,缓解窗口与窗口之间的毛刺现象

package counterimport ("sync""time"
)type SlideWinCounter struct {window      []int         // 计数窗口size        int           // 窗口大小maxReq      int           // 单位时间内最大请求数startTime   time.Time     // 窗口开始时间startOffset int           // 窗口左侧偏移量interval    time.Duration // 滑动单位(时间间隔)mu          sync.Mutex    // 互斥锁
}func NewSlideWinCounter(size, max int, interval time.Duration) *SlideWinCounter {win := make([]int, 0, size)for i := 0; i < size; i++ {win = append(win, 0)}return &SlideWinCounter{window:    win,startTime: time.Now(),interval:  interval,size:      size,maxReq:    max,}
}func (c *SlideWinCounter) Allow() bool {c.mu.Lock()defer c.mu.Unlock()now := time.Now()//根据当前时间更新窗口,得到当前窗口偏移量offset := c.updateWindow(now)if c.count() > c.maxReq {return false}c.window[offset] += 1return true
}func (c *SlideWinCounter) updateWindow(now time.Time) int {offset := 0dis := now.Sub(c.startTime)span := int(dis.Milliseconds())/int(c.interval.Milliseconds())if span-c.size >= c.size { // 窗口全部过期c.toZero()} else if span >= c.size { // 窗口部分过期zeroSpan := (span - c.size + 1) % c.sizefor i := 0; i < zeroSpan; i++ {c.window[(c.startOffset+i)%c.size] = 0}c.startOffset = (c.startOffset + span - c.size) % c.sizeoffset = (c.startOffset + c.size - 1) % c.sizec.startTime = now} else { // 窗口没过期offset = (c.startOffset + span) % c.size}return offset
}func (c *SlideWinCounter) count() int {count := 0for _, c := range c.window {count += c}return count
}func (c *SlideWinCounter) toZero() {for i := 0; i < c.size; i++ {c.window[i] = 0}c.startOffset = 0c.startTime = time.Now()
}

参考go-zero的分布式限流实现:https://pandaychen.github.io/2020/09/21/A-DISTRIBUTE-GOREDIS-RATELIMITER-ANALYSIS/

四、熔断降级

五、重试、负载均衡

相关文章:

  • 【ARM系统】基础知识总结
  • 什么是SD NAND?
  • Linux 升级安装 Weblogic-补丁!
  • 别只知道Xmind了,这4款思维导图工具也都很实用!
  • 会声会影剪辑视频收费吗,会声会影最新破解版
  • RabbitMQ-消息队列之work使用
  • HTML—css
  • 鸿蒙Harmony实战开发:Touchscreen驱动器件硬件接口使用实例
  • top命令详解
  • avue-crud 自定义搜索项 插槽
  • 【Rust光年纪】从zlib到LZ4:探索Rust语言中的数据压缩和解压实现
  • Python 字符串去除空格
  • 批量处理图集SpriteAltas,关闭它的可读写属性
  • C++:IO流
  • AICon 全球人工智能与机器学习技术大会参会有感
  • css属性的继承、初识值、计算值、当前值、应用值
  • Docker 笔记(2):Dockerfile
  • ES6系统学习----从Apollo Client看解构赋值
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • iOS编译提示和导航提示
  • javascript数组去重/查找/插入/删除
  • mockjs让前端开发独立于后端
  • Netty源码解析1-Buffer
  • ReactNative开发常用的三方模块
  • redis学习笔记(三):列表、集合、有序集合
  • spring-boot List转Page
  • 阿里研究院入选中国企业智库系统影响力榜
  • 初识MongoDB分片
  • 关键词挖掘技术哪家强(一)基于node.js技术开发一个关键字查询工具
  • 关于 Cirru Editor 存储格式
  • 简析gRPC client 连接管理
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • #职场发展#其他
  • (20)docke容器
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (附源码)springboot高校宿舍交电费系统 毕业设计031552
  • (篇九)MySQL常用内置函数
  • (十) 初识 Docker file
  • (实战篇)如何缓存数据
  • (一)、python程序--模拟电脑鼠走迷宫
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • (转)fock函数详解
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .net core 外观者设计模式 实现,多种支付选择
  • .Net Web窗口页属性
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .NET 反射的使用
  • .Net 执行Linux下多行shell命令方法
  • .net 中viewstate的原理和使用
  • .net程序集学习心得
  • .net打印*三角形
  • @DateTimeFormat 和 @JsonFormat 注解详解
  • @WebService和@WebMethod注解的用法