返回 登录
3

法国新总统马卡龙的开源Web平台架构

原文:An open-source web platform for the new President of France
作者:Javier Eguiluz
翻译:王江平

本案例的研究是由EnMarche Project的首席工程师兼产品负责人Titouan Galopin撰写的客座讲话。 请注意,这是一个严格的技术文章;任何政治评论都会被自动删除。

项目背景

2016年4月,现任法国总统 Emmanuel Macron(埃曼纽尔·马克伦)创立了一个名为“En Marche!”的政治运动。 (英文名为“On the Move”),最初是作为上门对民众询问的这么一个运动,主要的询问内容是法国当前存在的问题。

不同于既定的政党,En Marche 没有任何的基础设施,预算和支持其事业的成员。 这就是为什么En Marche 从一开始就依靠互联网的力量来寻求支持者,推广活动和募集捐款。

2016年10月,我作为志愿者开始为En Marche 工作。刚开始的团队很小,所有的IT操作都是由一个人维持。 所以他们高兴地接受了我想帮助他们的提议。 那时候,这个平台是用WordPress创建的,但是我们需要用一些允许更快、更个性化的开发方法来取代它。 所以就选择了Symfony:它适合当前项目的规模,而且我有使用经验,可以轻松处理我们现有的大量用户。

架构概述

可扩展性是该项目首要考虑的要素,特别是在他们面临该平台的第一版的问题之后,此时还未使用Symfony构建平台。 下图显示了项目架构的概述,该架构在需要具有极强的可扩展性和冗余:


id

我们使用Google Container Engine和Kubernetes来提供可扩展性,滚动更新和负载平衡。

Symfony应用程序是作为Dockerized应用程序从底层构建的。配置使用环境变量,应用程序是只读的,以保持其可扩展性:我们不会在运行时容器中生成任何文件。 应用程序高速缓存在构建Docker镜像时生成,然后使用与Redis实例结合的Symfony Cache component服务器之间进行同步。

由两名RabbitMQ管理的工作人员在后台处理一些繁重的操作:发送电子邮件(有时我们必须在单个请求中发送45k个电子邮件),并构建应用程序的几个部分使用的序列化JSON用户列表,避免处理缓慢和复杂的SQL查询。

该数据库使用Google Cloud SQL,这是我们不必亲自管理的集中式MySQL数据库。我们使用Cloud SQL代理Docker镜像来连接到它。

部署

该项目使用持续交付策略,这与连续部署(Continuous Deployment)方法不同:每次提交自动部署在临时服务器上,但生产部署是手动的。 Google Container Engine和Kubernetes是我们部署流程的关键组件。

连续交付流程、单元和功能测试都由CircleCI处理。 我们还使用StyleCI(以确保新代码与项目其余部分的编码风格相匹配)和SensioLabsInsight(执行自动代码质量分析)。 这三个服务被配置为每个Pull请求在合并之前必须通过的检查。

当Pull请求合并时,连续传送过程开始(见配置文件):

  1.使用Circle CI环境变量在Google Cloud上进行身份验证。
  2.构建用于生产的Javascript文件。
  3.构建项目的三个Docker镜像(应用程序,邮件工作者,用户列表工作者)。
  4.将内置镜像推送到Google Container Registry。
  5.使用kubectl命令行工具更新登台服务器(滚动更新)。

手动执行的唯一过程是SQL迁移。 即使可以自动化操作,我们更倾向于在应用这些迁移之前仔细检查这些迁移,以防止严重的生产错误。

前端

应用程序前端不遵循单页应用程序模式。 事实上,我们希望尽可能少的使用JavaScript来提高性能并依靠本地浏览器功能。

React + Webpack

JavaScript应用程序的代码是由Webpack编写的React来实现的。 我们不使用Redux - 甚至React-Router - 而是使用纯粹的React代码,我们仅在页面上的特定作用域中加载组件,而不是使用它们构建整个页面。 这有两个原因:

  • 在React加载之前,HTML内容被完全渲染,然后React根据需要修改页面内容。 这使得应用程序在没有JavaScript的情况下可以运行,即使页面仍然在慢速网络上加载。 这种技术被称为“渐进增强”,它显著地提高了感知性能。

  • 我们使用Webpack 2进行tree shaking和chunks加载,所以每个页面的组件只在需要时加载,因此不会使最小化的应用程序代码膨胀。

这种技术使我们组织前端代码如下:

  • 应用程序的根目录前端/目录存放所有的SASS和JavaScript文件。
  • 微型kernel.js文件并行加载JavaScript和应用程序代码。
  • app.js文件用于加载不同的应用程序组件。
  • 在Twig模板中,我们加载每个页面所需的组件(例如,地址自动完成组件)。

前端性能

前端性能往往被忽视,但网络通常是应用程序的最大瓶颈。 在后端保存几毫秒不会占用太多的时间,但为图像保存3秒或更长的加载时间将会影响网站的体验效果。

图像是主要的前端性能问题。 市场活动经理希望发布大量图像,但用户想要快速加载。解决此问题的方案是使用强大的压缩算法并应用其他技巧。

首先,我们将图像内容存储在Google Cloud Storage及其元数据中(使用名为Media的Doctrine实体)。例如, 允许我们不需要加载就能知道图像尺寸。 这有助于在创建一个网页设计,图像加载时不会跳转。

其次,我们将Media实体日期与Glide库结合起来实现:

  • 上下文图像调整大小:例如,显示在主页上的小网格块上的图像可以比显示为主要文章图像的相同图像小得多,分辨率更低。
  • 更好的图像压缩:所有图像都被编码为jpegs格式。 与其他格式(如PNG)相比,明显减少了加载时间。

在 AssetController 中引入了一个简单的端点,并使用了签名和缓存来减少对此端点的 DDoS 攻击。

第三,在滚动加载图像时需要一些技巧,遵循三步骤:

  1.在滚动时尽可能快地加载上方的所有元素,并等待下面的元素。

  2.加载超低分辨率版本(使用Glide生成),并使用本地JavaScript代码为图片做gaussian blur filter(高斯模糊滤镜)处理。

  3.当加载高质量的图像时,更换这些模糊的占位符。

我们实现了一个应用范围广泛的Javascript listener(JavaScript监听器),用于网站的各个地方。


id

Forms(表单)

该项目用了一些有趣的表单。 第一个是注册网站:根据国家和邮政编码字段,城市字段从输入文本更改为下拉选择列表。

技术上有两个字段:“cityName”和“city”(第二个是根据法国规定分配给城市的代码)。 窗体组件正常从请求中填充这两个字段。

在视图方面,最初仅显示cityName字段。 如果所选国家是法国,我们使用一些JavaScript脚本来显示城市的选择列表。 JavaScript还可以侦听邮政编码字段的更改事件,并发出AJAX请求以获取相关城市列表。 在服务器端,如果所选国家是法国,我们需要提供城市代码,否则我们使用cityName字段。

这个技术是本文前面讨论的渐进增强技术的一个很好的例子。 JavaScript使整个项目的体验更好,但对整体功能不起决定性作用。

由于这些地址字段在应用程序中被大量调用,我们将其抽象为与地址Javascript组件关联AddressType form type(AddressType表单类型。)

另一个是有趣的表单是发邮件给一个试图说服他们投票给候选人。 这是一个多级的表单,询问有关其他人(性别,年龄,工作类型,兴趣爱好等)的一些问题,然后生成可通过电子邮件发送的定制内容。

在技术上,该表格结合了一个高度动态的Symfony Form与Workflow组件。 该实现基于InvitationProcessor模型类,由多级和动态表单类型填充,并将内容存储在会话中。 Workflow组件用于确保模型对象有效,定义每个模型状态允许的转换:请参阅InvitationProcessorHandlerworkflows.yml config

搜索引擎

Algolia提供的搜索引擎,具有速度快、搜索结果实时性强。 使用AlgoliaSearchBundle对应用实体(文章,页面,委员会,事件等)进行索引的整合。

这个捆绑是非常有用的。 我们刚刚向Doctrine实体添加了一些注释,之后,每当创建,更新或删除实体时,搜索索引都会自动更新。 从技术上讲,捆绑包监听了Doctrine事件,因此不需要担心搜索内容的是否为最新的问题。

安全
跟其它网站一样,我们的站点也成为某些组织攻击的目标。且大多都是暴力攻击,其目的是将网站关闭而不是攻破。

该网站遭受过DDoS攻击,其中五次在最后两周内。 它们对Symfony应用程序没有影响,因为Cloudflare缓解和基于Kubernetes的按需扩展性。


id

首先,我们遭受了基于WordPress pingback的三次攻击。 攻击者使用数千个被黑客攻击的WordPress网站将pingback请求发送到我们的网站,迅速重载。 我们在nginx配置中添加了一些检查来减轻这种攻击。

其他攻击更加复杂,Cloudflare和Varnish都需要减缓攻击。 使用Cloudfare来缓存资源是非常有效的,我们认为没必要使用反向代理。 然而,在DDoS攻击中被证明需要一个反向代理:在这个运动的最后几天,攻击是巨大的(每秒可能达到30万个请求),我们不得不禁用用户系统并启用Cloudfare上的“Cache Everything”。

我们不能阻止安全攻击,但可以通过遵循Symfony的最佳做法来减轻它们,顺便说一下,这是具有公共安全审计的少数开源项目之一。

开源

@EnMarche github 帐户中,已公开了.enEnmarche.fr平台及其相关项目。 不过,我们并没有推广这个想法,因为开源对于非技术人员来说是非常复杂的。然而,我们收到了一些人的贡献,并且很高兴将它开源。

我们也在考虑通过为项目开发一些元素来回馈给Symfony。 例如,UnitedNationsCountryType表单类型可能对某些项目很有用。 我们还开发了与Mailjet服务的集成,可以作为Symfony捆绑包发布。

评论