手撸了一个Golang协程池

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 传送门: vearne/executor 0. 引言 本项目受到Java Executors的启发 Executors,并参考了ExecutorService的接口设计。 支持取消单个任务,或者取消整个集群的任务。 并提供了一个可以动态扩容和缩容的实现DynamicGPool。 欢迎提PR。下面是更详细的说明: 1. 特性 支持取消单个任务,也可以取消协程池上的所有任务 Future.Cancel() ExecutorService.Cancel() 你也可以使用context.Context取消task或者pool。 可以创建多种不同类型的协程池(SingleGPool|FixedGPool|DynamicGPool) 2. 多种类型的协程池 类别 说明 备注 SingleGPool 单个worker协程池 FixedGPool 固定数量worker协程池 DynamicGPool worker数量可以动态变化的协程池 min:最少协程数量 max:最大协程数量 2.1 SingleGPool NewSingleGPool(ctx context.Context, opts ...option) ExecutorService 2.2 FixedGPool NewFixedGPool(ctx context.Context, size int, opts ...option) ExecutorService 2.3 DynamicGPool NewDynamicGPool(ctx context.Context, min int, max int, opts ...dynamicOption) ExecutorService 2.3.1 扩容规则 提交任务时,如果任务队列已经满了,则尝试增加worker去执行任务。 2.3.2 缩容规则: 条件: 如果处于忙碌状态的worker少于worker总数的1/4,则认为满足条件 执行meetCondNum次连续检测,每次间隔detectInterval。如果每次都满足条件,触发缩容。 缩容动作尝试减少一半的worker 3. 注意 由于executor使用了channel作为作为任务队列,所以提交任务时,可能会发生阻塞。 ...

September 26, 2022 · 2 min

Golang 模糊测试 简明教程

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 Golang1.18 引入了一个模糊测试的功能。文本萌叔将会简单介绍下这个特性。 2.模糊测试 传送门 比如我们编写了一个工具包来做除法 package fuzz func Div(a, b int) int { return a / b } 由于疏忽忽略了某些边界条件。显然在上面的代码中,b不能等于0,否则会发生运行时错误。 普通的单元测试 func TestDiv(t *testing.T) { testcases := []struct { a, b, want int }{ {10, 2, 5}, {5, 3, 1}, {-6, 3, -2}, {-6, -3, 2}, } for _, tc := range testcases { result := Div(tc.a, tc.b) if Div(tc.a, tc.b) != tc.want { t.Errorf("Div: %q, want %q", result, tc.want) } } } 由于对分支条件和异常情况考虑的不周全,自测的单元测试能够正常通过。但是一旦b=0,程序将会崩溃。 ...

September 14, 2022 · 1 min

Golang embed(内嵌)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引言 Golang编译成单个bin文件,发布的时候相当方便了。但是某个配置文件没有办法直接打在bin文件中,比如SSL的密钥,html模板等等。 Golang 1.16 标准库引入了embed包,它可以在编译期间将外部文件嵌入到bin包中。 嵌入的文件都是只读的,因此是协程安全的。 另外由于编译期间嵌入的,因此如果文件发生变化,需要重新编译。 2. 示例 2.2 示例1 嵌入文件夹 传送门 . ├── html │ ├── a.html │ └── index.html └── main.go package main import ( "embed" "log" "net/http" ) //go:embed html var content embed.FS func main() { mutex := http.NewServeMux() mutex.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content)))) err := http.ListenAndServe(":8080", mutex) if err != nil { log.Fatal(err) } } 2.2 示例2 嵌入模板文件 传送门 . ├── main.go └── template ├── hello.tpl └── hello2.tpl package main import ( "bytes" "embed" "html/template" "log" ) //go:embed template/*.tpl var mytpl embed.FS func main() { check := func(err error) { if err != nil { log.Fatal(err) } } list, err := mytpl.ReadDir("template") check(err) for _, item := range list { log.Println("template/" + item.Name()) } data, err := mytpl.ReadFile("template/hello.tpl") log.Println("data:", string(data)) check(err) t, err := template.New("hello").Parse(string(data)) content := bytes.NewBufferString("") err = t.Execute(content, map[string]string{"name": "张三"}) check(err) log.Println(content.String()) } 2.3 示例3 嵌入字符串/字节数据 传送门 ...

September 2, 2022 · 1 min

REDIS-CLUSTER集群slot迁移过程分析

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.前言 在前面的文章中, 《REDIS-CLUSTER集群创建内部细节详解》 萌叔创建一个Redis集群。 这篇文章,我会为集群添加2个节点,并介绍slot的迁移过程。 2.集群扩容 添加主节点127.0.0.1:7006 redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 添加从节点127.0.0.1:7007(并指定其Master节点) redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7000 --cluster-slave --cluster-master-id 86f3cb72813a2d07711b56b3143ff727911f4e1e 新添加的节点上并没有slot分布,需要通过命令让slot重新分布 root@BJ-02:~/cluster-test/7007# redis-cli --cluster reshard 127.0.0.1:7000 >>> Performing Cluster Check (using node 127.0.0.1:7000) S: 7eb7ceb4d886580c6d122e7fd92e436594cc105e 127.0.0.1:7000 slots: (0 slots) slave replicates be905740b96469fc6f20339fc9898e153c06d497 M: 86f3cb72813a2d07711b56b3143ff727911f4e1e 127.0.0.1:7006 slots: (0 slots) master 1 additional replica(s) M: be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005 slots:[0-5460] (5461 slots) master 1 additional replica(s) S: 9e29dd4b2a7318e0e29a48ae4b37a7bd5ea0a828 127.0.0.1:7007 slots: (0 slots) slave replicates 86f3cb72813a2d07711b56b3143ff727911f4e1e S: 603a8a403536f625f53467881f5f78def9bd46e5 127.0.0.1:7003 slots: (0 slots) slave replicates 784fa4b720213b0e2b51a4542469f5e318e8658b M: 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master 1 additional replica(s) S: 585c7df69fb267941a40611bbd8ed90349b49175 127.0.0.1:7004 slots: (0 slots) slave replicates 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 M: 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. How many slots do you want to move (from 1 to 16384)? 0 How many slots do you want to move (from 1 to 16384)? 1 What is the receiving node ID? 86f3cb72813a2d07711b56b3143ff727911f4e1e Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node #1: all Ready to move 1 slots. Source nodes: M: be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005 slots:[0-5460] (5461 slots) master 1 additional replica(s) M: 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master 1 additional replica(s) M: 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master 1 additional replica(s) Destination node: M: 86f3cb72813a2d07711b56b3143ff727911f4e1e 127.0.0.1:7006 slots: (0 slots) master 1 additional replica(s) Resharding plan: Moving slot 5461 from 784fa4b720213b0e2b51a4542469f5e318e8658b Do you want to proceed with the proposed reshard plan (yes/no)? yes Moving slot 5461 from 127.0.0.1:7001 to 127.0.0.1:7006: 3. Slot迁移过程 迁移过程在clusterManagerMoveSlot() 中 ...

June 4, 2022 · 3 min

Golang协程调度

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 本文基于go1.17.6 1.什么是协程? 协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』, 具有对内核来说不可见的特性。 1.1 协程的特点 线程的切换由操作系统负责调度,协程由用户自己进行调度 线程的默认Stack大小是MB级别,而协程更轻量,接近KB级别。 在同一个线程上的多个协程,访问某些资源可以不需要锁 适用于被阻塞的,且需要大量并发的场景。 1.2 Golang的GMP模型 CPU驱动线程上的任务执行 线程由操作系统内核进行调度,Goroutine由Golang运行时(runtime)进行调度 P的 local runnable queue是无锁的,global runnable queue是有锁的 P的 local runnable queue长度限制为256 注意: M和P是绑定关系 M和G是绑定关系 P只是暂存G,他们之间不是绑定关系 E-R图 简化后的E-R图 注意: 后面为了书写简单直接将 local runnable queue表示为本地队列 global runnable queue表示为全局队列 延伸 timer的四叉堆和内存分配器使用的mcache也是每个P一个 Q: 为什么默认情况下P的数量与CPU数量一致? A: 这样可以避免把CPU时间浪费在上线文切换上 1.3 协程和线程的资源消耗对比 类别 栈内存 上下文切换 备注 Thread 1MB 1us 内存占用使用的是Java线程的默认栈大小 Goroutine 4KB 0.2us 内存占用使用的是Linux+x86下的栈大小 2. 常见的Goroutine 让出/调度/抢占场景 ...

March 25, 2022 · 5 min

打印struct中Field内存对齐的小工具

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 因为使用golangci-lint时长有关于内存对齐的告警提示,所以做了一个小工具,可以查看struct中Field在内存中的对齐情况 传送门: vearne/mem-align 2. 使用 package main import ( "github.com/vearne/mem-align" ) type Car struct { flag bool age int32 F1 int8 age2 int32 age3 int16 F2 int64 F3 *int32 InnerStruct struct{ InnerByte byte //InnerStr string } F4 []byte Name string F5 error } func main(){ memalign.PrintStructAlignment(Car{}) } 输出: 相同字符,相同颜色的的字符,表示同一个Field。 后记 其实golang官方在go vet中提供了一个子工具fieldalignment golangci-lint也是使用这个分析器进行内存对齐的检查 安装 fieldalignment go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest 使用-fix参数可以直接修改代码文件 fieldalignment -fix {packagePath} 参考资料 opennota/check mdempsky/maligned Structure size optimization in Golang (alignment/padding) size and alignment guarantees fieldalignment

December 13, 2021 · 1 min

rocketmq分享

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 注意:文中使用的部分图,误将ConsumeQueue写成了ConsumerQueue 1.简介 RocketMQ是一个分布式消息和流数据平台,具有低延迟、高性能、高可靠性、万亿级容量和灵活的可扩展性。RocketMQ是2012年阿里巴巴开源的第三代分布式消息中间件,2016年11月21日,阿里巴巴向Apache软件基金会捐赠了RocketMQ;第二年2月20日,Apache软件基金会宣布Apache RocketMQ成为顶级项目。 2.架构 正常情况,写和读都走Master,Master如果宕机,读可以走Slave 在 RocketMQ 4.5 版本之前,RocketMQ 只有 Master/Slave 一种部署方式,虽然这种模式可以提供一定的高可用性但也存在比较大的缺陷。为了实现新的高可用多副本架构,RockeMQ 最终选用了基于 Raft 协议的 commitlog 存储库 DLedger。 2.1 四种角色 2.1.1 NameServer 存储元数据 topic -> broker 无状态 接收来自broker的心跳 检查与borker的通讯是否过期 Topic路由信息 { "OrderTopicConf": "", "queueDatas": [{ "brokerName": "broker-3", "readQueueNums": 4, "writeQueueNums": 4, "perm": 6, "topicSynFlag": 0 }, { "brokerName": "broker-4", "readQueueNums": 4, "writeQueueNums": 4, "perm": 6, "topicSynFlag": 0 }], "brokerDatas": [{ "cluster": "Default_Cluster", "brokerName": "broker-4", "brokerAddrs": { "0": "192.168.12.123:10911", "1": "192.168.12.127:10911" } }, { "cluster": "Default_Cluster", "brokerName": "broker-3", "brokerAddrs": { "1": "192.168.12.220:10911", "0": "192.168.12.12:10911" } }] } 2.1.2 Producter 有发往broker的心跳(Master) ...

November 25, 2021 · 3 min

Grafana-Variable配置小技巧

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 Dashboard是Grafana中非常重要的概念,每个Dashboard都是一个巨大的看板,在Dashboard上可以配置Panel(图表)。在Dashboard中有个特别的配置–Variables,Variables提供了用户和面板交互,可以动态刷新面板。 有些特殊的场景,需要有些小技巧,否则Variables仍然无法满足我们的要求。 1. Case 1 我要查看不同股票的日线图 1.1 Query SELECT trade_date AS "time", close FROM daily_data where ts_code = "$ts_code" order by trade_date ts_code是股票的代码,显然如果只看股票代码,很难想起这个代码究竟对应的是哪一只股票。 最好的方式是如下图这样,在变量的下拉列表中,显示股票名称,但是在实际的Query中使用股票代码查询。 grafana支持这种玩法,称为text和value 1.2 变量配置 注意需要Grafana的版本在7.4.5 或以上 1.2.1 配置Query select concat(name, "#", ts_code) from target; 这一步会查出形如下面的结果 中国平安#601318.SH 三一重工#600031.SH ... 1.2.2 分离出text和value 配置Regex /(?<text>.*)#(?<value>.*)/ 2. Case 2 我们有一组MySQL实例需要监控,MySQL的数量多达几十个。 但是实际上大家都知道,MySQL是主从的,1个主和多个从构成了1个集群。每个集群单独为1个业务提供服务。 MySQL实例如果这样列出来,萌叔根本不可能知道,这个实例属于哪个集群,支撑那个业务。 我们希望的效果,是先选择partner(业务方),然后再选择再从这个集群中选出关心的实例 2.1 Query 事实上,我们可以通过构建2个Variables来达到这个目标 2.1.1 Query1 label_values(mysql_up, partner) 得到变量partner 2.1.2 Query2 label_values(mysql_up{partner="$partner"}, instance) Query2引用Query1的结果 3. Case 3 萌叔的服务部署在2个机房,机房名称是由k8s的维护定义好的,形如 ...

November 18, 2021 · 1 min

利用划分子集限制连接池的大小(2)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 2.3 连接池的变化情况 如果采用确定性算法来划分子集还需要考虑在有节点宕机或者Backend进行过滚动升级的情况下,引起的连接池中连接的变化情况。 这里先上结论,要保证连接池中的连接不大量的销毁和创建,Backend也需要绑定backendID,以确保对于每个Client选出的子集不发生重大的变化 绑定backendID确保调用Subset时,Backend在backends中的位置不发生变化。(绑定backendID可以利用分布式锁来实现) func Subset(backends []string, clientID, subsetSize int) 我们来看一个反例,假定某个Backend backend-10实例宕机,连接池的变化情况 subset/certainty4/verify.go 结果 所有连接数:2500, 重建的连接总数:1257, 百分比: 50.280000 % 因为一个实例宕机,整个集群的连接池中一半的连接都要重新销毁,重连一次,开销还是非常大的。大家都知道集群做滚动发布,持续时间可能长达数分钟。也就是在这数分钟内Client的连接池可能会频繁的发生销毁和创建,这不得不引起我们的重视。不过好在发布动作在服务运行的整个生命周期占比不大。 3. 效果 最后我们来看下,使用划分子集方案后,带来的实际效果 连接数大概下降了30% 常驻内存大概下降40% 协程数下降30% 令人惊喜的是,GC耗时也有了一定程度的下降 总结 使用划分子集的方案来限制连接池的大小确实有不俗的效果,但也要注意细节和风险点。河豚虽然美味,但也有可能有毒。 参考资料 用subsetting 限制连接池中的连接数量 load balancing datacenter/

August 24, 2021 · 1 min

玩转consul(5)--大规模部署的性能开销定量分析(补充说明)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 在萌叔的文章玩转consul(3)–大规模部署的性能开销定量分析 中,探讨了consul支持大规模集群可能出现性能瓶颈。引出可以通过拆分consul集群或者逻辑切分DC的方法,来降低consul的压力。本文将展开说明一下。 2. 解释 2.1 单个DC的情况 假设服务A依赖服务B(调用服务B), 服务A和服务B各有200个实例, 服务B注册到consul集群上, 服务A通过consul集群发现服务B。 如果服务B做滚动发布,考虑最坏的情况,每次发布一个实例, consul总共发出的通知数为 200 * 200 = 40,000 2.2 2个DC的情况 拆分成2个DC后,单个DC中,服务A和服务B各有100个实例,限制不允许跨DC调用。如果服务B做滚动发布,考虑最坏的情况,DC1和DC2同时发布,每次发布一个实例,consul总共发出的通知数为 100 * 100 + 100 * 100 = 20,000 3. 总结 当服务的规模很大时,即使拆分成2个DC,服务整体的高可用能力不会受到影响。对比2.1和2.2,consul集群的压力减少了一半。另外咱们之前提过,单个DC中consul集群(server)的规模不宜过大,否则会影响日志复制、还有选主的速度。拆分到2个DC后,原有的consul集群也被拆分成了2个集群,单个DC中consul集群的规模下降了,这样还给我们留出扩容的余地。 后记 2022年1月29日 用单元化的思想(见参考资料1、2)来解决注册中心压力大的问题与我文章中提到的,划分逻辑DC的思想不谋而合。 参考资料 1.单元化架构解决了什么问题 2.单元化介绍 请我喝瓶饮料

June 16, 2021 · 1 min