分类目录归档:代码相关

code

in-source builds or out-source builds

最近一段时间在跟进解决一系列的跟编译相关的事情,发现大家特别喜欢在源码目录直接编译代码,功能角度上这没有什么问题,但从源码管理角度来看这种方式还是存在很多不好的地方,最直观的感受就是一堆凌乱的编译中间文件。那么为什么我们应该选择out-source builds而不是in-source builds呢?(这篇文章列得很详细http://voices.canonical.com/jussi.pakkanen/2013/04/16/why-you-should-consider-using-separate-build-directories/)

1. 易于清理
这个是最容易理解也最显而易见的,一堆中间临时文件,干扰你阅读源码,干扰你打包源码tree,干扰你清理编译中间文件。

2. 可以针对不同的编译选项设立独立的编译目录
这样的好处显而易见,可能做测试,可能给不同类型的用户使用,需要多套不同的编译选项,这样in-source builds就不能满足要求了。

3. 可以对源码目录和编译目录做特定控制或优化
比如源码不允许修改(只读),或者需要加速编译,可能会把编译目录放到更快速的存储设备上。

向目标文件注入编译环境信息

【背景】
前段时间一个离职同事几年前的老模块需要交接,由于长时间无人维护,一时找不到代码路径,只得从整个后台中心的代码库里面“人肉“。记得曾经还出现过类似的情况,找不到老的编译环境(现有的系统,机器都升级了),被迫重构了老代码。工作时间越长,这样的问题也就会越来越频繁,老是这么死板的处理也不是个办法,费时费力,苦不堪言。如果可以将server部署前的编译环境信息集成到目标文件中,是否就可以根据线上运行的目标文件定位到相关的信息呢?目前部门的开发方式都是依赖于框架,所以只得在框架上下功夫。定好2个核心原则,撸起袖子就是干:对开发者透明,能便捷的获取信息。

【实现】
整个框架的编译体系是通过cmake维系的,通过其丰富的能力可以实现一些特殊的功效。框架使用gflags来维护命令行参数,本已经集成一些基本的参数,比如threads, conf,pid_file等,所以直接通过这种方式来获取编译环境信息对开发者更加熟悉和便捷。
1. cmake获取编译环境信息。

2. cmake将信息string以宏的方式透传给框架头文件。

3. 框架头文件使用宏来区分框架编译和业务编译。

4. 运行效果。

 

 

控制cmake查询路径

【背景】
部门依赖特定框架开发代码,框架中已经集成了protobuf,但是用户却有可能在开发机上胡乱安装protobuf。而业务链接到的版本是框架的protobuf,但cmake中find_package(Protobuf)默认的是用系统protobuf中的protoc编译描述文件,当系统中pb和框架pb版本不同时,就会造成编译错误。

【构思】
研究了下Cmake中的FindProtobuf.cmake文件,_protobuf_find_libraries|_protobuf_find_libraries|_protobuf_find_libraries中查找lib,include,bin均是通过find_path,find_program,find_library来完成的,只要控制查找路径即可。

于是,框架的xxx-config.cmake中添加一下两行解决问题。业务依赖框架时find_pakcage(xxx)就会自动设置cmake的路径变量,最终达到控制查询结果的目的。

 

框架自动化部署

【背景】
部门框架是需要部署到开发机上的,框架的更新一直是手动到每台开发机上去更新部署,开发人员和开发机多了之后,框架维护人员已经很难管理住所有开发机的框架环境了。

【构思】
一切只围绕一个目的:在框架有更新时能够更新开发机的框架环境。总体思路是利用CMAKE的ExternalProject_Add来维护项目的编译,crontab来保证检查执行:设定最新的tag_url(如果与历史svn_url不一样就svn switch),svn up + cmake + make。

 

【后续】
现在能够自动更新了,后面最好能够呈现开发机框架视图,所以还需要上报本机的框架环境信息,在服务端呈现出来。

glog FATAL日志写多次的问题

【背景】
LOG(FATAL)能够使代码更加简洁,在项目中用得比较多。但实际真正触发的时候,看日志总感觉没有写进去。去日志目录观察,实际上是写了两份日志,而当前的日志软连接链到了空日志文件上。这在日志目录下日志文件比较多时,查看上一次挂掉的日志非常不方便。
glog原始情况

【研究】
实际上阅读glog代码发现SendToLog这个函数在打印完所有日志之后,针对FATAL级别的日志,又重新写了一次,而且时间戳是0即(19700101-080000)。

 【解决】
注释掉该段代码搞定。
glog-after-fix

记一次负载均衡不生效的问题

【背景】
公司目前使用一种基于调用方反馈来衡量后端服务的负载均衡组件,姑且称之为LB_T。正常情况下后端服务异常都会让调用方将此问题机器M_T踢掉,因为每一次成功的调用,调用方都会上报一个成功状态和时耗用以对后端可用性和负载做统计。

【问题】
但这一次只看到后端一直返回错误,而调用方却仍然再往这台机器发包。在基础组件上没有找到太多蛛丝马迹,而后进一步排查发现后端过载了仍然会向前端回包。这也就解释得通了,LB_T这种LB方式通常后端有回包就会认为是成功。虽然可以特化处理错误码,但为了整体的通用性,修改框架让后端过载时只上报自身服务质量和记录日志,不向前端回包,问题解决。

【思考】
问题虽然解决,但是引发了我们的思考:代码中使用了LB组件就一定能做到负载均衡,异常时能切换能容灾?答案是否定的:
1. 姿势非常关键。每一次请求都必定要对LB进行一次Update。
2. 上下游配合。一定要清楚的知道后端何为异常,对后端足够的了解,相互配合才能达到效果。

 

 

没有C++11我们如何实现c++的函数变参

传统的变参方法都是基于上古的VA_ARGS来实现的,c++11引入了变参模板,把编写变参方法的便利度提高了的一个前所未有的高度。re2这个库很好的诠释了C++里面即便没有变参模板,也能做便利的变参实现。

【背景】

FullMatch需要提取匹配到的所有子串,而正则表达式本身的灵活性决定了这里的参数(匹配到的子串)必然是变化的。

【RE2实现】

【使用c++11变参模板的实现】

RE2的变参实现通过整齐的重载operator()来实现,最大支持32个参数,因为只实现了32个重载。
而反观使用C++11的变参模板,则简化了很多代码,且没有参数32的限制。后者的便利性自然是极高的,但是在编译器的通用性上,前者更加通用。在没有C++11的情况下,google::re2又给出了实现变参的很好的一个例子。

轮server监控的层次

监控是服务的晴雨表,对开发者尤为重要,基本是server上线的标配,主要分两个层次。

1. 标准化监控
这里主要有两类 : 标准化的网络设备(主机监控),例如路由器交换机,通常都可以通过snmp协议采集到(即便是私有的od指标也能从生产商处获得);标准化的服务(中间件监控),例如httpd,nginx,mysql,web页面等等,通过对标准服务的特性采集,例如运行端口,进程名称,日志情况等来配置监控参数。
标准化维度的监控,绝大部分开源软件(zabbix,cacti等)都都能满足。

2. 业务服务监控
具体的业务监控,开源的东东就捉襟见肘了,仅仅少部分场景能通过脚本勉强满足,这就是为什么大部分成熟的互联网公司都有自己的一套或几套监控系统。监控 = 上报 + 绘图 + 告警,监控系统 = 上报api + 视图管理 + 告警管理。这里面又大致分成两大类 : 数据量维度的监控和状态维度的监控。

1). 数据量维度
上报点包括总请求量,总失败量,总成功量,各分支请求量,各分支失败量,各分支成功量,特殊上报量,时延区间量(例如50ms以下的处理请求量等)。
数据量是业务监控的最低层次,各项指标不正常,server必然是有问题,但指标正常并不代表server没有问题。

a) server嵌入上报api,monitor_api(int attr_id, int attr_value)
b) monitor_agent周期汇总本机的所有的attr_id的上报值发送给collect_svr
c) collect_svr对数据做处理,按(attr_id, report_ip) -> attr_value存储数据,按attr_id -> report_ip_list和report_ip -> attr_id_list存储索引关系
d) 页面上根据数据和索引关系聚合出各种视图 (attr_id, report_ip, day_timeline),(attr_id, all_report_ip, day_timeline),(report_ip, attr_id_list, day_timeline)
e) 告警模块中针对各个视图关系设置最大值/最小值等告警策略

2). 状态维度
服务的质量通常都是状态量,最常见的指标就是 时延和成功率。这两个指标基本可以反映出某条服务或接口的服务质量。

a) server调用后端结束时嵌入上报api,stat_api(int service_id, int stat_code, uint32_t cost_time)
b) stat_agent周期汇总或者直接转发本机的所有的service_id的上报数据发送给collect_svr
c) collect_svr对数据做处理,按(service_id, called_ip, report_ip) -> stat_value存储数据,按(service_id, called_ip) -> report_ip_list, (service_id, report_ip) -> called_ip_list存储索引
d) 页面上根据数据和索引关系聚合出各种视图 (service_id, report_ip, called_ip_list, day_timeline),(attr_id, called_ip, report_ip_list, day_timeline),(service_id, report_ip, called_ip, day_timeline)
e) 告警模块中对单机或者整个服务的 时延或成功率设置阀值

3. 调用链监控
仅仅是服务自身的监控是不够,还得能够拓扑出一条请求的来龙去脉。这里又分两种:这里业界通用做法都是通过一个traceId贯穿整个调用路径,然后由后端的日志平台离线分析去统计;另外前者必须保证整个公司所有路径都要规范上报,有时候比较困难,局部实现的话,在某一个程序上以调用者的视角去统计所有服务接口的各种信息去上报统计拓扑更容易实施。

yaaf_server的几种结构

总结下不同业务场景下yaaf(yaaf框架是个基于zmq的线程池框架写后台server)server不同的内部结构,大体有以下几种形式:

yaaf框架server结构

 

1. 单一zmq接口的dispatcher+worker(server_1)
早期,部门几乎绝大部分是web业务,中心绝大部分也是cgi+业务server这种形式,当时后台几乎都是面向cgi的,所有逻辑落在worker中,此时业务server的结构是单个zmq_dispatcher + 一组worker的形式。

2.支持多种接口的dispatcher+workerserver_2)
随着业务的增长,与外部门的合作增多,后台server除了给cgi调用之外,还会给诸如客户端提供服务接口 - 原生的udp/tcp/http等,此时框架封装了更多的dispacher负责对接不同来源的调用方,worker仍然保持尽可能的只关注业务逻辑,所以业务server的结构是多个worker_dispatcher + 一组worker的形式。

3.woker_dispatcher+client_dispatcher+worker(server_3)
随着部门越来越大,所做的业务也越来越复杂,后台存在较多的调用更底层服务的情况。此时在server中用框架封装更多的client_dispatcher,用于向后端发请求。worker继续保持尽可能的只关注业务逻辑,这时业务server的结构是worker_dispatcher + client_dispatcher + 一组worker的形式。

4.多组dispatcher+多组worker(server_4)
随着参与业务的增多,越来越多的业务场景出现。当逻辑复杂到一定程度的时候,可能会将dispatcher-worker分组,来方便编写/维护不同的逻辑代码,不同分组内的worker负责模块内相对独立的逻辑,此时的业务server结构是多个dispatcher+ 多组worker。

5.去dispatcher(server_5)
当遇到请求量巨大或者cpu密集型的业务时,dispatcher本身可能成为瓶颈,虽然框架可以平行扩展,但为了榨干单机性能实现上会去掉dispatcher让worker直接对外提供服务,此时的server结构是一组worker。

zmq自连接问题

【背景】
最近的项目过程中偶然的被同一个问题骚扰:一个基础server挂了(基于zmq的服务),自动拉起的脚本不能成功重启server,查询后发现是监听的端口被绑定了,且是被一个client绑定了,每次都得kill掉那个client然后再重启。

【认知】
经过多方查阅发现是linux的自连接问题,linux允许发生自连接,而zeromq官网也有人提出了这个bug – LIBZMQ-549。那么为神马现网环境的server从来没出现过这个问题呢?因为只有在同一台机器上,client和server五元组才有可能完全相等。

【解决】
1. 保证随机分配的端口与server绑定的端口不重合(/proc/sys/net/ipv4/ip_local_port_range)
2. hack代码,发现ip/port相等立马断开