创业公司技术体系建设

用户头像
星际行者
关注
发布于: 2020 年 06 月 21 日
创业公司技术体系建设

技术体系建设对一个公司来讲至关重要,做为整个公司的技术基石,很大程度上决定了一个公司是否能够走的更远、发展的更好。互联网时代技术的影响力越来越大,本文将回顾我所在的团队近一年多时间里所做的技术体系建设工作。



背景

我所在的公司是一个国内Top地产企业所投资的互联网公司,致力于智慧社区建设。如同⼤多数创业公司一样,初期以追求满足业务快速上线为主,很难放缓脚步进行全面的技术体系建设。随着时间的推移,技术上的缺陷逐渐暴漏出来,甚至影响到业务的发展。具体表现为:



  1. 系统编译、部署复杂,上线成功率低且耗时较长,一次上线甚至经常持续数小时之久。

  2. 应用、数据库、服务器等环境缺少监控,线上故障发生时,不能及时处理,由于没有有效监控,平时也难以针对性优化。

  3. 缺少高可靠建设,故障发生时恢复时间较长甚至难以恢复。

  4. IT资源管理混乱,带来严重的管理及安全风险。

  5. 系统架构风格各异,开发效率低下。



为了解决这些问题,我们从基础设施、平台架构两方面进行了技术体系建设规划,近一年来,通过我所在团队全体成员的努力,初步完成了技术体系建设。技术体系建设无外乎从技术、管理、招募人才等几个方面进行,在正式进入技术话题之前,我们先谈谈管理这两个热门话题。



一、研发管理

规范不一定有效,趋同认知更好

提到研发管理,很多管理者很自然想到制定规范管理整个研发团队,很遗憾的是效果并不理想。制定完善的规范规避各种风险是制造业所经常采用的方法,2000年后,伴随着中国加入WTO,很多软件公司加入到软件外包的队伍中,由于语言、地域、文化不同,为了保证离岸开发质量,必须有一套行之有效的管理方法保证交付,于是国内各大公司纷纷执着追求CMMI(Capability Maturity Model Integration)认证,通过管理水平的提升来获取海外项目订单。



有趣的是,无论是Google、Facebook这些世界级的互联网巨头,还是国内的BAT都很少提及CMMI,年轻一代的程序员更不知道CMMI为何物,问题出在哪了呢,自工业革命起几百年所积累的各种管理方法、规章制度为什么在互联网时代失效了呢?背后的原因恐怕是复杂度的指数级增长,以制造业的代表汽车厂商为例,即使像丰田这样的厂商也仅仅几款车型在生产、售卖,而一个互联网产品却在每日持续增加功能,传统的管理方法很难适应快节奏的互联网开发。



就一个国家而言,每个公民必须遵纪守法否则就会受到法律的制裁。对于普通民众来讲,根本不会去主动买一部法典来学习,更谈不上温故知新。即便如此,社会还是处于有序运行中,背后的原因就是民众有趋同的认知,这是在长期生产、生活实际中所形成的。传统制造业注重群体力量,常常忽视个体,而互联网却处处体现人本主义,面对高复杂性、不确定性,研发人员间趋同的认知才是解决问题的有效手段。但这种趋同的认知并非靠简单的规范能够形成,而是靠不断招募人才、稳定的团队才能形成的。



敏捷不一定有效,DevOps看起来不错

敏捷开发是一套软件工程管理方法,虽然不是新概念却也算一个高频词汇,很多公司一推行管理就很自然想到敏捷开发。读过《人月神话》的工程师都知道没有银弹这样一个公理,没有哪种软件工程方法可以很好解决开发过程中的各种复杂问题,这也是被业界广泛认可的,更何况也不能像做药品试验一样做大样本随机双盲实验验证有效性。从务实的态度来讲,我们并不是太看中敏捷开发。



尽管没有银弹,但DevOps却是一个更为先进、有效的方法。不同于敏捷,DevOps更倾向于通过技术方法来解决问题。DevOps围绕着开发过程中的各个环节,通过各种工具的引入来推动技术体系的进步。



二、基础设施架构

技术体系建设包含基础设施架构、平台架构建设两部分,规划基础设施架构时,我们遵循传统的Iaas、Paas分层设计,如下图所示进行了一个完整的技术规划。





拥抱Kubernetes

未使用Kubernetes之前,应用部署在虚拟机中,完全通过人工方式进行服务器资源管理,尽管使用了Ansible这类管理工具,管理效能仍旧非常低下,主要体现在:



  1. 应用稳定性差,通常一台8Core 16G的虚拟机中部署若干个应用,由于未进行环境隔离,程序之间相互竞争资源影响其他程序的稳定运行。

  2. 部署复杂度高,由于一台虚拟机内部存在端口占用问题,需要对程序占用端口进行预先分配,如果程序被迁移到其他虚拟机中可能存在端口冲突,还需要再次分配端口。

  3. 服务器资源利用率不高,存在大量的资源碎片。

  4. 管理成本高、无法进行弹性扩容及故障自动转移,由于应用被预先分配到指定的虚拟机中,如果需要扩容非常麻烦,需要找到另一个资源空闲更高的虚拟机,将程序重新部署。



OpenStack、Kubernetes都是云时代的代表产物,相比较而言OpenStack更适合公有云使用,而Kubernetes更适合私有云使用。根据公司的业务特点,我们直接跳过了基于OpenStack的Iaas层,选择了Kubernetes做为云计算平台。无论使用哪种技术都面临有状态还是无状态这个问题,在实际应用中,我们仅仅把应用程序进行无状态改造,迁移到Kubernetes平台上,而数据库、消息队列等等有状态的系统,仍然直接部署在物理机上。



系统监控

系统的稳定运行离不开监控,一般来讲,监控主要从基础设施监控、应用监控两个方面进行。对于基础设施监控,我们使用业界比较流行的Prometheus+AlertManager进行监控。Prometheus内置时间序列数据库,查询性能非常好,针对各种不同的中间件都有相应的exporter,并且还有Thanos、VictoriaMetrics这种大规模应用解决方案,在业界应用非常广泛。针对目前公司所使用的服务器、中间件、数据库,我们都基于Prometheus+AlertManager建立了完善的监控系统。





相比基础设施监控,应用程序监控要复杂很多,应用监控涉及Log、Metrics、Trace几个方面,Log、Metrics比较容易收集,但Trace收集要复杂很多,主要难点体现在是否接受代码侵入性改造。侵入性改造常见的可选方案有Zipkin、Jager、Cat,无侵入性改造的常见方案有PinPoint、SkyWalking、Elastic APM。



对于应用监控,我们内部先后设计过两个版本:

版本一,内部集成Prometheus Client,通过AOP方式拦截接口调用采集接口响应指标,同时通过定时器采集JVM等指标,采集到的指标通过URL暴漏给Prometheus,结合AlertManager完成系统监控告警。



版本二,在版本一的实现中,仅仅能够采集Metrics,不能采集Trace信息。为此我们希望在新一代的APM中引入Trace监控。如之前所述,Trace收集复杂很多,难点体现在是否接受代码侵入性改造。为了尽可能减少研发介入,我们选择了非侵入型方案,对PinPoint、Skywalking、Elastic APM进行了详细的评估。由于PinPoint部署过于复杂,需要HBase环境,首先排除了PinPoint。SkyWalking初步看起来是一个被给予厚望的系统,但经过深入的调研也被放弃了,原因有二:

  1. 不能运行时修改告警规则。

  2. 无法自定义指标。



公司很多项目中Http接口设计不佳,即使Http接口内部错误仍然返回Http Status Code等于200,而在具体的返回值中自定义错误码,对于这种情况Skywalking无法识别出错误,也就无法进行错误告警,其实使用其他Trace跟踪方案也存在同样问题。为了解决这一问题,我们试图绕过这种机制,采用版本一中的方法,用AOP进行错误拦截,自定义错误指标,通过统计错误指标进行告警。遗憾的是我们没有在Skywalking中找到自定义指标的方法,同时按照官方文档没有编译成功Skywalking,于是我们对Elastic APM进行了调研,尽管Elastic APM不支持Dubbo,但代码结构清晰容易扩展开发,同时Elastic APM可以于Kibana结合,非常方便的展示运行时信息。最终我们选择了Elastic APM用于应用监控,并在Agent中进行改造支持Dubbo Trace收集,整个APM架构如下:





Elastic APM中带有告警功能,可以在Kibana上配置告警规则,但Kibana免费版缺少登录、权限控制等等功能,不适合面向生产环境使用,更为关键的是不同版本间收费策略不同,有的版本收费有的版本免费。最终我们没有选择Elastic APM Alert发送告警,而是参考Prometheus的PromQL语句自研了一套告警系统。比如:avg(http{appname="qdp-polaris-server"} by url range 5m) > 2000,计算5分钟内qdp-polaris-server系统各个http接口的平均响应时间,如果超过2000ms则产生报警。 相当于SQL中的select avg(response_time) from table where timestamp between now() - 5m and now() group by url。



值得一提的是,2019/11月,Skywalking 6.5.0发布后也实现了运行时配置告警规则的功能,但webui上仍然没有实现这个功能,相信很快在webui上也能实现这个功能。



通常APM都是被动式的根据用户请求响应情况发送告警通知,而非主动式探测,为此我们基于Http Runner开发了一个主动探测系统,周期性主动向系统发送请求,起到双保险的作用。



毫无疑问,监控是必要的,但仅仅进行监控仍然不足以保证系统稳定,就如同经常做体检也不一定身体健康一样。通常来讲,通过长周期的系统监控,可以详细记录下来系统的运行时状态进行针对性优化。2B业务不同于2C业务,系统出现问题后,用户有快速的反馈渠道,经常出现系统收到告警后用户的报障也随之而来,仅凭告警也解决不了太多问题。极端情况下甚至出现系统卡顿无法向外发出告警的情况。因此时刻关注系统运行状态,针对性优化系统才能够保证系统稳定可靠运行。





一次生产环境故障,一个程序突然启动3w+线程,操作系统出现严重卡顿,系统内部的告警程序无法向外发出告警。



开放CI/CD

几年前一本运维界的小说《凤凰项目—一个IT运维的传奇故事》将我带入DevOps世界。持续构建、持续集成、持续部署、按需创建环境、限制半成品、流程自动化、保证上游的质量、不断试错、持续改进、构建能够顺利变更的安全系统和组织是这本书的精髓。很长一段时间以来,整个研发团队之间基本没有默契,各个产品线差异非常大,甚至编译方法都不同,上线失败率极高。



DevOps给了我们很好的启发,围绕整个开发流程引入生产力工具降低复杂度、提高质量。毫无疑问CI/CD是首先要做的事情。对于CI/CD来讲,难点不是选择Jenkins还是其他工具,而是是否让研发不通过运维直接自己操作上线发布过程。让研发自己操作上线,意味着需要做更多的细节控制,以免出现失误引起生产故障,比如:随意修改应用配置、运行时资源配额、随意发布上线等等,各种使用细节问题综合起来后,为了将CI/CD能力彻底面向研发开放出来,为此我们开发了一套称之为”研发云“的全新CI/CD系统,这个系统对接K8s平台,除了具有CI/CD功能外,还具有一定Paas平台能力。在研发云中,编译过程仍然使用Jenkins,通过调用Jenkins API进行编译。之前Jenkins编译脚本采用Shell,为了支持多语言环境,我们使用了Jenkins 2.0之后出现的Pipeline+Groovy开发编译脚本,每个Stage根据不同的编译器及版本,在相应的Docker容器中运行,避免了在一个Jenkins编译主机上安装不同编译环境所引起的环境冲突问题。同时,我们将编译脚本放到Git上,相同语言的项目对应同一个编译脚本,当需要修改编译过程时,只需要全局修改即可。程序编译通过后,将编译好的程序打包成Docker镜像,推送至Docker Hub中。运行时,选择相应镜像直接运行在Kubernetes环境中。



通常程序运行依赖与一些三方工具系统,几乎每个人都有自己独特的用法,从整体角度看会产生极大的差异化分裂,降低生产力。本着少即多这样一种设计哲学,我们甄选了其中核心且最为常用的功能集成到研发云中,避免了工具滥用所带来的潜在风险。





不同于开发、测试环境,生产环境上运行程序需要考虑的更多,如实现资源配额管理、版本回滚、蓝绿发布、灰度发布等等。研发云上,我们通过启动系统时设定资源配额的方式,将配额设定传递给Kubernetes,由Kubernetes来管理运行时程序所能使用的计算资源。在蓝绿发布、灰度发布方面,我们与华为云合作,将Istio引入研发云。



研发云在公司内部,除了做为CI/CD工具外,还做为整个Paas平台的入口,可以完成资源申请、监控告警设置等等功能。整体上我们将研发云定义为一个Paas平台工具。





在研发云中配置的应用告警规则





通过企业微信发出的告警



CMDB

对于一个复杂的基础设施环境而言,即涉及众多硬件也涉及众多基础软件及应用程序,如何管理这些系统以及之间的关系是一个非常重要的任务。对此我们计划采用图建模方式进行管理,图数据库选型上,我们采用国内最新开源的Nebula Graph,这部分工作仅仅刚开始,相信很快可以完成系统建设。



三、平台架构设计

Data PipeLine

随着微服务的流行,越来越多的系统从单一且庞大的系统拆分成若干个服务独立运行,尽管有领域建模的指导,但如何进行服务拆分,仍然是一个充满艺术且有挑战的工作。服务的拆分随之而来的是数据的分散,原本集中存储在一个数据库中的数据被分散在不同数据库中,甚至是不同类型的数据库。在有数据聚合的场景下,如何进行数据聚合是一个非常有挑战的任务。



传统上进行多数据源数据聚合,有基于SQL也有基于CDC处理的。SQL方式进行数据聚合有一定的延迟,且对表结构有一定的要求,如必须有主键、必须有变更时间戳等等。而CDC处理数据又需要进行开发,比如用Canal,两种方式都不够理想。



经过一系列调研,我们将目光集中在Kafka Connect上,Kafka Connect无需开发可以通过配置方式,使用SQL、 CDC两种方式进行不同类型数据源间的数据同步。





尽管Kafka Connect看上去非常美好,但在实际使用过程中还是遇到不少问题。Kafka Connect即支持SQ模式又支持Debeizum CDC模式,在使用SQL模式过程中,我们很快发现对嵌套查询等等SQL不支持。在使用Debeizum的CDC模式时,也出现时区转换等一系列Bug,数据从源表进入Kafka时会被转换成UTC时间,但写入目标表时,却没有进行转换,导致目标表时间显示错误。在启动自动变更表结构时也同样产生错误,没有将原表的表结构同步到Kafka Sink,导致无法执行表结构变更。在数据类型转换时也同样出现错误。为此我们通读了Kafka Connect源代码,并与社区合作修复了其中的一些Bug,同时也开发了面向研发的WebUI工具。





Service Mesh

随着微服务的兴起,很快发现需要引入服务治理。之前公司内部使用Dubbo,遗憾的是Dubbo并没有引入太多服务治理功能。随着Spring Cloud的出现,业界很快接受了熔断、限流等服务治理概念并应用起来。



在Kubernetes体系下,Service Mesh的出现可谓让混沌的世界出现一缕明亮的阳光,Service Mesh配合Kubernetes使用,可以在网络层直接进行熔断、限流等服务治理,程序无需改动,带来了全新的服务治理方法。





在Service Mesh的选择上,由于最初我们使用的是Dubbo,因此希望实现上能够兼容Dubbo,在一次技术交流峰会上,了解到蚂蚁金服开源了新的微服务框架Sofa。遗憾的是,经过我们的调研Sofa并未支持Dubbo,还有一些Bug,而且与主流的实现Istio开始分裂,无奈之下我们放弃了Sofa,决定使用原生的Istio,整个微服务技术栈由Dubbo转向Http。值得一提的是,华为是Istio的金牌会员,在Istio的使用上给予了我们很多的帮助。目前我们仅仅在研发云中集成了Istio灰度发布功能。



缓存

缓存方面,之前我们使用Redis Cluster,但Redis没有细粒度的配额管理,容易造成资源使用不均衡的情况。比如个别应用占用太多的内存空间导致其他应用无资源可用。为此,我们采用Kubernetes部署Redis,为每个应用单独创建Redis单例或者集群,按需申请Redis资源来解决这一问题。



四、总结

技术体系建设涉及到大量的老旧系统改造,无疑过程中会遇到很多阻力,技术人员间经常讨论,技术是变复杂了还是变简单了,这确实是一个有趣的话题。在我看来,整体上技术变复杂了,但技术的进步又带来了局部的简单。在后面的文章中,还会对其中涉及到的内容做进一步的展开。




系列文章

一、创业公司技术体系建设

二、创业公司技术体系建设-CI/CD

三、创业公司技术体系建设-APM



发布于: 2020 年 06 月 21 日 阅读数: 512
用户头像

星际行者

关注

编程多年依旧热爱。。。 2019.03.28 加入

还未添加个人简介

评论

发布
暂无评论
创业公司技术体系建设