线程启动器适配任意类型

框架的线程启动器start以前只能启动继承自common::Runnable的类(以一个线程去run这个任务类),这在绝大部分的业务代码中都是合理的;但有时候你需要写一个工具或者一个简单的任务处理(比如一个函数),再去继承一个Runnable就显得略微繁琐了些。基于次背景我们决定扩充下start的能力。

1. 实现功能 – start_any
c++11的thread支持传统C函数,C++成员函数,函数对象(实现了()运算符的类的实例),lambda表达式(特殊函数对象)等作为参数传入,极大的简化了代码。

 

2. 老接口融合 – SFINAE
start_any的名字对开发来说比较陌生,如果能统一用start来启动会更加便利。函数名称相同的情况下问题就落在如何区分传入参数是Runnable的继承类这个问题上了,既然用了模板,那么就应该能使用“模板替换不是一个错误”原则来规避。

 

3. 兼容所有形式 – std::decay
上面的版本,发现在某些场景中is_base_of判断会失效,原因是模板类型没有“清洗”干净,比如const T& 跟 T不是一个类型,使用decay退化掉所有修饰验证通过。

 

原创文章,转载请注明: 转载自下雨天
本文链接地址: 线程启动器适配任意类型

QR:  线程启动器适配任意类型

api变更怎么搞

api的变更在库的开发维护过程中非常常见,库使用者很少时,直接强制更新没毛病,用的人多了,影响就会被放大,此时兼容就成了不得不考虑的事情。目前我大概见过几种比较常见的变更做法。

  1. 完全兼容。
    新api随便怎么搞,但要为老接口单独做一份兼容。例如libevent会单独将一些需要兼容的api统一放到一个特定的头文件中暴露出来。
  2. 编译时提醒。
    比如c++里面,可以直接把老接口的函数头加上__attribute__((deprecated(msg)))的修饰,用于提示新的替代api在哪里或者给出建议,当然也可以根据其他的预编译宏#error, #warning, #pragma message(msg)等来给出相应的提示。
  3. 文档化。
    每个版本或者库文档,说明的地方罗列好changes和api。总体说来,“完全兼容“通常对维护者是个包袱对用户是个便利,但是若原接口有隐含bug或者性能问题的时候,就不得不强制用户使用新api了。“编译提醒”是个折中,通常无需用户感知库的具体变化根据提示就能完成切换工作。“文档化”则直接把工作全部推给使用者了,通常老用户最为反感,工作量较大,一般在出现在大版本或存在重大特性无法进行兼容的场景下。

原创文章,转载请注明: 转载自下雨天
本文链接地址: api变更怎么搞

QR:  api变更怎么搞

定时器接口优化之function和bind

框架早年的timer是通过一系列函数指针来封装回调函数,期间使用0x的function和bind优化了一波,极大的简化了代码,但是我们的timer回调函数的形式仍然保持着void timer_callback(int fd, short event, void *arg);的形式(仿libevent与普通fd事件回调函数保持一致)。但在大量的开发实践中,绝大部分用不到其中任何一个参数,极少部分会使用到arg,而我们仍然不得不完整的写完整个函数头部。经过分析发现我们可以利用function和bind的一些“特质“来再度优化这个繁琐的回调范式。

  1. std::bind让std::function看起来可以接受任意形式的回调函数形式,但std::function严格限制调用时的实参形式必须跟定义时形参保持一致
  2. std::bind并不是只能bind函数,还可以嵌套bind
  3. 让timer支持任意类型。
    1) timer内部持有的function切换成std::function<void(void)>,通过内部嵌套bind来兼容原回调形式void callback_old(int, short, void*);
    2)timer接口支持任意类型(AddTimer(std::function<void(void)>)),只需bind时具名所有参数即可(例如void callback_xxxx(int type, void *arg); AddTimer(std::bind(callback_xxxx, ARG_type, ARG_ptr)))。

原创文章,转载请注明: 转载自下雨天
本文链接地址: 定时器接口优化之function和bind

QR:  定时器接口优化之function和bind

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

原创文章,转载请注明: 转载自下雨天
本文链接地址: in-source builds or out-source builds

QR:  in-source builds or out-source builds

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

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

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

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

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

4. 运行效果。

 

 

原创文章,转载请注明: 转载自下雨天
本文链接地址: 向目标文件注入编译环境信息

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

控制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查询路径

QR:  控制cmake查询路径

框架自动化部署

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

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

 

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

原创文章,转载请注明: 转载自下雨天
本文链接地址: 框架自动化部署

QR:  框架自动化部署

glog FATAL日志写多次的问题

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

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

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

原创文章,转载请注明: 转载自下雨天
本文链接地址: glog FATAL日志写多次的问题

QR:  glog FATAL日志写多次的问题

监控的盲点

【背景】
公司已经有较为完善的监控手段。monitor,通过累积量等一维数据监控自身的情况;智能监控,通过多维数据统计成功率和时耗。当然我们一直认为“一切尽在掌握”。

【问题】
某业务B_T的调用方发现出现超时的情况,排除网络问题,在我们看来只可能是后端服务S_T出问题了。而后查询对应的后端自身服务monitor监控正常,智能监控也变化ok。最后经后端童鞋深入排查,发现是自己server假死了。那么问题来了:
1) 为啥监控没啥变化
假死的server,在LB里面权重被放低了,所以请求量就少了,咋眼看去,monitor视图请求量几乎没变化(减少的很小),因此没有产生告警。而因为假死,所以正常的请求并未被S_T处理,所以智能监控不会被上报,成功率和时耗没有变。
2) 为啥其他业务没太大影响
S_T为很多业务服务,量大的服务,由于成功率都很高,所以通常不会有告警。而量小的业务(或者长尾业务)却很容易受到波动。
3) 为啥没有被LB完全踢掉
这个跟后端S_T的异常姿势有关系,由于是假死,偶尔处理一个请求,LB探测到S_T还alive,又会扔正常的业务数据过来,挂一会儿,又踢掉,周而复始。

【思考】
虽说根本上是后端的server开发者的问题,但是后台开发,监控为王。没有完美的程序,只有不完美的监控。这里暴露出来监控的一些特点,就是不能简单的通过自身的监控上报来判断服务质量,这样就会存在上诉的盲点,监控应该是一个纵深,立体化的过程。将调用方视角的后端服务质量纳入后端服务的监控范围,整合上下游的监控或许才能更加精准的为后端服务保驾护航。

原创文章,转载请注明: 转载自下雨天
本文链接地址: 监控的盲点

QR:  监控的盲点

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

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

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

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

 

 

原创文章,转载请注明: 转载自下雨天
本文链接地址: 记一次负载均衡不生效的问题

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