grpc-go单连接的设计问题

beego是基于http1.1实现的框架,每个请求会启动单独的goroutine处理,这个goroutine会独占tcp连接,所以无论write请求还是read回复都在单独的goroutine中,所以beego的在请求级别是并发的,而brpc请求的read和process也进行了并发,只是brpc的并发能力考虑到了cpu级别的cache,体现形式是bthread。 这里着重讨论的是grpc-go,前面对beego和brpc进行简单的描述,目的是在并发能力这块grpc-go因为基于http2,有着协议层面的先天的难度,http2为了在web端提升连接的利用率,设计了多路复用,tcp连接的复用导致在框架层面引入流控和请求分frame,这样对于框架层面的并发能力处理也引入了不小的复杂度。 grpc-go当前在sender端做的事主要有几个方面: 新建stream,并向controlBuffer注册这个stream,之后和receiver就通过这个stream交互 发送请求的序列化和压缩,并分frame发送给receiver 监听stream中的chan,等待收到所有receiver的response内容 grpc-go在以上3方面的处理选择了简单的方案,每个连接配置一个controlBuffer(以下称为cb)和一个该buffer的consumer(loopyWriter),一个stream上的frame要求是按序发出,这里的按序就是按照进行write的系统调用,其他交给tcp。

  • 防爆菊座椅
    防爆菊座椅
4 min read

grpc-go性能优化(一)

HandleStreams是所有frame的入口,grpc-go当前处理的frame类型分为6种,下面分类型讨论下怎么让frame中的互相干扰逻辑并发起来。 MetaHeadersFrame 请求的第一个frame,第一步是解析header中的内容,解析过程耗时固定,不能分离到goroutine中,但是当解析出错,就用到controlbuf,这里是第一个点,这里给到controlbuf的是一个cleanupStream,loopyWriter这个对外接口人负责处理,就是向外发一个RST_STREAM,receiver端还要从transport全局的map中摘除这个frame,这一个put操作还是会收到loopyWriter的干扰,但本身两者可以尽量没有关系,这个点一定是不被允许的,虽然出现的几率很小,但是大型高并发线上服务一旦出现波动是很难重现和调试的。cleanupStream是异步被处理,虽然尽快处理是理想的,这时候就要在几种方法下tradeoff: rst操作保留在read流程,rst操作是要通过conn的Write写出,这里存在竞争,这块依赖于response的写出竞争程度,不可靠 rst操作通过lock-free queue分离,最终在loopyWriter中发出 rst操作通过wait-free

  • themonk
6 min read

grpc-go中controlbuf是否是性能瓶颈

之前一直在关注grpc-go性能问题,之所以说是问题,高并发I/O的情况下,read和write都会有ms级别的耗时波动。 被http2协议的流控是否干扰性能这个问题困扰很久,因为grpc-go是我唯一阅读的rpc框架,没有参考,所以对于rpc框架该怎么设计合理,心中没有标准,也没办法衡量。我们之前使用的brpc,性能表现相当好,cpu吃的也不紧,而且github上的文档干活比较多,当然干活多不代表文档全,brpc这方面还是要优化下。所以反复读了brpc在io部分的设计,总结下框架处理高并发I/O要做些什么: 流水线:不同类工作分开处理,防止干扰,针对不同req处理耗时波动大或者不可预估的环节一定要加入并发能力(例如执行用户逻辑的部分,这块通常没有框架会犯低级错误,让用户的业务逻辑互相干扰) lock-free和wait-free:涉及到连接r和w,如果是http2还涉及到frame的顺序保证,所以在框架层内部一定要做到其中一种,最好是wait-free,就是线程之间完全不干扰,

  • themonk
2 min read

http2为什么需要流控

最近在思考一个问题,http2协议中的流控和tcp协议的流控是否有关系,是否是多余的,分别是应对什么场景的问题? tcp的流控是连接级别,不同连接是共享网络,网络中大部分都是非直连的情况,中介的数据buf和转发能力不统一,所以窗口大小只在最近的endpoints之间互通,这样会一段一段的共享窗口大小。两点之间网络的能力通过bdp衡量,该能力不提升硬件能力的情况下是有理论最大值的,tcp的流控就是防止单连接无估计的向网络中写入大量数据导致网络拥塞和丢包,endpoints通信延迟上升,用户无法容忍的情况下就视为网络不可用。那么tcp的流控是怎么做的呢,首先窗口有默认大小,应用层不需要关心,类似64k这种,随着bdp的进一步探测,窗口会有调整,bdp探测模块认为当前连接可以占用更多带宽资源,那么就调整窗口大小,当然窗口大小的调整还依赖于上层读取tcp buffer中数据的效率,如果读取很快证明tcp buffer的写入速度可能成为app处理的瓶颈,那么tcp就会扩大窗口,向sender索要更多数据。 http1.1是一段时间内(一个rtt)独占tcp连接的,

  • 防爆菊座椅
    防爆菊座椅
3 min read

grpc-go服务内存攀升

grpc-go服务在个别response比较大的情况下会导致服务占用内存也随之增大,这块拍脑袋想可能是自然的情况,response大那么需要的网络io时间就长,那么这些response的[]byte就一直留在内存中。 rpc框架是否会发部分清除这部分数据,看go的slice的原理,感觉是能做到的,相当于不断把slice中的ptr向后移动就行了,这块后续可以考虑进行验证。 继续上边说的,这个问题如果我们不拍脑袋就纸上谈兵,那么怎么做到数据层面的计算: 在cpu仍有余力的情况下,内存增大是我们要探讨的问题,response大是当前状况,当前网络io假设我们独占。 基于以上条件,在这里说下grpc-go的sendResponse设计方式,结合起来看是否grpc-go设计上有问题还是grpc-go已经尽力了,其实是网络带宽不够,client和server之间保持一个长连接,基于h2协议,单连接启动单个goroutine处理所有可以异步的任务(这里只说server的逻辑):窗口调整,headerFrame和dataFrame发送,单response是分frame(最大16k)发送出去,在当前server其他的api都是常规流量的情况下,该response发送出去的时间应该与大小成近似线性的增长,单response不能并发发送,

  • 防爆菊座椅
    防爆菊座椅
5 min read

grpc-go脑爆-流控

流控是不是导致单连接中stream处理耗时不稳定的原因。 在http1.1中,连接是请求独占,没有流量控制,请求分包发送直接写入tcp连接中,原理简单些。 http2,随着连接建立持续时间的推移,流控导致sender发送速度和receiver处理速度相匹配,可以想象一下假设h2没有流控,所有stream争相写入tcp连接。 注意在该场景下个别stream在receiver的处理速度较慢会不会干扰到同一连接的其他stream的处理, 首先,对receiver处理速度进行细致的描述,receiver的处理要具体分为框架内和框架外两种不同的场景: 框架内,单goroutine从framer中读取Frame,写入相应stream的recvBuffer,从headerFrame收到开始点建立的Stream一直block在recvBuffer的Read上,一旦发现io.EOF,迅速往下走,交给app层处理。 面对这种场景,首先是懵逼的,那么stream的流控到底在处理app层处理慢的stream上有什么措施,这里就需要对流控中WINDOW_UPDATE在grpc-go中receiver端的比较细致把控才可以。 receiver触发窗口调节的第一个为止是stream的Read,这是app层在要数据,所以这个地方的窗口增大数值和初始化grpc server时设置的最大接收字节数有直接关系,

  • 防爆菊座椅
    防爆菊座椅
6 min read

grpc-go脑爆-性能

最近在研究grpc-go的源码。 http2的协议内容。 断断续续看了近两周,初始阶段一直在跟着client和server的流程走。 基于tcp传输,使用go标准库中的TcpListener,开始Accept连接,单goroutine负责接收新连接。 h2中只有长连接,且引入stream和frame在协议上让连接变成有状态的,连接两端的endpoints使用单连接发送数据,发送数据仅受flow-control的控制,流控分stream level和connection level,这样做的目的是让同connection上的stream之间不过于互相干扰,因为connection上的stream是独立的,有自己的write quota,不同stream分开管理。 而http1.1中单request独占connection,肯定不会互相干扰,但是这样的之所以有h2就是连接建立开销+连接独占和共享连接带来的竞争的trade off,相信google是经过大量试验得到的经验协议。 每个新连接对应一个新goroutine,该goroutine内部在net.Conn之上封装frame读取和stream管理。frame管理使用golang开发组提供的http2库。注意单连接只有一个goroutine负责读入所有frame,新stream是通过headerFrame带新的streamId触发创建的。

  • 防爆菊座椅
    防爆菊座椅
7 min read

grpc c++版本,自定义naming resolver

=编译grpc=,保证系统protobuf和grpc相应版本一致,gprc每个版本都会提供third_party/protobuf,可以直接进去sudo make install把当前系统的protoc给改了。make不过主要就是proto不一致的事。 =理解Makefile=,目的是libgrpc.a中引入自研的naming service sdk,如下: // 你可以直接在Makefile中写sdk的compile和link的过程,也可以提前编译好 // 这里罗列cc文件的唯一目的就是为了生成下面的WEBFOOT_MERGE_OBJS,在连接libgrpc.a的时候使用 LIBWEBFOOT_SRC = \ /home/lihao/grpc/third_party/naming-webfoot/webfootremote.cc \ /home/

  • 防爆菊座椅
    防爆菊座椅
3 min read

github.com/pkg/errors凭什么俘获一众开源项目的芳心

go标准库中的errors包实现标准error接口,实现上大简至道,error最终存储于string中,于是errorString进行一次包装,就完成了。但我这里不是针对errors包的描述,要从需求和应用角度进行细化的分析,errors包中的实现通用、直观且灵活,为什么这么说: 作为error的容器,不能各种类型混杂,增加各种可能输入的处理,应对各种情况,这种事情很可能画蛇添足。我们需要的就是能装下所有需求的容器,这个容器就是string,这就是通用。 直观是从需求角度出发,error一般输出在log中,传递给log库的东西一般是什么,string啊,string几乎可以是程序和人类交互的基础,什么信息都是通过string给出的,这里的string不特指字符串。所以我得到错误对象,第一步要干什么,提取string输出。 error等价于string这种思想非常灵活,string可以是任何形式,可以包含任何内容,所以灵活度相当高,作为最下层的库,

  • 防爆菊座椅
    防爆菊座椅
4 min read

grpc http proxy

0x00 最近系统在从c++迁移到go,之前使用brpc,也需要转移到grpc,但是grpc提供的接口服务原生无法被http访问到,这对我们调试来说也很麻烦,所以需要让grpc跟brpc一样,http也能访问rpc接口 0x01 grpc-gateway项目: 该项目是在grpc外面加一层反向代理,由代理服务器转发json格式,转变成protobuf格式来访问grpc服务,官方解释图如下: 你的grpc服务按照正常的方式启动就行了,然后根据proto文件生成gateway专有的gw.pb.go文件,然后我们重新启动一个gateway服务,有自己独立的端口,然后有一个入口,入口就是你grpc提供服务的ip和端口。 实验 启动grpc服务 grpc提供服务的端口为7777 启动代理服务 package main import ( "flag"

  • doctorq
2 min read

围绕grpc,打造工具库和代码生成工具

配置加载 app采用ini格式的配置,也考虑过使用yaml,但yaml的灵活度较高,代码生成比较难搞。将app的配置分为两种: 值配置:app中使用该值,支持string/int/int_array/string_array 对象配置:framework中加载该值,但不直接使用,支持redis/mysql/es,代码生成逻辑回识别这些section并生成对应的对象,对象的具体使用方法根据选择的sdk有所不同,这里不详细描述。 在迁移go之前,我做过几个简单的项目,类似http网关、http服务、对接kafka的消费者,采用的yaml配置,每次新增项目,就需要将旧项目的配置加载部分抄过来,需要根据配置的构建对应的struct,需要copy

  • 防爆菊座椅
    防爆菊座椅
7 min read

redigo

服务对接上述存储是比较常见的。通常我们会在github上寻找star比较多的,或者在mysql和redis的官网找推荐sdk。当面临sdk报错或者我们的角色变为服务提供方的时候,sdk就是免不了的一部分。之前没有细致的读redis的sdk。对于sdk包含的内容,认为有如下几部分: 协议封装 连接池管理 api提供 实际上也没错,sdk确实包含这几部分,但涉及到代码设计层面,就不能balabala光吹牛逼了。废话到此,带来redigo这个项目的像素级分析。一起真正体验下别人的设计思想。 各文件具体工作,不做具体描述,文件按功能性划分,有连接池,有提供给上层的api,有对接redis服务的部分等。 pool.go var _ ConnWithTimeout = (*activeConn)(nil) ConnWithTimeout是在redis.go中定义的接口,上面代码的作用是在编译的时候验证当前文件的activeConn和errorConn是否实现了该接口。

  • 防爆菊座椅
    防爆菊座椅
11 min read

coredump

使用c++,难免coredump,coredump主要由以下几种: segmentation fault 进程访问受限内存触发,OS会通过发信号SIGSEGV给进程,告知这个问题,如果进程没有自定义handler,用默认的,会终结掉进程。使用c/c++这种提供存在底层内存控制函数的语言,会导致这个问题。想java/rust一些高级语言会设计自己的机制,为用户屏蔽该问题,或者以更友好的方式让程序员更易于处理。通常的原因如下: 访问不存在的内存空间或该进程外的地址空间 访问本进程没有权限访问的内存,例如内核地址空间 写只读内存空间,例如进程内存空间中的代码段空间 应用程序中常见的错误包括: 使用指向非本进程内存空间的指针 使用未初始化的指针 使用被释放的指针 buffer overflow 或者 stack

  • 防爆菊座椅
    防爆菊座椅
6 min read

c++进化之路(一)

之前的主要经验是java/golang,虽然一直有长期写c/c++的希望,但一直没有计划付诸实践,主要还是工作机会和自身水平两方面。java/golang拥有作为高级语言的各种特征,让程序员的生产效率有提升,在屏蔽一些复杂度的同时,也增加了学习成本。 最近加入新团队,有机会长期base在c++上做一些技术工作,但技术内容和之前的很相似,有点像java到golang的迁移,但这次语言迁移的难度上会大一些,主要试试对于指针以及内存管理的理解问题。同时c++程序设计方式的对于初学者来说相比其他语言,有难度。经过近5年的工作,渐渐意识到,解决问题的思路和刚就业时还是有很多相同的地方,虽然经验丰富了许多,但仍旧有些稚嫩,不像之前学习高等数学,能有理有据的对答案进行推到,在最终结局问题之前会遇到很多阻碍比较难以克服。举最近处理一个问题的例子,记录下思路,供后续查看,

  • 防爆菊座椅
    防爆菊座椅
5 min read

shadowsocks搭建

如果你有个美国或者日本的机器,那么就用10分钟搭建一下。这里只将ubuntu的。 https://gist.github.com/nathanielove/40c1dcac777e64ceeb63d8296d263d6d 按照上面的提示一步步做,注意pip install shadowsocks可能失败,关于locale的,错误在谷歌上贴下,通过export xxx=xxx,其实就是设置一个环境变量就行了。 还有需要注意的是防火墙的设置,ufw是ubuntu 16.04中管理防火墙的工具,你不需要在关心iptables的各种命令,类似sudo ufw allow 8388,sudo ufw reload这种。 怎么确认8388已经对外开放了那?先暂停你的vpn服务,

  • 防爆菊座椅
    防爆菊座椅
1 min read

个人吸金服务的崛起

腾讯围绕qq、微信这两款用户重度依赖的产品,坐拥巨大流量。除了继续在其他领域收敛用户外,重中之重是怎么变现当前的流量,来满足预言类和基础支撑类服务的消耗,而这两类服务是保持大公司竞争力的基础。 腾讯根植于沟通,在微信和qq两大平台上集成与个体用户相关的服务。服务的供应商最开始分两类,腾讯直系和腾讯伙伴关系,之后微信小程序的出现试图为中小企业甚至个人开发者提供一条通道接入微信的流量体。小程序的推出备受瞩目,因为它在试图构建生态,优良的服务会和收敛用户流量的工作形成良性循环,这对用户是好事,但对竞争者却是很大的威胁。这和iphone和appstore的模式一致,生态就是要吸引多端元素(供应商、用户、代理商、个人开发者),最好是各种细枝末节都有人去优化,加上平台的宏观调控,这个生态就很难有人去打破。而作为规则制定者的平台,名利双收。 生态的建立是平台类产品的核心,阿里的支付宝、小米的miui都是比较典型的试图建立生态圈的产品。大多以用户的某一类需求为切入点。今日头条、

  • 防爆菊座椅
    防爆菊座椅
6 min read

也许‘云’是这样的

云服务不是最近才出现的新型业务,互联网群雄割据,面对巨大的用户流量,需要维护大量的服务器资源,历来资源运作的效率都是企业的核心竞争力。服务器理所应当的被推到风口浪尖,就和之前‘淘宝’一样,物质诉求的上升加上创造力,就会衍生出便利。 ‘淘宝’创立之初,随着电脑的普及,商家开始接受电子商务这个行业,电脑作为流量的入口,也因为娱乐和办公的便利,引入了大量的流量。催生电商的快速发展。而‘淘宝’网店也催生了大量的辅助性的服务,有很多小公司因此赚了一大笔。随着流量的增加,以及‘天猫11’,展现了阿里对于服务器资源的掌控力度,我甚至都怀疑这些购物节,就是催生云服务的前奏。 服务器托管由大公司把持,它们有大量的复杂服务来测试稳定性,理论总是有,但实践这事就被大公司占得先机。

  • 防爆菊座椅
    防爆菊座椅
5 min read

go语言编程习惯

写go的代码我纯凭感觉,通过接触到的源码找些go特有的感觉,然后把代码改到自己看着顺眼为止。不过最近需要输出一份go语言编码规范,我就感觉到,需要读各种go官方给出的文档,从里面抽取出个人用的比较多的东西翻译并组合一下。麻烦,但确实有些技术点是值得注意的。 格式化 最优先,也是最简单的,可以使用go自己的gofmt,google内部使用golint,集成到自己的编译器上即可。 注释 这里有点学问,以package为单位划分,每个package需要有个与包同名的go文件,用于描述当前包是做什么的,可以详细描述包的设计理念。注释使用c语言中常用的block注释/* xxx */。 包内部的方法或者变量的注释,使用c++风格的注释// xxx。 有这样一些注释的技术点需要注意: 常量或者变量的初始化,最好分组,并添加注释,之前我没有注意到这个问题。 方法的注释,

  • 防爆菊座椅
    防爆菊座椅
5 min read

go调度器注释

Go Preemptive Scheduler Design Doc Contiguous stacks Implemetion of Golang:协程栈(三) Golang调度器源码分析 go调度器proc.go文件头部的注释中有一些值得关注的技术点,下面会逐条的分析。 // 1. Centralize all scheduler state (would inhibit scalability). 集中管理所有调度器的状态会抑制可扩展性。集中体现在代码中用一个struct例如sched管理所有g的分配,sched通过lock变量保证所有共享资源操作的并发安全,这样势必导致cpu在锁竞争上有大量额外的消耗。抑制体现在当用户代码的并发力度越小时,这个损耗占比会越大。这会阻碍go语言释放多核性能的初衷。 // 2. Direct

  • 防爆菊座椅
    防爆菊座椅
2 min read

raft分布式一致性算法

raft算法可以作为很多服务实现一致性的指导协议。 为什么要一致性? 单台服务器,用户访问服务器,服务器做事,成功/失败的状态和用户都会达成一致的共识。 多台服务器,为了和用户随时保持共识,就要在集群内部通过互相沟通,然后把结果输出给用户,才能达成共识。 共识的对于用户来说,就是系统的信用,系统说a已经做了,那么如果a没有做,用户被骗,这个系统就没有存在的意义。 那么服务器内部的怎么沟通能保证一致性? raft协议下的集群会有一个leader作为代表,负责和用户沟通,代表保证内部口径一致的方式是对于用户的一个意见(操作)进行投票,赞成票多就通过,实际不会有服务器试图投反对票,只有某个服务器在外力作用下失去投票能力。这里用的是,比较常见的两段式提交。 面对失去leader的问题时,集群内部服务器都会试图申请作为下个leader(源码中使用随机timeout减小冲突),票多者当选,

  • 防爆菊座椅
    防爆菊座椅
3 min read

go调度器-startm

今天聊聊startm函数,字面意思理解,新增或者从M的空闲队列中取出M并“开始”,“开始”在这里是个模糊的意思,需要进行精确的定义 作者认为单独启动M是没有意义的,一定要配上P一起,才能进行G的处理(我自己设计可能不会考虑到作为整体启动),按照这个思路就可以理解作者传入参数_p_的目的 _p_==nil,从空闲p队列获取 _p_!=nil,就使用_p_与M关联 参数spinning==true代表调用方已经增加了nmspinning(空闲M的数量),startm中有可能减少nmspinning或者让新分配的M处于spinning状态 阻止startm使用idle M的代码如下: if mp.spinning { // 从midle中获取的mp,不应该是spinning状态,

  • 防爆菊座椅
    防爆菊座椅
3 min read

go运行库-调度器(一)

之前写过一段时间java,java vm的gc机制几乎是准高级java程序员的必须了解的东西。虽然一直没报以重视,但最近在看完golang的hashmap机制之后,觉得有必要深入了解下golang的gc机制。 gc是现代语言的标配,因为内存日益廉价,回收内存的时机允许一定的异步导致gc这个方案的诞生,当然,gc作为从业务逻辑中分离出内存管理的部分(并加入跨平台的特征),解放了业务逻辑的开发。但从某种意义上讲并没有解放程序员,因为程序员需要深入了解这部分内容才能针对特定语言运行时内存状况有把控能力。 本以为gc是相对独立的一套算法的组合,看起来不会依赖过多。但实际阅读过程中还是遇到很多难理解的非算法相关内容,比如g p m,这些对象的出现增加了阅读gc的辐射范围。痛苦挣扎后,阅读目标逐渐转向golang运行库,支撑业务运行的透明层。 src/runtime目录下的文件是golang运行库的实现,也可以参考《程序员自我修养》中的个别章节对于运行库的描述,之前对于语言的使用都是浮于表面,例如,golang据说以多核并发运行效率的提升为核心目标,

  • 防爆菊座椅
    防爆菊座椅
5 min read