Archive for the ‘编程语言’ Category

线上存储服务崩溃问题分析记录

上周我们的存储服务在某个线上项目频繁出现崩溃,花了几天的时间来查找解决该问题。在这里,将这个过程做一下记录。

加入调试信息

由于问题在线上发生,较难重现,首先想到的是能不能加上更多的信息,在问题出现时提供更多的解决思路。

首先,我们的代码里,在捕获到进程退出的信号比如SIGABRT、SIGSEGV、SIGILL等信号时,会打印出主线程的堆栈,用于帮助我们发现问题。

但是在崩溃的几次情况中,打印出来的信息并不足以帮助我们解决问题,因为打印的崩溃堆栈只有主线程,猜测是不是在辅助线程中发生的异常,于是采取了两个策略:

  1. ulimit命令打开线上一台服务器的coredump,当再次有崩溃发生时有core文件产生,能够帮助发现问题。
  2. 加入了一些代码,用于在崩溃的时候同时也打印出所有辅助线程的堆栈信息。

在做这两部分工作之后,再次发生崩溃的情况下,辅助线程的堆栈并无异常,core文件由于数据错乱也看不出来啥有用的信息来。

复现问题

由于第一步工作受挫,接下来我的思路就在考虑怎么能在开发环境下复现这个问题。

我们的存储服务在其他项目上已经上线了有一段时间了,但是并没有出现类似的问题。那么,出现问题的项目,与其他已经上线的服务有啥不同,这里也许是一个突破口。

经过咨询业务方,该业务的特点是:

  • 单条数据大:有的数据可能有几KB,而之前的项目都只有几百字节。
  • 读请求并发大,而其他业务是写请求远大于读请求。

由于我们的存储服务兼容memcached协议,出现问题时也是以memcached协议进行访问的,所以此时我的考虑是找一个memcached压测工具,模拟前面的数据和请求特点来做模拟压测。

最后选择的是twitter出品的工具twemperf,其特点是可以指定写入缓存的数据范围,同时还可以指定请求的频率。

有了这个工具,首先尝试了往存储中写入大量数据量分布在4KB~10KB的数据,此时没有发现服务有core的情况出现。

然后,尝试构造大量的读请求,果然出现了core情况,重试了几次,都能稳定的重现问题了。

有了能稳定重现问题的办法,总算给问题的解决打开了一个口子。

首次尝试

此时,可以正式的在代码中查找问题的原因了。

来大概说明一下该存储服务的架构:

  1. 主线程负责接收客户端请求,并且进行解析。
  2. 如果是读请求,将分派给读请求处理线程,由这个线程与存储引擎库进行交互,查询数据。此时该线程数量配置为2。
  3. 存储引擎库负责存储落地到磁盘的数据,类似leveldb,只不过这部分是我们自己写的存储引擎。
  4. 在读线程从存储引擎中查询数据返回后,将把数据返回给主线程,由主线程负责应答客户端。

[erlang库源码阅读]gen_server.erl

继续接着上一篇文章分析gen.erl来分析下gen_server.

在gen.erl中,start操作最终会走到GenMod:init_it函数中,这里的GenMod就是gen_server/fsm/event三个模块,所以先来看看gen_server中的init_it函数.

%%% ---------------------------------------------------
%%% Initiate the new process.
%%% Register the name using the Rfunc function
%%% Calls the Mod:init/Args function.
%%% Finally an acknowledge is sent to Parent and the main
%%% loop is entered.
%%%

[erlang库源码阅读]gen.erl

gen系列代码,gen_server,gen_event,gen_fsm的代码都在erlang代码的lib/stdlib中,它们都是以gen.erl为基础的.gen.erl中定义了一些这几个behavior公有的代码,所以首先来看看gen.erl做了什么.

gen.erl中主要是定义了以下几个操作:start(启动一个gen系列的服务器),call(向gen系列服务器send请求并且等待回复).

1) start

start(GenMod, LinkP, Mod, Args, Options) ->
    do_spawn(GenMod, LinkP, Mod, Args, Options).
do_spawn(GenMod, _, Name, Mod, Args, Options) ->
    Time = timeout(Options),

根据erlang自定义behaviour生成编译依赖关系

erlang中可以自定义behaviour,但是如果一个模块是实现了该自定义behaviour的话,那么首先要编译behaviour,其次在编译该模块时加入-pa指定编译好的behaviour.beam路径,否则编译的时候会提示该behaviour未定义.

换句话说,实现需要依赖到behaviour模块编译.

因此需要根据代码中的这种依赖关系,对代码进行一次梳理,定义一个正确的编译顺序.幸而rabbitmq项目已经有这样的代码,我就抽取出来用了,具体的实现还没有细看.在这个框架的文件组织中,erl文件放在src子目录下面,在src/Makefile中,首先以当前目录的所有erl文件为参数调用../generate_deps(这个文件取自rabbitmq项目)来产生依赖关系文件deps.mk,注意要在Makefile中include这个文件,这样新的依赖关系才会起效,然后编译会自动根据新的依赖关系来安排编译的顺序了.有了这些,你所需要做的就是把你写的erl文件放在src目录下面,依赖关系什么的,由Makefie搞定了.

示例项目在

抽取rabbitmq网络层做的echo server

传说rabbitmq网络层实现的优雅高效,于是我就尝试着将其中的网络层抽取出来,模拟着做了一个echo服务器,代码放在

shared_ptr真能防止内存泄漏吗?

这个命题有些诡异,因为shared_ptr设计的初衷就是为了防止内存泄漏,但是先别急,等我把问题描述清楚.

事出缘由是这几天项目出现一个内存泄漏的bug,之前这部分是使用shared_ptr封装了很多指针的操作,后来出于效率的考虑,改回了裸指针.由于我们使用的google

全局定义结构体名称冲突导致的问题(C++)

上周项目代码中出现一个诡异的问题,我在某个数据结构中新增了一个string类型的成员变量,但是每次到了赋值的时候必然出现core dump.跟踪了好久,也猜想到应该是string的函数指针表之类的被破坏了,但是始终找不到原因.最后突然想到搜索代码中该结构体定义的地方,发现同时有两个.cc文件中都定义了该名字的结构体(其中一个也是这一次新增的),于是恍然大悟,应该是结构体的定义名称冲突导致的.

我将这段出错的代码,抽取出来写了个demo,放在

erlang中使用ets的一个弱问题

这几天纠结在一个erlang的ets操作问题上,最后才发现,其实是一个很弱的问题.

问题大致是这样的:程序启动之后,读取配置文件,将其中的配置保存到ets表中,然后创建superviser监控进程,再由这个进程拉起几个工作进程,工作进程中会根据情况通过之前创建的保存配置的ets表获取一些配置进行操作.可就是在work操作ets表的过程中,偶尔会有出现报错,大意是这个ets表是无效的,未定义的.查了一些ets的资料,起初以为问题在于表的时候没有设置属性为public所致,但是还是出现问题.

找了很多地方无果,最后想起之前有同事提过公司内有玩erlang比较熟悉的人,遂赶紧将出错的代码提炼出来求教.问题出在于,这个ets表是在程序开始之后马上创建的,而后这个进程又创建了superviser进程等,而在这个过程完结了之后,这个最初的进程就退出了,于是由它创建的ets表也就失效了.因此,偶有出现访问异常的原因,实际上跟erlang虚拟机的进程调度相关(一般这些灵异的问题都跟这些有关系).因此解决的办法是,在superviser进程中创建ets表,由于这个进程一直常驻,所以就不会由于它的退出导致访问异常了.

不过,话说回来,我的这个思路还是不那么COP的编程思路.更好的做法是,给这个ets表包一个gen_server的访问层,对这个表的访问,操作等都通过gen_server的回调函数handle_call/handle_cast等来进行,也就是对这个表的操作局限在这个进程中,其他进程如果要访问还是走的erlang惯用的消息通信的方式.而之前的方式,虽然也可以,但是必然会造成大量的对ets表的加解锁同步问题.

erlang中使用google protobuf进行通信

初学erlang,花了不少的功夫,想要在erlang中集成google的protobuf用于消息通信.个人觉得,使用类似protobuf这样通用的编解码模块,有一个好处就是这部分完全交给别人,再不用自己关心什么很操蛋的大小端,数据长度等琐碎的问题,另外,protobuf使用.proto文件自描述协议,C/S端人员可以通过这个来讨论问题,一目了然.

然而,要把它集成到erlang中还是一件比较麻烦的事情,一来google官方没有对erlang进行支持,这也许是因为google官方认定的编程语言只有C++,java,Python三种的缘故吧,而它的竞争对手,如thrift等都提供了erlang的支持.虽然官网上给出了第三方做的其他语言的实现,但是毕竟不是官方的实现,可能会有些未知的问题.比如我很担心我使用erlang非官方的protobuf实现写了一个服务器,但是再用比如python写了一个客户端,C/S两端都使用protobuf进行通信,但是由于编解码实现的差异,导致了协议数据有出入.

不过,鉴于我在搜索资料的时候未发现比较好的介绍如何在erlang中使用protobuf的方式,还是记录一下吧.

1)

C/C++相关文章收集

1) 详解extern “C”

2) 重载,覆盖,和隐藏

3) Callback在C\C++中的实现

4) 解读google C++ code style谈对C++的理解