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,打造工具库和代码生成工具

配置加载 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

go的hashmap实现

Disque中存储server相关的数据结构时,一般都使用dict.hz定义的hashmap,这个是作者直接从redis迁移过来。内部通过keyTypekey的数据类型,使用C中经常使用的union方法防止空间的浪费。存储采用类似指针数组,数组中的每个指针指向一个链表(链表用于解决hash冲突)。遇到存储空间不够(当总的元素数/指针数组的数量的几倍时,这代表hash查询的效率会下降,上述的几倍作者提供了一个经验值),采用rehash的方法,初始化时会有两个table,一个用作当前操作table,另一个在rehash时作为扩展后的table,rehash过程不是一次性完成,会在每次有操作时出发一步rehash,具体的rehash算法是每一步rehash会跳过10个空的slot后停止,防止会对正常的hash操作有影响,还有在rehash操作过程中,涉及到查询操作会同时查询两个table。 很久之前读过云风的一片blog,里面对比了golang中hashmap和lua中的实现,一头雾水。最近看了下hashmap.go的代码,这个整体的设计思路和dict一样,rehash都是重要的组成部分。但是细节上差异还是比较大的。首先,

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

Choose Concurrency-Friendly Data Structures(翻译)

为什么想翻译这篇文章,最近在看disque的代码,里面queue在存储job时用的是skiplist,插入和删除时间复杂度O(lgn),获取job是O(1)。最初的想法是红黑树在插入和删除的情况下也可以达到同样水准,只是弹出job的时候需要维护指向最前面节点的指针。并没有想到什么特殊的理由,用一个全新的数据结构,看了下面的文章,才知道并发性也是选择数据结构的一个考量,特别是在高性能场景。 Choose Concurrency-Friendly Data Structures 链表和平衡搜索树是相似的数据结构,但是它们是否能在并发环境下实现实现性能的飞跃? 什么是高性能的数据结构?为了回答这个问题,我们通常会考虑以下因素:时间复杂度,内存占用情况,局部性,以及遍历顺序。以上的因素在顺序和并发软件中同样使用。 但是在并发代码中,我们需要考虑余外的两个情况,用来帮助我们判定一个数据结构是否是并发友好的: 在并发代码中,性能的提升要求数据结构允许多线程的同时操作。

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

PG:prepared statement `1` already exist问题推敲

这里记录一个没有搞定的问题。问题的起因是由于生产环境pg更改表主键类型导致(int->bigint)机器io负载很高,pgbouncer连接pg server超时,然后上层掉用方过来找才发现。当时果断停止修改表结构,重启pgbouncer释放已有连接使pg回收相关session资源。同时我们维护的推送中间层服务也重启(实际不需要重启,当时报出大量的pgbouncer连接不上的错误,只要pgbouncer恢复接受连接,sql.go中的连接池会自动丢弃bad connnection,生成新的)。重启后,发现只有一个机房的集群都启动成功,并正常开始工作,另一个机房推送服务都启动不了。 单机房服务启动不了的问题比较简单,运行bin会panic,原因是程序defer stmt.Close()放在了错误的位置导致npe。值得注意的是db.Prepare这个语句的报错prepared statement 1

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

pq报出EOF错误

前天生产环境突然出现大量操作pg报错-EOF错误,没有其他提示。问题本身比较容易查明。elb配置了错误的映射端口,导致app已经建立的连接,不能正常访问pg,然后elb切断与app的连接导致的错误。不过这里有几个问题需要讨论下: pq库遇到连接被切断的情况会通过panic把EOF传递给上层,然后上层就处于蒙逼状态,因为EOF潜在的意思就是io.reader读到了输入流的末尾,流返回空字符。所以第一个问题就是,作为sdk的pg库是否能应该把EOF错误在具体场景下处理成相应的具体错误。但实际上,上层也是知道当前在什么情况下报出的EOF错误,所以这个问题只能待定,期望将来能看到大牛对这种问题的处理方式。 elb切断与app的连接,推测是内部进行系统调用传入相应的sockfd,在tcp层进行的交互,即app server收到了FIN进入CLOSE_WAIT状态,应用层没有收到任何一个byte。开始,我以为肯定会发送一些协议字节,或者起码发送错误描述字符串过来(因为elb是同通用负载均衡中间件,不会了解具体协议),但连一个自己都没有收到,并导致io.

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

Simple Dynamic Strings(翻译)

sds是antirez从redis中提取出的字符串操作类库 Simple Dynamic Strings 关于version 2的提示:这是一个更新后的版本,目的是同意Redis,Disque,Hiredis,和独立的SDS版本。该版本和version 1的SDS Not binary compatible,但是API是99%兼容的,所以迁移起来不难。 SDS与V1相比在一定工作负荷的场景下稍慢一些,但使用更少的内存,因为header的大小是动态的并且依赖于你需要存储的string的大小。 而且它包含更多的API方法,尤其是sdscatfmt是加速版的sdscatprintf,可以在简单的场景下是使用(避免printf的性能损失)。 How SDS strings work SDS通过堆上收集的字符串补充有限的libc字符串处理方法: 简单易用 二进制安全

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

使用streadway/amqp出错

问题描述:服务a接收http请求,并将请求内容存入db,启动一个goroutine将这些内容推送到rabbitmq中,生产环境运行后,每天都通过脚本比对db以及rabbitmq的consumer得到的结果,发现经常少推送记录。 假设consumer不存在问题,现在需要保证的就是服务a中的goroutine从db中拿到内容,推送到rabbitmq,并更新db内容的状态,这件事的一致性。 代码中按照以上描述的顺序执行。在推送mq成功后,更新db状态,单线程无并发的情况,db状态变了,但是consumer统计的结果确实是丢失了内容。这种情况,直接查看关于mq sdk的使用部分的逻辑。发现官方例子中,有一个publish后,server异步给ack的机制,允许调用sdk的app注册channel,除了性能可能有些问题外,这个问题到这里就结束了。 但是,上线后,发现大量的超时,并且,worker会突然定制运作,不会定期处理db中的记录。

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

下游服务处理时延与长连接

场景:服务a访问服务b,a对外提供http,a和b之间通过thrift协议(沟通方式是tcp)交互 时延与长连接,随着服务b时延上升,对服务a造成什么影响?a使用sdk访问b,sdk中有保持长连接的pool,假设pool大小=4096: b处理时间1ms,一个conn1s能处理1000个req,排除网络延迟的影响,qps上限=4096*1000=400w+ b处理时间100ms,一个conn1s能处理10个req,qps上限=4096*10=4w+ 假设b处理能力足够,那么什么情况下b的处理时延会影响到a,就是从a发起到b的请求qps超过上面计算结果的情况下,会导致a和b之间建立大量的长连接(前提是b允许突破4096,给新的req新增连接,且a使用的sdk内部也保护策略的情况下)

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

imageproxy阅读

之前在zhuqu.com时,同事写了一个图片服务器,调研了很多blog,用php写的,提供了上传、裁剪、负载均衡等功能。但有一些缺点, 负载均衡是硬编码到配置中(当然量不大,问题也不大) 提供图片和图片转换功能糅合在一起,一个项目维护,服务架构不清晰 为了在保存原始图片的同时,支持水印,不同尺寸缩略图的需求,文件目录设计的很复杂(当然有明确的规则实际还好) 图片安全问题,因为原始图片和对外图片放在同一个服务器上,为了不让外部调用到原始图片,禁止掉了特定的目录 一些规则分布在了项目的各处,没有统一起来,增加维护代价 imageproxy将规则和图片转化,以及权限控制单独提出了一层,这里对外的图片都是imageproxy转化后的结果,并且图片的存储可以自由实现,可以使用disk,也可以使用mem,

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

gateway动态调用soa服务

thrift文件是soa服务之间交流的协议,调用方提前生成sdk用于访问soa服务,sdk除了针对struct的定义生成go的结构体外,还包含了send和recv两大部分。 send。序列化thrift中的struct,通过与soa服务建立的长连接发送出去。在sdk中的表现主要是write开头的一些方法。 recv。反序列化thrift中的struct,由于代码时提前生成的,且[]byte数组中的各field都是类型已知的,直接调用read的方法即可。 为什么会产生动态调用的问题?由于之前引入gateway这层,作为http请求的统一入口,gateway存在调用soa服务的可能性,又不能引入大量的sdk,希望gateway尽量少的上线和改动。逻辑尽量是通用的,不针对具体业务。所以才有了动态调用soa服务这个方案。 当然,通过在gateway和soa服务之间增加一层代理,将大量的sdk引入分散在多个中间层服务上也是可以的,我们也恰恰要采用这个方案。 但是,动态调用soa服务这件事还是有必要阐述下,为了动态,将sdk中的针对具体thrift做的操作,用通用的方法替换,因为通用的sdk通过gateway是可以得到thrift文件的具体内容的,

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