webgame中Mysql Deadlock ERROR 1213 (40001)错误的排查历程 案例发现: 从我们正在运营的一款webgame的异常日志中看到一些程序执行MYSQL 语句的报错信息。比较多的是“Deadlock found when trying to get lock; try restarting transaction”,少部分是“Error number: 1205:Lock wait timeout exceeded; try restarting transaction”,如下: 从报错的英文上理解,大约是发生了“死锁”,以及“事务锁等待超时”两个错误异常。而且,都是我们后台PHP常驻进程遇到的问题。异常的代码对应行数上,大约可理解为执行SQL语句的一个指令,并无特殊的东西。有经验的程序员,很容易看出来,这不是程序的异常,这是MYSQL事务中,锁竞争的异常,客户端(PHP常驻进程)是没有语法上的错误的。那该如何排查呢? 一串疑问: 这是什么问题?如何排查?什么时候发生死锁? 我怎么知道他发生了? 发生之后去哪里排查?如何排查?怎么确定他们对应的事务中的所有SQL语句? 分别在哪几个事务中? 谁先锁的?谁后锁的?谁没锁到?谁报的死锁错误? 死锁是什么?为什么发生了? 如何避免?还有哪些因素影响? 毫无头绪: 程序间数据交互,上strace神器? 跟踪谁? 客户端(php)?你知道哪个客户端会发生这个问题?你知道啥时候会发生?在你开始抓包到抓到死锁的期间,这得是多大的数据量? 跟踪谁? 服务端(Mysql)?玩笑开大了吧?mysql以进程模式来处理客户端请求,每次都是一个新的进程,strace -ff参数的话,想想日志文件得被创建多少个,数据量会小么? "万军之中取上将首级"这本事我可没...strace排查这种错误,还是算了吧。 这是谁报的错?显然是mysql,那就从根源找起--MYSQL server。 抓获现场: 我们要还原案发现场,有幸的时,我们有监控记录BINLOG以及SHOW ENGINE INNODB STATUS。到对应mysql服务器上,执行“show engine innodb status”获取INNODB引擎当前信息,大约如下: 这是我精简之后的信息,我抓去了LATEST DETECTED DEADLOCK部分的数据,这部分的数据是INNODB的最后一次发生死锁的信息,更详细的说明见MYSQL官方手册对Standard Monitor and Lock Monitor Output返回结果的解释。 OK,发现一场案例,保存这个INNODB的状态数据备用。迅速到程序异常日志中查看相同时间点是否有死锁发生。果然,我们程序异常日志中记录了这起案例(文章开头的日志)。 再到binlog中抓去这个时间段前后10分钟(大约范围)的mysql sql语句执行日志。 案情分析: engine status中,大约看出MYSQL记录了两个事务之间发生锁竞争时,遗留的数据, 事务1“执行”(注意,这里加了双引号) 发现被修改资源已经被lock_mode X locks了(详情见:INNODB锁模式),准备等待该资源锁被释放。 事务2执行 也发现该资源被lock_mode X locks了。 最后部分,mysql给了很重要的一个数据“WE ROLL BACK TRANSACTION (1)” MYSQL回滚了事物1。既然mysql回滚了1,那么肯定是事务1的语句触发了死锁,被mysql回滚了,也就是应该为程序中的异常日志所记录的那部分。同时,MYSQL执行了事务2,那么事务2的SQL语句肯定被记录在BINLOG中了。 抽丝剥茧: 如何确定事务1、事务2执行了哪些SQL语句呢? 根据show engine innodb status的结果,确定事务2被执行的 SQL语句(业务逻辑的role_id唯一标识): REPLACE INTO `role_fight` (`role_id`, `life_max`, `mana_max`, `attack_physical`, `attack_internal`,****) VALUES ('53016', 4967, 3291, 350, 174, ***) 线程ID(mysql的唯一标识): MySQL thread id 122679850 执行时间(时间线):121128 15:59:46 根据这三个标识,以及BINLOG的起始表示“BEGIN、COMMIT”,几乎可以100%确定该事务所包含的SQL语句。 BINLOG信息大约如下: OK,事务2的SQL语句全部找齐了。那么事务1的呢?如何找? 根据php的异常报错,确定主要包含的语句SQL信息,以及程序跟踪的代码行数,根据代码逻辑去确定该事务的所有SQL语句。再去BINLOG中找到该用户的该业务的类似BINLOG: (请勿过于纠结上面binlog的thread_id跟show engine innodb status的thread_id一致的问题,这是因为我们程序是常驻进程,mysql连接不断开,不销毁,故一致了。而且,此日志是程序发现死锁之后,被mysql回滚之后,又重新提交的事务,算是不同时间点的相同事务) 案情还原: 根据案发现场的两个MYSQL INNODB事务的全部SQL语句,以及形成MYSQL INNODB 死锁的原因(感谢DBA组大雄哥的纠正),我们大约可以这么还原案情: 事务1: UPDATE `role_fight` SET `last_update_life` = '1354089587' WHERE `role_id` = '53016' UPDATE `role_state` SET `state` = 1 WHERE `role_id` = '53016' 事务2: UPDATE `role_state` SET `pet` = 0, `pet_level` = 0 WHERE `role_id` = '53016' REPLACE INTO `role_fight` (`role_id`, `life_max`, `mana_max`, `attack_physical`, `attack_internal`,****) VALUES ('53016', 4967, 3291, 350, 174, ***) 这四条语句构成了本次事务死锁的全部原因。 执行顺序肯定如下: 时间点 事务1 事务2 备注 1 begin 2 begin 3 UPDATE `role_state` SET `pet` = 0, `pet_level` = 0 WHERE `role_id` = '53016' 事务2 给 role_state表 role_id 53016记录上 X 锁 4 UPDATE `role_fight` SET `last_update_life` = '1354089587' WHERE `role_id` = '53016' 事务1 给 role_fight表 role_id 53016记录上 X 锁 5 REPLACE INTO `role_fight` (`role_id`, `life_max`, `mana_max`, `attack_physical`, `attack_internal`,****) VALUES ('53016', 4967, 3291, 350, 174, ***) 这里是重点,事务2给role_fight表role_id的记录上 X 锁,发现被其他人(事务1)上锁了,开始等待他人提交事务...等待... 6 UPDATE `role_state` SET `state` = 1 WHERE `role_id` = '53016' 事物1打算给role_state表role_id为53016记录上 X 排它锁,发现被其他事务上了,而且此事务居然还在等他提交,这时MYSQL立刻回滚事务1...(php发现MYSQL返回死锁信息,随记录该信息到异常日志...发送回滚指令...mysql已经“帮”他回滚了...) 7 【执行成功...】 事务2发现别人释放锁了,OK,获取X锁,修改成功 8 commit PHP程序发现上一条指令执行完毕,且执行无错,即,发送commit指令,提交事务。 好像有个参数%^&#: innodb_lock_wait_timeout参数是干啥的呢?从mysql官方手册上看,此参数是针对锁等待时,一个限定等待时间的参数。跟死锁并无关系,一旦mysql发现死锁,立刻回滚导致死锁的语句。并不会用到该参数。 规避方式: 缩小事务的语句数量 调整SQL语句执行顺序,变“死锁”为“锁等待”,等待一会,总比整个事务回滚掉,全部重新再执行这个流程要强. 其他.请补充 关于锁等待: 缩小事务间SQL语句的数量,减小规模吧。当然,提高检索速度,提高查询时间也是首要因素,我们就发现我们的SQL语句中,有几个没有用到索引,导致锁表,导致锁等待发生... 备注: 年底了,冲KPI的,各位见笑了. 2012-12-04 +2 6 0
使用APC来保护PHP代码 问题就那么发生了: PHP的项目在生产服务器运行时,基本都是明文的文件,如果要保护代码的话,多数使用商业的加密软件,比如Zend Guard或者其他软件。zend也就是php程序语言Zend Engine语法分析引擎的公司出品。该产品会对php代码进行加密转换,费用大约是$600每年(大约4000人民币),按照公司授权。zend guard收费倒不是主要问题,如果单单购买加密功能的话,项目性能会奇差,相信很多朋友都遇到了,尤其是webgame中,刚开服时,几乎都是cpu 100%,load 奇高。使用时,需要服务端在装一个解密的程序,每个脚本文件,在每次被访问时,都需要解码、验证授权。这种繁琐的步骤,无疑浪费了时间、系统资源,也是我们一个项目当初在“友好邻邦”服务器上出现超高占用CPU的原因。(OS load占用高达100倍) 这些都是些什么玩意: 这产品是加密,如果想要程序执行流程优化的话,Zend又很“周到”的为我们提供另外一款产品 Zend Server,每年只需要最低支付$1695美元(大约11000人民币)(其他黄金版、白金版需要上万美元)。。。这样一来,我们的产品被绑架在zend上,如果我们需要定制功能的话,那是不可想象的。 如果是zend产品有bug,那我们只能干等着,等他们解决。除了zend这款,还有另外一款代码加密产品:ioncube,费用倒是不高,大约400美元每年。但用户量少,产品是否稳定不清楚,不开源,社区支持不是很好。 找寻: 有没有一款可以保护代码,社区支持好,用户量大,反馈处理及时,费用低的产品吗? 幸运的是,有这么一款开源产品APC:http://pecl.php.net/package/APC Alternative PHP Cache (APC)是一款php代码加速产品,该产品作为php程序的一个拓展,是一个开放自由的PHP opcode 缓存。它的目标是提供一个自由、 开放,和健全的框架用于缓存和优化PHP的中间代码。 早在3-4个月之前,鸟哥博客上一篇文章《关于PHP的编译和执行分离》中提到APC来作为PHP代码保护的方案。从文中可以看出,鸟哥的想法是每个php文件,导出一个opcode 的bin文件,加载时,也是挨个加载,这样也实现了代码保护,但一个项目几百个php文件的话,也得相应存在几百个bin文件,量比较大,操作比较复杂,管理不方便,不好做版本验证(以后会提到)。末学比较倾向于单个bin文件的导出,单个opcode bin文件的加载。而且,单个bin文件的加载,可以避免项目中出现部分文件跟整体版本不一致的情况发生,运维同事再也不用担心个别文件跟整个项目版本不一致的情况了。 原来是你: APC是替换了php zend引擎的zend_compile_file函数,来决定是否重新对php脚本文件的读取,扫描、解释、编译等步骤。启用apc之后,将跳过读取、扫描、解释、编译这几步,节省内存、CPU开销,直接执行OPCODE。Apc还提供对php源码的opcode的缓存,导出到一个二进制文件中,还提供加载这个二进制文件。起初,我们开始使用这个功能时,遇到该拓展的多个BUG,都已经提交到php官方,分别是: BUG #62757 、BUG #62765 、BUG #62825 ,并积极配合php内核组开发成员重现BUG,抓获coredump,提取bug demo代码,在PHP官方成员-APC拓展开发组长Xinchen Hui(以下称鸟哥 laruence)的帮助下,很快的修复了这些BUG 。 如此繁琐: 这样仍存在一个繁琐的步骤,即php-fpm每次启动时,没有自动加载bin文件,需要管理员去手动执行或者访问脚本,(该脚本内使用apc_bin_load/apc_bin_loadfile函数去加载bin文件)让php-fpm加载bin文件,对于单大区多台前端,以及单服务器运行多个php-fpm主进程来说,如何判断那个php-fpm主进程是否成功加载,这是个很麻烦的问题。对于程序执行opcode清除之后,又需要再次加载,这样是个非常复杂繁琐的过程,也容易出问题。 自力更生: 可否在fpm启动时,自动加载bin的功能呢?末学阅读了APC的源码,照葫芦画瓢的添加了自动加载的opcode bin文件的功能。 我们是在 APC SVN http://svn.php.net/viewvc/pecl/apc/trunk/ 的 r327454版本基础上,在apc_module_init函数中,模块初始化时,增加了opcode bin文件的自动加载功能,patch代码见:https://github.com/cfc4n/cnxct/blob/master/apc_r327454_add_preload_binfile.patch 。有兴趣的朋友可以git使用下。当然,末学水平有限,难免存在BUG,还请见谅。 后来,末学将此patch提交到PHP官方,请他们审核一下,是否可以收入apc官方中,免得我们以后都一直作为patch来使用。或者协助完善,或者给出更好的解决办法。 很伤心,未被采纳,原因是这是小众的需求。但末学表示不解,那apc_preload_path这个自动加载user cache的功能不也是小众需求吗?或许,这是他们委婉的拒绝方式。 (同时,还发现了APC自带的一个未公布的功能,自动加载user cache的功能,配置项为apc.preload_path,配置的值是目录地址,目录下可有多个文件,这些文件也应该是apc_bin_dump/apc_bin_dumpfile函数导出的user cache。看来,官方也有这么个打算。但为何没有实现 opcode bin文件 的自动加载呢?) 如何使用: opcode bin文件导出与导入: 导出的php脚本,末学提供一份,需要的朋友,可以参照修改一下即可: https://github.com/cfc4n/cnxct/blob/master/apc_dump.php 导出成功后,会在PROJECTROOT目录下面生成一个dumproot目录,dumproot目录中的所有文件,就是你需要上传到生产服务器上的文件。bin目录中,会生成两个文件,一个是opcode的bin文件,一个是生成之前的php文件的md5 hash值。其他目录就是项目被保护的目录,目录中文件都是空的php文件。将dumproot目录上传至生产服务器,保持项目路径正确,重启fpm即可。可通过apc拓展源码包内的apc.php查看被缓存文件数。如图: 版本更新、回滚: 遇到bug需要修复时,只需要在opcode bin文件导出服务器上,重新导出一个bin文件,将此bin文件上传到生产服务器,替换到之前的bin文件即可。偶尔会遇到项目因种种原因,需要对项目进行回滚,如果使用我们的方案之后,会发现所有php文件都已经是空的了。而我们的回滚更方便,只需要将之前需要回滚的版本的bin文件,替换到当前的文件即可。或者更改php.ini中apc.preload_binfile的路径。 版本检测 当怀疑个别文件不是某个版本的程序是,怎么办呢?apc提供了另外一个参数 apc.file_md5来存储被cache文件的md5 hash。但当使用已到处的opcode bin文件时,apc却没有重新抓去md5 hash,也没有重新存入apc中,导致我们看到的md5值是个错误的hash值。为此,末学斗胆做了修改,并提供了patch,且提交该BUG #63491到pecl apc官方,斗胆请官方采纳。当打上该patch之后,您可以在apc.php 看到这些信息。并且,跟导出bin文件时,生成的md5 hash文件中校对以确认哦。(2012/12/17 更新,官方已修复) 批量检测也是可以的,参加末学另一个脚本:https://github.com/cfc4n/cnxct/blob/master/check.php APC其他已知bug: 一、php5.4.x中,class中的array()静态成员属性在跨服务器使用时,产生core,已经提交到 BUG #63636(此bug由recoye发现) 临时解决方案如下: 使用php5.4.x时,不要在class中定义 $_user = array(),直接定义为 $_user; 不要使用php5.4.x 删除或注释apc_compile.c的1992、2003行(apc 3.1.13),即 zval_ptr_dtor(&src->default_static_members_table[i]); 字样的代码。 等待PHP官方解决,或微博@鸟哥,催鸟哥解决 二、web访问apc_dump.php 去生成的bin文件,在cli模式下使用时,会在RSHUTDOWN时,即请求结束时,回收系统资源时,会产生core。(该结果的php版本为php5.3.x,初步确认为上一个bug有密切关系,暂未提交至bugs.php.net) 这两个bug以末学的理解来看,与末学的patch无关,为apc的bug。 注意事项: 导出bin文件的服务器上,不要开启 apc.preload_binfile ,避免bin文件存在时,使用bin文件内的代码,而不使用php文件内的代码,导致导出的代码不是你想要的。 php5.4.x class中array 静态成员以及属性 操作系统32bit、64bit的导出的bin文件不能相互乱用。(乱用将产生core) 导出服务器的web路径跟目标生产服务器的web路径一致 生产服务器使用bin文件之后,必须存在同名的php文件,内容可以为空。 apc.ttl、apc.gc_ttl参数保持为0 apc.serializer 如果改用其他序列化函数发生问题,那么请保持为空,或者为 apc.serializer=php apc.num_files_hint的值务必大于等于cache files 的数量,可以先设置大点,再通过生产服务器上观察一段时间来确认 啰嗦一下: 关于32bit\64bit服务器上导出opcode bin文件的乱用问题,将产生如下core 因为long之类类型,在不同bit操作系统上的长度不一致导致内存越界的BUG。 末学不知道这算不算bug,或者您别混用,或者APC程序上也可以解决... 惊喜: 不光可以代码保护,性能提升,方便运维,还可以防黑客入侵哦。(项目目录不允许创建新文件,那么即使黑客改了php脚本,php仍会去apc里读,也不会去读取它。哪怕黑客执行了 apc_cache_clean清除opcode cache,我们的补丁仍会自动加载,那么黑客的行为还是阻拦了。) 备注: 此patch已经在我们的“友好邻邦”服务器上稳定运行3-4个月左右,无异常案例,各位可放心使用。 Apc不算一个php encoder,算是个cache,一个opcode cache,起到中间码缓存的作用,故本文中用“代码保护”,而不是“代码加密”。 但实质上实现了我们的目的,起到保护源代码的作用,那怕可以逆向,也是有一定难度的。 PS:感谢Ivon的openstack,感谢终极修炼师的协助测试。 PPS: 如果你能保证php文件不会被SAPI形式访问到的话,只会被include/require等访问到的话,甚至连同名空文件都不用放。比如单入口框架的项目,只要放个空的入口文件即可。 (感谢recoye纠正) PPPS: 更新日期:2014/10/17 近来,好多朋友问我要完整版,鉴于apc已经被官方删了,那个SVN地址无法正常checkout,故我在硬盘里找了个压缩包,应该是可用的版本,如果有其他异常,请留言,谢谢。 适用于PHP5.3的完整拓展:点击这里下载包含patch的完整版apc 2012-11-29 +39 43 0
nginx、php-fpm默认配置与性能–TCP socket还是unix domain socket 前几天看到一篇博客,提到php所在服务器在大并发情况下,频繁创建TCP短连接,而其所在服务器的2MSL时间过长,导致没有端口可用,系统无法创建TCP socket,而大量报错。博主在后面给的解决方案是减少2MSL的时间,尽快清除TIME_WAIT状态的TCP连接,回收端口。同时,文章结尾写了不用长连接的理由,但这真的是最好的解决办法吗?有其他办法可以更好的做法吗? 类似经历 之所以多这篇文章兴趣这么高,是因为在前段时间,末学也经历了一件类似的优化历程,先简短的描述下我们的服务器架构。如图 想必看到这幅架构图的同学,都比较熟悉吧,也比较简单。“最前面”的nginx 反代负责与玩家的http请求通讯,这里是长连接。在其与后端游戏大区通讯时,使用了短连接,也就是意味着,每处理用户的一个http请求,都要重新与后端的nginx建立一次TCP(http)请求,后端nginx处理完之后就关闭。后端的nginx与php在同一台服务器上,通讯配置如下: 在这个架构图中,反代承担这承上启下的作用,前面是用户,后面是web app服务器,长连接用户,短连接后端。若用户数在1W人,1W人与反代的连接,在反代服务器上都用80端口。但这1W人的每一个请求,反代都要重新使用服务器的端口与后端nginx的80创建tcp socket,来处理请求。若2MSL时间为1分钟以上,同时,玩家两次请求间隔很短,想我们游戏的服务器,两次请求间隔大约5秒(甚至2-3秒)。那么反代的端口,5秒用1W个,10秒2W,15秒3W,20秒4W,25秒5W,30秒6W!!!,前面已经用掉的端口还没被回收掉。那么将会有很多的用户请求被nginx反代接收之后,因没有端口资源而无法与后端nginx创建tcp连接。。。以游戏的其中一个大区为例: 关于这个问题,末学在新浪微博上请教过其他大牛,其中tengine负责人淘叔度前辈以及@120斤的大青蛙前辈告诉我,nginx将在1.1.4版本开始支持ngx_http_upstream_keepalive,3月1号左右,是1.1.16 dev版本,我们的运维团队稍微由于几天,之后在部分小区的服务器上适用了1.1.16,效果不错,Nginx反代上与目标服务器的80端口的TCP连接中处于ESTABLISHED状态的非常多,TIME_WAIT的几乎没有,很稳定。我们几乎计划为了这个性能的提升,决定用dev版了。接着nginx1.2Stable version也出来了,那么也就用这个版本了。至于反代上nginx与后端app server建立TCP连接个数,也就是端口数(满载情况下,维持ESTABLISHED状态)的为nginx worker_processes * ((upstream1 server * keepalive参数数量) + (upstream2 server * keepalive参数数量) + ...),并不多。 如上,端口占用数少了,那么还有其他有点吗?先看一副 socket创建,监听、数据接收发送、关闭与客户端连接的socket流程图: 如上图,nginx反代跟后端nginx的形式跟php-fpm与mysqld socket的模型一样的。 nginx反代跟php-fpm 相对来说,就是客户端,这种做法,他们的每个新socket 请求的发起,都会跟着左边的流程走一遍,一遍又一遍。。。。每次的socket创建,端口等资源申请,与服务端的三次握手,关闭时的四次握手,端口的回收。。。。 改用长连接之后,如下图: 只有php-fpm(或者nginx反代)的子进程分别与mysqld创建1次TCP连接,之后都是send、recv数据的事情了,端口占用也是为数不多的几个(每个fpm子进程将与mysqld维持一个TCP长连接)。 同样,后端服务器的nginx与php-fpm的通讯也是如此,只是请求两没有反代那么大。区别就是IP地址是回环地址127.0.0.1。 既然是回环地址,那么两个服务都是在同一台机器上跑的,既然是同一台机器,为何不用进程间通讯的socket--unix domain socket呢? socket是神马?摘抄一段描述: Socket 可以被定义描述为两个应用通信通道的端点。一个 Socket 端点可以用 Socket 地址来描述, Socket 地址结构由 IP 地址,端口和使用协议组成( TCP or UDP )。http协议可以通过socket实现,socket在传输层上实现。从这个角度来说,socket介于应用层和传输层之间。但是socket作为一种进程通信机制,操作系统分配唯一一个socket号,是依赖于通信协议的,但是这个通信协议不仅仅是 tcp或udp,也可以是其它协议。 在同一台服务器上,用tcp socket与unix domain socket有什么区别? 如图所示,对于进程间通讯的两个程序,unix domain socket的流程不会走到TCP 那层,直接以文件形式,以stream socket通讯。如果是TCP socket,则需要走到IP层。 对于非同一台服务器上,TCP socket走的就更多了。 至于localhost\127.0.0.1以及网络IP他们之间的区别,无意中找到一篇博客写的是以mysql作为验证,来说明localhost不走TCP/IP层,跟127.0.0.1不一样。末学认为他理解错了。他的理由如下 (以下截图均在linux上,windows的没有unix domain socket) mysql连接本机时,不加-h参数: mysql连接本机时,加-h参数且值是localhost: mysql连接本机时,加-h参数且值是127.0.0.1: 那位同学从mysql工具的使用方法、与结果的区别,来理解推导localhost与127.0.0.1的区别,这从方向上就存在问题,我更相信,这是mysql这个程序自己的行为,遇到-h参数没加,或者-h参数的值不是IP形式,且my.cnf里指定mysql的socket路径时,则直接使用unix domain socket来连接服务器,当然,这也是我的猜测,没有去验证,大家听听就好,别相信。 鉴于末学对以上的理解,将服务器的架构配置变更如下 至此,优化还为完毕,若php-fpm与mysql使用mysql_pconnect的话,那么php-fpm的子进程生成模式最好用static模式,若为dynamic模式,可能会出现mysql连接数被占满的情况,这也跟mysql服务的连接超时时间有关,适当调整也容易避免。 不过,我们目前还没用mysql_pconnect,主要原因是我们的代码中,有些事务处理开启之后,对于代码的失败处理,忘记写回滚语句,在短连接的情况下,这个连接的销毁,哪怕客户端没提交ROLLBACK或者COMMIT指令,mysql会自动回滚之前的事务。但使用长连接之后,不同请求会使用同一个MYSQL连接句柄,每个事务开启都会禁用MYSQL的自动提交,即SET AUTOCOMMIT=0语句,这语句会提交之前的事务。对于我们代码忘记写回滚,而直接返回结果的情况下,这是会出大问题的,也是我们目前唯一没有使用MYSQL_pconnect的原因。(计划近期找到没有写回滚语句的代码,修复,继续使用mysql_pconnect) 其实还有,我们php-fpm使用了APC来缓存php file,以及 变量数据等,这些也是有优化的地方(如果有时间的话,则待续)。 回过头来再理解下文章开头那位同学给的解决办法,我仍不能从他给的理由中,理解长连接的缺点,哪怕是解决了TIME_WAIT的问题,但每次创建TCP socket ,连接到服务器时三次握手,关闭TCP socket时的四次握手 这些也是开销。当然,缩短2MSL的时间,也是更好利用服务器资源的一个好方法。 最后,我们调整优化的服务器架构图如下: 好像有点偏离这篇文章的标题了,其实我更想说我不能理解为啥nginx跟php-fpm给的默认配置中,都是TCP socket通讯的,为啥不默认给unix domain socket的默认配置呢?如果说为了方便非同一台服务器时的情况,但给的默认IP也是回环地址呀。 而且,nginx给默认配置中,对于uri请求中的php文件的处理,匹配规则还是老的,之前发生因为NGINX与PHP的配置而导致的安全问题,虽然不是nginx的错,但nginx也可给出更严谨的范例,但仍没有。 值得欣慰的是,在UBUNTU 12.4中,nginx的默认配置有了很大的改进,不管是匹配uri的规则,还是nginx与php-fpm的交互方式: PS:末学只是个web程序员,这些只是末学学习研究如上知识的总结,理解上难免有错,各位请海涵。 2012-05-31 +18 22 0
CodeIgniter框架中关于DB事务处理的设计缺陷 起因: 在我们线上的某个业务中,使用较老版本的CodeIgniter框架,其中的DB类中,对DB事物处理部分存在着一个设计上的缺陷,或许也算不上缺陷吧。但他却影响了我们生产环境,导致连锁反应。对业务产生较大影响,且不容易排查。这个问题,我在今年的3月中旬,曾向http://codeigniter.org.cn/的站长Hex 报告过,之后,我也忘记这件事情了。直到今天,我们线上业务又一次以为这个问题,害的我又排查一次。具体原因,各位且先听我慢慢说完。(这个问题同样存在于最新版本Version 2.1.0中) 分析: 以CodeIgniter框架Version 2.1.0为例,在system\database\DB_driver.php的CI_DB_driver类中第58行有个$_trans_status属性。 同时,这个类的query方法中,有赋值此属性的代码,见文件306、307行 这里也给了注释,告诉我们,如果使用了事物处理,那么这属性将成为一个回滚的决定条件。 在520行的事物提交方法trans_complete中,如下代码 在535行中,如果_trans_status属性如果是false,那么将发生回滚,并且返回false。 在我们的业务代码中,由于程序员疏忽,没有判断trans_complete()方法是否正确执行,直接告诉用户操作成功,但实际上,程序已经向DB下达回滚指令,并未成功更新DB记录。当用户执行下一步操作时,程序又发现相应记录并未更新,又提醒用户上个操作没有完成,通知用户重新执行。如此反复... 排查的过程,也是挺有意思的,起初从PHP代码中,总是不能确定问题所在,并没有把焦点放到trans_complete()方法的返回上。直到后来strace抓包分析,才知道是因为此属性而导致了回滚。 可以看到,在22:54:08.380085时间点处,发送更新SQL语句指令,在22:54:08.380089时间读取返回结果,得到SQL执行错误,不存在字段"cfc4n_user_lock";22:54:08.381791和22:54:08.382703两个时间点,PHP发送停止“自动提交”与“开始事务处理”指令,在 22:54:08.433954 发送“事务回滚”指令。 配合如上的代码分析,可以清楚的知道,因为“UPDATE `cfc4n_user_info` SET `cfc4n_user_lock` = 1 WHERE `cfc4n_user_id` = '6154' AND `cfc4n_user_lock` = 0”这句SQL执行错误,导致$_trans_status属性被设置为FALSE,当代码提交事务时,被trans_complete()方法判断,认为“上一个事务处理”(下面将仔细分析)中存在SQL语句执行失败,决定回滚事务,不提交。 刚刚提到“上一个事务处理”,可能有些朋友不能理解,我们先继续回到代码中来,继续看该属性,同样在trans_complete方法中,542-545行: 也可以很容易的从注释中看明白,设置CI的设计者,为了更严谨的处理 同一个脚本中,存在多个事务时,事务间彼此关系重要,一荣俱荣,一损俱损。这里的trans_strict属性,是个开关,当 trans_strict为false,便是非严格模式,意味着多个事务之间,彼此关系不重要,不影响。当前一个事务中有SQL语句执行失败,影响不到自己。便将_trans_status 设置为TRUE。 毫无疑问,这是个非常周全的考虑。考虑了多个事务之间的关系,保证业务跑在更严谨的代码上。 可是,我们的代码中,错误的SQL语句是执行在事务处理以外的,并不是事务之内。按照我们对事务的认识,可以很清晰的知道,事务之外的SQL相比事务之内的SQL来说,事务之内的SQL更重要,之外的可以允许出错,但事务之内的,务必要正确,不受外界干扰。但CI的框架中,因为一个事务以外的语句执行失败,却导致整个事务回滚...当然,我们的程序员没有对事务提交方法的返回做判断,这也是个问题。 问题已经很清晰了,那么解决方法想必对你来说,是多么的简单。 比如在trans_start方法中,对_trans_status 属性赋值,设置为TRUE,不理会事务外的问题。 结束: 在不明白对方设计意图的情况下,不能盲目的定义对方的代码评价,不管程序作者的水平如何。比自己强,也不能盲目崇拜;比自己弱,更不能乱加指责;理解读懂设计意图,学习他人优秀的设计思路、代码风格、算法效率,这才是一个好习惯。当然codeigniter框架是优秀的。 2012-04-30 +3 7 0
php5.3.8中编译pdo_mysql的艰难历程 我们的一个项目,用了PDO_MYSQL拓展,准备迁移服务器,新环境需要编译安装环境。昨天,运维同事抽空编译了一下,一直编译不上pdo_mysql,同时,公司的一款新webgame临近上线,他们实在太忙,我这个三流运维技术的程序员来试试吧。 运维同事描述: 服务器系统版本:Linux version 2.6.32-71.el6.x86_64 (mockbuild@c6b6.centos.org) (gcc version 4.4.4 20100726 (Red Hat 4.4.4-13) (GCC) ) #1 SMP Fri May 20 03:51:51 BST 2011 编译PHP的参数 之后再次编译 pdo_mysql 拓展 之后, shell里执行 php -i 和 php -m都没有看到 pdo_mysql拓展。搜pdo_mysql,在将路径添加到php.ini中,仍找不到这个扩展,判断为编译失败。 这里是将pdo_mysql作为一个拓展引入使用的。在php5.3中,PHP开发组把mysqlnd作为默认的连接MYSQL的数据库驱动来使用,据官方描述,节省内存40%,速度更快,当然或许是为了解决许可协议的问题。之前PHP连接MYSQL,是调用MYSQL官方提供的C/C++编写的lib_mysql的dll/so,来实现。这个类库同样可以给PYTHON等脚本语言调用,只要按照API规范来。我们改用mysqlnd之后,就不用再为了lib_mysql去安装mysql client了。详情见:mysqlnd插件mysqlnd_ms的介绍。 两种方法都可以,运维同事都尝试了,由于时间关系,他们没做过多的尝试研究,就转向更紧急的项目了。 运维同事下载使用的PDO_MYSQL拓展的地址是 http://pecl.php.net/package/PDO_MYSQL ,里面用很耀眼的颜色,标注如下几行字 This package is not maintained anymore and has been superseded. Package has moved to channel http://svn.php.net/viewvc/php/php-src/trunk/ext/pdo_mysql/, package ext/pdo_mysql. 也就是说,早在2006年5月1(我是根据最后一个打包文件日期猜的,或许不准)之后,PHP已经将这个pdo拓展放到PHP源码的 ext/pdo_mysql下内置了。这里的这个包,将不会在更新维护了。 在PHP官方文档上对pdo_mysql使用mysqlnd的时候,是这么描述的 在php5.3中,已经支持mysqlnd作为数据库连接驱动了,无需再使用libmysql。而在将来的php5.4中,将变为默认的连接驱动。如图: 开启这个类库的 之后,我的编译参数如下 区别是使用php内置的pdo_mysql类库,使用mysqlnd作为连接驱动,不在使用之前的MYSQL官方提供的libmysql.so。 make之后,提示如下错误 这种错误,让我手足无措,GOOGLE搜了下,也没找到相关案例,而且,一直被墙,打不开国外网页。百度搜的结果,还是算了。(中文还行,E文的话,百度确实不行,尤其是程序相关) 之后,尝试make clean,清除之前的编译结果缓存之类。 再次make,有个小意外 再次搜索,这倒是很多网友遇到过,大部分的建议就是无视这个错误,不影响编译。照做。make install ,一路挺顺畅。 接着,php -i / php -m 也没发现pdo_mysql模块。 郁闷无比,决定看下web下的phpinfo结果,发现居然有了。问题终于解决了。。。万岁。。 可是!!! 1,为啥php -i 、php -m 的结果里看不到呢? 思考,为什么呢? 结合刚刚的报错,联想到CLI 模式下的php 脚本(以及相关的php.ini)跟刚刚从web下访问的php程序不是同一个。检查环境变量,以及切换到编译好的目录下执行php -i,发现pdo_mysql、mysqlnd等相关添加的模块了。 2,php.ini里没启用pdo_mysql拓展,为什么还能看的到呢? 这次使用的是PHP内置的类库,不是以新拓展方式加载运行的,所以,不用更改php.ini,再添加相关so路径。 综上所述,文章没有高深的东西,只是有几个需要细心的点。 1,以官方文档为准,一切跟着官方文档来,不轻易采信网络上网友提供的编译参数,包括这边博文。不论对方是老手、大牛,还是其他什么什么有威望的人。他们提供的方法或许跟你当前的环境不一致,时间也相差很大,或许相隔好几年了。 2,确认得到的结果是准确的,怎么说呢,文中的例子中php-i的路径不是我们新编译的,而是之前编译,或者yum安装的,一定要到自己编译的程序目录下,用自己新编译的脚本去执行测试,获得测试结果,下结论,不为了偷懒,不敲路径,直接写程序名进行测试。 3,遇到诡异的错误,我总会想要一个全新的系统,进行安装,以确保不被各种冗余、缓存等垃圾文件干扰。例子中用了make clean进行清除相关缓存,来解决文件缓存问题。(感谢@ivon_lee 的帮助) 4,自动安装脚本要及时更新,当然,不是意味着追求最新版本。例子中的pdo_mysql的拓展,官方提供了更好的方式,不论是效率,资源占用,都有更好的提升,为啥不使用呢。 备注:mysqlnd 的相关有点对比见http://developer.51cto.com/art/200903/115995.htm PS:文中出现的我搜索无果两次错误,如果有人知道,请告诉我,谢谢。 2011-11-25 4 0
WEB开发安全与运维安全浅见 前段时间,同事LeeQueen为公司同事做了个《安全意识防护》的PPT,效果很好,各位新同事也知道日常公工作中,不太注意到的隐私保护问题。PPT中也提到了一些社会工程学的案例,提到了某团购网站前台人员被声称是新开礼品公司的工作人员,以填写信息就送礼品为由,获取到此团购网站的所有员工联系方式。后此团购网站的高层打电话回访,得知对方是猎头公司[详情见乌云网的链接]。此案例非常典型,讨论也尤为激烈,可谓是非常成功的培训。 此次培训之后,职业欠钱[sina微博][腾讯微博]让我给我们开发部门的PHP程序员同事们做一次PHP开发、运维时候一些安全注意事项。由于我在安全这块资历较浅,没有经验,我了解的大家都懂,这个话题太高深,我的水平完全不够,没法写这个PPT。而且,同事里有好几位安全界的前辈,我怎可关公面前耍大刀呢。上次写过一次关于正则表达式PPT,也就是一个月之前,深刻的体会到写PPT的痛苦,尤其是排版。实在不想写了。 后来,经常看到网上的安全工程师提到程序员安全意识较差,以至于同样的危险代码,危险程序,却屡次不改。当然,不是程序员不想改,而是程序员不太理解代码形成安全漏洞的原因。为此,我也觉得有必要提高一下我们程序员的安全意识了。 PPT中写了网络上常见的漏洞,形成原因,漏洞原理,防护建议等;也列举了近年来跟WEB相关的安全漏洞,以程序员的视角理解漏洞原理,并给出修复建议。这些建议仅供参考,不是正确答案。 由于本人水平原因,PPT中难免有错误,请见谅。也请安全界工程师指正。 借用网友的一句话,作为程序员,尽量要做到“知其然,知其所以然”,多多关注web安全,不要给安全工程师添麻烦,不能丢了程序员的脸。 同时,公司招聘PHP程序员,地点上海,公司规模为150人+,做网络游戏研发、运营。招聘条件以及待遇之类的,可以通过以下方式联系我,我们私聊。 http://weibo.com/cfc4nx http://t.qq.com/cfc4nx 邮箱:cfc4n@博客域名点COM 最后,PPT是周末两天写的,这两天老婆挺着6个月的大肚子,为我做饭,老婆实在是太辛苦了,感谢我老婆。 PS:讨厌写PPT[流泪]。 在线阅读: Web开发与运维安全浅见View more presentations from cfc4n. 下载到本地: PPT下载:web开发与运维安全浅见PPT下载,猛点这里 更多亮点在PPT的备注里,各位一定要看备注。转载的朋友麻烦保留PPT完整,包括招人信息。尤其是备注要保留。 ------- PPT在2011/09/19 更新了,更换匹配NGINX 对URI中PHP脚本的正则,考虑到了拓展名为 .phpa .phpb .phpc之类的情况。主要是更换了正则匹配的图,更改了正则内容。 2015/06/09 备注, nginx匹配URI时,对于PHP的解析,更改为 location ~ ^(?:(? 2011-09-19 +13 17 0
YSLOW法则中,为什么yahoo推荐用GET代替POST? 背景: 上上周五,公司前端工程师培训,提到前端优化的一些技巧,当然不能少了yahoo yslow的优化法则。其中有这么一条“Use GET for AJAX Requests”,这些法则从最开始的14条,到现在的35条,一直都时刻关注的。可这么一条的原因我却一点都不清楚。在提问的环节里,我对yahoo WEB前端优化法则推荐AJAX中,使用GET代替POST的原因有疑问,便请教前端工程师。我们的工程师说GET的话,浏览器发送一个包,POST会发两个等等。我对这个解释仍带有疑问,甚至怀疑。培训结束后,我随便搜索了一下,并没有得到理想的结果,可能很少人对Yahoo这么有权威的组织提出的优化法则产生怀疑,也很少人想知道为什么建议这么做,更多的人会唯命是从,墨守成规。之后,我又看了遍优化法则,看到一条是推荐开发者使用AJAX缓存的,这时,一个“伟大”的想法在我脑袋中一闪,莫非是GET请求可以缓存,而POST不可以?接着,我把我这个“伟大”的猜测告诉我的同事们,当初已经是下班时间,好多同事都离开公司,我也匆忙收拾东西下班了,没有仔细查找答案。 周末期间,脑袋中频繁的闪现这个问题,仍对我的想法有怀疑,Yahoo前端这么牛X的团队的想法,岂是我这样的菜鸟能这么容易的猜测推断到的?我对我当初的推测的怀疑就像“小时候就怀疑小JJ绝对不是只用来撒尿那么简单”一样坚定。但向我这么懒惰的同学,实在找不出一点时间来验证我这个想法,空闲的时间宁愿多打几盘CS。一直拖到现在,台风来了,在家宅两天,头都睡扁了,也找不出不写这篇文章的理由。 验证Yahoo推荐的理由: 验证XHR请求中yahoo推荐用GET代替POST做法的理由 POST is implemented in the browsers as a two-step process: sending the headers first, then sending data. So it's best to use GET POST请求分两步:发送http headers,再发送http data HTML+JS代码: 抓包工具:wireshark 提示:wireshark(1.2.5版)在抓http包的时候,会默认合并packet reassembly选项,记得全部去掉。如下图(edit-->Preferences) 我分别发了一个GET、一个POST的XHR(XMLHttpRequest)请求,其数据包如下: 如上图,GET请求发送的数据包为第一个红框内的结果;POST请求发送的数据包为第二个红框内结果,但多了一个第12条数据包(粉红色框内),从10.0.0.108(我的PC)发往98.126.129.106(www.cnxct.com的服务器IP,也就是表单提交的目标服务器IP),wireshark给出的信息是“Continuation or non-HTTP traffic”,这个提示就是说,本次数据包是接着上一次的HTTP请求发的,没有HTTP header,只有http data。 详情如下图 XHR HTTP POST请求的header部分数据: XHR HTTP POST 请求的DATA部分: 结论?: 果然,如伟大的YAhoo前端团队所说,XHR HTTP的POST请求会分为两步,先发HTTP HEADER,再发HTTP DATA部分。 然而,新的疑问又来了。为什么要分为两部?谁(例如W3C这种机构)规定的?每个浏览器都是这样的么?分两次比一次的的效率更高吗? 继续: 带这我新的疑问,又进行了如下尝试:先分浏览器,IE8、Firefox5.0、Chrome13分别发送XHR GET 、XHR POST请求,抓包对比结果。 我惊奇的发现(细心的同学会注意到第三张图中,有椭圆形的框标出那些结果的浏览器是Chrome13),Firefox5发送POST的数据包确是没有像yahoo前端优化法则中提到的那样,分为两次,两个包发送,而是一次完成http headers和 http data的发送。如下图: 大家可以从图中看到line-based text data:application/x-www-from-urlcoded下面就是POST的数据。 这时,又有很多疑问产生了,其他浏览器呢?IE的所有版本都会分两次发么?Firefox的其他版本呢? 当我想一个一个尝试抓包对比的同时,幸运的搜到了关于我这个疑问的PDF(Analysis_of_browser_specific_characteristics.pdf) 其中,提到firefox大部分版本在XP、WIN7、UBUNTU、MAC OS等系统上都是以1个包来实现的,其他常见浏览器都是分为两个包。 相比大家很清楚的知道,HTTP(TCP)完成一次事务,通讯次数越多,越有可能出现故障(网络延迟等因素),开销越大,浏览器(客户端)、服务端都要再进行一次TCP通讯,而且,需要一定的时间。对于我们追求更高的用户体验,需要HTTP通讯都避免到这些缺点,而各大浏览器开发商为何仍这么做呢?firefox的做法是最好的吗? 上面的PDF里,模拟了各种网络环境,比如网络延迟、网络丢包等情况,分别来对比POST请求的1次包和2次包的优缺点。得出的结论是:当网络环境好的情况下,1次包跟2次包的在时间上差别基本可以无视。而在网络环境差的情况下,(2次包)TCP的验证数据包完整性上,有非常大的优点,客户端先告诉服务端即将发送的数据包大小,MD5等标识,当服务端告诉客户端收到(ACK包)的时候,客户端再次向服务端发送POST 的DATA。假如网络环境不好,网络延迟、丢包的时候,服务端会等待(延迟时),客户端重发POST的DATA数据到服务单,来确保本次请求的完整性。 撰写这个PDF的作者在他的博客里详细的描述了写这个博客的起因,以及结果,还有一些关于与yahoo yslow前端团队的一些沟通过程,大家可以在这里阅读下(yahoo 的前端团队好像不太友好,哈)。 结论: 通过参考这个PDF,以及我自己做的抓包测试,让我了解Yahoo YSLOW前端团队的这个推荐(他们没详细的说为啥这么推荐,只是简单的提了下GET请求产生TCP一个包;POST请求,产生2个TCP包。甚至都没告诉我们Firefox的多数版本[可能是所有版本]都是发一个TCP包的。更详细、更深层原因也没说,这里还得感谢下http://loadimpact.com的作者)。 备注: 这里提供下我抓包测试时候的数据包(截图用到的数据包中包含我的一些cookie,没上传。这里的是我新抓的,各位见谅),各位可以参考下,如果我的文字、方向有错,欢迎指出。 IE8、FF5、Chrome13发起XHR请求数据包 当你读到这里时,我承认,我骗了你,文章的内容不光是标题中所写的,为何推荐使用POST代替GET,更多的是抓去TCP、HTTP通讯包来验证各个浏览器是否如YSLOW所述的那样分2次包的过程,以及2次包与1次包的优缺点(PDF中)。希望你看到最后的时候,忘记标题讲的是什么。 更新日期:2014年10月27日 在网上看到一篇文章也是介绍GET跟POST区别的,见:XMLHttpRequest (XHR) Uses Multiple Packets for HTTP POST? 2011-08-07 +14 18 0
《正则表达式》PPT共享(公司内部培训) 近来我越发懒惰,博客很少更新,最近在公司内部做了一个培训,关于正则表达式的,发到博客里来凑数吧,也算分享给其他朋友了。至于正则表达式的重要性,想必不用我说了吧。 发明BSD、TCP/IP、csh、vi和NFS的SUN首席科学家Bill Joy说过,在计算机体系结构领域里,缓存是唯一称得上伟大的思想的。其他的一切发明和技术不过是在不同场景下应用这一思想而已。在计算机软件领域里,情形也大体相似。如果罗列这个领域的伟大发明,绝对不超过二十项。这些包括分组交换网络、WEB、lisp、哈希算法、UNIX、编译技术、关系模型、面向对象、XML这些大名鼎鼎的家伙,而正则表达式绝对不应该被漏掉。 你还记得大学时候的课程编译原理中提到的 NFA\DFA 吗?还有印象吗?你知道PHP编译器扫描你写的PHP代码字符串的时候,是如何区分哪个是变量,哪个是常量的吗?你知道SQL语法分析器是如何判断你的WHERE条件中出现的SELECT\FROM\ WHERE等关键字是字符串,而不是子查询等关键字的? 你知道.ini配置文件跟xml配置文件对程序来说,是如何扫描解析的吗?他们一样吗?如果你想知道,那你务必的熟悉正则表达式,精通正则表达式。不光能写出正则实现功能,还得能写出高效的正则表达式。(编译原理的课程里,有关于NFA\DNA的大篇幅描述,想写出自己的编程语言,正则是躲不掉的。) 不扯淡了,附PPT吧。 (PPT模版是网上网友的,我是借用他的,其中有几张PPT是直接用他的。很好区分,动画效果很炫的(主要是文字层下的颜色,大约10页吧,简单描述那几页)就是原作者的,其他都是我自己写的。作者信息在PPT最后一页有) PDF也放出来,效果没PPTX好,很多层都被遮住了。 猛击这里下载《正则表达式》PPTX 或者您也可以在线阅读: 正则表达式匹配原理 View more presentations from cfc4n 2011-07-18 +9 13 0
编译xhprof时的一个小意外 新项目基本开发完毕,剩余收尾工作。趁美工调整新UI的时间,赶紧在测试机上安装PHP的性能检测利器xhprof。 下载地址http://pecl.php.net/package/xhprof,这里有tar包,我下了http://pecl.php.net/get/xhprof-0.9.2.tgz, 之后,遍完成了。一路很“顺利”。 php.ini如下设置 代码中,程序头部如下设置: 程序最下面: 之后遍看到统计数据结果了。 可是,当我查看[View Full Callgraph]的时候,却提示如下:Error: either we can not find profile data for run_id 4d7f0bd99a12f or the threshold 0.01 is too small or you do not have 'dot' image generation utility installed.,这TMD神马玩意?是什么错呢? 关于dot的介绍,xhprof在这里写出来了: http://mirror.facebook.net/facebook/xhprof/doc.html dot (image generation utility): The callgraph image visualization ([View Callgraph]) feature relies on the presence of Graphviz "dot" utility in your path. "dot" is a utility to draw/generate an image for a directed graph. GOOGLE了半天,也没找到可用的信息。问了下群里的同学,大牛老王告诉俺,xhprof绘制的是png图,系统(graphviz-2.24.0)不支持。才知道绘图的dot拓展没装成功。我的操作系统是UBUNTU 10.10 SERVER版的,也就是dot 不支持PNG。赶紧再次编译下graphviz,看看提示信息有什么缺少的。果然: png: No (missing png.h)果然。。。。 赶紧到libpng官网down分源码,再次编译一下。 SF.NET上地址是 http://sourceforge.net/projects/libpng/files/libpng15/1.5.1/,我下的是http://sourceforge.net/projects/libpng/files/libpng15/1.5.1/libpng-1.5.1.tar.gz/download ,一气呵成,很顺利。 再次编译graphviz的时候,提示如下: 好了,内牛满面。之后就是 make&make install了。 当打开[View Full Callgraph]的时候,果然是性感的资源占用统计图了。 展示下效果图: 优化前: 找到问题所在,稍微调试: 最终代码优化结果: 2011-03-15 +2 6 0
程序员的谦逊编程 译注:开发人员如何从无休止的需求、项目进度中摆脱烦躁的心态,这是每个人都值得思考的话题。无意间看见了这篇文章,恐于太长遂将其精简翻译,错误之处难免欢迎指正。 同时如果你有有关程序员修身养性的观点和心得,欢迎说说你的看法。 -- Split -- 其实每个程序员或多或少都会有个毛病,就是具有某种有强烈的“优越感”。而这种“优越感” 有可能成为激励自身不断发展的动力,同时也有可能成为其职场中的绊脚石。 程序员的这种心态,源自自身掌握的技术、以及多年积累的经验。正如上面所言,这种心态 能使其一切都力求完美、同时准确按照自己的思路行事,能使其技术不断的提升。而另一方 面,如果将这种态度套用给身边其他的人(包括陌生人、同事、朋友甚至家庭),则会发现 他的生活将会如履薄冰 -- 他们只会看见完美的一面而忽略了更多更需要关注的事物。 总而言之,越早发现并解决这一问题,越对自身有利。套用 GeraldWeinberg 在《计算机编程心理学》中的一段话 这种想法是程序员必须解决的,他们对待自己的代码犹如对待自己身体的 一部分,因此他们拒绝所有的负面评价。相反,它们(指代这种心态)应 该及时的引导到正途,使其发挥真正的效用。人非圣贤,这不仅仅是心态 更是精神上的境界,并非所有人都能达到,但仍旧值得去尝试。 症状 那么,你如何得知这种“优越感”正在伤害到自己?除了应付那些没完没了的催促项目进度的 电话,以及给同事擦屁股的优化工程,其它的现象并非显而易见。 其实就我个人而言,时常也会自我责备,这就能窥出事态的严重。例如一方面你对项目疲于 奔命,而同时却忽略身边的人对你表达的看法(该死,这个时候我应该放下手头的工作听他 们说完的)。或者你“假装”静下心来听取他们的意见,但不就繁杂的工作却让你左耳进右耳 出。 其他的些症状 如上面所说的,不会妥善处理批评 不放心同伴的代码,经常性地对他们进行代码审查(Review) 报复性的编写大量充斥着错误的代码 个人的消极心态,对自身和团队造成不利的影响 必须要求进行测试,但出发点却是炫耀 对事物的看法仅仅局限于个人或者本职位的角度 这不仅仅是你个人的事情,编程以及项目开发实际上是团队活动。了解到这些,你将会意识到 你的心态将会直接影响到你的同事。 事实就是这样,当我对您的代码提出写意见甚至批评时,你应该听、并且认 真的听,这样你才能理解我的看法。 有可能最糟糕的情况就是,即便早已经收到其他同事的提醒,当事人已经陷入此泥潭无法自拔。 准则 让我们回到文章的题目本身,正如上面的例子中看到,“谦逊编程”不是编程技术本身,而是 种态度,但它的确会比你掌握的某种技术要有用的得多。 行为准则的确能改变人的心态,下面是些不成文的建议,或许你可以尝试下 不要草率的宣布你的决定,在大多数情况下,你应该和你们的同事们讨论 不要使用这些论调,这非常让人感到不适:“这是见过的最糟糕的代码了”,换之你可以这样说,“我有个更好的解决方案,要不看看?” 不要轻易认为他们没有考虑到你想的方式,即便很不幸是这样,应该善意的提醒。例如“你觉得我这个看法怎么样...” 不要无理由的批评你认为很弱智的现象,例如“我觉得 DBA 脑门子被夹了,这个字段竟然使用 INT 型” 更多的,可以参考 Tech Republic 中的“谦逊编程”十条诫律: 理解和接受你将犯下的“错误”。 重点是及早的发现你已经犯下的错误,当代码投入使用以后,改动起来就会非常的困难。 你的代码不能代表你的人。 记住始终要 Review 你的代码,即便你已经认为无懈可击,经验证明总能发现些错误。 不管怎么样,有些“奇技淫巧”总能派上用场,而可能这些技巧别人知道的比你更多。 如果你坚持不耻下问,你的同伴总能分享你更多。 不要在完全没有沟通的情况下,自作多情的进行代码重构。 当你确定要更改别人的代码时,必须加上良好的修改记录,这也是出于对他人的种尊重。 对待那些新手要保持充分的尊重、细心以及耐心。 记住当他们成长起来后,能帮你解决的问题会比你想象中的还要多。 唯一不变的是变化。 怀着开放的心态对待变化,对于各种需求、平台甚至开发工具的变更,应该是迅速适应而不是牢骚满腹 -- 这样解决不了问题。 真正的权威来自学识,而不是立场。 权威源自学识、尊重源自权威。 优雅的接受失败。 最终你的一些观点将会被推翻,即便你有能力证明你的观点是正确的,请不要重复的争辩。帮助其他人意识到这点的最好工具,就是你的理解以及时间。 不要成为“办公室男”。 不要在昏暗的办公室里独自喝着可乐敲着代码。当与外界隔绝,离开同伴的视线,也就说明你离开了一个开放、合作的环境。 批判代码而不是编写它的人。 要知道你的意见可以影响到代码也可以影响到其人,如果你想尝试下如何打击别人的自信并造成冲突,那么尝试下吧。 此文转载自:http://www.gracecode.com/archives/2971/ ,自勉自勉。 2011-01-21 1 0