Laravel 项目深度优化过程

简述

公司的系统是面向商户营销的CRM系统,采用的是saas模式而非独立部署,用户表200W,日活3W,日增长5000+,每天的请求量总量目前是200多W次,其中服务端的请求量主要来自前端API请求+商户ERP数据同歩+微信事件回调。以下均在抛去系统设计的问题不讨论,小公司的产品活下去才是第一要素,只能现有的情况下尽量优化,一步步摸着石头过河。

以下为2019年的方案,后续迭代暂未更新。

服务配置:

  1. 单台阿里云ECS服务器 - 12核24G (redis/nginx/php7.1/laravel5.7)

  2. 单台阿里云RDS数据库 - 2核4G (mysql)

  3. Supervisor开多个进程跑队列任务

  4. php的woker进程设为100个

  5. 跟据laravel优化文档做了优化https://learnku.com/articles/2020/ten-laravel-5-program-optimization-techniques
    Laravel 项目深度优化过程

  6. php-fpm实际并发如下(siege压测-2核4G测试环境 ):

  7. 大部分简单逻辑接口并发300QPS

  8. 数据库操作多的接口并发70QPS

  9. 全查缓存接口接口并发550QPS

存在问题

当三个十万粉丝的商家同时发推文和多个商家线下门店做促销活动时,一秒点击人数过多或线下ERP消费小票、积分回传过多,服务器就会崩溃掉,服务器崩溃的时间如果超过几分钟不能响应接口,就会触发微信事件的3次重推机制和ERP回传5次重推机制,触发重推机制的情况下我们是可以关闭erp的回传,但无法停止80家商户的微信事件重推,这个时侯系统一般就会瘫痪1个多小时,瘫痪结束后又会面临数据找回的问题

一阶段方案

引入Laravel-S,利用swoole的长驻内存的机制, 常驻内存后每个请求减少了一大堆的初始化开销,能一定程度的增加并发数,实测要连接数据库的接口中的并发数提升了35% ~ 50%,不连接数据库的接口中的并发数提升400% ~ 500%,整体的请求失败率大幅度降低,正式环境下每天200多W个请求整体是正常的,没有发现有内存泄漏,在整体修改不大的情况下得到这个提升是很值得满意的。在此感谢Laravel-S作者和swoole社区,以下是Laravel-S作者对数据库连接提升不大的解释

Laravel 项目深度优化过程

laravel-s文档
github.com/hhxsv5/laravel-s

在此swoole实际并发如下(siege压测-2核4G测试环境):

  1. 大部分简单逻辑接口600QPS
  2. 数据库操作多的接口并发96QPS
  3. 全查缓存接口接口并发2000QPS

Laravel 项目深度优化过程

引入心得

  • 任务队列和定时任务是可以直接用laravel原生的,这部份还是用php-fpm跑,不用过多改动,这样引入的过程中只用去改控制器上的业务代码,当然也可以把定时任务也写到swoole中,这样就可能得到毫秒级的定时任务。项目整体的运行模式为swoole(业务代码)+php-fpm(任务队列和定时任务)
  • 控制器的业务逻辑中不要用$this来保存和调用变量,laravel下这一部份会被处理为单例模式的,Swoole Server下,所有单例对象会常驻于内存,这个时候单例对象的生命周期与FPM不同,单例对象依在请求结束后不会被清除,需要开发者自己维护单例的状态
  • 认真的看文档的注意事项,文档能解决90%的问题,不能解决的加群或提issue

二阶段方案

在swoole的基础上引入负载均衡做服务器集群和分离项目内的模块(独立ERP模块、微信模块),用堆机器来提高总的并发数

负载均衡存在问题

  1. 文件如何同步 - 已解决,大部分文件已上云,小部分用nginx转发到特定服务器
  2. 日志如何收集 - 已解决,ELK日志分析系统
  3. session、缓存如何同步 - 已解决,全局切换redis
  4. 多服务器代码同步自动更新 - 已解决,jenkins + gitee(WebHooks)

分离模块存在问题

  1. 模型、队列、事件监听器高度藕合如何分离 - 半解决,多个服务器代码相同,只是不同业务访问特定服务器

以上的问题附了代码如何自动更新外,其实都可以在项目开始前设计好,希望各位如果开新项目不妨直接考虑上负载均衡会遇到的问题,尽量文件上云,尽量不要用本地文件缓存,尽量在一开始分离模块。

后续

3台机器搭好了负载均衡,同时代码层和业务层也做了很多的优化,能加缓存接口的加缓存,能走异步的不走同步,在全解决二阶段方案现有的问题后,相信在现有的下架构是可以撑半年时间来开发业务的了。
到此,laravel的优化应该是到头了,后面就是数据库的优化、引入mq队列、引入Elasticsearch、用协程的swoole框架如hyperf、easyswoole之类的重构,这类型的框架都相对应的提供了微服务的使用。

在12月在深圳开源中国开发者大会上听了韩天峰对php的分享,也听了下午的架构专场,目前看来在对于中大型的项目上,php的生态是远远比不上java的,像Sharding-JDBC的分布式数据库、Spring Cloud的微服务实现,php在这方面上还是很初级,希望以后在面向中大型项目上的生态能更好吧,在小型项目上php已经十分优秀了。

2020年12月更新

自2019年尾完成第二阶段的改造后,2020年公司的业务量增长200%,因此2020年做了以下优化,但系统还要不断优化。

  • 增加更多的服务器
  • 全面使用docker
  • 引入Elasticsearch搜索
  • 引入阿里云polardb集群
  • 引入阿里云日志系统
  • 引入数据仓库
  • 重构数据统计业务
  • 负载均衡增加业务隔离
  • 代码增加业务隔离
本作品采用《CC 协议》,转载必须注明作者和本文链接
未经允许禁止转载 -- 苦力小林,
本帖由 Summer 于 4年前 加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 59

代码部署方案应该很多吧,自己可以搭建jenkins,syncd或者用云服务商的持续集成,但是原理都是差不多的;日志我们使用的自建ELK,也可以使用阿里云的日志服务,挺方便的

4年前 评论
oidns 4年前
Double-Jin (楼主) 4年前

超链貌似有问题

4年前 评论
Double-Jin (楼主) 4年前

多机部署,简单的话,好像有个叫 deployer 的 php 插件,部署都不用进远程

4年前 评论
Double-Jin (楼主) 4年前

Supervisor 跑着正常吗?我用着总是死表,不知道怎么解决

4年前 评论
Double-Jin (楼主) 4年前
jamesZhao (作者) 4年前
_null_ 4年前
三石寰宇 4年前

General error: 2006 MySQL server has gone away 错误有可能是MySQL服务端超时主动关闭长连接导致的,可以往这个方向排查

4年前 评论

12 核 24G建议换成多台4核4G试试

4年前 评论

你用laravel-s是不是没打开Mysql长连接选项

4年前 评论
Double-Jin (楼主) 4年前

General error: 2006 MySQL server has gone away 貌似是mysql没有断线重连,常驻进程的都有这个毛病好像
搞个断线重连机制

4年前 评论
Double-Jin (楼主) 4年前
lidongyoo 3年前

可以直接上阿里云的kubernetes (serverless) 服务

4年前 评论

不要单机部署,你可以考虑 2核4G作为主机,前面套一程负载均衡。你的12核24G可以分成6台ECS,然后给这6台分组,每组3台。负载均衡中的7层负载中吧特定的URL导向A组,其他导向B组。然后弹性伸缩1台。这一台备用,也是母鸡,用来操作和更新软件,更新完打包成ecs镜像,然后滚动升级替换到其他的ECS。这样你特定的功能即使由于性能全部导向A组ECS,相当于你B组来负载核心业务,至少不让他挂了。

4年前 评论
Double-Jin (楼主) 4年前
terranc 4年前
terranc 4年前
Double-Jin (楼主) 4年前
terranc 4年前
方圆百里找对手 (作者) 4年前
ma1232006 4年前

优秀的文章。

Redis 可以把很多接口缓存化,ElasticSearch 可以大幅提高搜索速度:例如某商品的关联商品。

架构上还是尽量单机,可以先尝试对 web server 和数据库提高配置,能解决大部分性能问题。负载均衡是会让不可用时间上升一个数量级的,能不用就不用,300W 这个量级还行,用好缓存的话单机没问题的。

4年前 评论
Double-Jin (楼主) 4年前
Leesinyii 1年前

牛逼呀,非常羡慕业务这么大的公司 :+1:现在我工作这里还没这个量呢

4年前 评论
  1. 不要单机,单机风险太大,如果出问题,全挂了。
  2. 异步处理,一些逻辑或是数据统计,放到一个异步队列里面。
  3. 微信本来就是异步的过程,把回调放到一个队列里,用固定的进程数去跑这个队列,减少同时并发对数据库的压力
4年前 评论
Double-Jin (楼主) 4年前
dongjw321 (作者) 4年前
Double-Jin (楼主) 4年前
johnlui 4年前
dongjw321 (作者) 4年前
johnlui 4年前
johnlui 4年前
dongjw321 (作者) 4年前

认真看了下文章,很赞哦。

貌似之前有差不多的业务场景,对于多机部署,当时使用的是gitlab+Jenkins,不过这个方案感觉并不是特别好,看文章的同时,有个不成熟的想法,因为是多机器,更新严格意义来说必然有先后,假如n台机器,是否可以流量切到n2/3,更新n/3机器,然后依次切换知道n都完成。
另外,数据同步当时用的是canal(监听binlog)+mq。
最后,看到提到的很多优化,那么数据库设计这一块是不是也能去尝试做一些优化,水平拆分,垂直拆分。还有引入连接池。

4年前 评论
Double-Jin (楼主) 4年前

楼主,有没有你公司的资料可以了解下阿 :blush:

4年前 评论
Double-Jin (楼主) 4年前
llocry00 (作者) 3年前
Double-Jin (楼主) 3年前

对国内CRM这块还挺感兴趣的,作者可以多普及下这方面的内容。

3年前 评论
Double-Jin (楼主) 3年前

2020 年 12 月更新内容

3年前 评论

最后随着业务发展越来越大。。。。就会变成java的生态了 :joy:

2年前 评论
Double-Jin (楼主) 2年前
air93610 1年前
porygonCN

优化的链接有问题 还是会跳转到本文章 直接复制倒是可以 博客:十个 Laravel 5 程序优化技巧

2年前 评论

有个问题想请教下,多机的队列消费你们是怎么处理,如果存在多机队列是否会存在重复消费的问题,目前我用的是horizon,没有看过源码,是否可以分享下

2年前 评论
Double-Jin (楼主) 2年前

非常赞的帖子。解决了我关于负载均衡中的很多疑问。

1年前 评论

swoole常驻内存不能用单例, 那你们service里面怎么调用, new service 吗

8个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!