Linux Shell 命令小技巧

Top 监控某一多进程任务

以nginx 为例: d PL=pidof nginx

PL=${PL//\ /,}

top -p $PL

SSH

ssh scp等消除每次问yes/no

ssh -o StrictHostKeyChecking no dev.zuocheng.net

链接复用

创建ssh sock链接目录

mkdir ~/.ssh/socks

修改~/.ssh/config文件,若该文件不存在,则创建。增加以下内容:

Host *
    KeepAlive yes
    ServerAliveInterval 60
    ControlMaster auto
    ControlPersist yes
    ControlPath ~/.ssh/socks/%h-%p-%r

grep

常用参数 -Irn

find

清空所有log文件

find ./ -name *.log | xargs truncate -s 0

删除所有nohup文件

find ./ -name nohup.out | xargs rm -rf

find ./ -name *.swp | xargs rm -rf

删除过期日志(60天前)

find ./ -mtime +60 -name log.* -exec rm -rf {} \;

使用正则删除过期日志(7天有效期)
30 4 * * * (find /workdir -type f -mtime +7 -regex '.+?/log/app\.20[0-9]+' -exec rm -f {} \;)

把当前目录下所有文件都MD5

find ./ -type f -exec md5sum {} \; | sort -k 2 > md5sum

date

依据时间戳获取时间

date -d @1525856172

生成日期
DATE=$(date +%Y%m%d)

比较两个文件夹的不同

生成MD5值 find ./dir -type f -exec md5sum {} \; > md5sum.txt

生成MD5值,并排除日志文件 find "dir" -path 'dir/log*' -prune -o -type f -exec md5sum {} \; > md5sum.txt

校验md5值 md5sum -c > md5sum.txt

sed 相关

sed -i '/^$/d' 删除空行
sed -i 's/\s*$//g' #删除行尾空白

使用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;
}

C/C++ 中如何写“空语句”

最近我的同事和一些网友都说C/C++中“空语句”(就是单独一个分号的语句)具有延时的作用,可以用来写延时代码。其实这是一种错误的理解。

首先,有人认为空语句经编译后,生成汇编代码是“NOP”指令,NOP指令是空操作指令,执行一个指令周期时间,所以认为C/C++中的“空语句”还有延时的功能,其实这是错误的,“空语句”是不会生成任何有效的指令代码的,是不具有延时做用的。

有人说如下代码是具有延时做用,实际上下边的延时功能主要是加法运算和条件判断运算指令起到了延时的作用。

define DELAY asm(“nop”);

Google C++ 代码风格示例

They say a good example is worth 100 pages of API documentation, a million directives, or a thousand words.

最近项目中决定使用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

系统管理工具 cockpit

source /etc/os-release
sudo apt install -t ${VERSION_CODENAME}-backports cockpit -y

安装第三方组件,可以参考 https://www.bilibili.com/read/cv20917247

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 –all
  • git merge
  • git branch -b develop origin/develop
  • git clean -df

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

常用配置

用户名和密码

$ git config --global user.name liuzuocheng

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

权限验证

在git pull/push/clone 时输入密码会比较麻烦。在所有的解决方法中,除了使用密钥之外,还可以使用.git-credentials配置文件。这种方法的坏处则是密码用明文存储,安全风险大。

touch ${HOME}/.git-credentials
输入内容
https://username:password@gitlab.zuocheng.net
执行下面命令使其生效
git config --global credential.helper store

默认编辑器

$ 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

分支间文件差异大,频繁切换显示aborting, git status后发现大量为trace的文件

通过下面的命令清理它们
git clean -df

解决版本冲突

暂存本地修改

$ 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

示例,打包master分支

git archive --format tar.gz --output "../project.tar.gz" master

与 Gerrit 配合使用

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

submodule

一键更新所有submodule,包含所有层级

git submodule update --init --recursive

submodule 更新后,版本不一致,引发意外出core

重新初始化并拉取所有的submodule

git submodule deinit -f --all
git submodule update --init --recursive

高级用法

git rebase

这是介绍rebase用法特别好的博客,http://blog.chinaunix.net/uid-27714502-id-3436706.html

git清除所有commit历史记录

# 新建初始分支
git checkout --orphan new_branch 
# 添加文件
git add -A
# 提交修改
git commit -m "Initial commit" -s
# 覆盖原master分支后push
git branch -D master
git branch -m master
git push origin master -f

常见异常处理

fatal: Not possible to fast-forward, aborting.

处理方法是 rebase
git pull origin master --rebase
git rebase --continue

error: remote unpack failed: error Missing tree

执行push时添加–no-thin选项,参考stackoverflow
git push --no-thin

Server does not allow request for unadvertised object

常为更改了submodule的引用地址造成,可以使用git submodule sync命令解决,参考stackoverflow

敏捷软件开发 – 总结

敏捷软件开发简介

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

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 可得到下载链接

浏览器

  • Chrome
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
sudo apt-get update
sudo apt-get install google-chrome-stable

系统工具

  • SysPeek

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

远程桌面

  • xrdp

sudo apt-get install xrdp

下载工具

  • 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.不使用共享的标志变量。

内存空间使用的安全性

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

  • 小心使用栈空间

逻辑的安全性

  • 参数检查一定要严格,任何一个不合法的参数能让线程异常,然后整个app异常

  • 容错

内存泄露原因

软件的稳定性

  • 如果一个线程体挂掉

  • 悬空指针

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

线程空间限制

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

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

高效使用栈空间技巧

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

多线程下的设计模式

资源竞争与互斥

  • 单例模式

循环中不使用wait

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

线程安全

语言特性 valatile

内存泄露

和多进程编程相同的陷阱

运维的安全性

如果程序在线程在启动时固定,则在可动态扩展的容器中,可能会有因为线程数过多而不能充分使用cpu资源。在性能压力测试时,常常会引起在cpu资源使用率较低时,系统则已经到达瓶颈。

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。

h3 {
    font-weight: bold;
    front-size: 12px;
}

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

WP Super Cache 插件

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

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

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

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

在content-entry 里添加如下代码

转载请注明来源,原地址保持永久更新。
博客首页:作程的技术博客
文章标题:>《
本文链接:>

访问 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、展示数等等

更新、安装 直接下载,不使用FTP

修改wp-config.php,添加命令:

define("FS_METHOD","direct"); 
define("FS_CHMOD_DIR", 0777); 
define("FS_CHMOD_FILE", 0777); 

系统优化

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

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

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

添加mysqld配置

[mysqld] wait_timeout=120 interactive_timeout=120

统计过滤机器人列表

服务器迁移

数据迁移

把数据表导出
[admin@src]mysqldump -uroot -password BLOG > /tmp/DB.sql
scp 到目标机 scp /tmp/DB.sql target:/tmp
在目标机导入数据库
mysql>use DB; souce /tmp/DB.sql;
配置权限
grant all privileges on DB to user@'%' identified by 'password';
grant all privileges on DB.* to user@'%' identified by 'password';
flush privileges;

msyql 8之后版本
CREATE USER 'user_name'@'%' IDENTIFIED BY 'pass';
grant all privileges on *.* to 'user_name'@'%';
flush privileges;

工程师手记-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

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

系统的鲁棒性

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

单机缓存

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

软件设计之浮点运算

工程项目中,需要注意浮点数的点

  • 不能有 == 比较
  • 上下游注意nan值检查

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

二进制表示

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

IEEE 754 标准下的浮点

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

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

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

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

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

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

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

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

附录

优秀文档总结:https://gywbd.github.io/posts/2015/9/floating-point-number.html

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). 写单测时更多地站在业务的角度,而不是代码的角度。