Sentinel-Go 源码系列,二)|初始化流程和责任链设计模式

技术Sentinel-Go 源码系列,二)|初始化流程和责任链设计模式 Sentinel-Go 源码系列(二)|初始化流程和责任链设计模式上节中我们知道了 Sentinel-Go 大概能做什么事情,最简

Sentinel-Go源代码系列(二)|初始化流程和责任链设计模式

在最后一节中,我们了解了Sentinel-Go可以做什么,最简单的例子是如何运行。

其实我已经写了这个系列的第二篇文章,但是很久没有发布了。感觉光初始化过程好像有点单一,所以加了责任链模型,二合一,内容更丰富。

初始化流程

初始化做了什么

Sentinel-Go在初始化时做了以下两件事:

以各种方式(文件、环境变量等)加载全局配置。)

启动异步调度任务或服务,如机器cpu、内存信息收集、Metricalog写入等。

初始化流程详解

提供的 API

在前面的例子中,我们使用了最简单的初始化方法。

func InitDefault()错误

此外,它还提供了其他几种初始化方法。

//使用给定的解析器方法初始化以解析配置。

func initwithParser(config bytes[]字节),parser func([]字节)(*config。实体,错误))(错误错误)

//用解析的配置对象初始化。

func InitWithConfig(Compentity * config。实体)(错误)

//从yaml文件加载配置初始化

func InitWithConfigFile(配置路径字符串)错误

从命名上我们可以看出,它们只是获得配置的方式不同。其中,InitWithParser很有意思。传入的解析器是一个函数指针,对于习惯用Java编写的我来说还是有点陌生。例如,下面的解析器可以通过json解析来编写。

解析器:=func(config bytes[]byte)(* config。实体,错误){ 0

conf :=配置。实体{}

err :=json。解组(配置字节,配置)

返回确认,错误

}

conf :=' { \ ' Version ' : ' v1 \ ',\ ' Sentinel ' : { \ ' App ' : { \ ' Name ' : ' roshi-App \ ',\ ' Type \ ' :0 } } '

err :=api。InitWithParser([]字节(conf),解析器)

配置项

简单看一下Sentinel-Go的配置项。首先,配置被包装在一个实体中,该实体包含一个版本和真实的配置信息SentinelConfig。

实体类型结构{

版本字符串

哨兵SentinelConfig

}

然后,SentinelConfig看起来是这样的:

类型SentinelConfig结构{

应用程序结构{

//应用程序名称

名称字符串

//应用类型:通用应用、网关

int32型

}

//导出器配置

导出器导出配置

//日志配置

日志配置

//统计配置

Stat StatConfig

//是否缓存时间戳?

UseCacheTime bool ` YAML : ' UseCacheTime ' '

}

应用程序信息

应用程序名称

应用类型:如通用应用、网关应用等。

导出器配置:普罗米修斯导出器公开服务的端口和路径。

类型ExporterConfig结构{

度量度量导出配置

}

类型MetricExporterConfig结构{

//http服务地址,如' :8080 '

HttpAddr字符串` yaml:'http_addr ' '

//http服务路径,如“/metrics”。

HttpPath字符串` yaml:'http_path ' '

}

日志配置:包括使用什么记录器,日志目录,文件是否使用pid(防止出现

机器部署两个应用日志混合),以及 metric log 的单个文件大小、最多保留文件个数、刷新时间

type LogConfig struct {	// logger,可自定义	Logger logging.Logger	// 日志目录	Dir string	// 是否在日志文件后加 PID	UsePid bool `yaml:"usePid"`	// metric 日志配置	Metric MetricLogConfig}type MetricLogConfig struct {  // 单个文件最大占用空间	SingleFileMaxSize uint64 `yaml:"singleFileMaxSize"`	// 最多文件个数	MaxFileCount      uint32 `yaml:"maxFileCount"`	// 刷新间隔	FlushIntervalSec  uint32 `yaml:"flushIntervalSec"`}
  • StatConfig:统计配置包括资源采集窗口配置,metric 统计的窗口、系统信息收集间隔
type StatConfig struct {	// 全局统计资源的窗口(后续文章再解释)	GlobalStatisticSampleCountTotal uint32 `yaml:"globalStatisticSampleCountTotal"`	GlobalStatisticIntervalMsTotal  uint32 `yaml:"globalStatisticIntervalMsTotal"`	// metric 统计的窗口(后续文章再解释)	MetricStatisticSampleCount uint32 `yaml:"metricStatisticSampleCount"`	MetricStatisticIntervalMs  uint32 `yaml:"metricStatisticIntervalMs"`	// 系统采集配置	System SystemStatConfig `yaml:"system"`}type SystemStatConfig struct {	// 采集默认间隔	CollectIntervalMs uint32 `yaml:"collectIntervalMs"`	// 采集 cpu load 间隔	CollectLoadIntervalMs uint32 `yaml:"collectLoadIntervalMs"`	// 采集 cpu 使用间隔	CollectCpuIntervalMs uint32 `yaml:"collectCpuIntervalMs"`	// 采集内存间隔使用	CollectMemoryIntervalMs uint32 `yaml:"collectMemoryIntervalMs"`}

配置覆盖

从上文知道,参数可以通过自定义 parser / 文件 / 默认 的方式来传入配置,但后面这个配置还可以用系统的环境变量覆盖,覆盖项目前只包括应用名、应用类型、日志文件使用使用 PID 结尾、日志目录

func OverrideConfigFromEnvAndInitLog() error {
	// 系统环境变量可覆盖传入的配置
	err := overrideItemsFromSystemEnv()
	if err != nil {
		return err
	}
	...
	return nil
}

启动后台服务

  • 启动 聚合 metric 定时任务,聚合后发送到 chan,聚合后的格式如下:
_, err := fmt.Fprintf(b, "%d|%s|%s|%d|%d|%d|%d|%d|%d|%d|%d",
		m.Timestamp, timeStr, finalName, m.PassQps,
		m.BlockQps, m.CompleteQps, m.ErrorQps, m.AvgRt,
		m.OccupiedPassQps, m.Concurrency, m.Classification)

时间戳|时间字符串|名称|通过QPS|阻断QPS|完成QPS|出错QPS|平均RT|已经通过QPS|并发|类别

  • 启动 metric 写入日志定时任务,可配置间隔时间(秒级),接受上个任务写入 chan 的数据

  • 启动单独 goroutine 收集 cpu 使用率 / load、内存使用,收集间隔可配置,收集到的信息存放在 system_metric 下的私有变量

var (
	currentLoad        atomic.Value
	currentCpuUsage    atomic.Value
	currentMemoryUsage atomic.Value
)
  • 若开启,则启动单独 goroutine 缓存时间戳,间隔是 1ms,这个主要是为了高并发下提高获取时间戳的性能
func (t *RealClock) CurrentTimeMillis() uint64 {
  // 从缓存获取时间戳
	tickerNow := CurrentTimeMillsWithTicker()
	if tickerNow  uint64(0) {
		return tickerNow
	}
	return uint64(time.Now().UnixNano()) / UnixTimeUnitOffset
}

获取时,如果拿到 0 则说明未开启缓存时间戳,取当前,如果拿到值说明已开启,可直接使用

  • 若配置了 metric exporter,则启动服务,监听端口,暴露 prometheus 的 exporter

责任链模式

什么是责任链模式

可以用这样一张图形象地解释什么是责任链:

责任链模式为每次请求创建了一个,链上有 N 多个处理者,处理者可在不同阶段处理不同的事情,就像这幅图上的小人,拿到一桶水(请求)后都可以完成各自的事情,比如往头上浇,然后再传递给下一个。

为什么叫责任因为每个处理者只关心自己的责任,跟自己没关系就递交给链上的下一个处理者。

责任链在哪里有用到很多开源产品都是用了责任链模式,如 DubboSpring MVC 等等

这么设计有什么好处

  • 简化编码难度,抽象出处理模型,只需关注关心的点即可
  • 扩展性好,如果需要自定义责任链中的一环或者插拔某一环,非常容易实现

关于扩展性除了大家理解的软件设计中的扩展性外,这里还想提两点,阿里开源的软件其实都有高扩展性这个特性,一是因为是开源,别人使用场景未必和自己一致,留出扩展接口,不符合要求的,用户可以自行实现,二是如果要追溯,阿里开源扩展性 Dubbo 可能算是祖师爷(未考证),Dubbo 作者(梁飞)的博客中说过为什么 Dubbo 要设计这么强的扩展性,他对代码有一定的追求,在他维护时期,代码能保证高质量,但如果项目交给别人,如何才能保持现在的水准呢于是他设计出一套很强的扩展,后面开发基于这个扩展去做,代码就不会差到哪里去

  • 可动态,可针对每个请求构造不同的责任链

Sentinel-Go 责任链设计

先看责任链的数据结构定义,Sentinel-Go 把处理者叫 Slot(插槽),将 Slot 分为了前置统计、规则校验、统计三组,且每组是有有序的

type SlotChain struct {
	// 前置准备(有序)
	statPres []StatPrepareSlot
	// 规则校验(有序)
	ruleChecks []RuleCheckSlot
	// 统计(有序)
	stats []StatSlot
	// 上线文对象池(复用对象)
	ctxPool *sync.Pool
}

在调用 Entry 开始进入 Sentinel 逻辑时,如果没有手动构造 SlotChain,则使用默认。

为什么这里要设计成三个 Slot组呢因为每组 Slot 的行为稍有不同,比如前置准备的 Slot 不需要返回值,规则校验组需要返回值,如果校验当前流量不通过,还需要返回原因、类型等信息,统计 Slot 还会有一些入参,比如请求是否失败等等

type BaseSlot interface {
	Order() uint32
}
type StatPrepareSlot interface {
	BaseSlot
	Prepare(ctx *EntryContext)
}
type RuleCheckSlot interface {
	BaseSlot
	Check(ctx *EntryContext) *TokenResult
}
type StatSlot interface {
	BaseSlot
	OnEntryPassed(ctx *EntryContext)
	OnEntryBlocked(ctx *EntryContext, blockError *BlockError)
	OnCompleted(ctx *EntryContext)
}

总结

本文从源码角度分析了 Sentinel-Go 的初始化流程和责任链的设计,总体上来说还是比较简单,接下来的系列文章将会分析 Sentinel-Go 的限流熔断等的核心设计与实现。


搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/77302.html

(0)

相关推荐

  • 怎么安装BBED

    技术怎么安装BBED这篇文章主要介绍“怎么安装BBED”,在日常操作中,相信很多人在怎么安装BBED问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么安装BBED”的疑惑有所帮助!接下来,

    攻略 2021年11月5日
  • 如何把word压缩小一点

    技术如何把word压缩小一点这篇文章给大家分享的是有关如何把word压缩小一点的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。  使用工具:迅捷压缩  工具介绍:  迅捷压缩软件是一款集图片压缩

    攻略 2021年10月26日
  • #define do{...}while(0)形式的意义,转载)

    技术#define do{...}while(0)形式的意义,转载) #define do{...}while(0)形式的意义(转载)#define do{...}while(0)这种奇怪形式的宏定义经

    礼包 2021年11月29日
  • C#操作MongoDb插入更新和删除的方法是什么

    技术C#操作MongoDb插入更新和删除的方法是什么C#操作MongoDb插入更新和删除的方法是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所

    攻略 2021年11月3日
  • Linux权限位介绍

    技术Linux权限位介绍 Linux权限位介绍Linux 系统,最常见的文件权限有 3 种,即对文件的读(用 r 表示)、写(用 w 表示)和执行(用 x 表示,针对可执行文件或目录)权限。在 Linu

    礼包 2021年12月7日
  • 幸福是什么医生说,对于90后来说,什么是幸福

    技术幸福是什么医生说,对于90后来说,什么是幸福感谢邀请幸福是什么医生说。对于90后来说,什么是幸福?大调查发现,90后的年轻人幸福感还是很高的,只有13.9%的年轻人感觉不幸福。他们对整体社会环境和生活状态的满意度都很

    生活 2021年10月28日