如何开始一个宠物项目而不获得收益

如何开始一个宠物项目而不获得收益


TL; DR

本文介绍了使用宠物项目作为维护和提高技能的一种方法。 作者创建了一个PHP库,用于从XML文件安装FIAS


目的


我很少更换工作,因此,考虑到每个组织对固定流程的天生渴望,任何任务都会变成例行工作。 一方面,对企业而言,保持这种状态是有益的,另一方面,对我而言,这意味着完全丧失技能或淘汰技能。 PHP正在迅速发展,因此潜在的滞后性也在迅速增长。 最后,我们都知道,今天,如果没有对Elasticsearch,RabbitMQ,Kafka和其他我日常工作中不常使用的技术的了解,程序员很难找到一份好工作。


在启动下一个典型站点之后,我认为是时候进行一些更改了。 我不想改变自己的工作,但我记得演讲者在一次会议中建议如何使用他自己的可选项目,即所谓的宠物项目进行培训。 该方法似乎合适,我决定尝试一下。


任务选择


任务的选择原来是这项工作中最困难的部分。 没什么特别的:某些服务,例如可以轻松在熟悉的堆栈上实现的作业解析器。 我放弃了这个项目的想法几个月,直到我不小心看到了财政部黑客松的消息。 它建议使用开放数据源列表之一来创建服务。 除其他外,还指出了联邦信息地址系统(FIAS)。 不幸的是,黑客马拉松比赛已经结束了。


我是第一次了解FIAS,但这项任务似乎很有趣。 自己判断:大约30 GB的XML文件,数据库中大约6000万行,此外,该库以后可能会在工作中证明有用。 Github上有一些现成的解决方案,但这并没有阻止我。 相反,根据他们的分析,我提出了其他要求,以突出我的实现。


展望未来,我注意到我遇到的困难比预期的要少得多。


任务说明


成功的90%是对问题的正确陈述。 经过数年的模板工作,让自己清楚地表述问题是非常困难的。 我只是想开始工作,但是在此过程中,所有事情都会自己清除。 经过一个小时的拖延,我终于写了:用PHP创建一个库来导入FIAS数据。


后来,在品尝之后,我添加了一些其他要求:


  • 无需第三方工具即可在PHP中实现,PHP专有代码以及PECL扩展,
  • 从FIAS集中导入所有数据,
  • 完整的安装和更新周期:搜索所需的版本,接收归档文件,解压缩,写入数据库,
  • 最大的灵活性:能够更改存储位置,在记录之前修改数据,过滤必要的内容等,
  • 该库应易于集成到现有项目中。

FIAS


FIAS有一个官方网站 ,它为我们提供了创建系统的定义和目的


联邦信息地址系统(FIAS)是联邦状态信息系统,它提供状态地址寄存器的形成,维护和使用。

创建FIAS的目的是形成单一的联邦资源,其中包含可靠,统一,可公开获得的结构化地址信息。 由于实施了FIAS,可以通过Internet在官方注册的FIAS门户上免费获取此信息。

在FIAS网站Habré上 ,具有描述的材料已经足够了,因此我将不着重于此。


简而言之。 FIAS有两种格式:FIAS和KLADR。 第二个已弃用,不再使用。 信息存储在DBF或XML中。 FIAS组成的每项更改均标有新版本。 您可以请求一个包含当前最新数据的软件包,或者仅包含两个版本之间的更改。 链接提供SOAP服务。 该软件包是一个RAR存档,其中包含具有特殊格式名称的文件。 它们由前缀,数据集名称和生成日期组成。 前缀有两种类型:AS_表示必须从中将数据添加到数据库的文件,AS DEL表示应从数据库中删除数据的文件。


FIAS包含以下数据:


  • 地址形成元素的寄存器(这是地址的图形:地区,城市和街道),
  • 标识可寻址对象的地址元素(房屋编号和房屋数据),
  • 有关地块的信息
  • 有关场所(公寓,办公室,房间等)的信息,
  • 有关规范性文件的信息,这是将名称分配给地址元素的基础。

以及几个字典
  • 房屋间隔的可能值列表(常规,偶数,奇数),
  • KLADR4.0分类器列出的地址元素条目相关性状态列表
  • FIAS与地址元素条目相关性的状态列表
  • 地址元素类型的完整缩写名称列表及其分类级别,
  • 建筑物类型清单
  • 可能的所有权类型列表
  • 具有可寻址对象的操作代码列表,
  • 房地产的可能条件清单,
  • 处所或办公室类型的清单,
  • 房间类型列表
  • 管理单元地址对象的可能状态(中心)列表,
  • 监管文件的类型。

数据结构在文档中描述,可以在更新部分中找到。


最终,我们有了一个相当简单且线性的FIAS安装算法:


  • 从SOAP服务获得指向归档文件和当前版本号的链接,
  • 下载档案,
  • 打开包装
  • 将前缀为AS_的文件中的所有数据写入数据库,
  • 从数据库中删除带有AS DEL前缀的文件中的所有数据(是的,是的,在安装过程中,您还必须删除一些数据),
  • 写下已安装版本的编号。

同样简单的更新算法:


  • 从SOAP服务中获得包含版本号的列表,以及包含更改的文件的链接,
  • 如果本地数据库中的当前版本是最新版本,则停止执行,
  • 获取到包含下一个版本更改的存档链接,
  • 下载档案,
  • 打开包装
  • 将前缀为AS_的文件中的所有数据写入数据库,
  • 从数据库中删除前缀为AS DEL的文件中的所有数据,
  • 写下更新版本的编号,
  • 返回第一步。

FIAS产生相互矛盾的印象。 一方面:整个过程的完全自动化,开放格式,良好的文档编制。 另一方面:对于开放数据使用专有RAR的奇怪决定; 文档与现实之间的差异(主要与强制属性有关),这会导致许多小但不愉快的问题; 偶尔会有无法在Linux中解压缩的档案; 版本之间的某些增量占用4-5 GB。


建筑学


每个库都应基于一个基本概念,即其他功能将围绕其发展的核心。 在我看来,“职责链”模式对于这种想法的作用似乎是最佳选择。 首先,这是理想的:如果一个人想要手动安装FIAS,他们将要执行的几个连续操作对于开发人员来说是显而易见的,并且非常适合以SOLID样式编写的小类。 其次,这种链条几乎可以在任何阶段通过新操作轻松扩展,从而提供了良好的灵活性。 第三,我很想编写自己的实现。


除了操作之外,我还创建了一些可以使用DI进行传输的服务。 它们使您可以重用代码,轻松替换低级系统任务的实现(下载文件,解压缩归档文件,写入数据库等),并借助模拟功能很好地覆盖了测试。


因此,该库包含四种主要类型的对象,每种类型的对象都明确定义了责任范围:


  • 服务-提供执行底层系统任务的工具,
  • 状态对象-存储信息以进行操作之间的传输,
  • 操作-使用服务并声明它们实现了业务逻辑的原子部分,
  • 操作链-执行操作并在它们之间转移状态。

使用库提供的操作和服务的链接,您可以轻松获取任何新链或仅使用配置文件来补充现有链。


构架


经过长时间的休息和不断的重构,我在图书馆工作了一年半。


第一个相对稳定的版本已准备在晚上工作两个月。 实际上,它可以独立于框架而存在,并且包含所有必要的内容:在控制台中运行的输入脚本,DI容器,PDO上的附加组件,其自己的记录器和数据库结构迁移,这让我感到非常自豪。


当然,她的同事们毫不留情地拒绝了她。


对此的主要争论是缺乏对流行框架的支持。 没有人愿意为该库编写一个单独的包装器。 因此,我在时间上犯了最昂贵的错误:我开始为每个框架同时支持独立版本和单个包装。 实际的FIAS文件与文档中编写的文件不同。 每次有必要删除或添加(例如,在列描述中不为null时),我都必须对三个存储库进行更改。 由于该过程很乏味,因此该工作又停了六个月。


这种不完整的感觉一直困扰着我,在与懒惰进行了一场血腥的战斗之后,我不得不重新设计新版本。 首先,我认为没有人需要一个独立的库,这意味着您应该丢弃所有从包中提供框架的服务,并用接口代替它们。 因此,他们付诸行动:在控制台中运行的输入脚本,DI容器,PDO上的附件,其自己的记录器和数据库结构迁移。 接下来,我决定为每个框架制作单独的程序包,这些程序包将从主要部分连接到工作脚本的所有部分,并提供服务的特定实现。


关键是模型。 不想不断更新多个存储库中的异构对象集。 同时,在主要工作中,我得到了一个关于Symfony的项目。 快速了解之后,我认为SF最有用的功能是代码生成,它将解决我的所有问题。 我在主程序包中创建了一个yaml文件 ,其中包含FIAS数据的声明性描述。 然后,我添加了基于以下描述为模型创建特定类的代码生成器:Symfony的Doctrine实体和Laravel的Eloquent对象。 在生成器的开发过程中,我意识到树枝模板不适合于此,因此选择了专门的解决方案-Nette PHP Generator


作为概念的证明,我为LaravelSymfony创建了捆绑包。 由于我在第二篇文章中的工作时间更长,因此我将在其上下文中描述所有后续内容。


基础设施


我的大部分战斗项目都是使用过时的技术编写的,因此我无法在其中任何一个上使用现代的代码分析器。 摆脱了传统的压迫,我安装并配置了所有可以实现的代码质量控制工具:



使用Travis在Github中进行集成验证。 最后,他添加了一个Docker文件来创建本地开发人员环境,其中包含一个make文件,该make文件包含容器的主要命令(启动检查,测试,创建模型等)。


学习成果


PHP 7


在开始使用该库之前,我从未真正使用过PHP 7新功能 。 它们很漂亮:从严格的类型到生产率的显着提高。 特别感谢开发人员提供的空合并运算符。 引入一个运算符后,我没有看到代码库中的严重减少。


拉尔


令人惊讶的是,在PECL中有一个与RAR一起使用软件包 。 通常,此类扩展名不受信任,因此我尽量避免使用它们。 事实证明它是稳定的:它是在7.2中安装的,没有问题,它能够相对快速地解压缩庞大的存档,并且具有较低的RAM消耗(根据可用的系统资源,在10-20分钟内解压缩6 GB)。 我仍然担心这是墨菲定律的某种体现。


Xmlreader


读取巨大的xml文件不是一件容易的事。 PECL扩展名再次出现在抢救中-XmlReader 。 我没有立即意识到它的全部功能,但是我通过几种方法将它与Symfony序列化程序结合使用,以快速,经济地从FIAS文件中获取数据。 在库方面,阅读器对象实现了迭代器接口,该接口依次返回与文件中一条记录对应的xml字符串。 使用Symfony序列化程序,这些字符串将转换为对象。 在不超过50 MB的RAM的情况下,可以在3-4分钟内读取20 GB的文件。


写入数据库


当然,我从具有数据和庞大表描述的关联数组开始。 该代码很快变成了配置和转换器类的哈希。


教义实体的魔力表明了物体如何自我描述。 我决定使用相同的方法,但同时摆脱了我自己使用PDO将数据写入数据库的实现。 相反,我创建了一个存储接口,该接口描述了处理对象的方法。 基于实体类,特定的Storage实现完全确定如何以及在何处写入数据。 这种方法使连接各种存储变得很容易:从MySql到csv文件。


数据插入优化


在超过48小时后,我中断了第一次导入。 很明显,您需要优化插入数据的过程。


首先,我切换到PostgreSql内置主键的ugid-type列 。 用索引写入uuid列比写入字符串要快得多。


之后,我放弃了所有非关键索引和外键,因为对数据完整性的关注完全由FIAS团队负责。


然后,我重命名了Storage接口,以便外部脚本可以显式通知它导入的完成。 这允许使用批量插入,这有时会加快录制速度。 在寻找信息时,我还遇到了copy命令以及query_to_xml 。 它有两个很大的缺点:首先,PostgreSql用户必须具有文件的读取权限,而我不能保证这一点;其次,在写入之前修改脚本中的数据的能力已丧失。


尽管有这些更改,导入时间仍超过30小时。 需要彻底改变方法。


并行流程


互联网上充斥着有关PHP异步的文章。 我的选择落在Amp上 。 只是无法异步解决。 首先,代码很快变成了可怕的回调和不明显的调用(这可能是我的错,而不是异步方法)。 其次,我必须放弃使用标准ORM,因为需要通过特殊框架对数据库进行非阻塞调用。 第三,尽管在某些条件下PostgreSql可以并行插入行,但是它们很难实现。 结果,经过5个小时的工作,我观察了如何在数据库侧强制所有“异步”请求“同步”。


但是导入被很好地划分为并行过程:几个完全独立的任务,它们没有可以竞争的公共资源以及可以交换的数据。 而且,在一个线程的框架内,我收到了漂亮且线性的代码。


我首先决定尝试并行扩展。 它有一个致命的缺陷-解释器必须在支持ZTS(Zend线程安全)的情况下构建。 由于ZTS在常规的Web脚本中不起作用,因此必须具有两种不同版本的解释器。 一种是不带ZTS的网络版,另一种是带ZTS的网络版安装FIAS。 潜在的性能提升超过了这种不便,尤其是考虑到组装一个新的Docker容器并与旧的容器结合使用是多么容易。 不幸的是,在新线程中启动Symfony导致PHP堆栈溢出,而我还没有准备好拒绝DI容器和方便的配置。


最后,我发现了symfony的过程 。 实际上,它将为指定的控制台命令启动一个新进程并监视其完成。 我必须添加两个附加链。 第一个下载档案,将其解压缩并启动并行处理以进行数据处理。 第二个从命令行参数获取文件列表,并将文件内容写入数据库。


由于缺乏并行处理的经验,我似乎犯了所有新手错误。


例如,我的启动过程使用一个无限循环来检查子项的完成情况,当然,它为此花费了大量的处理器资源。 迭代之间的睡眠调用有所帮助。


在第一个实现中,文件在各个进程之间分布不均。 最大的两个落入一条流,处理了20多个小时。 在第二种实现中,我添加了一个特殊的管理器,该管理器根据导入文件所需的时间来分发文件。 现在,进程已均匀加载。


完成这些编辑后,根据服务器资源的不同,我可以在16到20小时内导入完整版本的FIAS。 没有我们想要的那么好,但是我继续致力于优化。 下一步是完全拒绝使用PostgreSql来支持Elasticsearch。


结论


值得吗? 在一个从未参与过任何战斗项目的图书馆工作了两年?


是的,完全是。


我还是换了工作。 在巡回一打的采访中,由于我的宠物项目,我回答了许多棘手的问题。


PHP即将死去的恐慌正在不断加剧。 我不会掩盖我自己正在考虑迁移到另一种语言的事实。


在我看到PHP团队在版本7中所做的巨大工作之后; 他的个人榜样使​​他深信该语言变得多么成熟以及其生态系统已经变得多么丰富。 我可以肯定地说,有关PHP死亡的谣言被大大夸大了。 这仅仅是开始:将来,我们将等待JIT,开箱即用的异步功能以及更多其他功能。

Source: https://habr.com/ru/post/zh-CN479122/


All Articles