转载

如何拯救一个有历史问题的PHP项目

本文未经许可禁止转载,如有转载意愿请与作者联系。

1、项目历史

我们团队现在做的是一个微信第三方平台项目,项目起步时间不长,到现在差不多两年。起初是个探索性的小项目,但是随着业务的发展,已有的结构渐渐不能满足业务需求以及高峰时段的压力;不仅如此,一些历史问题也给我的开发流程带来了不少问题。所以这半年以来,再满足产品高速迭代的需求的前提下,我们也对后台框架以及服务器的结构做了持续的调整和优化。本文主要是针对后台部分的变动进行整理。

项目开始的时候,组里还有其他两个项目处于维护阶段。并且这个项目后台的起步也是直接从原来的项目里 copy 了部分结构代码并在这基础上进行改动,所以有很多历史遗留问题在里面。项目本身的框架还是 11 年左右的一个 cakePHP 的变种框架,项目框架从来没有更新过,甚至项目本身的 String 类封装每次调用都会触发 bug。

在服务器上,三个项目最开始的时候是共享所有的服务器资源的。而较老的项目由于只维护不开发,一些业务组件,比如 memcached、sphinx 中文版(Coreseek)以及一些 PHP 的扩展早已没有更新,但是每次服务器变更的时候还得考虑这些东西。

最近半年,通过不断的调整和修改,我们解决了大部分历史遗留问题,并成功升级到 PHP7。

2、基本能力

PHP 最近几年发展势头也是很快,Composer、Laravel 等组件和框架逐渐流行起来。借助第三方的力量能够很大程度的简化自己的工作、提高开发效率。

虽然以前项目中也有引入 PHPExcel 等第三方库,但都是通过下载源码丢到项目本身的方式来做的。这样做的问题是引入并不方便,并且一般不会及时通过升级来解决一些第三方库的 bug、漏洞等问题。为了提高框架的基础能力,我们在框架中引入了 Composer 。

在 Compoer 的引入的同时我们也引入了一些优秀的库来解决基础问题。之前项目中的 cURL 封装到处都是,不统一不是很理想,所以在有了 Composer 之后项目中直接加入了 guzzle 。同样,为了解决异常日志栈的记录问题, monolog 也被加入到项目中。

这其中最大的一个变更是,受限于框架底层实现的问题,框架本身的 ORM 极其难用,并且整个是基于 mysql_query 来实现的。所以我们设法直接在框架中集成了 Laravel 的 ORM eloquent 减轻痛苦。

至此,项目解决了四个基本问题:

  1. 第三方库的引入;
  2. 服务端发送请求的处理;
  3. 项目日志记录;
  4. ORM 的易用性。

这些改动都是为了解决开发上的问题。但开发上某些问题依旧存在:老的代码难以改动,并且 PHP 5.5 之后 mysql_query 系列的方法已经逐步被废弃。所以在后续的修改中一次性将这些方法都替换成了 mysqli 的实现,这也使得后续的 PHP7 升级工作能够继续进行下去。

3、项目拆分

项目的拆分分为两个部分,一个是逐步隔离两个老项目和新项目之间的资源、使新项目能够摆脱历史包袱快步前进;第二个是对新项目本身的拆分工作。

上文中提到,项目起步时的基础代码和环境都是从原来的两个项目里直接 copy 过来的,甚至硬件资源都是公用的。因为几个项目整个的战线拉的很长,所以一些依赖的组件和服务有些都是很古老的,甚至是没有继续维护的。也有一些内部的组件依赖。这导致无论想做什么新的尝试都需要瞻前顾后,总是要考虑到是不是会影响到原来的东西。

由于硬件资源完全共享,而一些业务高峰时间带来的压力总是会导致服务器资源占用飙升,几个项目的服务同时挂掉,并且经常短时间内难以恢复正常。为了解决这个问题,我们对之前的两个项目和这个项目的物理资源分配做了一些调整,并做了隔离。

在项目上,最开始这个项目的所有功能模块,包括消息转发系统和主站部分全部都是耦合在一起的一套代码,由于框架本身的问题,效率并不高。在项目的改进中,我们逐渐分离了核心的消息系统部分并采用性能更高的框架(phalcon)完全重写。硬件上消息系统和主站也进行了分开部署,使两个部分不会相互影响。

4、持续改进

技术总是在持续的优化中进步,项目改进的脚步也从不曾停止。

在持续的改进过程中,我们继续进行了以下的部分工作:

  1. 使用 Redis 完全替换了以前依赖的 memcached 和 memcacheq,在缓存和消息处理上变得结构单一易于维护,引入 Redis 集群提高可靠性;
  2. 引入优秀第三方的库封装微信 API 调用的部分,节省工作时间;
  3. 在发布环境,增加了预发布环境尽量靠近线上环境,降低每周发布之后的 bug 量;
  4. 在开发环境打开所有 E_NOTICE 及以上的告警,尽量减少 bug 并优化代码逻辑,搭建 sentry 平台记录线上的错误及异常日志,便于查找和及时修复;
  5. 使用 elasticsearch 存储服务器日志以及全文检索的内容;
  6. 整理所有 crontab 脚本,将所有本身需要长期服务的任务使用 supervisor 集中管理;
  7. 推动 https 切换。

5、版本升级

PHP7 带来的内存使用量降低和性能优化是很令人心动的,在很长一段时间里,受限与项目环境的原因,项目基本上没有可能在短期内升级。

但在升级 PHP7 之前实际上已经进行过另外一次升级工作。起因是机房迁移,需要将所有的服务器都迁移到新的机器上去。借助这个机会,我们把所有的服务组件都进行了整理和重新编译,并且相应的升级了 Nginx(1.4 到 1.8)、openssl(解决之前 openssl 爆出的一些列安全问题),也将 PHP 从 5.4 升级到了 5.6。

在这期间,由于对 MySQL 存储 emoji 表情有需求,通过较长时间调研和实践,最终也将 MySQL 从 5.5 升级到了 5.7(但请注意:MySQL 主从同步,无法直接从 5.5 同步到 5.7,只能通过 5.5 -> 5.6 -> 5.7 这种方式)。

之后的几个月里,项目基本上保持着告诉稳定的状态运行着。经过一段时间的等待,项目需要的几个开源的核心扩展完成 PHP7 版本的开发工作之后,我们也开始进行升级 PHP7 的工作。

升级的工作主要包括两部分:检查语法兼容性,解决扩展兼容问题。因为有些扩展并不是来自开源社区,需要自己手动去改。

整个升级的节奏也是分成了两个部分:先动消息框架,再升级主站。由于两个老项目的状态,所以决定不对其进行变更,还是维持在原来的状态。丢掉包袱才能快步前进。

消息框架依赖较少,主要就是 phalcon 和 redis,所以升级过程比较顺利。

主站依赖的内部扩展较多,语法兼容性问题也比较多。所以使用了开源项目检查语法兼容性。对框架底层做了一些改动,这里面最主要的还是从 mysql 换到 mysqli 的工作。

扩展部分在编译过程中就可以把大部分不兼容的语法暴露出来,进行修改并重新编译测试即可。

总结起来,一共经历过以下步骤:

  1. 重新编译所有服务组件、升级 Nginx(1.4 到 1.6),PHP (5.4 到 5.6);
  2. 升级 MySQL (5.5 到 5.7);
  3. 消息框架升级 PHP7;
  4. 修改主站框架,将底层 mysql_query 替换为 mysqli
  5. 检查主站代码兼容性;
  6. 修改主站使用的内部扩展,使其兼容 PHP7;
  7. 主站升级 PHP7。

6、结语

随着业务的发展,项目也会面对越来越多的问题。技术上不断前进,不断提高个人水平。做好迎接更多的挑战的准备,也总会有更多的惊喜。

原文  http://0x1.im/blog/php/how-to-save-a-php-project.html
正文到此结束
Loading...