1.direct io 详解
2.golangï¼contextä»ç»
3.gRPC 流量控制详解
4.Windows平台C++ 使用VS2015 编译gRPC(总结)
5.CockroachDB 源码闲逛 - II (insert a row)
6.PolarisMesh源码系列--Polaris-Go注册发现流程
direct io 详解
DDD课程:
DDD 案例实战课 - 天涯兰 - 掘金小册
Netty 实现微信课程:
Netty 入门与实战:仿写微信 IM 即时通讯系统
MySQL 原理详解:
MySQL的源码日志、事务原理-undolog、源码redolog、源码binlog两阶段提交详解 - 学新通
不太帅的源码程序员:图解 MySql 原理
MySQL架构原理(详解)-腾讯云开发者社区-腾讯云
Redis底层数据结构(图文详解):
Redis底层数据结构(图文详解)_玄郭郭的博客-CSDN博客
epoll 源码详解:
Epoll源码深度剖析--转自坚持,每天进步一点点 - Desh - 博客园
C++ 标准库参考手册:
Eajack Lau:Cpp标准库速查手册
Go-lang 多环境配置:
Go 多环境下配置管理方案(多种方案)
C++ 多线程和多进程编程:
c++ 多线程和多进程编程_c++多线程和多进程_CapticalAmerican的源码博客-CSDN博客
多线程和多进程的区别(C++) - Vae永Silence - 博客园
C++5万字面试题(加精):
五万字长文 C C++ 面试知识总结(中)
Sentinel 原理详解:
sentinel运行原理详解_sentinel原理_龚厂长的博客-CSDN博客
sentinel 基本原理 - monkeydai - 博客园
Nginx 加锁原理:
nginx分布式锁以及accept锁简单整理_yun的博客-CSDN博客
Spring Cloud 原理:
mikechen.cc/.html
GraalVM和Spring Native尝鲜,一步步让Springboot启动飞起来,源码源码iosms完成启动
Dubbo 原理详解:
秒懂Dubbo框架(原理篇) - 高楼
Netty 原理详解:
授人以渔:这可能是源码目前最透彻的Netty原理架构解析
Grpc golang 源码详解:
解析grpc架构与原理-腾讯云开发者社区-腾讯云
LVS 原理详解:
深入浅出 | 阿里巴巴处理千万并发访问利器——LVS - 老男孩教育
Nginx、LVS、源码Keepalived的源码关系(加精):
Nginx、LVS、源码Keepalived的源码关系_lvs nginx keepalived_Dnils的博客-CSDN博客
Nacos 原理详解:
Nacos原理详解(注册中心,配置中心)-腾讯云开发者社区-腾讯云
Nacos实现原理详细讲解_nacos原理详解_木鱼-的博客-CSDN博客
Nacos 持久化详解:
东小西:Nacos「持久化」
Nacos 配置中心详解:
Spring Cloud Alibaba(六)Nacos配置中心原理分析
Nacos 长轮询详解(加精):
如何用Java实现简单的长轮询?-java教程-PHP中文网
Nacos 架构与原理深度分析:
Nacos架构与原理深度分析_nacos架构原理_Doker 多克的博客-CSDN博客
Apollo原理分析及功能详解(配置管理、集群管理等):
Apollo原理分析及功能详解(配置管理、源码集群管理等)_apollo的源码作用_、楽.的源码博客-CSDN博客
Nacos 实现 AP+CP原理[Raft 算法 ]:
Nacos 实现 AP+CP原理[Raft 算法 ]
Nacos 高可用:
敏中午:Nacos 高可用介绍
MySQL 复制与高可用水平扩展架构实战:
MySQL复制与高可用水平扩展架构实战_mysql 水平扩展_小二上酒8的博客-CSDN博客
Apollo 架构详解:
Apollo(阿波罗)架构深度剖析-CSDN社区
apollo @value 原理详解:
企业级代码探究: @Value + Apollo动态刷新原理~
NSQ 原理详解:
NSQ(分布式消息队列) - Martin - 博客园
ZeroQ 原理详解:
ZeroMQ详解 - 南哥的天下 - 博客园
Redis 实现延时队列详解:
redis zrangebyscore 详解
如何用 Redis 实现延迟队列?
灵感来袭,基于Redis的源码分布式延迟队列(续)
Redis实战篇:巧用zset实现延迟队列
RocketMQ 延时队列原理(加精):
RocketMq延时队列的实现原理 - MaXianZhe - 博客园
Linux 磁盘管理详解:
Linux磁盘与文件系统管理_yu.deqiang的博客-CSDN博客
Linux文件系统、磁盘I/O是怎么工作的-一口Linux-电子技术应用-AET-中国科技核心期刊-最丰富的电子设计资源平台
Linux 缓存IO、直接IO、内存映射
如何高效的传输文件 -- 零拷贝技术
什么是mmap?零拷贝?DMA?
Java NIO之大文件缓存MappedByteBuffer详解
NIO源码解析-FileChannel高阶知识点map和transferTo、transferFrom(加精)
RocketMQ 如何基于mmap+page cache实现磁盘文件的高性能读写?
面试官:RocketMQ 如何基于mmap+page cache实现磁盘文件的高性能读写?
Rocket MQ : 拒绝神化零拷贝
Linux内存分配小结--malloc、brk、mmap:
Linux内存分配小结--malloc、brk、mmap - bdy - 博客园
Linux 操作系统原理 - 内存管理 - 内存分配算法
Linux 操作系统原理 — 内存管理 — 内存分配算法
golangï¼contextä»ç»
1 åè¨
æè¿å®ç°ç³»ç»çåå¸å¼æ¥å¿ä¸äºå¡ç®¡çæ¶,å¨å¯»æ±æè°çå ¨å±å¯ä¸Goroutine IDæ æä¹å,å³å®è¿æ¯ç®åå©ç¨Contextæºå¶å®ç°äºåºæ¬çæ³æ³,ä¸å¤é«æ,ä½æ¯å¥½ç¨.äºæ¯å¯¹å®å½åç设计æ¯è¾å¥½å¥,便æäºæ¤æ.
Contextæ¯golangå®æ¹å®ä¹çä¸ä¸ªpackage,å®å®ä¹äºContextç±»å,éé¢å å«äºDeadline/Done/Erræ¹æ³ä»¥åç»å®å°Contextä¸çæååéå¼Value,å ·ä½å®ä¹å¦ä¸ï¼
typeContextinterface{ //è¿åContextçè¶ æ¶æ¶é´ï¼è¶ æ¶è¿ååºæ¯ï¼Deadline()(deadlinetime.Time,okbool)//å¨Contextè¶ æ¶æåæ¶æ¶ï¼å³ç»æäºï¼è¿åä¸ä¸ªå ³éçchannel//å³å¦æå½åContextè¶ æ¶æåæ¶æ¶,Doneæ¹æ³ä¼è¿åä¸ä¸ªchannel,ç¶åå ¶ä»å°æ¹å°±å¯ä»¥éè¿å¤æDoneæ¹æ³æ¯å¦æè¿åï¼channelï¼,å¦ææå说æContextå·²ç»æ//æ å ¶å¯ä»¥ä½ä¸ºå¹¿æéç¥å ¶ä»ç¸å ³æ¹æ¬Contextå·²ç»æ,请åç¸å ³å¤ç.Done()<-chanstruct{ }//è¿åContextåæ¶çåå Err()error//è¿åContextç¸å ³æ°æ®Value(keyinterface{ })interface{ }}é£ä¹å°åºä»ä¹Contextï¼å¯ä»¥åé¢ææå¯ä»¥ç解为ä¸ä¸æ,æ¯è¾çæçæè¿ç¨/线ç¨ä¸çº¿æ,å ³äºgolangä¸çä¸ä¸æ,ä¸å¥è¯æ¦æ¬å°±æ¯ï¼ goroutineçç¸å ³ç¯å¢å¿«ç §,å ¶ä¸å å«å½æ°è°ç¨ä»¥åæ¶åçç¸å ³çåéå¼. éè¿Contextå¯ä»¥åºåä¸åçgoroutine请æ±,å 为å¨golang Seversä¸,æ¯ä¸ªè¯·æ±é½æ¯å¨å个goroutineä¸å®æç.
æè¿å¨å ¬å¸åægRPCæºç ,protoæ件çæç代ç ,æ¥å£å½æ°ç¬¬ä¸ä¸ªåæ°ç»ä¸æ¯ctx context.Contextæ¥å£,å ¬å¸ä¸å°åäºé½ä¸äºè§£è¿æ ·è®¾è®¡çåºåç¹æ¯ä»ä¹,å ¶å®æä¹ä¸äºè§£å ¶èåçåç.ä»å¤©è¶ç妮妲å°é£å¦¹åæ£é¢ç»éæ·±å³,å ¨å¸åå·¥,å课,åä¸,å¨å®¶ä¼æ¯æ¾äºä¸äºèµæç 究æç©ä¸æ.
Contexté常被è¯ä½ä¸ä¸æ,å®æ¯ä¸ä¸ªæ¯è¾æ½è±¡çæ¦å¿µ.å¨å ¬å¸ææ¯è®¨è®ºæ¶ä¹ç»å¸¸ä¼æå°ä¸ä¸æ.ä¸è¬ç解为ç¨åºåå çä¸ä¸ªè¿è¡ç¶æ,ç°åº,å¿«ç §,èç¿»è¯ä¸ä¸ä¸åå¾å¥½å°è¯ éäºå ¶æ¬è´¨,ä¸ä¸ä¸ä¸åæ¯åå¨ä¸ä¸å±çä¼ é,ä¸ä¼æå å®¹ä¼ éç»ä¸.å¨Goè¯è¨ä¸,ç¨åºåå ä¹å°±æçæ¯Goroutine.
æ¯ä¸ªGoroutineå¨æ§è¡ä¹å,é½è¦å ç¥éç¨åºå½åçæ§è¡ç¶æ,é常å°è¿äºæ§è¡ç¶æå°è£ å¨ä¸ä¸ªContextåéä¸,ä¼ éç»è¦æ§è¡çGoroutineä¸. ä¸ä¸æåå ä¹å·²ç»æä¸ºä¼ éä¸è¯·æ±åçåå¨æåéçæ åæ¹æ³.å¨ç½ç»ç¼ç¨ä¸,å½æ¥æ¶å°ä¸ä¸ªç½ç»è¯·æ±Request,å¤çRequestæ¶,æ们å¯è½éè¦å¼å¯ä¸åçGoroutineæ¥è·åæ°æ®ä¸é»è¾å¤ç,å³ä¸ä¸ªè¯·æ±Request,ä¼å¨å¤ä¸ªGoroutineä¸å¤ç. èè¿äºGoroutineå¯è½éè¦å ±äº«Requestçä¸äºä¿¡æ¯;åæ¶å½Request被åæ¶æè è¶ æ¶çæ¶å,ææä»è¿ä¸ªRequestå建çææGoroutineä¹åºè¯¥è¢«ç»æ.
注ï¼å ³äºgoroutineçç解å¯ä»¥ç§»æ¥è¿é.
2 为ä»ä¹ä½¿ç¨contextç±äºå¨golang seversä¸,æ¯ä¸ªrequesté½æ¯å¨å个goroutineä¸å®æ,并ä¸å¨å个goroutineï¼ä¸å¦¨ç§°ä¹ä¸ºAï¼ä¸ä¹ä¼æ请æ±å ¶ä»æå¡ï¼å¯å¨å¦ä¸ä¸ªgoroutineï¼ç§°ä¹ä¸ºBï¼å»å®æï¼çåºæ¯,è¿å°±ä¼æ¶åå¤ä¸ªGoroutineä¹é´çè°ç¨.å¦ææä¸æ¶å»è¯·æ±å ¶ä»æå¡è¢«åæ¶æè è¶ æ¶,åä½ä¸ºæ·±é·å ¶ä¸çå½ågoroutine Béè¦ç«å³éåº,ç¶åç³»ç»æå¯åæ¶Bæå ç¨çèµæº. å³ä¸ä¸ªrequestä¸é常å å«å¤ä¸ªgoroutine,è¿äºgoroutineä¹é´é常ä¼æ交äº.
é£ä¹,å¦ä½ææ管çè¿äºgoroutineæ为ä¸ä¸ªé®é¢ï¼ä¸»è¦æ¯éåºéç¥åå æ°æ®ä¼ éé®é¢ï¼,Googleç解å³æ¹æ³æ¯Contextæºå¶,ç¸äºè°ç¨çgoroutineä¹é´éè¿ä¼ écontextåéä¿æå ³è,è¿æ ·å¨ä¸ç¨æ´é²ågoroutineå é¨å®ç°ç»èçåæä¸,ææå°æ§å¶ågoroutineçè¿è¡.
å¦æ¤ä¸æ¥,éè¿ä¼ éContextå°±å¯ä»¥è¿½è¸ªgoroutineè°ç¨æ ,并å¨è¿äºè°ç¨æ ä¹é´ä¼ ééç¥åå æ°æ®. è½ç¶goroutineä¹é´æ¯å¹³è¡ç,没æ继æ¿å ³ç³»,ä½æ¯Context设计ææ¯å å«ç¶åå ³ç³»ç,è¿æ ·å¯ä»¥æ´å¥½çæè¿°goroutineè°ç¨ä¹é´çæ åå ³ç³».
3 æä¹ä½¿ç¨contextçæä¸ä¸ªContext主è¦æ两类æ¹æ³ï¼
3.1 顶å±Contextï¼Backgroundè¦å建Contextæ ,é¦å å°±æ¯è¦åå»ºæ ¹èç¹
//è¿åä¸ä¸ªç©ºçContext,å®ä½ä¸ºææç±æ¤ç»§æ¿Contextçæ ¹èç¹funcBackground()Context该Contexté常ç±æ¥æ¶requestç第ä¸ä¸ªgoroutineå建,å®ä¸è½è¢«åæ¶,没æå¼,ä¹æ²¡æè¿ææ¶é´,常ä½ä¸ºå¤çrequestç顶å±contextåå¨.
3.2 ä¸å±Contextï¼WithCancel/WithDeadline/WithTimeoutæäºæ ¹èç¹ä¹å,æ¥ä¸æ¥å°±æ¯å建ååèç¹.为äºå¯ä»¥å¾å¥½çæ§å¶ååèç¹,Contextå æä¾çå建æ¹æ³åæ¯å¸¦æ第äºè¿åå¼ï¼CancelFuncç±»åï¼,å®ç¸å½äºä¸ä¸ªHook,å¨ågoroutineæ§è¡è¿ç¨ä¸,å¯ä»¥éè¿è§¦åHookæ¥è¾¾å°æ§å¶ågoroutineçç®çï¼é常æ¯åæ¶,å³è®©å ¶åä¸æ¥ï¼.åé åContextæä¾çDoneæ¹æ³,ågoroutineå¯ä»¥æ£æ¥èªèº«æ¯å¦è¢«ç¶çº§èç¹Cancelï¼
select{ case<-ctx.Done()://dosomecleanâ¦}注ï¼ç¶èç¹Contextå¯ä»¥ä¸»å¨éè¿è°ç¨cancelæ¹æ³åæ¶åèç¹Context,èåèç¹Contextåªè½è¢«å¨çå¾ .åæ¶ç¶èç¹Contextèªèº«ä¸æ¦è¢«åæ¶ï¼å¦å ¶ä¸çº§èç¹Cancelï¼,å ¶ä¸çææåèç¹Contextåä¼èªå¨è¢«åæ¶.
æä¸ç§å建æ¹æ³ï¼
//带cancelè¿åå¼çContext,ä¸æ¦cancel被è°ç¨,å³åæ¶è¯¥å建çcontextfuncWithCancel(parentContext)(ctxContext,cancelCancelFunc)//带æææcancelè¿åå¼çContext,å³å¿ é¡»å°è¾¾æå®æ¶é´ç¹è°ç¨çcacelæ¹æ³æä¼è¢«æ§è¡funcWithDeadline(parentContext,deadlinetime.Time)(Context,CancelFunc)//å¸¦è¶ æ¶æ¶é´cancelè¿åå¼çContext,类似Deadline,åè æ¯æ¶é´ç¹,åè 为æ¶é´é´é//ç¸å½äºWithDeadline(parent,time.Now().Add(timeout)).funcWithTimeout(parentContext,timeouttime.Duration)(Context,CancelFunc)ä¸é¢æ¥çæ¹ç¼èªAdvanced Go Concurrency Patternsè§é¢æä¾çä¸ä¸ªç®åä¾åï¼
packagemainimport("context""fmt""time")funcsomeHandler(){ //å建继æ¿Backgroundçåèç¹Contextctx,cancel:=context.WithCancel(context.Background())godoSth(ctx)//模æç¨åºè¿è¡-Sleep5ç§time.Sleep(5*time.Second)cancel()}//æ¯1ç§workä¸ä¸,åæ¶ä¼å¤æctxæ¯å¦è¢«åæ¶,å¦ææ¯å°±éåºfuncdoSth(ctxcontext.Context){ vari=1for{ time.Sleep(1*time.Second)select{ case<-ctx.Done():fmt.Println("done")returndefault:fmt.Printf("work%dseconds:\n",i)}i++}}funcmain(){ fmt.Println("start...")someHandler()fmt.Println("end.")}è¾åºç»æï¼
注æ,æ¤æ¶doSthæ¹æ³ä¸caseä¹doneçfmt.Println("done")并没æ被æå°åºæ¥.
è¶ æ¶åºæ¯ï¼
packagemainimport("context""fmt""time")functimeoutHandler(){ //å建继æ¿Backgroundçåèç¹Contextctx,cancel:=context.WithTimeout(context.Background(),3*time.Second)godoSth(ctx)//模æç¨åºè¿è¡-Sleepç§time.Sleep(*time.Second)cancel()//3ç§åå°æååæ¶doSthgoroutine}//æ¯1ç§workä¸ä¸,åæ¶ä¼å¤æctxæ¯å¦è¢«åæ¶,å¦ææ¯å°±éåºfuncdoSth(ctxcontext.Context){ vari=1for{ time.Sleep(1*time.Second)select{ case<-ctx.Done():fmt.Println("done")returndefault:fmt.Printf("work%dseconds:\n",i)}i++}}funcmain(){ fmt.Println("start...")timeoutHandler()fmt.Println("end.")}è¾åºç»æï¼
4 contextæ¯ä¸ä¸ªä¼é ç设计å?ç¡®å®,éè¿å¼å ¥Contextå ,ä¸ä¸ªrequestèå´å æægoroutineè¿è¡æ¶çåæ¶å¯ä»¥å¾å°æRæçæ§å¶.ä½æ¯è¿ç§è§£å³æ¹å¼å´ä¸å¤ä¼é .
4.1 context åç æ¯ä¸æ ·æ©æ£ä¸æ¦ä»£ç ä¸æå¤ç¨å°äºContext,ä¼ éContextåéï¼é常ä½ä¸ºå½æ°ç第ä¸ä¸ªåæ°ï¼ä¼åç æ¯ä¸æ ·è延å¨åå¤è°ç¨å®çå°æ¹. æ¯å¦å¨ä¸ä¸ªrequestä¸å®ç°æ°æ®åºäºå¡æè åå¸å¼æ¥å¿è®°å½, å建çcontext,ä¼ä½ä¸ºåæ°ä¼ éå°ä»»ä½ææ°æ®åºæä½ææ¥å¿è®°å½éæ±çå½æ°ä»£ç å¤. å³æ¯ä¸ä¸ªç¸å ³å½æ°é½å¿ é¡»å¢å ä¸ä¸ªcontext.Contextç±»åçåæ°,ä¸ä½ä¸ºç¬¬ä¸ä¸ªåæ°,è¿å¯¹æ å ³ä»£ç å®å ¨æ¯ä¾µå ¥å¼ç.
æ´å¤è¯¦ç»å 容å¯åè§ï¼Michal Strba çcontext-should-go-away-go2æç«
Google Groupä¸ç讨论å¯ç§»æ¥è¿é.
4.2 Context ä¸ä» ä» åªæ¯cancelä¿¡å·Contextæºå¶ææ ¸å¿çåè½æ¯å¨goroutineä¹é´ä¼ écancelä¿¡å·,ä½æ¯å®çå®ç°æ¯ä¸å®å ¨ç.
Cancelå¯ä»¥ç»å为主å¨ä¸è¢«å¨ä¸¤ç§,éè¿ä¼ écontextåæ°,让è°ç¨goroutineå¯ä»¥ä¸»å¨cancel被è°ç¨goroutine.ä½æ¯å¦ä½å¾ç¥è¢«è°ç¨goroutineä»ä¹æ¶åæ§è¡å®æ¯,è¿é¨åContextæºå¶æ¯æ²¡æå®ç°ç.èç°å®ä¸çç¡®åæä¸äºè¿æ ·çåºæ¯,æ¯å¦ä¸ä¸ªç»è£ æ°æ®çgoroutineå¿ é¡»çå¾ å ¶ä»goroutineå®ææå¯å¼å§æ§è¡,è¿æ¯contextææ¾ä¸å¤ç¨äº,å¿ é¡»åå©sync.WaitGroup.
funcserve(lnet.Listener)error{ varwgsync.WaitGroupvarconnnet.Connvarerrerrorfor{ conn,err=l.Accept()iferr!=nil{ break}wg.Add(1)gofunc(cnet.Conn){ deferwg.Done()handle(c)}(conn)}wg.Wait()returnerr}4.3 context.valuecontext.Valueç¸å½äºgoroutineçTLSï¼Thread Local Storageï¼,ä½å®ä¸æ¯éæç±»åå®å ¨ç,ä»»ä½ç»æä½åéé½å¿ é¡»ä½ä¸ºå符串形å¼åå¨.åæ¶,ææcontexté½ä¼å¨å ¶ä¸å®ä¹åé,å¾å®¹æé æå½åå²çª.
5 æ»ç»contextå éè¿æ建æ åå ³ç³»çContext,æ¥è¾¾å°ä¸ä¸å±Goroutineè½å¯¹ä¼ éç»ä¸ä¸å±Goroutineçæ§å¶.对äºå¤çä¸ä¸ªRequest请æ±æä½,éè¦éç¨contextæ¥å±å±æ§å¶Goroutine,以åä¼ éä¸äºåéæ¥å ±äº«.
Context对象ççåå¨æä¸è¬ä» 为ä¸ä¸ªè¯·æ±çå¤çå¨æ.å³é对ä¸ä¸ªè¯·æ±å建ä¸ä¸ªContextåéï¼å®ä¸ºContextæ ç»æçæ ¹ï¼;å¨è¯·æ±å¤çç»æå,æ¤éæ¤ctxåé,éæ¾èµæº.
æ¯æ¬¡å建ä¸ä¸ªGoroutine,è¦ä¹å°åæçContextä¼ éç»Goroutine,è¦ä¹å建ä¸ä¸ªåContextå¹¶ä¼ éç»Goroutine.
Contextè½çµæ´»å°åå¨ä¸åç±»å,ä¸åæ°ç®çå¼,并ä¸ä½¿å¤ä¸ªGoroutineå®å ¨å°è¯»åå ¶ä¸çå¼.
å½éè¿ç¶Context对象å建åContext对象æ¶,å¯åæ¶è·å¾åContextçä¸ä¸ªæ¤éå½æ°,è¿æ ·ç¶Context对象çå建ç¯å¢å°±è·å¾äºå¯¹åContextå°è¦è¢«ä¼ éå°çGoroutineçæ¤éæ.
å¨åContextè¢«ä¼ éå°çgoroutineä¸,åºè¯¥å¯¹è¯¥åContextçDoneä¿¡éï¼channelï¼è¿è¡çæ§,ä¸æ¦è¯¥ä¿¡éè¢«å ³éï¼å³ä¸å±è¿è¡ç¯å¢æ¤éäºæ¬goroutineçæ§è¡ï¼,åºä¸»å¨ç»æ¢å¯¹å½å请æ±ä¿¡æ¯çå¤ç,éæ¾èµæºå¹¶è¿å.
6 è´è°¢pkg/context
context-should-go-away-go2
ç解 Go Context æºå¶
context-isnt-for-cancellation
context-is-for-cancelation
thread-local-a-convenient-abomination
gRPC 流量控制详解
gRPC 流量控制详解
流量控制, 一般来说指的是在网络传输中, 发送者主动限制自身发送数据的速率或发送的数据量, 以适应接收者处理数据的速度. 当接收者的处理速度较慢时, 来不及处理的数据会被存放在内存中, 而当内存中的数据缓存区被填满之后, 新收到的数据就会被扔掉, 导致发送者不得不重新发送, 就会造成网络带宽的浪费.
流量控制是一个网络组件的基本功能, 我们熟知的 TCP 协议就规定了流量控制算法. gRPC 建立在 TCP 之上, 也依赖于 HTTP/2 WindowUpdate Frame 实现了自己在应用层的流量控制.
在 gRPC 中, 流量控制体现在三个维度:
采样流量控制: gRCP 接收者检测一段时间内收到的数据量, 从而推测出 on-wire 的数据量, 并指导发送者调整流量控制窗口.
Connection level 流量控制: 发送者在初始化时被分配一个 quota (额度), quota 随数据发送减少, 并在收到接收者的反馈之后增加. 发送者在耗尽 quota 之后不能再发送数据.
Stream level 流量控制: 和 connection level 的流量控制类似, 只不过 connection level 管理的是一个发送者和一个接收者之间的全部流量, 而 stream level 管理的是 connection 中诸多 stream 中的一个.
在本篇剩余的部分中, 我们将结合代码一起来看看这三种流量控制的实现原理和实现细节.
本篇中的源代码均来自 /grpc/grpc-go, 并且为了方便展示, 在不影响表述的前提下截断了部分代码.
流量控制是双向的, 为了减少冗余的叙述, 在本篇中我们只讲述 gRPC 是如何控制 server 所发送的流量的.
gRPC 中的流量控制仅针对 HTTP/2 data frame.
采样流量控制原理采样流量控制, 准确来说应该叫做 BDP 估算和动态流量控制窗口, 是一种通过在接收端收集数据, 以决定发送端流量控制窗口大小的流量控制方法. 以下内容翻译自 gRPC 的一篇官方博客, 介绍了采样流量控制的意义和原理.
BDP 估算和动态流量控制这个 feature 缩小了 gRPC 和 HTTP/1.1 在高延迟网络环境下的性能差距.
Bandwidth Delay Product (BDP), 即带宽延迟积, 是网络连接的带宽和数据往返延迟的乘积. BDP 能够有效地告诉我们, 如果充分利用了网络连接, 那么在某一刻在网络连接上可以存在多少字节的数据.
计算 BDP 并进行相应调整的算法最开始是由 @ejona 提出的, 后来由 gRPC-C Core 和 gRPC-Java 实现. BDP 的想法简单而实用: 每次接收者得到一个 data frame, 它就会发出一个 BDP ping frame (一个只有 BDP 估算器使用的 ping frame). 之后, 接收者会统计指导收到 ACK 之前收到的字节数. 这个大约在 1.5RTT (往返时间) 中收到的所有字节的总和是有效 BDP1.5 的近似值. 如果该值接近当前流量窗口的大小 (例如超过 2/3), 接收者就需要增加窗口的大小. 窗口的大小被设定为 BDP (所有采样期间接受到的字节总和) 的两倍.
BDP 采样目前在 gRPC-go 的 server 端是默认开启的.
结合代码, 一起来看看具体的实现方式.
代码分析我们以 client 发送 BDP ping 给 server, 并决定 server 端的流量控制窗口为例.
在 gRPC-go 中定义了一个bdpEstimator , 是用来计算 BDP 的核心:
type?bdpEstimator?struct?{ //?sentAt?is?the?time?when?the?ping?was?sent.sentAt?time.Timemu?sync.Mutex//?bdp?is?the?current?bdp?estimate.bdp?uint//?sample?is?the?number?of?bytes?received?in?one?measurement?cycle.sample?uint//?bwMax?is?the?maximum?bandwidth?noted?so?far?(bytes/sec).bwMax?float//?bool?to?keep?track?of?the?beginning?of?a?new?measurement?cycle.isSent?bool//?Callback?to?update?the?window?sizes.updateFlowControl?func(n?uint)//?sampleCount?is?the?number?of?samples?taken?so?far.sampleCount?uint//?round?trip?time?(seconds)rtt?float}bdpEstimator 有两个主要的方法 add 和 calculate :
//?add?的返回值指示?是否发送?BDP?ping?frame?给?serverfunc?(b?*bdpEstimator)?add(n?uint)?bool?{ b.mu.Lock()defer?b.mu.Unlock()//?如果?bdp?已经达到上限,?就不再发送?BDP?ping?进行采样if?b.bdp?==?bdpLimit?{ return?false}//?如果在当前时间点没有?BDP?ping?frame?发送出去,?就应该发送,?来进行采样if?!b.isSent?{ b.isSent?=?trueb.sample?=?nb.sentAt?=?time.Time{ }b.sampleCount++return?true}//?已经有?BDP?ping?frame?发送出去了,?但是还没有收到?ACKb.sample?+=?nreturn?false}add 函数有两个作用:
决定 client 在接收到数据时是否开始采样.
记录采样开始的时间和初始数据量.
func?(t?*ing?flow?control?windows//?for?the?transport?and?the?stream?based?on?the?current?bdp//?estimation.func?(t?*ingWindowUpdateHandler?负责处理来自?client?的?window?update?framefunc?(l?*loopyWriter)?incomingWindowUpdateHandler(w?*incomingWindowUpdate)?error?{ if?w.streamID?==?0?{ //?增加?quotal.sendQuota?+=?w.incrementreturn?nil}......}sendQuota 在接收到来自 client 的 window update 后增加.
//?processData?负责发送?data?frame?给?clientfunc?(l?*loopyWriter)?processData()?(bool,?error)?{ ......//?根据发送的数据量减少?sendQuotal.sendQuota?-=?uint(size)......}并且 server 在发送数据时会减少 sendQuota .
Client 端//?add?的返回值指示?是否发送?BDP?ping?frame?给?serverfunc?(b?*bdpEstimator)?add(n?uint)?bool?{ b.mu.Lock()defer?b.mu.Unlock()//?如果?bdp?已经达到上限,?就不再发送?BDP?ping?进行采样if?b.bdp?==?bdpLimit?{ return?false}//?如果在当前时间点没有?BDP?ping?frame?发送出去,?就应该发送,?来进行采样if?!b.isSent?{ b.isSent?=?trueb.sample?=?nb.sentAt?=?time.Time{ }b.sampleCount++return?true}//?已经有?BDP?ping?frame?发送出去了,?但是还没有收到?ACKb.sample?+=?nreturn?false}0trInFlow 是 client 端控制是否发送 window update 的核心. 值得注意的是 client 端是否发送 window update 只取决于已经接收到的数据量, 而管这些数据是否被某些 stream 读取. 这一点是 gRPC 在流量控制中的优化, 即因为多个 stream 共享同一个 connection, 不应该因为某个 stream 读取数据较慢而影响到 connection level 的流量控制, 影响到其他 stream.
//?add?的返回值指示?是否发送?BDP?ping?frame?给?serverfunc?(b?*bdpEstimator)?add(n?uint)?bool?{ b.mu.Lock()defer?b.mu.Unlock()//?如果?bdp?已经达到上限,?就不再发送?BDP?ping?进行采样if?b.bdp?==?bdpLimit?{ return?false}//?如果在当前时间点没有?BDP?ping?frame?发送出去,?就应该发送,?来进行采样if?!b.isSent?{ b.isSent?=?trueb.sample?=?nb.sentAt?=?time.Time{ }b.sampleCount++return?true}//?已经有?BDP?ping?frame?发送出去了,?但是还没有收到?ACKb.sample?+=?nreturn?false}1这里 limit * 1/4 的限制其实是可以浮动的, 因为 limit 的数值会随着 server 端发来的 window update 而改变.
Stream level 流量控制原理Stream level 的流量控制和 connection level 的流量控制原理基本上一致的, 主要的区别有两点:
Stream level 的流量控制中的 quota 只针对单个 stream. 每个 stream 即受限于 stream level 流量控制, 又受限于 connection level 流量控制.
Client 端决定反馈给 server window update frame 的时机更复杂一点.
Stream level 的流量控制不光要记录已经收到的数据量, 还需要记录被 stream 消费掉的数据量, 以达到更加精准的流量控制. 实际上, client 会记录:
pendingData: stream 收到但还未被应用消费 (未被读取) 的数据量.
pendingUpdate: stream 收到且已经被应用消费 (已被读取) 的数据量.
limit: stream 能接受的数据上限, 被初始为 字节, 受到采样流量控制的影响.
delta: delta 是在 limit 基础上额外增加的数据量, 当应用试着去读取超过 limit 大小的数据是, 会临时在 limit 上增加 delta, 来允许应用读取数据.
Client 端的逻辑是这样的:
每当 client 接收到来自 server 的 data frame 的时候, pendingData += 接收到的数据量 .
每当 application 在从 stream 中读取数据之前 (即 pendingData 将被消费的时候),
Windows平台C++ 使用VS 编译gRPC(总结)
若要在Windows平台使用VS编译gRPC,首先确保您的开发环境支持最新版本。由于gRPC自3..1版本开始依赖protobuf 3.x,且C++的constexpr特性在VS及更早版本中不被支持,因此推荐使用VS及以上版本进行编译。 对于编译环境的配置,建议您采用以下步骤:下载并安装CMake-gui,后续步骤将通过其进行操作。
安装Active State Perl,通过命令行验证安装是否成功。
安装Golang,并同样通过命令行进行测试。
尽管Git可能遇到问题,但您可以手动从GitHub下载gRPC代码,版本选择1..0或更高版本。同时,需要下载并解压gRPC的第三方库,如BoringSSL、牛盟源码Protobuf、benchmark等,确保选择正确的版本。 在编译过程中,将gRPC源代码解压至无中文字符的目录,针对Windows 位系统,选择x版本。对于HelloWorld示例,需要在项目配置中添加特定预处理器定义,如_WIN_WINNT和安全警告开关。 确保项目中的编译设置正确匹配,例如调整运行时库版本,以避免LIBCMTD/LIBCMT、MSVCRTD/MSVCRT之间的冲突。最终的编译输出包括bin和lib文件,其中java和go有单独的库。 在使用gRPC时,将helloworld.proto文件复制到适当位置,生成pb和grpc.pb文件,并在客户端和服务器项目中集成。通过设置头文件路径、预处理器定义、库目录和附加依赖项,连接所有依赖,完成gRPC的测试和集成。CockroachDB 源码闲逛 - II (insert a row)
本文将深入探讨 CockroachDB 的启动过程以及处理一条简单 SQL(如插入一行数据)的具体流程。CockroachDB 使用 Go 语言中流行的 Cobra 库来构建其命令行界面(CLI),在使用 `start` 命令启动服务端后,代码从特定位置开始执行。
启动初期,CockroachDB 会准备好各种日志和 pprof 功能。pprof 功能允许通过开关控制定期导出 CPU 和内存(通过 go/jemalloc)的性能分析报告,并定期清除旧的 pprof 数据,这有助于在排查问题时找到事故现场的堆栈或性能数据。
之后,服务端使用一个端口同时处理 PostgreSQL、HTTP 和 gRPC 协议,代码进入 `Server.start()` 方法。这个方法包含复杂的酷秀源码逻辑用于节点发现和 bootstrap。主要关注点在于 SQL 处理,尤其是 PostgreSQL 协议下的客户端连接。
当客户端通过 PG 协议连接到服务端时,代码进入 `pgwire.Server#ServeConn` 方法。通过校验版本等步骤后,进入 `conn.serveImpl` 方法,这是处理请求的主要逻辑。在这里,每个客户端连接由两个 goroutine 分别处理读取协议解析和命令执行。这种设计允许在执行过程中同时接收客户端连接事件,例如在执行大规模 SQL 过程时,通过关闭其中一个 goroutine 可以在 SQL 执行的同时响应客户端的 `FIN` 指令。
在客户端连接的两个 goroutine 准备好后,发送的 SQL 语句开始在 `coordinator-side` 进行处理。首先,`read goroutine` 解析网络包,并根据不同的 PG cmd 分发到相应的方法进行处理。对于简单的文本执行查询,`handleSimpleQuery` 方法相对简单。为了区分不同批量的命令,当一组命令推送到 `stmtBuf` 后,会插入一个哨兵 `Sync` 来标记当前批次结束以及后续命令属于下一个批次。
随后,`process goroutine` 从 `stmtBuf` 中获取命令,根据不同的命令类型分发到相应的 `exec*` 方法。例如,简单查询产生的 `ExecStmt` 会进入 `execStmt` 方法,在此之前会创建 `stmtRes` 来封装后续返回客户端响应的缓冲区刷新逻辑。
在处理 SQL 语句时,CockroachDB 会维护一个状态机(StateMachine),用于管理当前连接的事务状态。状态机的定义和行为主要与事务相关,包括 `noTxn`、`open`、`abort`、`implicit` 等状态。在处理插入一行数据的简单语句(如 `INSERT INTO t (a) VALUES (1);`)时,流程如下:
首先,客户端与服务端建立连接,源码的编译启动两个 goroutine。当插入语句发送到服务端后,`read goroutine` 开始解析并放置到 `stmtBuf`。
随后,`process goroutine` 从 `stmtBuf` 拿出命令,识别为 `ExecStmt`。由于执行此语句前未开始事务,当前连接的状态机处于 `stateNoTxn`。因此,执行 `execStmtInNoTxnState` 方法,因为没有事务,仅执行 `execStmtInNoTxnState` 的默认分支,返回 `eventTxnStart` 事件和 `eventTxnStartPayload`。此时,状态机应用 `noTxnToOpen` 进程,为隐式事务的启动做准备。服务端通过 `client.NewTxn` 创建事务,获取时间戳并准备 `sender` 和 `coordinator` 等工作。接着,设置 `advanceInfo` 为 `advanceOne`、`noRewind`(无需回移 `stmtBuf`,通常重试时需要回移)和 `txnState` 为 `txnStart`。事务状态为 `open` 后,`execCmd` 会从 `stmtBuf` 中继续取出插入语句并执行。
当当前事务状态为 `open` 且为 `implicit` 时,`execStmtInOpenState` 方法继续执行。由于当前 SQL 不是 `BEGIN`、`COMMIT` 等操作,挂载了 `handleAutoCommit` 的 `defer` 函数,并处理 `AS OF` 时间逻辑后,进入 `dispatchToExecutionEngine` 方法。
在 `makeExecPlan` 方法中,创建逻辑计划。接下来,评估是否能够分布执行逻辑计划(对于插入操作,CockroachDB 当前不支持分布式计划)。然后,为逻辑计划准备上下文,调用 `execWithDistSQLEngine`。云源码网对于不可分布执行的情况,创建简化版的 `planCtx`,用于生成物理计划。在此步骤中,生成物理计划(如 `row count` 算子)并最终生成执行流程。
在准备和生成流程后,服务端启动在本地节点的执行流程。通过 `local execution` 的 `setup` 和 `run` 方法,执行生成的处理器(如 `planNodeToRowSource`)。在 `run` 方法中,执行 `rowCountNode` 算子,进而触发 `insertNode` 的 `BatchNext`,以火山模式(一次过一个批处理的多个行)执行插入操作。
插入操作中,`BatchNext` 分批处理,根据 `maxInsertBatchSize`(默认为 )进行分批。对于非最后一批次,会通过 `txn.Run` 发送至存储节点,将数据分批存储。在 `checkHelper` 函数中,检查表约束,分为 `eval` 和 `input` 模式,前者是老逻辑,后者在插入前检查约束结果,作为插入算子的输入,有利于优化插入操作。
添加批处理时,调用 `initResult` 准备每个 `CPut` 的结果。如果批处理中某个命令失败(如序列化失败),会在 `initResult` 中保存序列化失败信息。
之后,将准备好的批处理发送至 `replica-side`。在 `finalize` 中,将 `EndTransactionRequest` 添加到批处理的末尾,通过 `txn.Run` 发起。此时,批处理中包含一个条件 `put` 和一个结束事务请求,服务端通过 `DistSender.Send` 将批处理发送至 `replica-side`。批处理中的 `result` 包含 `err` 信息,用于验证批处理序列化无误。
在 `replica-side`,请求到达节点的存储层,找到相关范围的副本对象并处理等待逻辑。对于写入操作,使用 Raft 进行 `Replica.executeWriteBatch`。在此方法中,使用 `Latch` 机制来优化对交叠和非交叠批处理的处理,同时执行批处理命令的 `evaluateWriteBatch` 方法将所有命令应用到数据中,生成 `engine.Batch` 并构建 `ProposalData`。最终,通过 Raft 提出修改,实现数据的最终一致性。
最后,执行成功或失败后,结果会沿原路径返回至客户端。
总结,本文详细阐述了 CockroachDB 从启动到处理简单 SQL(如插入操作)的全过程。通过深入分析,读者能够更好地理解 CockroachDB 的内部工作机制,为后续阅读代码提供基础。未来计划将关注点扩展到重试处理逻辑,进一步探索 `stmtBuf` 和状态机在 CockroachDB 中的使用。
PolarisMesh源码系列--Polaris-Go注册发现流程
北极星是腾讯开源的一款服务治理平台,其目标在于解决分布式和微服务架构中的服务管理、流量管理、配置管理、故障容错和可观测性问题。与Spring Cloud、Apache Dubbo和Istio等其他流行技术相比,北极星提供了独特的优势与服务注册发现的实现。
从功能实现角度看,Spring Cloud、Apache Dubbo、Istio和北极星都实现了服务治理的关键功能,但它们的实现思路有所不同。Spring Cloud在Spring Boot框架基础上扩展,继承了其灵活性,能够方便地集成服务注册发现、服务治理和可观测组件。而北极星则直接从下一代架构基金会制定的服务治理标准出发,构建服务治理的模型,并基于此模型构建控制面和数据面,提供了统一的服务治理框架。
ServiceMesh采用Sidecar模式解耦业务逻辑和服务治理逻辑,将服务治理能力下沉到基础设施,增强整体架构的灵活性。然而,这种模式在性能上有所损耗,并且对中小团队的灵活性和扩展性提出了挑战。Istio虽然提供了基于虚拟机/物理机的部署方式,但对Kubernetes的依赖较高,非Kubernetes环境的团队可能难以部署。
北极星Mesh则通过融合和兼容多种技术,提供了一种自顶向下的正向思考过程。它先基于服务治理标准构建模型,然后围绕该模型构建控制面和数据面,支持与ServiceMesh的集成,为未来发展留有空间。此外,北极星Mesh通过插件机制为框架扩展预留了灵活性。
本文重点分析了Polaris-Go SDK在服务注册和发现过程中的技术实现和源码阅读。服务注册流程相对简单,线性操作,通过gRPC服务接口实现。服务发现流程则更为复杂,涉及本地缓存与远程服务器信息的懒加载同步,以及处理实例信息、服务信息、路由信息和限流信息等复杂内容。在服务发现过程中,gRPC接口被用于关键点的处理。
综上所述,北极星服务治理平台通过实现服务治理标准,提供了全面的服务发现和治理方案。其客户端与服务器端的数据同步与交互设计了良好的服务治理模型和通信机制,确保了可靠性和稳定性。同时,通过插件机制,Polaris-Go SDK框架提供了灵活的扩展能力。这一分析仅是基于现有信息,如有错误或遗漏,欢迎指正。
Golang 设计模式之装饰器模式
本期和大家交流的是设计模式中的装饰器模式。
装饰器模式的基本定义是:在不变更原对象结构的基础上,动态地为对象增加附属能力。它与“继承”有一定的相似之处,但侧重点不同,可以将装饰器模式视为“继承”的一种补充手段。
为了更好地理解装饰器模式,以下是一个实际案例的分析:
通过编程的方式,我们可以还原上述场景问题。一种常见的实现方式是采用继承。然而,这种实现方式需要对子类的等级和种类进行枚举,包括一系列一级和二级子类。这种固定的等级架构存在一些问题。
因此,在这种“加料”场景中,使用继承的设计模式可能并不合适。我们可以改变思路,不再关注对所有组合种类的枚举,而是将注意力放在“加料”的过程中。
在这种实现思路下,就诞生了基于“装饰器模式”的实现架构。例如,一份鸡蛋培根盖浇饭可以由一份白米饭(核心类)加上一份鸡蛋(装饰器1)和一份培根(装饰器2)组成,其中鸡蛋和培根的装饰顺序不限制。这样,无论后续有多少种新的“菜品”加入,我们只需声明其对应的装饰器类即可。
比如,双份鸡蛋盖浇饭 = 一份白米饭(核心类)+ 一份鸡蛋(装饰器1)+一份鸡蛋(装饰器1);鸡蛋火腿青椒盖浇饭 = 一份白米饭(核心类)+ 一份鸡蛋(装饰器1)+一份青椒(装饰器2)+一份火腿(装饰器3);双份牛肉青椒盖浇饭 = 一份白米饭(核心类)+ 一份青椒(装饰器4)+一份牛肉(装饰器5)+一份牛肉(装饰器5)。
至此,问题得到了圆满解决。接下来,我们对装饰器模式和继承模式进行对比总结。
下面进入代码实战环节,通过编程实现一个搭配食材的案例,展示装饰器模式的实现细节。
这个案例非常简单,我们需要在主食的基础上添加配菜,最终搭配出美味可口的食物套餐。其中主食包括米饭 rice 和面条 noodle 两条,配菜包括老干妈 LaoGanMa(老干妈拌饭顶呱呱)、火腿肠 HamSausage 和煎蛋 FriedEgg 三类。
事实上,如果需要,主食和配菜也可以随时进行扩展。在装饰器模式中,这种扩展行为的成本并不高。
接下来,展示一下总体的 UML 类图。
首先是对应于装饰器模式中核心类的是原始的主食 Food,我们声明了一个 interface,其中包含两个核心方法,Eat 和 Cost,含义分别为食用主食以及计算出主食对应的花费。
接下来是装饰器部分,我们声明了一个 Decorate interface,它们本身是在强依附于核心类(主食)的基础上产生的,只能起到锦上添花的作用,因此在构造器函数中,需要传入对应的主食 Food。
接下来分别声明三个装饰器的具体实现类,对应为老干妈 LaoGanMaDecorator、火腿肠 HamSausageDecorator 和煎蛋 FriedEggDecorator。
每个装饰器类的作用是对食物进行一轮装饰增强,因此需要在构造器函数中传入待装饰的食物,然后通过重写食物的 Eat 和 Cost 方法,实现对应的增强装饰效果。
下面提供另一种闭包实现装饰增强函数的实现示例,其实现也是遵循着装饰器模式的思路,但在形式上会更加简洁直观一些。
其中核心的处理方法 handleFunc 对应的是装饰器模式中的核心类,Decorate 增强方法对应的则是装饰器类,每次在执行 Decorate 的过程中,都会在 handleFunc 前后增加一些额外的附属逻辑。
为了加深理解,以下摘出一个实际项目中应用到装饰器模式的使用案例进行分析。
这里给到的案例是 grpc-go 中对拦截器链 chainUnaryInterceptors 的实现。
在 grpc-go 服务端模块中,每次接收到来自客户端的 grpc 请求,会根据请求的 path 映射到对应的 service 和 handler 进行执行逻辑的处理,但在真正调用 handler 之前,会先经历一轮对拦截器链 chainUnaryInterceptors 的遍历调用。
下面我们来观察一下其中具体的源码细节。
首先,对于拦截器类 UnaryServerInterceptor,本身是一个函数的类型。
下面是生成拦截器链的方法 chainUnaryInterceptors。该方法入参是用户定义好的一系列拦截器 interceptors,内部会按照顺序对拦截器进行组装,最终通过层层装饰增强的方式,将整个执行链路压缩成一个拦截器 UnaryServerInterceptor 的形式进行方法。
在这个过程中,就体现了我们今天讨论的装饰器模式的设计思路。核心业务处理方法 handler 对应的就是装饰器模式中的核心类,每一轮通过拦截器 UnaryServerInterceptor 对 handler 进行增强的过程,对应的就是一次“装饰”的步骤。
下面给出一个具体实现的装饰器的代码示例,可以看到其中在核心方法 handler 前后分别执行了对应的附属逻辑,起到了装饰的效果。
如果各位读友们想了解更多关于 grpc-go 的内容,可以阅读我之前发表的相关话题文章。
本期和大家交流了设计模式中的装饰器模式。装饰器模式能够动态地为对象增加某种特定的附属能力,相比于继承模式显得更加灵活,且符合开闭原则,可以作为继承模式的一种有效补充手段。