使用Thrift的网络框架搭建一般性网络应用

Idea的提出

Thrift 存在的一些问题:

  • 相比于protobuf,Thrift的序列化和反序列化性能表现欠佳,大概比protobuf慢10倍。
  • 相比于其他RPC框架,Thrift拥有优秀的底层通信框架。(作者简单比较过thrift和grpc1.0的通信框架,grpc的设计实在太过简单。)

由此提出猜想和假设:

  • 将 Thrift 的底层通信框架抛离出Thrift框架,利用其来构建一般性的网络应用。
  • 组合 Thrift 的底层通信框架 和 protobuf序列化协议,使之成为一个新的RPC框架。

从实现难度和工作量上的考虑,本文尝试实现第一个假设,“将 Thrift 的底层通信框架抛离出Thrift框架,利用其来构建一般性的网络应用”。第二个假设希望日后,作者在时间和精力富余的时候再进行试验。

使用Thrift的网络框架搭建一般性网络应用的优点

  • 快速搭建网络应用,节省时间成本
  • 当Thrift协议序列化和反序列化成为系统性能瓶颈时,可对其进行替换,同时又能保留Thrift的网络框架,减少对上下游系统的影响

如何操作

有两种方法:

  • 在IDL文本中,将自定义协议的结构体存为一个thrift的string变量。
  • 构建自定义的Processor类

下面对这两种方法做详细介绍:

在IDL文本中,将自定义协议的结构体存为一个thrift的string变量

举例:

namespace cpp com.thrift.test

struct Parameter{
    1: required string bin_data;
}

service DemoService{
    i32 demoMethod(1:string param1, 2:Parameter param2);
}

将新的协议序列化后的数据放入bin_data中,这种方法缺点是,自己定义的协议,还要被thrift的序列化反序列协议包裹,不能完全消除thrift序列化和反序列化的代价。

第一种方法太过简单和粗糙,因此经过挖掘thrift代码后,探索出了更精细的方法。

构建自定义的Processor类

Thrift 底层通信模块的四大基类,TServer、TProcotol、TProcessor、TTransport,其中TProcessor::process是负责处理具体业务逻辑入口。

class TProcessor {
 public:
  virtual ~TProcessor() {}

  virtual bool process(boost::shared_ptr<protocol::TProtocol> in, 
                       boost::shared_ptr<protocol::TProtocol> out) = 0;

  bool process(boost::shared_ptr<apache::thrift::protocol::TProtocol> io) {
    return process(io, io);
  }

 protected:
  TProcessor() {}
};

因此,只要自定义实现TProcessor的基类,重写process方法,就能自定义自己的网络应用。

下面是一个Hello world应用的简单实现:

首先实现一个HelloWorldProcessor 类。’

class HelloWordProcessor : public apache::thrift::TProcessor {
public:
  virtual bool process(boost::shared_ptr<apache::thrift::protocol::tprotocol> in, boost::shared_ptr</apache::thrift::protocol::tprotocol><apache::thrift::protocol::tprotocol> out) {
    out->writeBinary("Hello world!");
    out->getTransport()->flush();
    out->getTransport()->close();
    GlobalOutput.printf("send bytes %s", "Hello world!");
    return true;
  }
};

然后构建main函数,本实例使用TSimpleServer模型

using namespace std;
using namespace apache::thrift;
using namespace apache::thrift::processor;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
using namespace apache::thrift::server;
int main(int argc, char **argv) {
boost::shared_ptr<tprotocolfactory> protocolFactory(new TBinaryProtocolFactory());
boost::shared_ptr<tprocessor> processor(new UwsgiProcessor());
boost::shared_ptr<tservertransport> serverTransport(new TServerSocket(9090));
boost::shared_ptr<ttransportfactory> transportFactory(new TBufferedTransportFactory());
TSimpleServer server(processor,
  serverTransport,
  transportFactory,
  protocolFactory);
printf("Starting the server...\n");
server.serve();
printf("done.\n");
return 0;
}

最后编译、链接和运行。

简单实现一个socket客户端,发送请求,就能得到HelloWord。

性能测试

待完善

Thrift 底层通信框架的优化和调优

待完善

本文小结

作者写本文,正是在工作中遇到了一些真实的问题,比如thrift序列化反序列化慢,thrift底层那么优秀的通信框架如何更高的加以利用呢?因此带着工作中的一些问题,开始阅读thrift的源代码。

除了本文中的一些实例,作者还做了一个小的代码库,里面就用到了本文中的方法,单独使用了thrift了网络框架,Github地址如下:https://github.com/zuocheng-liu/GI

HTTPS技术简介 – 团队分享

背景

苹果曾经发布新闻,到2017年1月1日为止,IOS上所有app只能支持https链接。因此几乎所有在IOS有app和业务的公司,都赶在截止时间升级自己的app和后端系统。

我们公司的广告系统也不例外,也需要升级。正巧2016年12月29号的团队每周技术分享,轮到了我,于是把HTTPS的相关知识简单整理了一下,分享了出来。

PPT 地址如下, 可直接进行下载,无需积分。仓促之作,粗陋难免,哪怕能帮助到1个人,作者心里也是高兴的。

http://www.docin.com/p-1827559135.html

分享思路

  • 团队中大多数人对HTTPS肯定都有所了解,只是可能对HTTPS的技术细节不是很清楚。
  • 分享最开始,先抛出几个问题,吊起大家的好奇心。
  • 其次分享HTTPS安全的原因是什么?如何使用?性能如何?
  • 最后再分享HTTPS的原理,把最难学习的东西放到最后面。
  • 增多听众感性的认识,比如实际抓包演示ssl链接的建立过程。
  • HTTPS 的加密涉及到密码学的内容,尤其是中间人攻击的部分,比较烧脑。又因为分享的时间有限,只能简单提及,不能详细讲解。

分享大纲

  • 抛出几个问题,比如为什么https安全?
  • HTTPS 简介
  • HTTPS 的优点和缺点
  • HTTPS 性能
  • HTTPS 小型网站部署实践
  • HTTPS 大型网站部署实践
  • HTTPS 协议和原理 — TLS 单向认证、双向认证 — TLS 链接建立、加解密过程
  • HTTPS 相关资源分享

分享的具体内容

具体内容在PPT中。

个人对HTTPS的几点认识

HTTPS会成为以后Web传输协议的主流吗?

个人认为会的,尤其是看到苹果对https热衷,https非常可能会成为web传输协议的主流。

我们自己为什么不颁布CA证书

  • 理论上可以,但是自己颁布的证书,被浏览器视为不信任的。而现有CA证书颁布机构都被现在主流浏览器视为信任。

HTTP 升级 HTTPS 会遇到哪些问题

  • 要支持HTTPS,就要全站部署,在HTTPS链接中,任何对http资源的引用都无效,比如图片、css、js等,所以要求引用的资源也要使用https链接。
  • 待补充

结合自己亲身项目,从HTTP升级到HTTPS的过程和方法?

自己负责的项目是广告投放系统,结合自己的经历,总结以下几点注意的地方。

注意时间点

苹果曾宣布2017年1月1号为截止时间。虽然之后宣布延期,但是很多app,已经发布只支持https的新版本。

对广告投放系统来说也要提前升级。

链接的替换

  • 只能在IOS上投放的广告资源中使用https,android不需要。因为运行在android比较旧版本sdk上的app,并不支持https。
  • 链接的替换有两种情况: 1, 直接将http:// 转换为https:// ;2,对于直接转换无效的url,则需要域名映射后进行再进行转换。

后端服务器对HTTPS的支持

  • 广告投放后的打点请求,或者落地页请求,需要回到后端Server,这些后端Server也要支持https。
  • 后端不同服务之间交互可以不使用https链接。

工程师手记-升级PNI以支持PHP7

本文内容简介

  • 简要介绍本博客写作背景和目的:升级PHP Native Interface的代码使其支持PHP7。
  • PNI 升级以支持PHP7的过程做简单介绍。
  • 对这次升级的思路和方法进行总结。
  • 思维发散,假设其他情况下应该用什么样的方法进行升级php7的扩展。

背景和目的

PNI在2016年10月之前仅支持php5系列的版本。9月份时进行升级,使其支持PHP7。

PNI是什么? 具体参考这边博文《PHP Native Interface》。PNI代码规模只有1000行左右,升级大概花费了一周时间(工作外时间),其中包含2天寻找思路、2天的代码升级,3-4天的问题排查。

这篇博客记录了一下PNI升级过程,包括自己学习PHP7扩展框架、API定义的过程,调试、压测、修复bug的过程等。

把这些东西记录下来,主要是为了总结出自己的学习方法和操作方法。写成博客后,可以更好地帮助自己反思升级过程中是否有哪些不好的地方。当然其中也会有自己的成功经验。

PNI 升级过程简述

面临哪些问题,以及有哪些不确定的地方?

  • 代码从何处改起?
  • 如何保证编译通过?
  • 功能能否保证不变,以及PNI自身定义的PHP接口是否需要发生变化。
  • 升级过程中遇到bug和陷阱怎么办?
  • 这次升级的代价有多大?

升级PNI,从哪入手?寻找突破点的过程。

用搜索引擎搜到了几篇博客,都是介绍PHP7和PHP5的 zend api 不同的网文。随意浏览了一下,感觉帮助不是特别大。

自己下载了PHP7.0的源代码,编译一下,稍微看了一下 Zend的源码(主要看Zend_API、zend_list、zend_hash),少数几个ext里的扩展的源码。

看了后收获特别大, 了解了 php7扩展编写的几个特点:

  • php7 ext 扩展编写框架结构变化小
  • API函数名称变化小,多数无变化
  • API函数参数列表变化大
  • 宏的变化小

升级的思路就有了。

升级方法?

依据发现的php7扩展代码的特点,决定以PHP5版本的PNI的代码为基础,进行升级。代码不需要做太大的变动,更不需要完全重写。

设立3个目标,并按顺序分步骤实现。 1. 升级代码,编译通过。 2. 功能验证通过。 3稳定性验证通过。

为了达到第一个目标,使编译通过,使用下面方法:

  • 参考其他extention,首先对比php7和php5扩展的框架,优先修改PNI的框架。

  • 对比PHP7 源码中的zend_API.h、zend_list.h、zend_hash.h等。

  • 要修改的函数多,可能会有API没有升级被遗忘怎么办? 所以一边升级代码,一边make,看着gcc的错误提示去一点一点改正代码。

达到第2个目标的方法,则多写功能验证的测试case。

达到第3个目标的方法,是做压力测试,观察内存和cpu的使用情况。

  • 观察变量和资源是否被及时回收,是否有内存泄漏
  • 观察cpu负荷是否过高

在升级过程中遇到过最大的一个问题是,pni中遇到了内存泄漏。为了定位内存泄漏的原因,花了非常多的时间。

  • 打日志,定位内存泄漏逻辑。
  • 定位后,添加efree、free等函数调试。
  • 添加efree逻辑无效,打印对象引用计数的个数
  • 找到原因,是存储资源类型的变量,作为另一个对象的属性时,如果对象释放,存储资源类型的成员变量不会主动释放。
  • 解决办法,在对象释放的析构函数中,添加释放资源的逻辑。

总结

总体上说,这次升级PNI比较顺利。

在做的过程中,思路其实并没有上面写的那么清楚,心中也只有个大概思路。

在刚开始时,寻找从何下手的过程中,还是走了一点弯路,但浪费的时间不多。

走的最大的弯路则是,上文说的定位和解决pni内存泄漏的过程中, 没有首先想到去观察对象或变量的引用计数的。这使pni升级的过程直接阻塞了。今后遇到内存泄漏的情况,应该首先观察zval的引用计数数目是否为0.

思维发散

下面开始做各种假设,假设我会面临不同的情况(实际并不存在),我该设计什么样的思路和方法进行PNI的升级呢?

  • 假设PHP7和PHP5扩展开发的框架和结构有大的不同,对PNI的升级,完全重写也许是好方案。

  • PNI的代码规模只有1000行,假设10W行规模以上,我在达到上文说的,第1个编译通过的目标,就不能直接用gcc的错误信息去定位没有升级的代码,不能用一边改代码一边查php7 zend api代码。应该把PHP7的代码都给熟悉了,再去升级代码。

  • 假设升级代码规模大,php7的扩展框架和代码与php5也有大不同呢?学习成本和工程量将变得非常大,这种情况怎么做呢? 1.大规模的代码,逻辑上应该做好分层和抽象。 在升级大规模代码前,应该先写一个小的PHP7扩展,拿来练手,这样学习的曲线就会平缓很多。

git-svn 使用经验

简介

git-svn可以帮助工程师使用git的管理方式,来管理svn代码库。

使用场景:

  • 历史原因,团队代码库使用svn管理,但同时又想使用git强大的分支管理的功能
  • 习惯了git且对svn不了解的工程师,但团队代码只能用svn管理的情况下,可以使用git-svn
  • 代码想从svn逐步迁移至git管理,中间过程中可以尝试使用git-svn

编译安装

安装包、依赖包列表

  • openssl # ssl
  • serf #让svn支持https
  • apr
  • apr-util
  • subversion 1.8+
  • git 1.9+
  • python 2.7+
  • scons 2.3+

其中,git-svn 工具已经被集成在了git 1.9+里,但是其同时也依赖于svn的perl接口。

以下是编译安装步骤和方法,安装路径以/home/liuzuocheng/local/为例:

编译安装apr 1.5+

官网: http://apr.apache.org/

./configure --prefix=/home/liuzuocheng/local/apr

make && make install

编译安装apr-util 1.5+

./configure --prefix=/home/liuzuocheng/local/apr-util --with-apr=/home/liuzuocheng/local/apr

make && make install

编译安装serf

官网: http://serf.apache.org/

scons APR=/home/liuzuocheng/local/apr APU=/home/liuzuocheng/local/apr-util OPENSSL=/usr/bin PREFIX=/home/liuzuocheng/local/serf

scons install

配置 LD_LIBRARY_PATH 写入/home/liuzuocheng/.bashrc

export LD_LIBRARY_PATH=/home/liuzuocheng/local/serf/lib:$LD_LIBRARY_PATH

编译安装subversion 1.8+ / 1.9+

官网: https://subversion.apache.org/

./configure --with-apr-util=/home/liuzuocheng/local/apr-util/ --with-apr=/home/liuzuocheng/local/apr/ --prefix=/home/liuzuocheng/local/subversion --with-serf=/home/liuzuocheng/local/serf

make && make install

同时安装 subversion perl 接口,在subversion源码根目录下执行:

make swig-pl

make install-swig-pl

配置 LD_LIBRARY_PATH 写入/home/liuzuocheng/.bashrc :

export LD_LIBRARY_PATH=/home/liuzuocheng/local/subversion/lib:$LD_LIBRARY_PATH

安装git

官网: https://git-scm.com/downloads

./configure --prefix=/home/liuzuocheng/local/git

make && make install

配置 LD_LIBRARY_PATH 写入/home/liuzuocheng/.bashrc :

export LD_LIBRARY_PATH=/home/liuzuocheng/local/git/lib:$LD_LIBRARY_PATH

安装问题汇总

  • 编译安装serf,执行scons 显示 no module named scons.script

解决方法,配置scons 环境变量:

export SCONS_LIB_DIR=$LOCAL/python/lib/python2.7/site-packages/scons-2.5.0-py2.7.egg/scons-2.5.0/

  • 执行git svn clone 等命令,提示错误 Can’t locate SVN/Core.pm

解决方法,将svn的perl接口软链至git库中:

ln -s /home/liuzuocheng/local/subversion/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi/SVN /home/liuzuocheng/local/git/lib/perl5/site_perl/5.8.8/SVN

ln -s /home/liuzuocheng/local/subversion/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi/auto /home/liuzuocheng/local/git/lib/perl5/site_perl/5.8.8/SVN/auto

使用方法

  • 检出代码 ,类同于 svn checkout 的功能

git svn clone https://svn.zuocheng.net/test/trunk

为了提高下载速度,避免从第一个版本开始下载,可通过添加参数-r$REVNUMBER:HEAD,检出指定版本的代码

git svn clone -r18000:HEAD https://svn.zuocheng.net/test/trunk

  • 从中心服务器的svn repository获取最新更新, 类同于 svn up 功能

git svn rebase

  • 可以在本地建立分支,操作和git相同

git branch master dev

  • 查看提交历史日志 , 类同于 svn log 功能

    git svn log #svn log的展示方式

或者

git log #git log的展示方式

  • 将本地代码同步到Svn服务器

    git-svn dcommit # 将你在本地使用git commit提交到本地代码库的所有更改逐一提交到svn库

  • 解决冲突

1 git-svn rebase , 更新版本,发现冲突文件conflict.c

2 编辑冲突文件vim conflict.c,解决冲突

3 git add conflict.c , 通知git,已经解决冲突

4 git rebase –continue , 重新更新版本,查看是否还有冲突,如果有,跳到第2步,否则继续下一步

5 git-svn dcommit, 提交更改

  • 如何切换远程分支, 类同于 svn relocate 的功能

wiki中曾提到有一个方法,但是在我的环境上不起作用 https://git.wiki.kernel.org/index.php/GitSvnSwitch

  • 其他

官方文档地址: https://git-scm.com/docs/git-svn

使用陷阱

  • 待完善

总结

Web服务压力测试工具BullBench

什么是 BullBench ? (what)

  • 一个可以对web服务进行压力测试的工具
  • 最大特点,BullBench 可以读取 nginx access log 中请求,并将其发送给web服务。
  • 也可以读取自定义文件,使用正则匹配和替换,定制请求,发送给web服务。
  • 可以同时模拟15000并发度请求(具体数值受限于系统配置)
  • 和bullbench类似的软件有 webbench、tcpcopy、jmeter等,与它们相比,bullbench有自己独特的地方,比webbench功能多,比tcpcopy操作简单,比jmeter编写测试用例的学习成本更低。

代码托管地址 : (where)

https://github.com/zuocheng-liu/BullBench

为何编写这个软件? (why)

主要原因是,我曾经在查找一个系统内存泄漏问题时,遇到了一些阻碍:

  • 线下压力测试时,要尽可能模拟线上请求
  • 理想方案是使用tcpcopy,复制线上流量,但是目前没有现成的tcpcopy环境

为了克服这些阻碍,就写一个简单的工具,先是读取nginx access log, 提取请求uri,然后模拟1000个客户端发送给Web后台。

这个工具经过再完善和变得更通用之后,bullbench诞生了。

什么时候用呢? 以及软件的局限有哪些。(when)

使用场景:

  • 压力测试时
  • 追踪bug,复现线上问题

局限:

  • 如果没有请求日志,无法使用bullbench进行压力测试
  • 只能模拟发送HTTP GET请求
  • 不能够处理 HTTP 3XX 重定向服务
  • 其他

作者联系方式 (who):

如何编译? (how )

进入源代码目录,执行make

如何使用?

执行 ./bin/bullbench -h 有详细说明和使用实例

    BoolBench 1.0

    -f <filename> 包含请求的文件,可以是nginx access log,也可以是自定义的数据文件
    -t <num>      请求文件的类型, 1 是 nginx access log, 0 其他 其他, 默认是 1
    -u <url>      请求的url前缀, 不支持 https, 比如 http://www.bullsoft.org
    -H <host>     HTTP请求头字段Host, 默认是 NULL
    -c <num>      并发请求的线程数, 默认是 1000
    -r <regex>    正则表达式,用于提取请求文件中特定的内容. 必须和参数'-t 0' 搭配。
    -o <string>   正则变量拼接后的字符串, 支持 $0-$9
    -h            显示帮助信息
实例1:  ./bullbench -f /var/log/nginx/access.log -u http://127.0.0.1:8080
实例2:  ./bullbench -f /var/log/nginx/access.log -u http://127.0.0.1:8080 -H www.bullsoft.org
实例3:  ./bullbench -f /var/log/nginx/access.log -u http://127.0.0.1:8080 -t 0 -r "[a-z]*([0-9]+)([a-z]*)" -o "/display?a=\$1&b=\$2"

一些参数(how much? how many?)

并发线程数设置多少,依据系统情况而定。

  • 在并发线程数过高的情况下,会打开非常多的连接,常会遇到打开文件数过多的错误
  • 使用root通过ulimit设置 open files的限制,使之变大

STL容器 vector list 性能对比(附带测试方法和测试数据)

最近在重构公司的一个C++模块,逻辑里有排序、过滤等操作。开发过程中,遇到下面的一些问题:

问题和猜想

  • 单链表的归并排序线性表的快速排序的时间复杂度都是 O(nlog(n)),但在实际情况中哪个更快呢?需要测试一下。
  • 猜想的发散,stl vector 和 list(双向链表)的迭代器遍历、顺序插入、clear操作, 时间复杂度好像都是相同的,但谁更快呢?
  • 看stl代码去研究vector和list的实现,并不能得到我们最终想要的结果,测试数据会更直观。

测试总结

先写测试总结,测试方法、测试结果都在下面。

  • 迭代器遍历,list 比vector 稍块,使用for_each 比使用for更快。
  • 顺序插入, vector比list 约快3倍
  • clear操作,vector 几乎不耗时,list 要耗费好多时间,vector比list至少快1千倍以上
  • 排序, vector 大约比list 快2倍。

结合项目,对比vector和list的性能

  • 在我重构前,模块使用的是vector存储数据,排序使用的是快排(大于16小于堆),过滤则是用临时数据vector转存。
  • 在我重构后,使用list存储,排序使用list的归并排序,过滤则是直接使用list的erase操作。
  • 重构前,耗时4000微秒,重构后1500微秒,时间减少60%。
  • list除了删除操作和遍历操作,排序和插入都比vector慢,但是我使用list后,却提升了性能。原因可能有2个: 1.单个元素数据结构大,vector移动这些元素代价较大,而list移动这些元素反而代价很小;2.去掉了中间临时存储,数据的转存代价也比较大。
  • 使用list后,模块的可扩展性也变得更好了。

这次项目最大的收获就是,如果有过滤操作,优选使用list。vector的缺点如下,1.vector快速排序需要移动元素,如果元素占据空间大,移动代价也非常大。2.vector过滤需要借助中间临时存储,直接erase的代价更大。

测试方法

  • 随机生成10万数据量,由vector和list结构的变量分别存储
  • 对vector 和 list 的数据分别做如下, 1)迭代器遍历 2)顺序插入 3) clear操作 4)排序
  • 记录每种操作消耗的时间
  • 多次测试,记录典型的数据结果

测试结果

直接列出测试结果,单位是微秒

vector for_each : 3263
list for_each : 2556
vector traverse : 4783
vector num traverse : 666
list traverse : 3078
vector push_back : 8136
list push_back : 24581
list delete : 7428
vector push_back : 7329
list push_back : 22968
vector clear : 0
list clear : 13079
vector sort : 89512
list sort : 146529

(附)测试程序

Github 托管地址,保持更新:https://github.com/zuocheng-liu/code-samples/blob/master/linux-c/stl/vector_list_performance_test.cpp

#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include <list>
#include <vector>
#include <iostream>

#define MAX_NUM 100000

using namespace std;

timeval tv;
uint64_t timer;


bool NumCmp(uint32_t a, uint32_t b) { return (a > b); }

void startTimer() {
    gettimeofday(&tv,NULL);
    timer = 1000000 * tv.tv_sec + tv.tv_usec;
}

uint64_t stopTimer() {
    gettimeofday(&tv,NULL);
    timer = 1000000 * tv.tv_sec + tv.tv_usec - timer;
    return timer;
}

int main() {
    vector<uint32_t> v;
    vector<uint32_t> v2;
    list<uint32_t> l;
    list<uint32_t> l2;
    for (int i = 0; i< MAX_NUM; ++i) {
        srand((int)time(0)  * i * i + 1);
        int num = rand();
        v.push_back(num);
        l.push_back(num);
    }

    // compare oper traverse
    startTimer();   
    for (vector<uint32_t>::iterator iter = v.begin(); iter != v.end(); ++ iter) {
        *iter;
    }
    cout<<"vector\t traverse\t :\t"<< stopTimer() << endl;  

    startTimer();   
    for (int i = 0 ; i < MAX_NUM; ++ i) {
        //v[i];
    }
    cout<<"vector\t num traverse\t :\t"<< stopTimer() << endl;  


    startTimer();   
    for (list<uint32_t>::iterator iter = l.begin(); iter != l.end(); ++ iter) {
    }
    cout<<"list\t traverse\t :\t"<< stopTimer() << endl;  


    // compare oper push_back   
    startTimer();   
    for (vector<uint32_t>::iterator iter = v.begin(); iter != v.end(); ++ iter) {
        v2.push_back(*iter);
    }
    cout<<"vector\t push_back\t :\t"<< stopTimer() << endl;  

    startTimer();   
    for (list<uint32_t>::iterator iter = l.begin(); iter != l.end(); ++ iter) {
        l2.push_back(*iter);
    }
    cout<<"list\t push_back\t :\t"<< stopTimer() << endl;  

    // compare oper delete
    startTimer();
    v2.clear();
    for (vector<uint32_t>::iterator iter = v2.begin(); iter != v2.end(); ++ iter) {
    //    v2.erase(iter);
    }
    //cout<<"vector\t delete\t :\t"<< stopTimer() << endl;  

    startTimer();   
    for (list<uint32_t>::iterator iter = l2.begin(); iter != l2.end(); ++ iter) {
        iter = l2.erase(iter);
    }
    cout<<"list\t delete\t :\t"<< stopTimer() << endl;  

    // compare oper push_back   
    startTimer();   
    for (vector<uint32_t>::iterator iter = v.begin(); iter != v.end(); ++ iter) {
        v2.push_back(*iter);
    }
    cout<<"vector\t push_back\t :\t"<< stopTimer() << endl;  

    startTimer();   
    for (list<uint32_t>::iterator iter = l.begin(); iter != l.end(); ++ iter) {
        l2.push_back(*iter);
    }
    cout<<"list\t push_back\t :\t"<< stopTimer() << endl;  


    // compare oper clear
    startTimer();   
    v2.clear();
    cout<<"vector\t clear      \t:\t"<< stopTimer() << endl;  

    startTimer();   
    l2.clear();
    cout<<"list\t clear     \t :\t"<< stopTimer() << endl;  

    // compare oper sort
    startTimer();   
    std::sort(v.begin(), v.end(), NumCmp);
    cout<<"vector\t sort    \t :\t"<< stopTimer() << endl;  

    startTimer();   
    l.sort(NumCmp);
    cout<<"list\t sort    \t :\t"<< stopTimer() << endl;  

    return 0;
}

Google C++ 代码风格示例

最近项目中决定使用Google的C++代码规范,然后写了两个代码示例文件(.h 和 .cc),这两个文件中的代码都是用Google的C++代码规范。

写示例代码的好处

  • 减少查看文档时间,降低学习成本
  • 帮助程序员快速了解Google的代码风格的概貌
  • 用于参考和模仿,帮助快速上手

代码托管地址

下面地址保持持续更新:

https://github.com/zuocheng-liu/code-samples/tree/master/code_style

仓促之作,错误和疏忽不可避免,但保证后续不断更新。

初版代码示例

建议点击上面的链接,获取最新版。编辑器的原因,缩进的显示可能有问题。

// Copyright (c) 2016, Zuocheng.net
//
// Author: Zuocheng Liu
//
// Lisence: GPL
//
// File: code_samples/code_style/google_code_style.h 
// (文件名要全部小写,可以包含下划线(_)或短线(-))
// C++文件以.cc 结尾,头文件以.h 结尾。
//
// Google C++ code Style 代码示例 (简体中文版)
//
// *注释  使用//或/* */,但要统一。
// *注释  每一个文件版权许可及作者信息后,对文件内容进行注释说明
// -------------------------------------------------------------最多不能超过80行
#ifndef CODE_SAMPLES_CODE_STYLE_GOOGLE_CODE_STYLE_
#define CODE_SAMPLES_CODE_STYLE_GOOGLE_CODE_STYLE_
// 命名空间的名称是全小写的,其命名基于项目名称和目录结构
// 不要声明命名空间std 下的任何内容
namespace code_samples {
namespace code_style {

// **变量命名**
// 尽可能给出描述性名称,不要节约空间
// 变量名一律小写,单词间以下划线相连
// 少用全用变量,可以以g_与局部变量区分
int g_my_exciting_global_variable;

// 只有在描述数据时用struct ,其他情况都用class
typedef struct CodeStyle {
    uint32_t type;
} CodeStyle;

// **类注释** 
// 每个类的定义要附着描述类的功能和用法的注释
//
// GoogleCodeStyle , 通过代码示例,展现google c++ code style
//
// (按需注明synchronization assumptions,是否线程安全)
class GoogleCodeStyle {
public :

  // ** 声明次序 **
  // - typedefs 和enums;
  // - 常量;
  // - 构造函数;
  // - 析构函数;
  // - 成员函数,含静态成员函数;
  // - 数据成员,含静态数据成员。

  // **类型命名**
  // 每个单词以大写字母开头,不包含下划线
  // 所有类型命名——类、结构体、类型定义(typedef)、枚举——使用相同约定
  typedef enum StyleType {
      // 枚举值应全部大写,单词间以下划线相连
      GOOGLE_CODE_STYLE = 0,
      K_AND_R,
      POCO
  } StyleType;

  // 常量命名,在名称前加k
  const int kDaysInAWeek = 7;   

  GoogleCodeStyle() {}
  ~GoogleCodeStyle() {}
  // **函数注释**
  // 函数声明处注释描述函数功能,定义处描述函数实现.
  // - inputs(输入)及outputs(输出);
  // - 对类成员函数而言:函数调用期间对象是否需要保持引用参数,是否会释放这些参数;
  // - 如果函数分配了空间,需要由调用者释放;
  // - 参数是否可以为NULL;
  // - 是否存在函数使用的性能隐忧(performance implications);
  // - 如果函数是可重入的(re-entrant),其同步前提(synchronization assumptions)
  // 举例 :
  // 初始化函数
  ~Init() {}

  // 普通函数名以大写字母开头,每个单词首字母大写,没有下划线
  // 
  uint32_t MyExcitingMethod(CodeStype &code_style, char *output);
  // 存取函数要与存取的变量名匹配
  inline int num_entries() const { return num_entries_; }
  inline void set_num_entries(int num_entries) { num_entries_ = num_entries; }

  // 类的成员变量以下划线结尾
  int num_completed_connections_;

protected :

private :
  // **类数据成员**
  // 每个类数据成员(也叫实例变量或成员变量)应注释说明用途。
  // 如果变量可以接受NULL 或-1, 等警戒值(sentinel values),须说明之
  int num_entries_;

}; // class GoogleCodeStyle

}} // namespace code_samples::code_style 

#endif //CODE_SAMPLES_CODE_STYLE_GOOGLE_CODE_STYLE

工程师手记-将Memcached内存管理机制移植至Redis

Idea 的提出

  • Redis 有其高效的异步网络框架
  • Memcached 有其高效的内存管理机制

将这两者结合在一起后,会如何呢?开始试验将Memcached内存管理机制移植至Redis。

本篇博客的姊妹篇链接: 《工程师手记-将Redis异步网络框架移植至Memcached》

调研和选型

Redis内存管理的几个缺点:

  • 使用tcmalloc 或者 jmalloc 库,这两个库封装较重,内部特性也较多。
  • tcmalloc 适合小空间分配,稍大的空间分配会有瓶颈。
  • Redis 主要是单线程运行(只在后台任务cache持久化功能处又启动了新线程), tcmalloc 和 jmalloc 有保证线程安全,但对redis来说是不必要的功能。尤其是jmalloc,为线程安全做了很重的设计。

软件选型

  • 并不是把 Memcached 的内存管理直接替换redis的内存分配,而是使用ae-memcached的内存分配方式。
  • ae-memcached 的内存分配和 Memcached在原理上毫无不同,仅是从软件架构上对其进行重构和优化。具体参考:《AE-Memcached 优化记录》
  • 选择 Redis 2.8.24 作为移植受体

Redis代码修改和编译 / 移植方案

  • 从ae-memcached中拿出mem_cache / slab 两个类,直接移植到Redis src 目录中
  • 新建两个文件 mc_malloc.h mc_malloc.c,封装mem_cache,让其提供类似 malloc、 alloc、realloc、free的接口
  • 修改 zmalloc.c zmalloc.h 这两个文件,让其支持mc_malloc
  • 修改 Makefile ,默认MALLOC 使用 mc_malloc
  • 修改bio.c 文件,把zmalloc 和 zfree用 libc的 malloc 和 free 代替,这么做主要考虑到线程安全
  • 编译、运行

代码托管地址

给新的redis起了一个新名字mc-redis,源代码托管于Github上:

https://github.com/zuocheng-liu/mc-redis

性能测试实验

硬件

  • Redis-server 服务端 GenuineIntel 6 Common KVM processor 6 核 2.0GHZ 4G 内存
  • redis-benchmark 和服务端部署在同一台服务器上

测试方法

  • 分别运行原本Redis 和 mc-redis, 分别作为实验和对照,参数为 redis-server –port 7777
  • 启动Redis,运行redis-benchmark 测试三次。重复前面步骤,Redis共重启3次,redis-benchmark共测试9次。
  • mc-redis 的测试也使用上面方法
  • 测试命令 ./redis-benchmark -h 127.0.0.1 -p 7778 -q -d 100
  • 只观察set / get 命令的并发度

测试结果

启动一次redis,做了三组实验,数据如下:

  • mc-redis GET 62972.29 / 58275.06 / 55897.15 (requests per second)
  • redis GET 47281.32 / 62034.74 / 51759.83 (requests per second)
  • mc-redis SET 64808.82 / 59031.88 / 56915.20 (requests per second)
  • redis SET 51733.06 / 53676.86 / 56947.61 (requests per second)

结论

在刚启动时(预热阶段),mc-redis 的 set 和 get 操作,比原版redis 的并发处理能力高大约有 15%-20%。 但是稳定运行后, mc-redis 和 原版redis,性能相差较小。

AE-Memcached 优化记录

优化背景和目的

  • 学习Memcached 代码
  • 将 Memcached 的代码成为自己的技术积累
  • 优化Memcache 代码,提高自己系统分析能力

源代码托管于Github上:

https://github.com/zuocheng-liu/ae-memcached

性能优化

网络模型的优化

  • 网络IO多路复用 + 单线程

  • 将 Redis 异步库 移植至 Memcached

优化动态申请内存机制

  • 使用预分配,减小系统调用 malloc、realloc、free的次数,主要出现在新建/关闭链接时,会有较多的系统调用

部分小的函数使用宏代替

优化Memcache协议命令的解析

  • 调整各个命令的解析顺序,把get 和 set 命令放到最前面

软件架构优化

软件架构优化,保证关键代码性能不变

使用宏加强代码复用

  • 重构verbose日志
  • 重构网络库
  • 重构slab

命令模式重构 Memcache 协议

  • 创建command_service类,统一管理命令的解析、处理

更深层次的抽象

将 stats 、 settings 、 logger 和全局资源进行抽象

解耦

  • 将各个模块接口化,减少模块间耦合,尤其是 slab item memcached之间的耦合
  • 依赖注入原则,增强各个模块的复用,其中mem_cache模块 settings等可以形成框架。
  • logger
  • command service

工程师手记-将Redis异步网络框架移植至Memcached

Idea 的提出

  • Redis 有其高效的异步网络框架
  • Memcached 有其高效的内存管理机制

将这两者结合在一起后,会如何呢?

本篇博客的姊妹篇链接: 《工程师手记-将Memcached内存管理机制移植至Redis》

调研和选型

Memcached 的几个缺点:

  • 使用封装较多的 libevent 异步库
  • Memcached 1.2.2 版本后,开始使用多线程,而多线程上下文切换、互斥锁的竞争带来了一定的性能开销
  • 每次新建tcp 连接都进行系统调用(malloc)申请空间

对Memcached 的一项性能测试

实验验证,多线程不会对Memcached带来性能的提高

参考链接:

对Memcached 1.2.2 的一次基准测试实验

软件选型

  • 从 Redis 3.2.0 (截止此文最新稳定版) 版本中选择 libae、 libanet (ae.h、ae.c、anet.h、anet.c 四个文件)
  • 选择 Memcached 1.2.0 作为移植受体

选取这两个版本的主要原因是,作为Redis 和 memcached 的早期版本,特性较少,代码复杂度低,适合进行初步实验移植。

为什么不直接使用epoll呢? 因为除了异步框架之外,还需要定时器的功能,而redis的异步库中已经有现成的实现。

代码托管地址

给新的memcached起了一个新名字ae-memcached,源代码托管于Github上:

https://github.com/zuocheng-liu/ae-memcached

性能测试实验

硬件

  • Memcached 服务端 GenuineIntel 6 Common KVM processor 6 核 2.0GHZ 4G 内存
  • Memcache 客户端 和服务端相同的另外一台服务器

测试方法

  • 分别运行Memcached 和 ae-memcached, 参数为 memcached -m 1024M -t 6 , 其中原本memcached运行6个线程,而ae-memcached 是单线程
  • 使用memslap 1.0.2, 测试10W次请求,100的并发度的情况下,memcached的处理时间
  • 测试命令 ./memslap –servers=test-server:11211 –concurrency=100 –execute-number=1000 –tcp-nodelay –non-blocking

测试结果

  • ae-memcached 6.709 / 6.878 / 7.362 / 7.196 (seconds) 平均7秒
  • 原版 memcached 5.079 / 5.043 / 5.069 / 5.206 (seconds) 平均5秒多

结论

原版Memcache 处理10W条数据的时间要比 ae-memcached少很多。多线程的确会给Memcached带来性能的提升.我们前面的假想并不正确。

对 Memcached 的持续优化

本文作者不仅只把Redis的异步库移植至Memcached,还对Memcached进行了持续的优化,详细请阅下面链接:

http://it.zuocheng.net/ae-memcached-optimization-zh

对Memcached 1.2.2 的一次基准测试实验

猜想假设

memcached这种系统的瓶颈在网络,不在计算和存储,而使用多线程,只能优化计算。用了多线程,反而会有上下文切换的代价。

测试概要

测试目标

让 Memcached 分别采用 单线程模型 和 多线程模型, 并对其性能进行对比

测试方法

测试软件

实验组 Memcached 1.2.2 (IO 多路复用 + 线程)

对照组 Memcached 1.2.2 (IO 多路复用 + 线程)

Memcached 1.2.2 是 Memcached 众多版本中的一个临界版本,在Memcached 1.2.2 之前的版本采用 “IO 多路复用 + 单线程” 模型, 在Memcached 1.2.2 之后的版本则采用 “IO 多路复用 + 多线程” 模型。

测试工具

memslap 1.0

测试环境

  • 软件 Ubuntu 15.10
  • 硬件 Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz 4核 2G 内存

测试步骤

搭建

  • 编译 memcached

先编译单线程的 memcached 版本。(正常编译,作为实验组)

再编译支持多线程 memcached 的版本(作为对照组), 编译方法如下:

修改configure.ac文件,将下面一行

AC_DEFINE([USE_THREADS],,[Define this if you want to use pthreads])

改为

AC_DEFINE([USE_THREADS],1,[Define this if you want to use pthreads])

执行 sh autogen.sh , 然后执行 ./configure --enable-threads ,最后执行 make

  • 分别运行 实现组和对照组的memcached, 然后对其进行基准测试

命令示例: memcslap --servers=localhost:11211 --concurrency=5000 --execute-number=10 --tcp-nodelay --non-blocking --flush

–concurrency=5000 –execute-number=10 这两个数据多次选型,反复测试。

实验结果

选取典型实验结果数据

  • 实验组 (单线程)

实验组 (单线程)

  • 对照组 (多线程)

enter image description here

实验推导结论

从上面的实验结果看,单线程的性能要比多线程性能略高。

使用Mini PC搭建个人用的NAS/服务器/离线下载

软硬件准备

硬件

  • Mini-ITX机箱
  • Mini-ITX 主板
  • 笔记本内存条 4G
  • Msata 固态硬盘 60G
  • 机械硬盘 500G
  • 拥有公网独立IP的服务器

软件

  • Windows 7
  • Ubuntu Server
  • VirtualBox
  • open-sshd
  • autossh

系统搭建

组装硬件

安装系统

Windows 7

  • 安装windows7是,出现提示框windows安装程序无法将windows配置为在此计算机的硬件上运行

不要关闭对话框,按下shift和f10键。弹出一个cmd,输入cd oobe 回车,再输入msoobe.exe回车,会弹出另一个界面,按照提示进行即可,完成后回到错误弹窗界面单击确定,系统重启。

穿透内网

SSH 反向代理

  • 建立 MiniPC 机器到 Server 的反向代理【MiniPC 机器上操作】

ssh -fCNR 10022:localhost:22 work@server.zuocheng.net

10022 为 server.zuocheng.net 机器上端口,用来与 MiniPC 机器上的22端口绑定。

  • 建立 server.zuocheng.net 机器上的正向代理,用作本地转发。做这一步是因为绑定后的 端口只支持本地访问【server.zuocheng.net 机器上操作】

ssh -fCNL "*:8022:localhost:10022' localhost

20022 为本地转发端口,用以和外网通信,并将数据转发到 10022,实现可以从其他机器访问。其中的*表示接受来自任意机器的访问。

  • 现在在 C 机器上可以通过 B 机器 ssh 到 A 机器

ssh -p 8022 work@server.zuocheng.net

SSH 反向代理经常掉线问题解决

  • Mini Server 上生成RSA公私密钥

ssh-keygen

  • 把公钥推送到 远程主机上

ssh-copy-id -i ~/.ssh/id_rsa.pub work@server.zuocheng.net

  • 创建代理通道,并且自动监控通道链接

autossh -M 20021 -NR 20022:localhost:22 work@server.zuocheng.net

  • 设置为开机启动

/bin/su -c '/usr/bin/autossh -M 20021 -NR 20022:localhost:22 work@server.zuocheng.net' - work 放入 /etc/inid.d/autossh

Git 使用经验总结

常用命令

按使用频度排序

  • git pull
  • git log
  • git diff
  • git commit
  • git commit –amend
  • git review
  • git clone
  • git push
  • git reset –hard/soft
  • git checkout
  • git fetch
  • git merge

git 命令非常多,但常用的只有以上几个

常用配置

  • 用户名和密码

$ git config --global user.name "liuzuocheng"

$ git config --global user.email zuocheng.liu@gmail.com

  • 默认编辑器

$ git config --global core.editor vim

  • 配置比较工具

git config --global merge.tool vimdiff

git config --global diff.tool vimdiff

git config --global difftool.prompt false

git config --global alias.d difftool

  • 或者直接更改配置文件 ~/.gitconfig,添加手工添加配置

常用经验

  • 将远程主干合并到本地分支

在代码上线前,这一步非常重要

git fetch && git rebase origin/master

git fetch --all && git merge origin master

  • 解决版本冲突

暂存本地修改

$ git stash

拉取版本库中最新版本

$ git pull

将本地修改与版本库中最新版本合并

$ git stash pop stash@{0}

解决冲突,使用下面的工具会非常方便

$ git d

  • 将源码导出tar包

git对应的功能是归档

mkdir ../working

git archive master | tar -x -C ../working

Git 高级功能

submodule

git submodule add 仓库地址 路径

git submodule update --init

git submodule update

fork后如何同步源的新更新 ?

  • 首先要先确定一下是否建立了主repo的远程源

git remote -v

  • 如果里面只能看到你自己的两个源(fetch 和 push),那就需要添加主repo的源:

git remote add upstream URL

  • 查看主repo的远程源

git remote -v

  • 拉取主repo源的代码

git fetch upstream

  • 合并

git merge upstream/master

  • 提交

git push

git 项目打包导出

示例, 项目中有为1.0的tag

git archive 1.0 | bzip2 > v1.0.tar.bz2

与 Gerrit 配合使用

提交代码审核 git push origin HEAD:refs/for/mybranch

敏捷软件开发 – 总结

敏捷软件开发简介

  • 一套适应变化,立足于变化的软件开发方法
  • 一套被证明过的目前最有效的软件开发方法

Scrum、 XP 和 敏捷的关系

  • 敏捷是一种指导思想或开发方式(价值观)
  • Scrum和XP是敏捷开发的具体实现方法 (方法论)

敏捷和瀑布

瀑布,主要继承自大工业时代遗留下来的思想,关注流程和契约

敏捷,因互联网兴起一种软件开发思想,应变化而生,关注反馈和沟通

互联网软件开发的特点

  • 需求总是在不断变化

  • 不清楚需求最终是什么,一切需求都来自于未经验证的假设

  • 对软件系统和团队的要求,必须快速适应变化

  • 互联网时代的产品需求来源已经发生变化

  • 传统IT的软件需求来自于客户,软件开发商必须基于合约按期向客户交割软件产品。(契约)

  • 互联网应用软件,以服务提供给用户/客户,需求来源于互联网企业对用户/客户体验或服务自身的挖掘。(变化、不确定性)

互联网开发的问题

  • 什么样规模的团队最能保证效率和安全?

小团队

  • 什么时候才能确定最终的技术方案

编程的时候

软件开发方法的演化过程 (敏捷开发的推导过程)

  • 小作坊式开发

  • 传统瀑布模式(螺旋模式等等一系列模式)

  • 迭代式开发

  • 敏捷开发

Scrum 流程简介

参考维基百科

感性体验

对敏捷的反思

  • A/B 实验

需求是不确定的、易变的,原因之一是因为需求往往来自于假设,对假设的验证可以进行A/B实验。

  • 灰度发布

保证安全,减小风险。

  • 技术驱动

技术驱动能最大的激发团队成员的积极性。

问题

  • 敏捷适合小团队?

并非如此,按照敏捷的思想,团队要保持精简。若原有团队是大团队,则拆分为小团队。

安全的C/C++网络应用的开发流程

本文概要

简要介绍C/C++ 网络应用系统的特点、应用场景,简述适用于C/C++ 网络应用的开发流程。

C/C++ 网络应用系统特点

  • 高性能
  • 高吞吐量
  • 节省内存
  • 开发、测试用时多、开发效率慢
  • 调式成本特别高

C/C++ 网络系统的应用场景

  • 数据接口服务
  • 计算密集型应用,比如搜索引擎、图像处理、统计学习等
  • 特殊领域应用,只能由C/C++ 完成

开发流程的目标

由于上面提到的C/C++ 的特点和应用场景,因此开发流程要达到下面的目标:

  • 安全
  • 控制bug
  • 保证收益

开发流程

需求

因为C/C++ 系统的自身特点,在网络应用中,它们常常担当后端系统、基础服务等。在整个产品的系统架构中,C/C++系统和业务系统常常被分离开来。C/C++ 系统不直接响应来自产品的需求,只响应业务系统提出的技术升级或改造。

设计

  • 日志一定全面,Debug、info、warning、error、fetal
  • 使用参数开关来控制新加的特性,对新的特性一定要追加日志
  • 单例测试覆盖率要尽可能高
  • 对输入的安全检查一定要做到位
  • 可扩展性一定要高

编码

  • C/C++ 代码要遵守代码规范和代码标准
  • C/C++ 代码书写尽量遵从ANSI/ISO 标准
  • 不要使用生僻的语法
  • 代码可读性和可维护性一定要高
  • 一定要书写单例测试

测试

  • A/B 测试

测试环境的搭建

必须多套测试环境

  • 普通测试环境
  • 压力测试环境
  • 沙箱环境
  • 线上小流量环境

必须在每一种测试环境都测试完成后,才能发布到生产环境或在生产环境上推全流量

发布

灰度发布

工程师手记-从PHP工程师到C/C++工程师的转变

本文分享一下,博主从PHP工程师变为C/C++工程师后的一些真实感受。

两句话写在前面

  • 编程语言只是工具
  • 工程师应该用正确的工具干活儿

但从我现在的经历和感受看,两句话真的不靠谱。

简单介绍转变的过程

2012年大学毕业,进入第一家公司,担任PHPer。之后两三年用的编程语言一直是PHP,即使14年中间换过工作,依然写PHP,做的都是公司后台的业务系统。

2015年,语言转为C/C++,负责公司商业广告投放系统检索端的开发。

技术方向的不同

  • PHP 关注业务的实现
  • C/C++ 关注底层

PHP程序员和C/C++程序员日常技术关注点

  • PHP 程序员关注对业务的抽象、关注系统的可扩展性,关注如何设计业务架构,使用什么样的设计模式等等

  • C/C++ 程序员更加关注系统的性能,系统的可伸缩性,关注使用什么样的工具提高性能,关注网络IO模型,数据结构和算法等等。

安全性

  • PHP 程序员关注的安全主要是避免系统漏洞和防止攻击,sql注入、XSS攻击、文件注入攻击等等, 系统安全比较重要。

  • C/C++ 程序员更关注逻辑的安全性, 比如输入输出的边界,异常处理等等,系统稳定最为重要。

PHPer 和 C/C++ 工程师何时相互欣赏?

  • PHPer 看 C/C++, C/C++ 好高级啊,底层实现都知道的那么清楚。我们了解就没有那么深啊。

  • C/C++工程师看PHP, 你们做的那么多应用好高级啊,那么复杂的金融系统,你们怎么就做出来了,如果用C来写,怎么才能写出来呀!

PHPer 和 C/C++ 工程师何时相互鄙视?

  • PHPer 看 C/C++, 你们代码怎么都是面向过程的,可读性那么低,多不好维护,抽象和复用都搞不好,软件架构乱成啥了,软件稍微大些,你们就要拆成服务。

  • C++ 工程师 看 PHP, 你们设计这么多类,代码那么啰嗦,难道不是不过度设计吗?你们写的代码性能能行吗?明明10台服务器就能满足的PV,竟然要200台!

同时有了PHP 和 C/C++ 背景之后,我的工作变成什么样子了?(以下故事基于事实,但与事实略有差异)

  • 老板:“那个项目是PHP的,你来接吧。” 我:“NO”。 老板:“只能你来接啊,我们辛亏有你啊,组里只有你会php啊,您一定要接啊!” 我:“OK。”(幽怨+无奈)

  • 猎头:“我这有PHP职位,薪水丰厚”。 我:“俺转C++了”。 猎头:“C++需求少啊,工作不好找啊”。 我:“俺还要写c++”。 猎头:“看那个PHP职位,背景待遇好”。我:“……”。

  • 某一线公司HR :“你C++履历太短,暂时还不适合我们的职位。” 我:”……”(心里流出两行泪)。HR:”但我们公司有PHP职位啊,你来呀”。我:“俺不去了……”(秋风吹皱偶滴心)。

博客写完了, 现在感觉,开头那两句话,说的无比正确。

开源消息队列Kestrel的安装、部署和运行

本文测试用的kestrel版本是2.4.1

下载

  • 下载官网已编译的包

    wget http://robey.github.com/kestrel/download/kestrel-2.4.1.zip
    unzip kestrel-2.4.1.zip

  • 下载源代码自行编译

环境配置

Java 的运行环境配置为1.60版本,以保持兼容

修改配置文件

修改 config/development.scala 和 config/production.scala,一个是开发环境,一个生产环境的配置

监听的IP和端口

队列持久化路径

queuePath = "/home/liuzuocheng/var/spool/kestrel"

日志目录

loggers = new LoggerConfig { 
    level = Level.INFO handlers = new FileHandlerConfig { 
        filename = "/home/liuzuocheng/var/log/kestrel/kestrel.log" roll = Policy.Never 
    }  
}

修改启动命令

生产环境启动脚本scripts/kestrel,将APP_HOME目录修改为实际目录地址

APP_HOME="/home/liuzuocheng/local/kestrel"

运行kestrel

调试环境启动脚本

sh script/devel.sh

生产环境后台启动kestrel服务,使用如下命令

nohup .script/devel.sh &

Rest – 架构风格与基于网络的软件架构设计 – 总结

简介

本文内容是博主的一篇读书笔记。读的书是Roy Thomas Fielding的博士论文 Architectrual Styles and the Design of Network-based Software Architectures (《架构风格与基于网络的软件架构设计》)。

为什么想读这篇论文?主要因为REST架构风格和Restful的框架,在Web系统设计中越来越流行。 Rest这个词,在软件研发人员之间,尤其是博主周围同事、同学之间,不断被提起,可见其影响之大。学习追踪了一下Rest,知道Rest架构风格最早由Fielding在他的博士论文中提出,于是就开始读这篇论文。

本博客可以成为一篇论文的导读,想阅读但未读 Fielding博士这篇论文的读者,希望本博客能给您帮助。

阅读此论文前的一点准备和对论文的一点小总结

Fielding博士的论文写成的时间在2000年,猜测Rest的概念形成时间比2000年还早。

而本博客写于2015年末,与论文写成已有十几年间隔,互联网领域里,无论是行业还是技术,发展太快,好多东西估计也让Fielding难以预料。2000年时,有很多概念并不明朗,好多软件还没有出现,因此论文中有一些东西,让现代的人看很具体,但论文中却写的并不清楚。

读Fielding博士的论文最好立足于2000年的环境下,从现在(2015年)的角度看论文里的内容,论文里有超前也有滞后的内容,有技术领域里早已被人熟知的概念,也有经典不变的原理和原则。

所以,最好用选择性的并略带批判的观点去看这篇论文,从中找寻具有价值的内容,并鉴别已经过时的内容。

论文目录及各章内容简单总结

第一章 软件架构

软件架构中,被用到的一些概念、术语。

第二章 基于网络应用的架构

话题范围缩小,关注基于网络应用的架构。谈及7个方面的架构属性,可作为评估一种架构优劣的7个维度。

第三章 基于网络的架构风格

话题范围进一步缩小,聚焦基于网络的架构风格。列举了很多种架构风格,并用上一章的提到的7个维度,对它们进行简单评估。

第四章 设计Web架构:问题与洞察力

提出Web系统的架构风格需求,比如需要满足容量大、可伸缩性强。(后面提出Rest风格正是满足了这些需求。)

第五章 表述性状态转移

博主认为,这是这篇论文的核心内容。

主要讲了Rest风格的具体内容,从5个方面约束和规范Web架构。

在描述Rest风格的时候,论文用了一个很特别的例子,这个例子是讲述了一个架构,从无到有,再从简单到复杂演化的过程。过程中,这个架构每一步变化都使用Rest风格来约束它,让架构表现出了很好的可伸缩性。论文中称这是Rest风格的推导过程。

Rest 风格的约束

  • 无状态, 请求的上下文无关
  • 缓存约束,缓存约束于客户端
  • 统一接口,Rest 风格核心特征
  • 分层系统,添加中间层和中间层约束。(类似于现在的Proxy 和 Proxy+缓存,比如nginx、nginx+cache、 msyql proxy、file proxy)
  • 按需代码。按照现在的理解则是,让B/S架构,替换掉以前的C/S架构。

第六章 经验与评估

这一章内容有些杂乱。

论文具有局限性的内容

  • Cookie 违反了Rest (6.3.4.2)
  • 会话状态全部保存在客户端
  • 缓存约束于客户端, 可靠性降低
  • 特别适合分布式超媒体系统

问题总结

  • Web应用程序的无状态性

    http协议作为技术背景的web应用程序请求——应答模式是无状态的

    web应用是有状态的,session、cookies等状态机制+其他辅助的机制(违反Rest)

  • 经常被谈及的Rest风格的URL是Rest定义的吗?

Rest风格是抽象的,而经常被谈及Rest风格的URL,只是遵从Rest风格的一种具体实现,Feilding博士的这篇论文里并没有具体去谈。

  • Rest 风格推荐HTTP中的GET,POST,PUT,DELETE对资源进行访问和控制是吗

此篇论文里并没有具体去写这个问题

后记

博主第一次听人提起 Rest 是 2012年时,当时博主刚大学毕业参加工作,在公司里听前辈工程师们谈起Rest。可惜博主3年后才开始主动去学习有关Rest的内容。

虽然读完了Feilding博士的论文,但是感觉Rest的内容好像不仅有这些,可能是其他人的补充,再去学习下。

2015年12月14日

判断平面上点和不规则多边形位置关系算法

本文列举5中计算点和不规则多边形位置关系的算法。

计算机算法实现方案,最好的是弧长法。

射线法 (铅垂线法、水平线法)

射线法是使用最广泛的算法,这是由于相比较其他算法而言,它不但可以正确使用在凹多边形上,而且不需要考虑精度误差问题。

该算法思想是从点出发向右水平做一条射线,计算该射线与多边形的边的相交点个数,当点不在多边形边上时,如果是奇数,那么点就一定在多边形内部,否则,在外部。

其中铅垂线法和水平线法是射线法的两个具体算法,更容易实现,在坐标计算中更方便。

面积法

面积法的思想是如果点在多边形内部或者边上,那么点与多边形所有边组成的三角形面积和等于多边形面积。多边形的面积公式可以用叉积计算。不过计算面积是会有一定误差的,需要设置精度的误差范围。

点线判断法

对于多边形,如果一个点它的所有边的左边,那么这个点一定在多边形内部。利用叉积正好可以判断点与给定边的关系,即点是在边的左边右边还是边上。

转角法

以被测点O为坐标原点,计算其与所有相邻两个多边形顶点点P[i]之间的角度和,所有角度相加后得到360度,即为点在多边形之内,若得到180度,则在多边形边上,0度则在多边形外。

弧长法

弧长法是改进后的转角法,解决了传统转角法的精度问题。算法思想是,以被测点O为坐标原点,将平面划分为4个象限,对每个多边形顶点P[i],计算其所在的象限,然后顺序访问多边形的各个顶点P[i],分析P[i]和P[i+1],有下列三种情况:

  • P[i+1]在P[i]的下一象限。此时弧长和加π/2;

  • P[i+1]在P[i]的上一象限。此时弧长和减π/2;

  • P[i+1]在Pi的相对象限。利用叉积f=y[i+1]x[i]-x[i+1]y[i]计算Op[i]与Op[i+1]的关系,若f=0,Op[i]与Op[i+1]共线,点在多边形边上;若f<0,Op[i+1]在Op[i]逆时针方向,弧长和减π;若f>0,Op[i+1]在Op[i]顺时针方向,弧长和加π。

由于顶点在原点和坐标轴上时需要特殊处理,为了准确性,应该在每次计算顶点时都用叉积判断P是否在当前边上,另外将π用整数代替可以避免浮点数的精度误差。

Ubuntu 使用问题汇总

SSH 连接Ubuntu反应慢

  • SSH服务默认启用了DNS反向解析的功能
  • /etc/ssh/sshd_config文件中修改或加入UseDNS=noGSSAPIAuthentication no 两行
  • 重启sshd sudo service sshd restart

删除旧内核

uname -a  #使用这个命令可以查看当前系统使用的内核。

dpkg --get-selections|grep linux #列出当前内核,带image的则是已经安装的内核。可以用类似以下命令卸载:

sudo apt-get remove linux-image-2.6.24-11-generic #其中 linux-image-2.6.24-11-generic 为版本号,输全 。

在最新的ubuntu (15.10以上)版本中,执行sudo apt-get autoremove也可以直接卸载旧内核

如何升级 Ubuntu 版本, 比如从 Ubuntu 15.10 升级到 Ubuntu 16.04

终端执行如下命令:

sudo do-release-upgrade -d

如果命令do-release-upgrade不存在,则安装 update-manager-core :

sudo apt-get install update-manager-core

Ubuntu中安装和卸载Gnome、KDE、XFACE桌面环境

  • 安装XFACE: sudo apt-get install xubuntu-desktop
  • 安装KDE:sudo apt-get install kubuntu-desktop
  • 安装Gnome:sudo apt-get install ubuntu-desktop

Ubuntu卸载桌面环境(应备份重要文件)

  • 卸载gnome:sudo apt-get --purge remove liborbit2
  • 卸载kde:sudo apt-get --purge remove kdelibs4c2a libarts1c2a
  • 卸载xface:sudo apt-get --purge remove xfce4

给用户添加sudo权限

  • root权限编辑/etc/sudoers文件

添加一行 xxx ALL=(ALL) ALL

或者添加 xxx ALL=(ALL) NOPASSWD: ALL , 使用不用输密码

配置静态IP后无法解析域名

编辑 /etc/network/interfaces 文件, 添加配置:

dns-nameservers 114.114.114.114 114.114.115.115

update-apt-xapi占用资源过高

  • 后台任务,会定期把cpu和磁盘IO打满
  • 解决方法,直接卸载掉 apt-get autoremove --purge apt-xapian-index

升级Ubuntu版本,比如从15.04升级到16.04

先备份!

sudo apt-get update

sudo apt-get install update-manager-core

sudo do-release-upgrade

Ubuntu 无法重启、关机

此问题发生于Ubuntu 16.04 LTS版本,由内核bug导致。

解决办法是,把内核升级到最新版本。截止本文,Ubuntu 16.04 源中的最新版本是4.10.0-22

sudo apt-get install linux-image-4.10.0-22-lowlatency

sudo apt-get install linux-headers-4.10.0-22-lowlatency

最新版本内核查找方法如下:

sudo apt-cache search linux-image

Ubuntu Desktop 软件推荐

本文所列都是作者正在使用的软件,也都是个人爱好的软件。

适用Ubuntu版本 Ubuntu 15.10 、 Ubuntu 15.04

文本编辑

  • wps-office

输入法

  • 搜狗拼音输入法

    百度上搜索搜狗拼音输入法 for linux 可得到下载链接

浏览器

  • Chrown

    sudo apt-get install libappindicator1 libindicator7 sudo dpkg -i google-chrome-stable_current_amd64.deb sudo apt-get -f install

系统工具

  • SysPeek

    sudo add-apt-repository ppa:nilarimogard/webupd8
    sudo apt-get update
    sudo apt-get install syspeek

下载工具

  • axel 相当于迅雷

sudo apt-get install axel

开发工具

  • vim
  • vpnc
  • git
  • Sublime Text 3

    sudo add-apt-repository ppa:webupd8team/sublime-text-3
    sudo apt-get update
    sudo apt-get install sublime-text

删除原装软件

sudo apt-get remove libreoffice-common
sudo apt-get remove unity-webapps-common  # 删除Amazon的链接
sudo apt-get remove thunderbird totem rhythmbox empathy brasero simple-scan gnome-mahjongg aisleriot gnome-mines cheese transmission-common gnome-orca webbrowser-app gnome-sudoku  landscape-client-ui-install
sudo apt-get remove onboard deja-dup

线程安全的单例模式-以C++代码为例

本文描述3种场景下的单例模式:

  • 进程体内无线程的单例模式
  • 进程体内多线程单例模式
  • 在单个线程体中的单例模式

本文所写单例模式代码都使用懒汉模式。

进程体内单例

注意问题:

  • 如果进程体中运行多个线程,则需要考虑多线程同步访问的问题。
  • 如果进程体重没有运行多个线程,则不需要考虑多线程同步访问。
  • 使用线程同步锁保证多进程同步

使用场景举例 :

  • 日志类、文件读写类
  • 资源管理类

代码示例:

进程体内没有运行多线程的单例模式,无需考虑线程同步与互斥

class Singleton {
  public:
    static Singleton* getInstance() {
        if (NULL == instance) {
          instance = new SingletonInside();
        }
        return instance;
    }
  private:
    SingletonInside(){}
    ~SingletonInside() {}
    static Singleton* instance;
};

Singleton::instance = NULL;    

进程体内运行多线程单例模式,使用系统mutex保证线程安全

class Singleton {
  public:
    static Singleton* getInstance() {
        pthread_once(&g_once_control, InitOnce);
        pthread_mutex_lock(&mutex);  // lock
        if (NULL == instance) {
          instance = new SingletonInside();
        }
        pthread_mutex_unlock(&mutex); // unlock
        return instance;
    }
  private:
    SingletonInside() {

    }
    ~SingletonInside() {
       pthread_mutex_destroy(&mutex);   // destroy lock
    }
    static void InitOnce(void) {
      pthread_mutex_init(&mutex,NULL);  // init lock
    }
    Singleton* instance;
    static pthread_once_t g_once_control;
    static pthread_mutex_t mutex;
};
Singleton::instance = NULL;
pthread_once_t Singleton::g_once_control = PTHREAD_ONCE_INIT;

单个线程体中的单例

某些资源在单个线程体内需要保持单例,即每个线程体内都保持唯一。每个线程体内,对象相互隔离,则无需考虑线程安全问题。

此种单例模式的实例需要调用系统提供的 TLS 接口,放于线程局部存储中。

使用场景举例:

  • 多路复用Socket封装类
  • 网络上下文环境类
  • 线程安全的资源

代码示例

class Singleton {
  public:
    static Singleton* getInstance() {
       pthread_once(&g_once_control, InitOnce);
       Singleton* instance = (Singleton*)pthread_getspecific(g_thread_data_key);

        if (NULL == instance) {
          instance = new SingletonInside();
          pthread_setspecific(g_thread_data_key, (void*)Singleton)
        }
        return instance;
    }
  private:
    SingletonInside() {}
    ~SingletonInside() {
       pthread_key_delete(g_thread_data_key);
    }
    static void InitOnce(void) {
      pthread_key_create(&g_thread_data_key, NULL);
    }

    static pthread_once_t g_once_control;
    static pthread_key_t g_thread_data_key;
};

pthread_once_t Singleton::g_once_control = PTHREAD_ONCE_INIT;
pthread_key_t Singleton::g_thread_data_key;

如果使用 Poco库 TreadLocal ,代码还会简洁很多,性能上也会好很多

    class Singleton {
      public:
        static Singleton* getInstance() {
            if (NULL == instance) {
              instance = new SingletonInside();
            }
            return instance;
        }
      private:
        SingletonInside() {}
        ~SingletonInside() {
           delete instance;
        }
        static Poco::ThreadLocal<Singleton> instance;
    };
Poco::ThreadLocal<singleton> Singleton::instance = NULL;

总结

  • 无论程序是多进程运行还是多线程运行,代码都要尽量兼容线程安全

多线程编程的陷阱

依据《Java 并 发编程实践》/《Java Concurrency in Practice》一书,一个线程安全的 class 应当满足三个条件:

  • 从多个线程访问时,其表现出正确的行为
  • 无论操作系统如何调度这些线程,无论这些线程的执行顺序 如何交织
  • 调用端代码无需额外的同步或其他协调动作

不安全的多线程编程容易造成哪些后果

  • 内存泄露
  • 资源、数据访问冲突、数据不一致
  • 进程空间用尽
  • 计算资源耗尽

* 错误处理不当导致整个进程异常结束

数据访问

  • 冲突
  • 不一致

多线程间共享的标志变量, 可能被编译器优化,存储于cpu寄存器,一个线程将其值改变(内存值),在另一个线程的cpu寄存器对应的值却未同步。

解决方法: 1. 添加 volatile 关键字修饰变量。 并且加互斥锁。 2.不使用共享的标志变量。

内存空间使用的安全性

  • 防止内存泄露,小心使用堆空间

  • 小心使用栈空间

逻辑的安全性

  • 参数检查一定要严格,任何一个不合法的参数能

  • 容错

内存泄露原因

软件的稳定性

  • 如果一个线程体挂掉

  • 悬空指针

释放时设置为NULL 声明定义时设置为NULL

线程空间限制

  • 所有线程受限于进程的空间
  • 系统会限制线程的栈空间(默认是10M)

在大规模软件设计中,线程数量的设定和栈空间的使用必须要小心。

高效使用栈空间技巧

  • 如果使用大的内存空间,则到堆里去申请
  • 不使用递归算法,而使用循环
  • 更加细致地拆分逻辑或模块,让函数体量更小
  • 复杂数据类型(类或者结构体)做为函数参数时,尽可能传递它们的指针或引用,以节省栈空间

多线程下的设计模式

资源竞争与互斥

  • 单例模式

循环中不使用wait

多线程下的全局变量、资源

线程安全

语言特性 valatile

内存泄露

和多进程编程相同的陷阱

Nginx 和 PHP 的两种部署方式比较

2种部署方式简介

第一种

  • 前置1台nginx服务器做HTTP反向代理和负载均衡
  • 后面多态服务器部署Nginx Web服务和php-fpm提供的fast cgi服务

第二种

  • 前置1台nginx服务器做Web服务
  • 后面服务器只部署php-fpm服务,供nginx服务器调用
  • 前置1台nginx服务器,在调用后面多例php-fpm服务时,也可以做到负载均衡

如下图 :

 2种部署方式

对比

从系统设计角度

第一种部署是常见部署方式,大中小规模网站都能适用。

第二种,web服务和php-fpm服务部署在不同服务器上,更加细致。但有几个问题:

  • 前置nginx充当Web服务。对静态资源的访问、压缩传输、缓存设置等,也都集中在这台服务器上。一旦访问量变多,压力变大,容易成为瓶颈。
  • 如果静态资源都存放于CDN,不需要HTTP 压缩传输,这种部署方式还算比较合理;
  • 承接上面两点,还可以对这种部署方式进行优化。如前置nginx负载均衡和反向代理,中间是nginx Web服务,后面部署php-fpm服务。

从性能角度

相比第二种部署方式,第一种多走了一次进程间交互。

  • 按照第一种部署,当一个http请求过来,先是nginx反向代理转发至nginx Web服务(通过网络),Web服务再通过fastcgi协议与php-fpm进行交互(进程间交互);
  • 按照第二种部署,当一个http请求过来,充当Web服务的nginx,直接通过网络与php-fpm进行交互

第一种部署,通过网络交互的是HTTP协议,第二种通过网络交互的是fast-cgi协议, 这两种协议对比如何呢?

  • fast cgi 的数据包会比HTTP稍微大一些,fast cgi协议会比HTTP携带更多的参数信息、传输控制信息等等。
  • fast cgi 协议比HTTP协议格式化严格一些,解析起来速度更快一些。

从运维角度

  • 第一种是最常见的部署方式,后面所有服务器上的服务都是同构的,简单粗放。

  • 第二种则是将nginx和php-fpm单独分开部署,不同服务在服务器集群上的分布更加细致。通过统计Web服务中的压力分布,可以更加精细地利用硬件资源。运维成本也更高。

从开发测试角度

两种部署方式都不合适开发环境或测试环境,都仅适用于生产环境。

开发和测试环境把nginx和PHP部署到一台服务器上即可,也不需要反向代理和负载均衡。

总结

如果是LAMP环境的部署,第一种比较常见。

如果不是LAMP,是nginx和其他fastcgi服务交互,比如C/C++、java的fastcgi程序,在大规模的网络应用中,类似第二种的部署是常见的。做到不同服务之间分开部署,反而是简化了系统的网络结构,更加便于维护。

后记

此篇博文的内容,都来自于和百度前同事在QQ群里的讨论。

本博客搭建和定制方法

搭建环境

硬件

  • 阿里云服务器

软件

  • LNMP 环境(Linux + PHP + Nginx + Mysql)
  • WordPress

定制本博客的Wordpress 功能

博客主题

Twenty Eleven, 简约风格

博客文本编辑器

使用了WP-Markdown 插件,可惜不支持GitHub 扩展的特性

HTML h3 标题又小又不明显 怎么办?

在主题目录 wp-content/themes/twentyeleven/style.css 中,h3 元素样式添加 font-weight: bold;更改front-size为12px。

添加缓存,提高博客响应速度

WP Super Cache 插件

让列表页只显示文章标题,而不把文章标题页显示出来

修改主题中文件,在 content.php 中,把调用the_content函数的语句注释掉

在文章末尾添加文章签名,如果文章被别的网站扒去,也可以把来源显示出来。

修改主题中文件,content-single.php,

在content-entry 里添加如下代码

<p style="border:1px dashed #ddd; padding:10px; margin:10px 0;line-height:26px;border-radius: 3px;">
        <b>转载请注明来源,原地址保持永久更新。</b>
        <br /><b>博客首页:</b><a target="_blank" href="//it.zuocheng.net">作程的技术博客</a>
        <br /><b>文章标题:</b><a target="_blank" href="<?php the_permalink() ?>">《<?php the_title(); ?>》</a>
        <br /><b>本文链接:</b><a target="_blank" href="<?php the_permalink() ?>"><?php the_permalink(); ?></a>
</p>

访问 http://it.zuocheng.net/sitemap.xml 提示错误

把xml 注释去掉,修改aioseop_sitemap.php

//$this->comment_string = __( "Sitemap %s generated by All in One SEO Pack %s by Michael Torbert of Semper Fi Web Design on %s", 'all_in_one_seo_pack' );

添加链接 (书签、友情链接等)

在数据库中,把wp_options.link_manager_enabled项设置为1

支持HTTPS

使用了 let’s encrypt 的证书

所使用的插件列表

  • All In One SEO Pack //用于SEO
  • Disable Google Fonts //禁止调用google字体,国内请求慢,不然会降低网站响应速度
  • Google XML Sitemaps //生成xml,SEO
  • Query Monitor //监控,一般情况下禁用
  • Table of Contents Plus //显示文章结构
  • Wordfence Security
  • WP Clean Up
  • WP Super Cache
  • WP-Markdown
  • WP-PostViews
  • WPtouch Mobile Plugin //让博客对移动端友好
  • 多说 //社会化登陆
  • 百度sitemap //百度搜索SEO
  • WP-Statistics //统计,包括来访、IP、展示数等等

系统优化

使用 Unix domain socket,减小 LAMP 通信代价

Nginx 和 PHP ,PHP 和 Mysql 通信都使用Unix domain socket,并且把套接字文件放到内存文件系统/dev/shm下面

mysqld 有大量sleep进程,占用过多内存。

添加mysqld配置

[mysqld]
wait_timeout=120
interactive_timeout=120

统计过滤机器人列表

工程师手记-PNI

PNI 简介

PNI,是PHP Native Interface的简写。它是PHP的一个扩展,可以通过它,让PHP直接调用C语言写的函数。

想法源自在百度做项目时,不时地会面临同样一个问题,PHP该如何直接快速地与其他语言交互呢?

PHP 扩展的不足

用C写PHP的扩展是常规的方法。不过使用这种方法总是要面临诸多问题:

  • 作为开发者,要去学习PHP-API、Zend-API,这是不小的学习成本,而且用C做开发测试的效率都不高。
  • 写好了PHP扩展,怎么部署呢,还要找运维工程师去讨论、去争取后才能上线。

来来回回项目进度就被拖慢了。

有如此技术痛点,就找个通用的法子吧。

调研过程

JNI 和 Python

Java 可以通过JNI调用C/C++,Python也有相应的包,比如ctypes

HHVM

稍微去翻了一下HHVM的扩展,发现HHVM有 native interface, 但稍微看了一下,却发现那只是HHVM的native interface,PHP无法使用。

这个项目,模仿JNI,就取名PNI吧

c 对动态链接库的动态调用

c 加载动态链接库及调用函数有现成的方法。

使用dlfcn.h 库中的dlopen、dlsym、dlclose三个函数就够了。

设计和实现过程

接口设计

最开始的方案,就是模仿JNI。因此在最初的实现里,PNI对动态链接库的查询,调用函数的方法都直接模仿了JNI的接口实现。测试使用时,却感觉PNI的接口非常不友好。

和Java、Python 不同,PHP是弱类型语言。JNI 可以在给函数传递参数时,参数的数据类型是已知的,但是PHP传的都是zval,类型并不可知。我把数据类型的控制交给了开发者,在PNI的扩展里,通过struct zval 中 type 字段判断,此种方案让PNI很难于被使用。

于是改进方案,在PNI里添加PNIInteger、PNIDouble、PNIChar和其他几个可以标明参数数据类型的类。

参数压栈

PHP 的函数调用后,动过dlfcn.h库,可以找到函数地址,但是如何调用函数呢?调用函数又如何传参呢?我们无法知道所调用函数的参数列表是什么样子。

由于没有找到比较好的方案,所以就借助于汇编,使用汇编模式C语言的参数压栈方法。

于是写了很多种C语言的函数,主要是参数列表不一样,总结了GCC编译器在x86_64架构CPU下参数压栈的几个特点:

  • 8、16、32位和64位整形的参数,按顺序传到64位的寄存器,当多于6个整形参数时,剩下的整形参数都压栈

  • 32位和64位浮点,都传到64位的浮点计算器,浮点参数多于6个时,剩下的浮点参数都无效

无论C函数的参数是什么样的类型,PNI都将其按64位的整形或浮点处理。

问题解决了

系统兼容性

因为参数压栈的问题,目前PNI只支持GCC编译器和x86_64架构的CPU。其他架构和编译器都没有来得及去实现。

总结

吭哧吭哧,PNI最初版本调试测试成功。

后记

如何让PNI被更多的人知道并使用,怎么做呢?(还在思考中)

  • 开源,传到GitHub
  • 融入社区
  • 在问答网站上多回答相关问题

其他方法呢?再想一下。

工程师手记-赶集易洗车后台和服务端

项目简介

赶集上门洗车业务服务器端,分为两部分:

  • 为洗车APP提供服务的接口
  • 洗车业务相关的管理后台

从整个项目上说,虽是个创业项目,但需求针对性强,从洗车仅一点上切入,易于发力。

相比曾经经历一些创业项目中,产品总是摸不住方向,不知何处发力,试错成本太高,做到最后都迷茫该做什么,怎么做。还有些项目,铺开面去搞,结果摊子太大,顾及不暇。

赶集易洗车与之相比要好太多,对我来说,做这个项目感觉很舒服。负责时间:2014-11 至 2015-08

框架代码

主要开发语言是PHP。典型 LNMP 架构。

代码框架是赶集网多年积累下来的老框架,对于老框架,工程师都是能够理解的,不过还是想说一说。

  • 简易实现的路由分发
  • 没有清晰的分层概念
  • 有一套简单的DB访问工具类
  • 对其他架构系统调用封装访问很全面
  • 赶集所有频道都使用一套框架代码

总之,有优有劣。

业务代码

虽然使用了类,但仍然使用了面向过程的思维在开发。代码复用性、可扩展性比较低。由于业务简单代码耦合还不是很严重。

代码没有分层也是问题。

项目发展过程

框架的改进

赶集易洗车框架和其他MVC框架的诟病一样,表现层逻辑直接调用数据访问逻辑,并没有业务逻辑层。于是劈出一个目录,存放业务逻辑层代码。建立规范约束DB层和表现层,老的业务逻辑代码日后慢慢迁出和重构。

面向对象

老逻辑中类的作用只被用于聚合。多态和继承并没有被使用。

对DB层的再次封装

在DB访问类,由于历史原因,用起来特别不友好,也有让新人不易察觉的逻辑陷阱。

对此,我使用了Service Locator Pattern + Proxy Pattern 这两个设计模式,对原DB层再次进行封装,简化上层对DB层的调用,添加缓存的优化。之所以使用这个方案,主要是根据项目当前情况,有如下考虑:

  • 不能抛弃和重构原有的DB访问类, 因此选择再其之上再进行封装。遵循开闭原则,不对老逻辑进行改动
  • 原有DB访问类有逻辑陷阱,因此使用Proxy Pattern,把陷阱的规避放到proxy类中,优化对外的接口
  • 原有DB类,每一个数据表都对应一个DB类,全局只需初始化一次,但会在不同的地方多次调用,所以选用Service Locator Pattern,每个DB类,初始化1次便缓存起来,供之后逻辑重复使用

用了上面的方案后,代码可维护性和可读性都提高了。

展示列表的封装

后台有很多的展示列表页面,主键往往是一样的,比如都是订单的ID,但是其他的列,有的一样,有的不一样。可以看成,不同的列表页面,就是不同的列的组合。

使用Decorator Pattern 对其进行重构。这个设计模式比较简单,但是实在是特别好。在我之后的开发工作中,凡是遇到列表,开发的时间估计就能缩小一半。维护的成本则是更低。

具体业务开发

具体业务的开发,也许是最枯燥的工作,不具有挑战性,只能付出劳力。估计只有在机器人程序猿出现后才会解决这个问题。

关于团队

团队每周有分享,可怜创业项目排期紧张,团队分享没有坚持几期,后来慢慢停掉。原因我认为可以从两个方面看:

  • 随着业务压力的增大,团队给予技术成长上的提升关注也会消弱。
  • 人有惰性,团队往往也是如此。

我在团队分享3次,《地理区域和点关系计算》、《代理和反向代理》、《PNI》 , 分享积分是团队最高的,本来有一部kiddle做奖品,团队解散后也没去要过来,哈哈。

开始写工程师手记

从12年4月大学毕业开始实习,至今工作3年半经验。

回忆这三年半,这些经验都给我带来了什么呢?不容置疑,有很多东西,但是具体有哪些呢?不好好收拾一下脑海,真就不能立刻说出来。

所以昨晚上班路上就想出了写工程手记这么一个idae,有了手记,我就不会对做过的项目有种“彻底忘记”的感觉了。自己做过什么,一看手记立刻就能回忆起来,曾经的经验、走过的陷阱、当时的思考,都可以立刻回到自己的身上。

通过工程手记,还可以记录在做项目过程中的所感和所想,以后也可以拿来回顾和反思,说不定能得到更多的东西。

总之,就是让自己以往的经历,不仅仅只是流逝的时间,只是过往的履历,一定要变成有价值的经验。

手记的内容

  • 项目简介 (what 、 when 、who)
  • 所遇问题 (why)
  • 如何做的呢? (how)
  • 总结和反思

给自己的手记制定几点原则

  • 原公司保密的东西不以泄露
  • 从客观角度描述问题,尽可能不夹带个人感情倾向
  • 不随意否定项目、团队或个人
  • 内容保持精简,不谈无关话题

写手记的想法是怎么来的呢?

主要是源于两件事吧。

刚玩微博时,关注了易胜华律师,从他发的内容里知道他有写律师手记的习惯。他的手记里,都记录着每次办案的一些重要或关键的细节,也包括他思考的过程。看了后深感佩服,不愧是名律师啊。

在大学时,曾是学校越野队队员,队里规定每次比赛完必须要写比赛总结。总结的目的很明确,就是经过每次的比赛,都能让队员有所提高。

所以我们软件研发工程师为什么不能有手记呢?

二进制运算技巧

假设都在x86或x86_64架构CPU上进行运算,二进制基本运算包括,加减乘除、与、或、异或、同或、移位

假设n 为 32 位整形数,取正整数n除以8的余数 : n & 0x07

假设n 为 32 位整形数,取正整数n除以16的余数 : n & 0x0F

假设n 为 32 位整形数,8位对齐 : (n + 7) & 0xFFFFFFF8)

假设n 为 32 位整形数,32位对齐 : (n + 31) & 0xFFFFFF80)

假设n 为 32 位整形数,求8位对齐填充位数 ((n + 7) & 0xFFFFFFF8) – n;

求int 最大值 : (1 << 31) – 1 或者干脆 0x7FFFFFFF

求int 最小值 : 1 << 31

判断奇偶数 : n & 1 == 1

交换a b 两个变量的值 : a ^= b ^= a ^= b;

n 乘以2的m次方 : n << m

spawn-fcgi 源码分析

梗概

本文内容对Spawn-fcgi源码进行解读,简要说明其原理,并具体说明其实现方式。

Spawn-fcgi 源码虽然只有600多行,但是初次阅读起来依然需要花很多时间。为了节省读者的学习成本,提高学习Spawn-fcgi 的效果,作者对Spawn-fcgi的源码做了裁剪,保留最核心的功能和原有的代码结构,且能编译后正常运行。最后代码只有200多行。

源码地址在这里

必备知识

要阅读Spawn-fcgi,读者至少需要掌握以下几个方面的知识或技能:

  • 了解CGI和FastCgi的概念,了解其使用场景
  • 基础的Linux C 环境编程,会使用常见的库函数比如getopt、exec 等
  • 基础的Linux C 的多进程编程,熟悉fork、waitpid、setsid等函数
  • 基础的Linux C 网络编程,熟悉建立tcp连接、select非阻塞方式通信,多路复用I/O等

裁剪后Spawn-fcgi的执行过程

  • 创建服务器socket
  • fork进程,子进程初始化会有两个主要操作:
  1. 把socket的文件描述符,复制到FCGI_LISTENSOCK_FILENO
  2. 会执行execl 函数,运行cgi程序,并让cgi程序拥有子进程的上下文环境

    运行cgi程序后,使用FCGI_LISTENSOCK_FILENO这个描述符,来与webserver进行通信。

如何编译

gcc -o spawn-fcgi spawn-fcgi.c

一行命令即可

如何调用

./spawn-fcgi -f cgi -p 9001 -F 256

裁剪后也仅支持接收这三个参数

作者对于Spawn-fcgi的思考

  • fastcgi 协议规定,fcgi管理器中把网络描述符定为FCGI_LISTENSOCK_FILENO,为了一致CGI程序中复用FCGI_LISTENSOCK_FILENO的套接字,总感觉不是很完美。
  • Spawn-fcgi太简单,不需要单独做一个软件,完全可以集成到cgi程序中
  • Spawn-fcgi使用的是多进程,如果集成到cgi程序中,可以自由选择多进程模型、多线程模型
  • 缺少进程守护监控,spawn-fcgi如果一个进程挂掉,不会被重启。
  • Spawn-fcgi 网络多路复用调用的是select,但现在最常用的是epoll

Spawn-fcgi 补充知识

与原版相比,裁剪后Spawn-fcgi的失去了哪些功能

  • 使用linux套接字文件建立tcp连接功能
  • 对IPv6的支持
  • 对root、group 用户的检查
  • 对windows、Solaris等编译环境的支持
  • 通过进程pid文件获取cgi进程
  • 去除对autoconf等编译工具的依赖

不同设计模式的适用场景总结

软件设计过程中,该选择那种设计模式,除了借鉴直接的经验,还可以从以下方面考虑:

  • 领域对象模型中,类之间的关系是什么(继承、实现、关联、依赖、组合、聚合)
  • 容易变化的部分是什么,即易于扩展的部分
  • 不容易变化的部分是什么,即需要复用的部分

按照我们的思路,设计模式之所以多种多样,就是对应的领域模型中上面三个方面有很大不同。把握住以上几点,就能正确地选用设计模式

门面模式,Facade Pattern

  • 门面类和被门面类封装的类之间是继承或者依赖关系
  • 类的内部接口、内部逻辑经常变化;不被需要的对外接口容易变化;
  • 类的对外部接口不容易变

适配器模式, Adapter Pattern

  • 适配器类和被适配的类之间是继承或者依赖关系
  • 需要适配的类,或者其接口容易变化
  • 适配的类的接口不容易变化

原型模式, Adapter Pattern

  • 不同类之间有共同的父类
  • 类的部分属性、方法容易变化
  • 类的另外一部分属性、方法不容易变化,且构建复杂,成本很高

单例模式,Singleton Pattern

  • 类在全局唯一存在,其他类与单例类是
  • 类的唯一性不会变化

多例模式,Multition Pattern

  • 多例类的对象之间是聚合关系
  • 多例类的对象的数量容易变化

策略模式,Strategy Pattern

  • 对象和算法,是依赖关系
  • 逻辑框架不容易变化
  • 策略算法容易变化

代理模式,Proxy Pattern

  • 代理和被代理的类之间是依赖关系
  • 代理者的接口不容易变化
  • 被代理者的接口具体实现容易变化

工厂模式,Factory Methond Pattern

  • 工厂模式产生的对象一般有相同的父类(继承关系)
  • 由工厂产生对象不会变
  • 工厂产生对象所属的类的种类是不断变化(增多或减少)

抽象工厂模式,Abstract Factory Pattern

  • 同工厂模式
  • 变化的是,工厂创建对象所属的类的接口会不断变化

门面模式,Facade Pattern

  • 封装类和被封装的类之间是依赖关系
  • 接口类(封装类)的接口不容易变化
  • 实现类(被封装的类)的接口容易变化

Adapter Pattern

  • 同门面模式
  • 适配器类更倾向于兼容现有系统的接口需求

模版模式,Template Method Pattern

建造者模式,Builder Pattern

  • 构建、配置、注入依赖容易变化,且较复杂

桥梁模式,Bridge Pattern

命令模式,Command Pattern

装饰模式,Decorator Pattern

迭代器模式,Iterator Pattern

组合模式,Composite Pattern

观察者模式,Observer Pattern

  • 观察者和被观察者是关联关系
  • 被观察者的数量或种类容易变化

责任链模式,Chain of Responsibility Pattern

访问者模式,Visitor Pattern

状态模式,State Patter

原型模式,Prototype Pattern

中介者模式,Mediator Pattern

解释器模式,Interpreter Pattern

亨元模式,Flyweight Pattern

备忘录模式,Memento Pattern

软件设计之缓存使用

本文主要讨论分布式环境下,缓存如何在软件设计作用、原理、实现方式及注意问题。

缓存的作用

  • 减小原始数据访问压力
  • 提高资源利用率

缓存的原理

局部性原理

缓存的实现方式

查询算法

  • 散列算法,Hash 、 MD5 等
  • B数、二叉树、有序二分查找等

存储

  • 只将访问量最高的部分数据放入缓存
  • 将数据放到比原始IO速率更高的存储介质中

缓存资源回收

  • RUL 算法
  • 定时清理
  • 设置资源有效时间

缓存的存储介质

  • CPU 寄存器
  • 内存
  • 本地文件
  • 分布式系统(Memcache 、 Redis)
  • 数据库缓存数据表

缓存设计注意的问题

缓存的一致性,Cache coherence

  • 避免数据脏读
  • 多级缓存的一致性协议

系统的鲁棒性

  • 在缓存系统停止服务,但仍能保证整体系统正常运行。因此在使用缓存之前,检查提供缓存系统的有效性。

单机缓存

  • 单机缓存是指,将系统资源存放于每个单台服务器上,而不是集中存储与分布式缓存系统中。 缺陷是,如果原数据发生更改,为保证一致性,则必须调用每一台服务器清理或更新缓存。

软件设计之浮点运算

计算机对浮点的表示和运算的特点

二进制表示

  • 部分十进制小数转化为二进制小数后,是无限循环小数,比如 0.05 转换为二进制 0.000011001100110011001100110011…… 如果截断,实际计算机存储的是 0.049999999999

IEEE 754 标准下的浮点

  • 小数位数是精确的
  • 有效位数是有限的

计算机做浮点运算的陷阱和缺陷

  • 丢失精度,近似比较不准确

正确的比较方法 : if (a - b < 0.0000001 && a-b > -0.0000001) {}

  • 四舍五入,导致误差累积

软件设计时如何保证高精度的计算

使用高精度计算库,比如:

  • Java 使用BigInteger 、BigDecimal 类
  • PHP 使用bcmath扩展提供的方法
  • C/C++ 自行编写高精度算法和数据结构

PHP与其他语言交互

软件或者系统,不同模块可能由不同的语言编写,必然会存在不同语言之间交互和通信的问题。本文站在PHP的角度,探讨PHP与其他语言交互的方式。

语言层面交互

使用胶水语言

胶水语言Shell scripts Python Ruby Lua Tcl Perl。

其本质是调用操作系统的输入与输出,由操作系统创建由其他语言写成的应用的新进程,再将其输出返回给PHP。

优点是简单容易实现。

缺点是:

  • 系统调用有一定风险;
  • 每次调用系统都创建新的进程,开销较大
  • 复杂数据结构无法直接传递

编写PHP扩展

将其他语言提供的API封装成C接口(往往需要再编译为动态链接库),在PHP扩展中再对其进行封装,使其可以在PHP语句中直接调用。

优点 :

  • 性能很高
  • 可以实现复杂结构数据的交互。

缺点 :

  • 开发、运维成本较高

使用PHP Native Interface (PNI)

PNI 是一个PHP 扩展,允许PHP代码中直接调用C的类库,地址 https://it.zuocheng.net/php-native-interface

使用SWIG

最终的目的是编写PHP扩展,不同的是SWIG提供的接口让开发者省去了学习比Zend API 和 PHP API 的成本。

进程间交互

如果在语言层面无法直接交互,可使用进程间的交互方法。本质是调用操作系统提供的进程间交互方法。

  • 信号
  • 信号量
  • 管道
  • 消息队列
  • 共享内存
  • 文件、内存数据库等

系统间交互

如果由不同语言编写的系统之间进行交互,则有以下几种方法:

  • 网络调用 (Http 、 Socket 、 Web Service)
  • 数据库
  • 数据文件
  • 分布式系统 (Memcache、Radis、rabitMQ)

MVC框架-路由分发总结

路由的类型 Route Type

无路由 No Route

HTTP请求直接定位到特定的脚本文件执行。比如http://domain/news/latest.php

动态路由

依据某种动态规则进行路由和分发,一般有如下形式:

强约束URL

将Class,function 等信息隐藏在url中,请求来时,依据规则解析URL就可以定位Action函数,比如http://domain/modelName/className/functionName

优点是简单,缺点是灵活性差,路由信息包含的少,比如拦截器、模版只能在action中写代码调用,不能集中配置。

依据注释信息自动路由

将路由信息,写到执行函数的注释里,请求来时,解析所有action函数的注释,找到合适的,再执行action函数。 Java Spring 和 PHP Symonfy 支持这种路由方式

注释的关键字有(举例)

  • @url
  • @template
  • @filter
  • @trigger

优点是维护起来比路由表简单,缺点是性能较低。

静态路由

路由配置写在配置文件中,依据配置文件,确定如何分发。

路由信息配置在配置文件中,文件或数据格式如下:

  • PHP
  • Yaml
  • Xml
  • Json

笔者最推荐的是 Yaml,因为Yaml 格式的可读性最高,最易于维护。

半静态半动态路由

动态和静态相结合的路由.

尤其是在URL解析中,使用正则匹配的路由方式。

路由的组成 Route Components

路由分发的元素

  • URL , URL特征决定分发到特定Action函数
  • Action 函数,函数所在的class、Namespace
  • 模版 template ,页面渲染的模版
  • 拦截过滤器,Filters , 比如权限检查、地区检查、无效请求检查等各类filter
  • 触发器,triggers , 页面调用后触发的操作

URL 的组成

URL 包含着路由的关键信息,决定请求分发到具体的Action函数

MVC 框架的 URL 一般由下列部分组成

  • 域名 domain name
  • 模块/目录 model/directory name
  • 功能 function name
  • 参数 parameter

参数有两种形式存于http请求中,一种是作为POST和GET方法的参数,一种是直接在URL中。

Route Style

Restfull 的路由分发

遵循Rest 风格、规范的URl

普通的路由分发

企业应用架构之分层 – 总结

常见分层架构模式

三层架构 3-tier architecture

微软.net 体系推荐的分层结构,因此早期在C/S架构和ASP编码的系统中被广泛应用,同时也被其他语言广泛借鉴。

表现层, Presentation Layer(PL)

主要负责数据的输入接口和输出。输入指在WEB、客户端或为外界提供的API的数据请求接口;输出则是Web界面、客户端输出、API的数据输出。

页面模版、 对外API数据格式化、Request接受 、Output推送、 Commander操作都在这一层。

业务逻辑层, Bisiness Logic Layer(BLL)

主要负责从原始数据到结果数据的中间过程。系统中最关键、中重要的一层。也被称作领域层(Domain Layer),领域中所有对象的逻辑关系和功能实现算法都在这一层中。

业务逻辑、服务等处于这一层。

数据访问层, Data Access Layer(DAL)

主要是对原始数据的操作层(CRUD操作),为业务逻辑层或表示层提供数据服务。数据源包括,数据库、文件、网络存储系统、其他系统开放的API、程序运行上下文环境等等。

许多框架中的ORM 、 Active Record 、 Dao 类或库都处于这一层。

三层架构总结

三层架构能较好的满足大部分的业务场景,实现高内聚、低耦合。不同层次之间逻辑解耦或者隔离,变为弱依赖,每层的逻辑内聚。从层次角度看,使系统有了较好的可扩展性。

三层架构的不足有:

  • 业务逻辑复杂度高的系统,业务逻辑层将变得庞大臃肿,为了解决这个问题,四层架构/多层架构被人提出。
  • 不关注表现层的实现。

四层架构 4-tier architecture

四层架构和三层架构极为类似,分为表示层、服务层、业务逻辑层、数据访问层。除了服务层,其他三层和三层架构的三层几乎一致,而服务层,是对三层架构中业务逻辑层的再细分,以解决业务逻辑层经常出现的臃肿的问题。

四层架构是Java EE(J2ee) 推荐的分层架构,尤其是Java Spring+Struct+Hibernate(SSH)组合的框架 将Dao层、 Service层,做了明确的定义和规范。由于SSH框架被广泛使用,其他框架,包括其他语言的框架(比如PHP)也都借鉴SSH,因此在这些框架里能看到Dao类、Service类的抽象定义。

服务层, Service Layer

在三层架构中,把业务逻辑层的上层逻辑分离出来,组成服务层。服务层往往是逻辑的表示层,即向上层(表示层)提供逻辑的外观。事务控制、安全检查、事务脚本等可以置入业务层(参考Martin fowler 的《企业应用的架构模式》)

四层架构总结

四层架构是三层架构的发展或进化。服务层的出现让三层架构的业务逻辑层不再变得臃肿。

四层架构和三层架构都存在一个不足,就是不强调前端的实现。当面对需要个性化定制界面、复杂用户交互、页面之间有依赖关系时,需要更好的解决方案。

MVC 模式

MVC 模式最主要包括三部分,Model View Controller(模型-视图-控制器)。

相比于三层架构或者四层架构,MVC最突出的优点是前端控制的灵活性。如果把MVC的View和Controller剥离出来,实际上是一种叫前端控制器模式的设计模式。

MVC 的缺点很明显,将前端以外逻辑都放到 Model里,随着业务增多,Model将越来越难以维护。

MVC 并不适合称作一种分层架构,更适合称为一种复合的设计模式。有人还将MVC模式归类为前端架构。

为什么MVC模式被广泛的采用? 笔者是这样认为的:

  • MVC模式最适合新闻门户网站、展示类网站,此类网站业务逻辑往往较为简单。
  • MVC模式最适合产品初创时被使用,因为项目初期逻辑简单, 使用MVC模式产品能快速成型,可以尽早投放市场进行试验(多数可能会被淘汰),这样就降低了试验成本。(如果产品有潜力,中后期随着业务增多和变得复杂,系统必然面临重构压力。)
  • MVC模式最适合产品原型的实现(注重前端)。

其他分层架构

除了三/四层架构,MVC模式,还有很多架构模式,但这些多是与三/四层架构、MVC模式类似,或是在他们之上进行扩展和改造。比如 MVVM (Model-View-ViewModel)、MVP、DDD架构模式等。

关于分层的总结

分层的思想或目的

  • 高内聚
  • 低耦合
  • 增强系统的可扩展性
  • 增强系统的可维护性

分层的优点

  • 开发人员可以只关注整个结构中的其中某一层;
  • 可以很容易的用新的实现来替换原有层次的实现;
  • 可以降低层与层之间的依赖;
  • 有利于标准化;
  • 利于各层逻辑的复用;
  • 结构更加的明确;
  • 在后期维护的时候,极大地降低了维护成本和维护时间

分层的缺点

相比于分层的优点,分层的缺点显得微不足道。

  • 使用分层虽然增多了代码量,但清晰的架构和代码复用却降低了开发成本、维护成本。
  • 分层虽然在逻辑上增多了代码的调用、增多了逻辑分支,降低了性能,但是这部分降低的性能与系统中真正的性能瓶颈(存储IO、网络IO、高层次的语言)相比,显得实在渺小。
  • 分层有时会导致级联的修改,但这种情况是可以通过面向接口的设计或者使用中介者模式、门面模式、适配器模式等设计模式解决。

如何设计分层架构

  • 软件的发展都是遵循着从简单到复杂的过程,软件架构也是一个迭代的过程,是一个循序渐进,不断完善的过程。简而言之,不断地重构。
  • 借鉴或者直接使用Java EE(j2me)的分层规范,并使用dao、service等类的命名。
  • 层与层之间交互部分,要遵循面向接口设计的原则,不吝舍使用设计模式。
  • 分层的选择要平衡成本和风险,使收益最大化。
  • 业务逻辑不要局限于四层架构或三层架构,依据领域业务特点可更细地划分层次。

PHP Native Interface

本页面转自: https://github.com/zuocheng-liu/pni/blob/master/README-zh.md

PNI github PAGE : https://github.com/zuocheng-liu/pni

什么是 PHP Native Interface (PNI) ?

  • PHP 的一个C扩展
  • 通过它,可以让PHP调用其他语言写的程序,比如C/C++、汇编等等
  • 需要PHP来调用,但PHP有限使用的领域里,PNI可以发挥用处,比如图像处理、统计学习、神经网络、实时性要求高的程序等等

使用场景

PHP 不是完美的语言,总有一些情况下,不得不使用其他语言来协助完成。在这些特殊的场景下,使用PNI就可以将PHP与其他语言连接起来:

  • 实时性要求特别高的程序,特别底层的程序
  • 用其他语言写的程序,历史遗留下来的程序,如果用PHP重新实现成本太高的程序或逻辑
  • 基于平台特性的代码,不能用PHP实现的程序
  • 调用系统的动态链接库

与直接编写PHP扩展相比

直接编写PHP扩展去调用其他语言的接口是常用方法,不过PNI有更多的好处:

  • 降低开发和运维成本

不需要每次有新的需求,就去编写或改动PHP的扩展。对PHP扩展的开发、调试会占用很多的时间。

PHP扩展更改后上线,需要重启PHP服务,这是有一定风险的。

如果使用PNI,就会便捷很多,对新功能的开发和上线,只需操作PHP的代码即可。

  • 降低学习成本

开发PHP扩展,需要开发人员去学习 PHP-API、 Zend-API 、 PHP扩展框架,甚至需要深入去理解PHP内核。
有了PNI,问题就简单多了。

  • 灵活性

使用PNI,可以更灵活地使用本地类库。

使用手册

类和方法列表

  • PNIFunction

方法类,此类定位动态链接库中的函数名
php
$pow = new PNIFunction(PNIDataType::DOUBLE, 'pow', 'libm.so.6');

上面的例子,在构造函数中,第一个参数是需要找寻函数的返回值类型,第二参数是函数的名字,第三个参数是到那个动态链接库中找寻函数。

  • PNIException

异常类,在无法找到动态链接库或函数名的时候,会抛出异常。

数据类型类

  • PNIDataType
  • PNIInteger
  • PNILong
  • PNIDouble
  • PNIFLOAT
  • PNIChar
  • PNIString
  • PNIPointer

所有数据类型类都继承PNIDataType抽象类,此抽象类包含3个共有方法

php
getValue(); // 获取值
setValue($value); // 重新赋值
getDataType(); // 获取数据类型

PNIString 和 PNIPointer 中还额外包含一个接口

php
systemFree();

用于释放C函数中malloc申请的内存资源。
强烈不推荐使用PNI直接调用这样的库函数。

定义的常量

表示数据类型常量
php
PNIDataType::VOID
PNIDataType::CHAR
PNIDataType::INTEGER
PNIDataType::LONG
PNIDataType::FLOAT
PNIDataType::DOUBLE
PNIDataType::POINTER

示例

示例 1 , 调用系统接口 :

php
try {
$pow = new PNIFunction(PNIDataType::DOUBLE, 'pow', 'libm.so.6');
$a = new PNIDouble(2);
$b = new PNIDouble(10);
$res = $pow($a, $b);
var_dump($res);
} catch (PNIException $e) {
}

上面例子,使用PNI调用系统math库中的pow函数

示例 2,调用自己定义的C/C++ 逻辑 :

  • 1.构建C程序

C++
// file user_math.c
u_int32_t sum(u_int32_t a, u_int32_t b) {
return a + b;
}

– 2.创建动态链接库,并把它放到 $LD_LIBRARY_PATH 包含的目录里
shell
gcc -fPIC -shared -o libusermath.so user_math.c

– 3.创建PHP程序

“`php
// file testPni.php
<?php
try {
$sum = new PNIFunction(PNIDataType::INTEGER, ‘sum’, ‘libusermath.so’);
$a = new PNIInteger(2);
$b = new PNIInteger(10);
$res = $sum($a, $b);
var_dump($res);
} catch (PNIException $e) {
}

“`
– 4.执行PHP程序

shell
$ php testPni.php

$res 是 PNIInteger类型,其中包含数值结果为12的成员变量

PNI 数据类型类和C语言数据类型对照

PNI 数据类型类 C 数据类型 说明
PNILong long int/ int PHP has no unsigned int
PNIInteger long int/ int PHP has no 32bit Int
PNIDouble double / float
PNIFloat double / float PHP has no 32bit float
PNIChar char
PNIString char*
PNIPointer char*

由于PHP只有64整形,所以PNILong 和 PNIInteger 实际上是等效的。

如果通过PNI调用的函数参数类型是32位、16位数据怎么办?需要开发人员保证PNILong和PNIInteger存放的值不能超出大小。

PNIDouble 和 PNIFloat 也是等效的,因为PHP只有64位浮点。如果调用的C函数参数列表里有32位浮点呢? 不用担心,即使是32位的浮点,在x86_64架构的CPU里,也是赋给了64位的浮点运算器。

缺点或注意事项

  • 目前还不支持PHP7 ,但作者会争取尽快开发适用PHP7版本的PNI
  • 如果PHP是多线程运行,需要注意PNI调用的动态链接库是否是线程安全的
  • 对于在动态链接库中申请的资源,要及时释放
  • 目前PNI还不支持对复杂数据类型的操作,比如struct,C++的类等

如何安装

环境要求

  • PHP 5.3 以上版本, 但不包含PHP 7
  • 必须是GCC编译器
  • CPU 必须是x86_64架构或被兼容的架构

安装步骤

  • 下载

shell
git clone https://github.com/zuocheng-liu/pni.git

– 编译和安装

shell
cd <src-pni>
phpize
./configure
make && make install

– 配置PHP,使其生效

把下面一行添加到 php.ini

shell
extension=pni.so;

– 重启PHP服务

bash
service php-fpm restart // cgi mode
apachectl restart // sapi mode
// do nothing in cli mode

开发

提出建议和提交Bug

热盼您的联系!

其他

网址

联系方式

协议

version 3.01 of the PHP license.(see LICENSE)

什么是设计模式 – 总结

设计模式是如何诞生的?

  • 领域中有些东西是不变的,有些是不停变化的
  • 不变的东西和变化的东西之间,总存在着某些特定的关系,符合某种特定的规则和规律
  • 因为这些特定关系,经验丰富的设计者总在做重复的设计,也不断地复用自己的设计。他们为了设计的通用,也提出了非常好设计
  • 没有经验的设计者,却察觉不到这些关系,做不出好的设计
  • 经验丰富的设计者向没有经验的设计者传授好的、被重复使用的设计和经验,并讲述那些事物之间某些亘古不变的关系,这些就是设计模式的雏形
  • 将这些关系和设计进行整理和总结之后,设计模式诞生了。

设计模式是什么?

  • 是对程序设计人员经常遇到的设计问题的可再现的解决方案(The Smalltalk Companion)
  • 建立了一系列描述如何完成软件开发领域中特定任务的规则
  • 关注与复用可重复出现的结构设计方案
  • 提出了一个发生在特定设计环境中的可重复出现的设计问题,并提供解决方案
  • 识别并确定类和实例层次上或组件层次上的抽象关系

设计模式的作用?

  • 让开发人员能够控制系统的复杂度,不至于失控
  • 增强设计的可伸缩性/可扩展性(Scalable/scalability)
  • 增强设计的复用
  • 设计领域的通用语言(传递、沟通),设计模式具有很强的业务表述力

初学者掌握设计模式的方法

  • 模仿 – 最快的方法,学习和内化都比较迅速
  • 学习书本 – 比较而言学习较快,内化很慢
  • 不断重构自己的代码,自己去领悟设计模式 – 学习效率最慢,但是理解最透彻,内化效果最好的方法
  • 团队讨论、交流共享 – 学习较快、内化较快,但是时间成本很高,而且团队讨论往往不是高频率的活动

如何提升自己使用设计模式的能力

  • 学习设计模式的使用场景,简单地使用常用的设计模式
  • 改造设计模式,使之更好地满足业务场景
  • 总结业务场景,创建和组合新的设计模式
  • 将重复使用的设计模式及设计模式组合,创建框架。框架形成后,完善和丰富它。
  • 总之,把设计模式使用好,离不开经验,离不开积累
  • 在经验和积累之上,再创新。发现和创造新的模式、新的思想,并将这些新东西注入框架。

主流MVC框架的设计模式及遵守的软件设计原则

摘要

本文以主流的MVC框架为例,比如Java 的SSH、PHP的Symfony和Zend Framework ,在简单地剖析他们的设计原理之后,找到其中使用的设计模式;鉴赏他们的代码实现,查看设计者们都遵守了哪些软件设计原则。作此文,一为学习,二为总结。其中下面所写内容可能并不全面,也可能不准确,但会不断修改完善。

框架模式


MVC 模式

  • y = f (x)
  • View/Response = Controller (Model, Request)

Active Record 模式

  • Yii Active Record
  • Ruby on Rails Active Record

ORM 模式

  • PHP 的 Doctorine
  • Java 的 Hibernate

三/四层架构,3/4-tier Architecture

  • MVC 框架会借鉴三/四层架构的分层思想,对整个系统进行分层,最常见的是在 Model 层中再细分层次。
  • SSH 的 DAO/JavaBean、Service、Response 等
  • Symfony 2 的 Doctrine、Service、view 等

设计模式的使用


前端控制器,Frontend Controller Pattern

拦截过滤器模式,Intercepting Filter Pattern

复合模式,Compound Pattern

  • MVC框架如果不使用复合模式,本文也没有任何意义了

单例模式,Singleton Pattern

  • Controller 、 Context、 View 等框架核心类使用了单例模式

多例模式,Multition Pattern

  • 各类资源池、进程池、线程池(PHP 的后面两个用不上)

策略模式,Strategy Pattern

代理模式,Proxy Pattern

  • 抽象类的使用

工厂模式,Factory Methond Pattern

Action 对象的创建

抽象工厂模式,Abstract Factory Pattern

  • ajxaAction 、 WebAction 、 APIAction 以及 ajax和web组合的Action 或者 ajax和API组合的Action,复杂的框架会使用抽象工厂模式

门面模式,Facade Pattern

Logger 类,将第三方开源Logger类封装统一接口,将第三方 Logger 繁冗接口屏蔽

适配器模式,Adapter Pattern

  • Zend Framework 的 Zend_Db_Adapter 类,将具体不同的数据库(Mysql、Oracle、SQLite)类接口通过Driver类封装,为上层提供统一接口
  • Zend_Cache 对不同cache的接口的再次封装

模版模式,Template Method Pattern

  • 各种复杂逻辑的抽象类
  • 框架的扩展类

建造者模式,Builder Pattern

  • 依赖注入原则的遵守和实践,将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
  • Content 对象的创建者是 Controller
  • Config 对象的创建
  • ORM 的 Object 对象创建

桥梁模式,Bridge Pattern

命令模式,Command Pattern

  • Http 请求的解析就是命令模式
  • Symfony 2 的 Console
  • Symfony 2 的 Command 类

装饰模式,Decorator Pattern

  • ajxaAction 、 WebAction 、 APIAction 以及 ajax和web组合的Action 或者 ajax和API组合的Action

迭代器模式,Iterator Pattern

  • 路由设置的遍历

组合模式,Composite Pattern

观察者模式,Observer Pattern

  • 各种 Trigger 类
  • 各类自定义的扩展

责任链模式,Chain of Responsibility Pattern

  • 路由的解析,尤其是Restful风格的URL,依据路由表(责任链),定位Action 类和方法

访问者模式,Visitor Pattern

状态模式,State Patter

原型模式,Prototype Pattern

  • Action 中,讲执行权移交给另外一个 Action ,比如redirect 或者 forward,获得执行的Action还要和原有Action的属性、状态、上下文一致。原型模式是比较好的选择。

中介者模式,Mediator Pattern

  • 充当中介者的类是 Context,某些框架Context类,为Request、Respose、GlobalConfig 等等单例类进行抽象,为业务代码提供唯一的调用方式,即Context的类提供的接口

解释器模式,Interpreter Pattern

亨元模式,Flyweight Pattern

  • 通用状态类
  • 各种 Util 类
  • HTTP meta 类、特殊字符享元归类

备忘录模式,Memento Pattern

  • 系统日志、状态,以支持回滚
  • 数据库事务

软件设计原则 SOLID


单一职责原则(Simple responsibility pinciple SRP)

开-闭原则(Open-Closed Principle, OCP)

里氏代换原则(Liskov Substitution Principle,LSP)

迪米特法则(Law of Demeter LoD),最少知识原则(Least Knowledge Principle,LKP)

接口隔离原则(Interface Segregation Principle, ISP)

依赖倒置原则(Dependence Inversion Principle)

  • 建造者模式的使用
  • 路由表的使用,或其他各种配置都收集到配置文件中,使用依赖注入容器,实现代码和实现的解耦

Service Locator Patten 总结

核心思想

将构建依赖的接口彻底与依赖者分离,并将此依赖作为“服务”绑定到一个标识符,而后依赖者则可通过这个标识符获取被绑定的依赖。

优点

  • 上层逻辑不依赖于服务接口的具体实现,实现解耦
  • 提高系统的可扩展性
  • 逻辑分层更加简洁清晰,使不同层次的开发人员各司其职,提高团队开发效率

适用场景

与 (Abstract) Factory Patten 比较

  • Factory Patten 创建的所有结果对象多有统一的抽象接口的(更具一般性的)对象
  • Service Locator Patten 创建的服务,异构性程度比较大,接口缺少一般性
  • Factory Patten 单纯为创建对象
  • Service Locator Patten 职能更多些,包括创建服务、配置服务、注入服务的依赖等等

与 Dependence Injection Container 配合

  • Service Locator Patten 和 Dependence Injection并不互斥
  • Service Locator Patten 在定位服务时,创建服务、配置服务、注入服务的依赖通过Dependence Injection Container实现,可以进一步解耦

与 Proxy Patten 配合

  • 对创建的服务进行抽象,增强服务的一般性(即抽象出统一接口)

与 Singleton Patten 配合

  • 承担特定职责的Service Locator的对象无需多次实例化,节省开销
  • 对Service Locator定位的服务缓存做集中存储,存储数据结构存于Service Locator的单例对象即可

优化和扩展

  • 与多种设计模式配合使用
  • 在Service Locator类里定义服务实例化对象的缓冲池,使服务只实例化1次,节省开销

代码示例

symfony2 framework 使用总结

安装

The following exception is caused by a lack of memory and not having swap configured

  • 内存不足
  • 将PHP环境变量memory_limit调大,增大swap 交换内存

Compile Error: Cannot redeclare class Symfony\Component\Debug\Exception\FlattenException

  • 重复定义类名
  • ./vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Exception/FlattenException.php
  • ./vendor/symfony/symfony/src/Symfony/Component/Debug/Exception/FlattenException.php
  • 暂时不要使用Dubug功能了吧

php app/console doctrine:schema:update –force

配置symfony2\app\config\config.yml

mapping_types:
enum: string
set: string
varbinary: string
tinyblob: text

后台自动任务设计和编码总结

资源预估

  • 预估数据量、算法的时间、空间复杂度
  • 依据预估的结果分配合理的资源(内存,CPU),避免资源不够用。

避免数据脏读

自动脚本处理大量数据速度快,尤其有写操作的任务,数据一致性在分布式环境下往往难以保证。这种情况下应该避免数据脏读,比如数据库会有主从复制同步延迟的现象,这时应该强制连接数据库主库。

幂等性

有写操作的脚本,需要考虑脚本执行的幂等性,即在输入相同参数多次运行与运行一次的结果相同。 保证幂等性具体应该考虑一下几点:

  • 逻辑上,检查待处理数据的状态,已经处理过的数据不再处理。
  • 禁止并发运行,不允许运行多个进程同步运行同一任务。如何保证?信号量、Memcache加锁。

参数控制

  • 在设计阶段,应该通过参数控制,限制脚本的操作范围。参数可以是时间,自动脚本周期运行,每次运行,只处理相应周期范围内的数据。
  • 注意默认参数的参数值。

读并发

自动脚本处理速度快,在和其他系统进行交互时,会对其他系统产生大量而密集的请求。

  • 要考虑其他系统的抗并发能力
  • 合理限制单位时间对其他系统的访问次数
  • 尽量将数据集中做批量请求,减少请求次数
  • 在本地做缓存,消除重复的请求。

容错 和 错误处理

  • 容错 自动脚本往往批量处理大量数据,循环处理每条数据。在单次循环中的错误或异常,应该全部捕获,并记下日志,让脚本继续运行。
  • 日志 记录重要操作,包括info、warning、error等级所有日志,形式可以是文本记录或数据库,便于追查和报警。

备灾和数据修复

  • 在不可抗拒因素(断电,其他原因宕机)等引起任务中途突然停止,应该依据日志定位、和追查处理到哪一阶段。修复错误数据。
  • 在保证脚本幂等性的前提下,重复执行脚本可自动处理未完成的工作。

优化

随着业务增长,任务处理数据量越来越大,可能会出现,任务超时,内存不够用的情况,应该及时对自动任务进行优化,优化的方案可以从算法、业务架构上考虑。

netsh winsock reset

倒腾windows,重启后突然上不了网

1.ip dns 设置正常

2.能ping dns的ip

3.nslook 正常

4.浏览器打不开网页,socket链接无法建立。

解决办法

netsh winsock reset

原理

重置Winsock目录,winsock工作在应用层,它提供与底层传输协议无关的高层数据传输编程接口。由症状得知就是问题出在应用层。

应该避免使用的PHP语法

业务逻辑层的魔法函数、魔法变量

魔法函数、魔法变量,最好只在框架代码、数据访问层中使用,在业务逻辑层或表现层使用有以下缺点

  • 会增加代码逻辑的复杂度
  • 不利于逻辑的移植,限制了代码在不同语言间的重用。

变量不作声明和初始化

PHP作为若类型语言,如果不做声明和初始化,非常容易出现脏数据,比如上一次循环结束后没有清理遗留数据等。

使用base

base的作用相当于多继承,多继承在语义上容易产生歧义。应该保持使用单继承的原则。

goto 语句

降低代码的可读性和可维护性

匿名函数(Lambda functions)和

匿名函数不利于重用和维护。

闭包(closures)

避免使用闭包,应该用Namespace或者class进行封装。

直接使用字符串反射变量、类、函数

使用Reflection类代替 ,提高代码可读性和可维护性。

数组遍历使用计数变量

使用 foreach遍历,因为foreach 性能更高,语法更简洁

总结

  • 如果逻辑在使用语言表达时,可以选用多种语法,则应该C/C++、Java 或其他语言中最通用的语法
  • 谨慎使用或者不用只有PHP拥有的特性函数或语法

PHP $_REQUEST 同名变量解析顺序

$_REQUEST包含了$_GET、$_POST、$_COOKIE的所有内容,是它们的集合体。

但有时$_GET和$_COOKIE有同名变量,或者$_POST和$_COOKIE有同名变量,用$_REQUEST调用变量时从哪里取呢?

其实$_REQUEST解析顺序由variables_order设置,php.ini中原文如下:
This directive describes the order in which PHP registers GET, POST, Cookie, Environment and Built-in variables (G, P, C, E & S respectively, often referred to as EGPCS or GPC). Registration is done from left to right, newer values override older values.

配置如下:
variables_order = “GPCS”

该设置配置PHP解析变量顺序,包括$_GET,$_POST,$_COOKIE,$_ENV ,$_SERVER 数组,解析顺序从左到右,后解析新值覆盖旧值。默认设定为EGPCS(Environment,GET,POST,Cookie,Server)。如果将其设为“GP”,会导致 PHP 完全忽略环境变量,cookies 和 server 变量,并用 POST 方法的变量覆盖 GET 方法的同名变量。

Redis 和 Memcahe 比较和总结

Redis 和 Memcahe 比较和总结
项目 Redis Memcache
读速率 批量读效率高
写速率
冗余备份 master-slave模式,交换文件备份,支持binlog
内存使用率 依赖具体使用场景
主从复制 支持master – slaver 不支持 ,若需支持需要代理软件memagent
数据结构 key-value 、Set、 List 、Zset、hash key-value
数据的持久化 支持 不支持
特性 内存数据库、VM特性 内存缓存
存储方式 内存 + 交换文件备份 内存
未来发展 支持cluster

单例测试总结

最近写了比较多的单测,对测试用例做了一下总结。          

网上也有很多讲如何写单例测试的教程和经验总结,而且都比较系统。但这里还是总结一下自己在写单例测试时总结的经验。

1. 经验总结

1).时间和对象          

只对比较稳定的代码写单测。        
从反面讲,还处于经常修改状态的代码是不稳定的代码,针对其写的单测也必然是不稳定的,随着设计和代码的不断更改,单测也需要不断更改。

2). 去除底层依赖          

对一段代码写单测时,其中提供API、IO和DB数据的类或对象应该mock掉,但对这些类也要单独写单测。        
一方面,这些类对系统环境有很大依赖,当环境出现问题时(比如API的服务、DB宕掉),单例测试将不能进行;另一方面,这些类的效率一般比较低,不mock掉会增加单测运行的时间。

3).写单测的误区        

只为覆盖率而写单测。        

因为单测覆盖率往往成为一个系统质量的考核目标,所以在写单测时,可能只为提高单测覆盖率而写单测,过少地从业务的角度去写单测。单测应该是保证系统在各种业务场景下,系统仍能正常工作的工具。行覆盖率或者分支覆盖率的提高,并不代表不同业务场景的覆盖也会提高。

2. 对以后写单测方式改进

1). 建立自己的测试样本工具,比如可以自动生成的不同角色的用户的工具。这样方便或者更能全面地对系统的业务进行自动化测试。

2). 写单测时更多地站在业务的角度,而不是代码的角度。

4种系统间交互方式比较

4种系统间交互方式比较

指相对独立子系统间的交互
指标\方式 API 数据文件 共享数据库 (web系统)根域名cookie
实效性 最高 实时
时间效率 最高
实时空间效率
实时占用带宽
系统设计正交性
系统设计耦合度
实现方式 同步/异步 异步 异步 异步
协议 http request,socket,… ftp,telnet,http,https,iSCSI,nfs… redis,memcache,mysql,MongoDB… http,https
数据结构 自定义 xml,yaml,csv,excel,txt,binany,… database
适用场景 时效性要求高,请求次数多,请求频率很高 时效性要求低,数据量小或中,请求频率最低 时效性要求最高,系统中,某几个对数据请求次数很高,请求频率最高 需要记录在浏览器中的信息
举例 单点登录中,cas服务器和cas客户端之间的交互 财务系统和银行的对账文件 分布式数据库,共享session,异步api,计费系统的数据库 单点登录系统中的登陆信息(ticket等)