一文搞懂分布式应用中的熔断器、断路器

17 篇文章 0 订阅
1 篇文章 0 订阅

熔断器是当依赖的服务已经出现故障时,为了保证自身服务的正常运行不在访问依赖的服务,防止雪崩效应

使用场景举个例子:

服务A调用服务B时,B的故障会导致A的故障。因为A调用B然后一直阻塞没有响应,资源被占用得不到释放。
在这里插入图片描述

两种分布式高可用的做法
  1. 限流器:服务B使用限流器限制A的请求量,从而导致过多的请求导致处理不过来导致宕机。【作用于被调用者(B)】
  2. 熔断器:服务A调用B的时候,如果B已经挂了,虽然B可能使用限流器什么的做到高可用,但是万一停电了或者其他的原因反正就是不可用了。A调用得到错误时就要使用熔断器,阻断自己的请求。【作用于调用者(A)】
熔断器工作原理

在熔断器中有三种状态:

  • 关闭:让请求通过的默认状态。如果请求成功/失败但低于阈值,则状态保持不变。可能出现的错误是超过最大并发数和超时错误。
  • 打开:当熔断器打开的时候,所有的请求都会被标记为失败;这是故障快速失败机制,而不需要等待超时时间完成。
  • 半开:定期的尝试发起请求来确认系统是否恢复。如果恢复了,熔断器将转为关闭状态或者保持打开

这三个状态是怎么维持并转换的:
需要服务调用者去统计一段时间内调用被调用者【也就是系统中的其他服务】的失败率(超时、或者500、或者不符合你的预期都可以算失败)。需要一个数据结构去统计保存这个状态,并且每个周期的开始清空这个状态。
在这里插入图片描述

如何手动实现一个熔断器:

大体分为三步

  • 划分时间窗口,设置判断条件
  • 请求进来判断是否满足熔断条件【并且拒绝or处理】
  • 请求处理完后,统计时间窗口内请求失败率、延迟不达标率、请求数等指标
package simplebreaker

import (
"log"
"sync/atomic"
"time"
)
 // 1.
type Counter int64

func (c *Counter) Add() int64 {
	return atomic.AddInt64((*int64)(c), 1)
}

func (c *Counter) Load() int64 {
	return atomic.LoadInt64((*int64)(c))
}

func (c *Counter) Reset() {
	atomic.StoreInt64((*int64)(c), 0)
}
// 2.
type CircuitBreaker struct {
	totalCounter    Counter
	failCounter     Counter
	duration        int64
	latencyLimit    int64
	totalLimit      int64
	failRateLimit   int64
	recoverFailRate int64
	lastTime        int64
	allow           int64
}

type CBOption func(cb *CircuitBreaker)

const (
	minDuration = 100
	minTotal    = 1000
	minFailRate = 2
)

func WithDuration(duration int64) CBOption {
	return func(cb *CircuitBreaker) {
		cb.duration = duration
	}
}

func WithLatencyLimit(latencyLimit int64) CBOption {
	return func(cb *CircuitBreaker) {
		cb.latencyLimit = latencyLimit
	}
}
func WithFailsLimit(failsRateLimit int64) CBOption {
	return func(cb *CircuitBreaker) {
		cb.failRateLimit = failsRateLimit
	}
}
func WithTotalLimit(totalLimit int64) CBOption {
	return func(cb *CircuitBreaker) {
		cb.totalLimit = totalLimit
	}
}

func NewCircuitBreaker(opts ...CBOption) *CircuitBreaker {
	cb := &CircuitBreaker{
		totalCounter:    0,
		failCounter:     0,
		duration:        0,
		latencyLimit:    0,
		totalLimit:      0,
		failRateLimit:   0,
		recoverFailRate: 0,
		lastTime:        0,
		allow:           1,
	}
	for _, opt := range opts {
		opt(cb)
	}
	if cb.duration < minDuration {
		cb.duration = minDuration
	}
	if cb.totalLimit < minTotal {
		cb.totalLimit = minTotal
	}
	if cb.failRateLimit < minFailRate {
		cb.failRateLimit = minFailRate
	}
	cb.recoverFailRate = cb.failRateLimit / 2
	return cb
}

// 3.
func (cb *CircuitBreaker) Allow(f func() bool) bool {
	fail := cb.failCounter.Load()
	total := cb.totalCounter.Load()
	start := time.Now().UnixNano() / int64(time.Millisecond)
	if start > cb.lastTime+cb.duration {
		atomic.StoreInt64(&cb.lastTime, start)
		cb.failCounter.Reset()
		cb.totalCounter.Reset()
		atomic.StoreInt64(&cb.allow, 1)
	}
	cb.totalCounter.Add()
	allow := !(total > 0 && fail*100/cb.failRateLimit >= total || total >= cb.totalLimit)
	if atomic.LoadInt64(&cb.allow) == 0 {
		if fail*100/cb.recoverFailRate > total {
			allow = false
		} else if allow {
			atomic.StoreInt64(&cb.allow, 1)
		}
	} else if !allow {
		atomic.StoreInt64(&cb.allow, 0)
	}
	if !allow {
		log.Fatalln("not allowed")
		return false
	}
	ok := f()
	end := time.Now().UnixNano()/int64(time.Millisecond)
	if (cb.latencyLimit > 0 && end-start >= cb.latencyLimit) || !ok {
		cb.failCounter.Add()
	}
	return true
}
go-zero中的熔断器实现
1. 理解go-zero面向对象设计

在这里插入图片描述
breakers调用了Breaker实现功能,实现了Breaker的复用。不用每次都去New新的Breaker。
在这里插入图片描述
breaker定义了支持包的所有的接口、结构体
在这里插入图片描述
googlebreaker是熔断器的具体实现
在这里插入图片描述

// 这个函数是理解go-zero熔断实现核心的函数
func (b *googleBreaker) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
	if err := b.accept(); err != nil {
		if fallback != nil {
			return fallback(err)
		}
		return err
	}

	defer func() {
		if e := recover(); e != nil {
			b.markFailure()
			panic(e)
		}
	}()

	err := req()
	if acceptable(err) {
		b.markSuccess()
	} else {
		b.markFailure()
	}

	return err
}
2. 理解算法实现
func (b *googleBreaker) accept() error {
	accepts, total := b.history()
	weightedAccepts := b.k * float64(accepts)
	// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
	dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
	if dropRatio <= 0 {
		return nil
	}

	if b.proba.TrueOnProba(dropRatio) {
		return ErrServiceUnavailable
	}

	return nil
}

Golang里面的熔断器:
https://github.com/tal-tech/go-zero/tree/master/core/breaker

https://github.com/go-kit/kit/tree/master/circuitbreaker

https://github.com/asim/go-micro/tree/master/plugins/wrapper/breaker

https://github.com/go-kratos/kratos/tree/master/pkg/net/netutil/breaker

Java里面的熔断器:
https://github.com/Netflix/Hystrix

https://github.com/alibaba/Sentinel

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
分布式数据库原理是指将数据分散存储在不同的物理节点上,通过网络进行通信和数据同步的数据库系统。它可以提高数据库的可扩展性、容错性和性能。 分布式数据库的设计思想是将数据划分为多个分片,每个分片存储在不同的节点上。通过数据划分,可以将数据存储在离用户更近的节点上,提高数据的访问速度。同时,分布式数据库可以通过数据复制和数据分发来提供容错性。数据复制可以将数据备份到多个节点上,当某个节点发生故障时,系统可以自动切换到其他节点上继续提供服务。数据分发可以将请求分发到不同的节点进行处理,提高系统的并发处理能力。 PostgreSQL是一种开源的关系型数据库管理系统,具备分布式架构。PostgreSQL的分布式架构包括一个主节点和多个从节点。主节点负责接收用户的请求,并将数据同步到从节点上。从节点可以进行读操作,提高系统的并发处理能力。如果主节点发生故障,从节点可以自动切换为主节点,保证系统的可用性。 PostgreSQL的分布式架构基于流复制技术。主节点将产生的日志记录(WAL日志)通过流复制传输到从节点,从节点会将这些日志记录应用到自己的数据库。这样可以确保主节点和从节点之间的数据一致。同时,PostgreSQL还支持逻辑复制和扩展查询,可以根据实际需求对数据进行同步和查询的优化。 总之,分布式数据库原理是通过数据的划分、复制和分发,提高数据库的可扩展性、容错性和性能。PostgreSQL的分布式架构基于流复制技术,通过多个节点的协作来提供可靠的数据存储和高效的数据访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值