原文链接:blog.coding.net
说起架构的话,稍微有点写程序经验的人来说,都可以理解架构对于整个服务的重要性。架构最核心的三个点就是:稳定性、扩展性、性能。一个好的架构主要通过这三点来看。
会不会宕机,你的服务会不会因为自身或者第三方的原因突然之间中断。可拓展性,当你的访问量增长的时候,你的服务能不能迅速的 Copy 出很多个副本出来以适应快速增长的业务。再一个就是比如说你要做电商啊秒杀啊之类的功能的时候,能不能扛得住这种压力。这就是评价一个架构好坏的三个基本点。
我们可以想想一下,一个架构比较乱是什么样子。就好像一个机房管理员面前所有的线乱成一团。
一个好的架构是什么样呢?这是Google 数据中心机房的排布,我们可以看到这是非常整齐的,看起来是赏心悦目的。
我们的软件架构和硬件架构甚至跟建筑学等都是有共通之处的。一个好的架构一定是逻辑清晰,条理明确的。
为什么要讲代码托管的架构呢。这要从 Coding 的最开始说起,Coding 做了一个代码托管,做了个代码的质量分析,一个演示平台,还有一个在线的协作工具。代码托管是我们非常重要的一个业务,在整个 Coding 的架构演变升级的过程中,包括对各种新技术的尝试中,代码托管一直走在最前列的,包括Docker 的尝试等等。所以我今天和大家分享一下代码托管架构的演变升级,我认为这个是整个 Coding 架构系列的一个典型。
我们从最开始开始讲,整个升级过程分为四大阶段。
第一阶段:远古时代
我们三月份开始写第一行代码,七月份把产品上线,所以当时非常仓促,但这也是很多创业公司不可避免的一个现状,就是你必须要快速上线,产品可以不够完美,但必须要快速切入市场。所以原则是先解决有没有的问题,再解决好不好的问题。我们当时以这样的思路来做这个工作。我们也参考了很多开源软件,比如我们的架构就是仿照 Gitlab。那时候说白了就是租了一台服务器,然后所有的服务跑在上面,用户所有的请求都通过这台服务器进行交互,我们来看一下当时的架构图。
用户从最左边,通过我们在 Ucloud 的 ULB 进来,到我们自己的应用服务器,应用服务器和数据库有些交互,和缓存等其他组件有些交互,和 Git 仓库有些交互。黑框框起来的部分就是我们的应用服务器和 Git 仓库,它们当时是部署在同一台服务器上的,也就是说应用程序,代码都是部署在一起的。所以显而易见,这个架构存在很多问题,如果 Ucloud 这台服务器出了问题,我们的网站就挂了。虽然是虚拟机,但是出问题是难保的,这就是一个很不好的架构。会不会宕机?会。能不能扩展?不能。能不能抗压?不能。所有东西都在一起。就像养宠物一样,把所有服务都放在你的宠物身上保护好,但一旦你的宠物出现问题整个服务就都瘫痪了。
后来我们上线之后就慢慢在做一些改进,我们发现我们遇到最严重的问题,就是代码托管和项目协作组合到一起了,但很多时候业务是相对独立的,那我们就把他拆开吧。另外一个是代码仓库数据量越来越大,单台服务器已经很难支撑了。我们的代码仓库必须具备负载均衡的策略和重新排布的这种能力。
第二阶段:农耕时代
我们的架构是这个样子的:
用户还是通过 ULB 进来,但是我们的代码仓库已经分离到单独的机器上,我们每一个机器上都会装备一个 RepoManager,用来专门管理这台服务器上的所有代码仓库。它和我们的主站服务是通过 RPC 这种方式来通讯的。我们的 Git 仓库,也有了独立的备份,不需要和主站一起备份。这是第二阶段做的主要改动。我们的 Git 有多个服务器,不只图上所示三个。每一台都有一个 Repomanager 管理器,再通过 RPC 做交互。大概是这样的一个结构。
然后我们就发现了更多问题,需要作出更多改变以承受更多的用户量。
第三阶段:手工业时代
代码仓库的服务分为两部分,一个是用户可以在本地使用 Git 工具来 push/pull 代码。另外一个是,可以在网页上直接看代码、编辑代码、查看提交历史等。这两部分操作在底层实现上来说是比较独立的,所以我们选择把他们进行更彻底的拆分。还有就是专门的认证服务,我和大家解释一下,我们每次从网站上 clone 代码或者 push 代码的时候,我们必须要认证一下用户的身份。而这个认证服务是决定了我们服务是否稳定的一个重要组件。现在我们就把他单独的拆离了出来,就是说我们有一套专门的认证服务来处理这个事情。达成我们希望的各个环节可以实现规模化增长。具体我们来看一下架构图。
这个图就稍微有点不同了,用户访问我们 Git 的服务分两条线,一部分是黑线标注出来的,另一部分是红线标注出来的。红线标注出来的实际上是使用 IDE 的插件、命令行工具来访问我们的代码仓库,通过 SHH 协议、或者 Git 协议、HTTP 协议到 ULB 之后,会到我们的 Git 的 Server,Server 会交由我们的认证服务去做一个用户权限的认证。这个认证服务是独立的,比如去数据库中校验密码、校验权限,回来之后 Git Server 会在内网发一个 SSH 请求到我们的具体的代码仓库的存储机器上,最终完成代码的交互。
另外一条线,黑线表示的是我们在网页上操作的时候,比如查看代码的文件数,编辑代码等等,这条线上的请求全部都是 HTTP 请求。所以用户到 ULB 之后,就直接代理到 Web Server,和阶段二一样,通过发送 RPC 请求,到具体的 Git 仓库的存储的 RepoManager 上面从而产生数据的交互。这是我们第三阶段做的主要改进。
然而随着时间的增长我们又发现了更多的问题。一个是我们把代码仓库按分区给分离开来,但会发现代码仓库的活跃是不均匀的。如果一台机器刚好这段时间的访问量非常大,那这台机器的压力就很大,尤其是计算方面的压力,其他的服务器又可能几乎处于闲置状态。存储方面的话我们一般会做 500G-1T 的存储,但是 CPU 我们一般不会配置太高,因为大多数都属于冷数据的。这时候我们就需要一个弹性的计算池。计算和存储是分离的,就是我们的存储可以任意搭配计算池来进行计算。另外一个就是自动化监控,我们的服务从单台机扩展到很多台机,还有分区。组件也越来越多,我们有很多独立的服务,比如有独立的发邮件啊,有独立的 markdown 编译器啊,还有 qc 的服务,还有 CodeInsight 的服务、WebIDE 等等等。服务一多,运维的压力就会成倍增长。这个时候我们需要自动化监控来帮我们解决一些问题。
第四阶段:工业时代
用户通过 Web、HTTP 或者 SSH Git 协议链接到我们的 ULB 之后,内网做转发,网页的访问这边我就没画,到 Web Server,还是通过 RPC 请求到 Repomanager。不一样的是红线区域。用户到 GitServer 之后,先认证之后会连到服务器池,下面也是一个服务器池,组成一个计算池,主要是 CPU 和内存的配备,并没有什么磁盘这种配置。下面是存储池,存储池通过网络文件系统挂载到计算池上,所以现在就形成了这样的结构。存储由存储的负载均衡策略来决定,但是计算池由计算的负载均衡决定。这样压力大时的请求并不会同时发在同一台机器上,就能解决我们之前说的不均匀问题。这个结构里还有很多细节我们接下来探讨。
其中一个细节是,我们所有的 Git 服务都用 CDN 将用户连接起来。哪怕是中国最好的带宽资源,都比不上用户访问的服务节点在他所在城市的骨干节点,这时候我们就找了 CDN 的供应商,CDN 的地域性骨干网络节点把我们的请求转发到 UCloud 源服务器上,虽然这样成本很高,我们付出了两倍带宽的价格,但是最终的使用效果还是不错的,很多用户反映速度和稳定性有明显提升。另外我们计划推广 CDN 到全站,所有的服务。使用 CDN 另外一个好处是可以防 DDOS 攻击,我们同行包括我们自己经常遭受这样的困扰。DDOS 的原理是针对一个 IP 地址,肉鸡不断往这个 IP 地址发送垃圾包,从而导致带宽被占满。使用 CDN 之后,我们给用户报 IP 地址都是全国性的,有几百个 IP 地址,DDOS 往往都是针对单个 IP 地址来攻击的。当我们的节点收到攻击的时候,供应商可以立马将节点替换掉,从而导致大范围问题。所以用 CDN某种程度上来说可以避免 DDOS 攻击。大家都知道 Git 服务关系着公司的线上部署,对稳定性要求非常高。所以我们还是愿意花很大的成本来做这个。看一下,CDN 大概是这样一个结构。
用户通过 CDN 节点,转发到 ULB 的相关端口,和传统的静态分发不太一样的是,传统的静态分发,CDN 节点往往都缓存一些图片、CSS、JS 这些东西,但我们现在所有的数据都是从我们的原站流出去的,CDN 节点并没有给我们节省流量。所有的流量只是用 CDN 节点做了个分发,因为我们的数据都是动态的,而且有些协议不是 HTTP 的。
第二个细节是 LB,LB 就是负载均衡,我们现在的服务器中大量的部署了这种形式的服务。LB 把无状态的服务接口实现了统一。什么叫无状态服务,就是服务不会在内存中做一些状态的存储,比如说缓存,无状态服务的请求应该是和前后文无关的,下一个请求不会受前面的请求影响导致数据改变。其优点是可以部署很多实例,这些实例没有任何差别。针对这些服务,我们通过LB 把这些服务统一了起来。内部服务的相互依赖都通过 LB 完成。
然后是一个监控系统,我们用了 Google 一个团队开源出来的叫 Prometheus 的一个监控器。据我们的 CTO 孙宇聪说这个监控器比较像谷歌内部的监控系统。目前我们使用的感觉还是很不错的。
为什么要做这个,大家都知道木桶原理,很多服务相互依赖的时候,必须所有的服务都可靠,你的服务最终才是可靠的。LB 系统的目的在于:当某个实例出现问题的时候,自动剔除掉,就是做监控级别的自动运维。
LB 和监控系统配合的工作流程是这个样子的。
这张图展示了我们内部系统的服务是如何相互依赖的。这里有几个角色,一个是 LB,一个是监控系统,一个是运维人员,就是左上角这个 ops。ops 操作线上的服务的时候,我么是直接操作这个 LB 配置。举个例子,我们有个 markdown 的编译器,以服务的形式存在,给我们服务中的其他服务提供 markdown 编译的底层的支持。假如有一天你发现 markdown 编译器的承载量已经不够了,必须新加一个实例。这时候上线之后,往往要做一大堆配置才能生效,但是我们操作 LB 让这个实例挂载在 LB上,LB 可是实现动态 reload ,所以可以实现快速上下线。LB 的工作是这样的,我们的 LB 是用配置文件来描述的,ops 操作完 LB,Confd 会生成最终的配置文件。把这个配置文件发送到 Etcd 集群,Etcd 就是一个配置中心,有很多配置项在里面。发到 Etcd 之后,会有另外一个程序就是 Confd,他会一直监控在这个 ETCD 的状态,当 LB 状态发生变化的时候,它就把这个变化过状态的配置拿下来,生成最终的 LB,生成最终配置文件,reload 后服务就上线了。
还有一个是监控系统的角色,我们的监控器是用 Prometheus 搭建的,监控系统有一个配置项,是用来配置服务监控的数据的接口,我们每一个服务都会起一个 HTTP 端口,提供基础的“关键指标”。“关键指标”能显示你的服务是否健康,压力有多少。还是举 markdown 编译器的例子,他的“关键指标”是每秒的处理量,一个 markdown 文本编译完的时间,就是一些自己健康状态的指标。每个服务必须统计好自己的关键指标,再把这些信息以 HTTP Metric 的形式暴露出来,我们的监控系统每隔一段时间去抓取一下这个数据,如果抓取不到或者抓取的关键指标出现了异常,根据配置的警报策略和自动处理策略开始行动,比如他认定某个实例 down 的时候,他就去通知 Etcd 某个实例 down 了, Confd 侦测到后 ,LB 就把这个实例下线。另外,当某个实例出现问题的时候,监控会通过 WebHook 的形式,去通知我们的通知中心,把这个实例有问题的信息发给我们的运维人员,比如发短信,发 App push、发邮件等让运维人员进行下一步处理。这是工业时代一个几乎半自动化的架构。无人值守的时候这套流程也基本可以正常运转。我们从系统上可以允许 Ucloud 内网出现波动,因为不论是任何情况,只要是“关键指标”有变化,我们的报警和自动处理策略就会生效。
工业时代,就是自动化生产的时代,另外要讲的一点,就是容器化。其实我们在第三阶段就开始尝试容器化了。到目前我们 95% 以上的服务都是用 Docker 来提供的。我来介绍一下我们目前是如何使用 Docker 的。一个是我们自己搭建了 Docker Rigestry,目前发了第二版,叫做 distribution ,考虑过迁移到新版但居然不兼容,迁移工具也不够好用,第一版又没有明显的问题,所以我们一直沿用到现在。
此外,我们线上的代码,都是编译在Docker 中,运行在 Docker 中,为什么要这么做呢?编译在 Docker 中有一个明显的好处,比如我们在本地开发的时候,我们是用 JDK8,那我们线上不可能用 JDK7 去编译这个版本,如果更严格我们可能要求小的版本号也一致。想保证版本使用严谨,用容器是一个非常方便的选择。如果在服务器上 linux 操作系统上装这个 JDK,可能过两天就要升级一下,非常麻烦,但是如果在 Docker 中使用,很容易指定版本。另外我们在 Docker 中编译程序,在 Docker 中运行程序。
然后是 Docker Daemon 的管理,每一台服务器上都装一台 Docker 的守护进程,它来管理上面的 Container,我们写了一套工具,原理就是给 Docker 发请求,告诉他应该起哪个 Container,应该停哪个 Container。
这个图大概展示了一下我们是如何用 Docker 的。
运维在操作的时候,有两个接口,有一个 UI Dashboard,我们可以在 Dashboard 上去控制某一个实例,也就是某一个容器,还可以通过命令行的形式来处理,最终形成 Docker 运行的指令,我们的程序由此进行管理和运行,比如我们发一个请求说要构建 markdown 编译器,代码被检出后,在 Docker 中构建,构建完了之后再把 Runtime 进行打包。打包了之后就把这个 Image 推到自建的 Docker Registry,这个时候我们的而其他服务器都可以从这里把 Image pull 下来,在推送完之后,他就通知某一台服务器,比如我们指定了某一台 markdown 编译器是运行在某一个服务器上,他就通知这个服务器,从上面拉下来 Image,然后去把他启动起来,我们一般情况下不会直接操纵这些服务器,都是直接在 UI Dashboard 完成了运维的操作。
下一阶段:信息时代
第四阶段我认为是一个自动化的阶段,下一步就是信息时代,也就是数据驱动的时代,自动化,规模化的生产才是我们的目标。最初农耕时代,可能所有都是程序员或者运维上去执行,搞完了之后进入手工业时代,有一些脚本和程序可以协助我们,后来又进入了工业时代,工业时代就有一些自动化的流程做一些自动化的运维处理,最终的目标是进入信息化时代,信息化时代就是整个我们的服务集群是一个云,这个云是弹性的,只需要告诉他我们需要什么就行了,后面的事情他自己会解决。当然这个目标离实现还是有一定距离。
来展望一下:要做到这些,一个是自动化监控,我们现在有了,但是这个监控还是有些问题,比如说每个组件的关键指标都不一样,每一个都需要单独去配置,我们希望把一些关键指标统一化,更好的量化,这样我们统一写一些监控的报警规则或者自动化处理规则就可以了。日志数据分析决策,这是什么呢,我们很多组件每天在产生上百万行上千万行的日志,这些日志靠运维去看是不可能的,我们希望能对日志进行一些分析,做一些自动化的决策,比如说某一个组件,当他数据出来,我们可能就认为他有问题了,通过 LB 把他下线,再把相关的问题发邮件给相关的人员去看,做自动化的目的是可以解放运维。
架构全球化,多机房异地部署,CODING 是肯定会走向国际的,这是我们已经在规划的一件事情。尤其是码市,会面向全球去接项目的,早晚有一天我们要向全球部署服务,所以我们的服务必须兼容异地化、高延时的跨机房部署。最后一个主要是为了节省成本,当我们服务越来越多的时候,有些服务这几天要求计算资源高,有些服务哪几天要求计算资源高,会存在浪费,所以我们希望实现整个系统可以自动的扩容,形成运维的闭环,在运维人员很少干涉的情况下,自动帮我们节省成本又不失稳定性,我相信在一个超大型的公司,几百万台服务器,肯定都是自动化处理的,希望有一天,我们也能实现这样的愿景。
这是我们希望最终实现的模型。
最底层我们还是会选择云服务商,比如说 Ucloud,AWS,包括我们在香港也部署了一些服务。像最底层的服务,物理机房、CDN,这些我们都选择找供应商,这些供应商都可以水平大规模扩展的。上面一层也是我们不用考虑的,这层主要是 VM,就是说这些服务商把下面这些硬件资源抽象化成上面的这些虚拟的计算机虚拟的网络,把虚拟的网络以 api 的形式提供给我们,我们去编写一些程序,这个程序可能会在某个适当的时机帮我们启动服务器,帮我们自动增加带宽,自动增加 CDN 节点,这就是Resource API。在这一层上面,是我们的 Docker Container 层,所有服务都运行在这层。再上面一层就是我们自己的服务了,例如一个 markdown 编译器,一个 Web 网站,一个 Git 服务,还有我们的 LB,监控、缓存、消息队列等等。我们的运维只通过 Job Manganer 告诉整个集群:我需要起一个实例,大概计算量是多少,那这个云就会自动帮我们调 Resource API、帮我们开虚拟机,配置网络,监控等等把事情全部搞定。最终就是希望实现的架构运维的闭环。
各位做java开发的朋友有学习架构的想法可以来我的java技术交流群:318261748 一起交流学习,大家分享一下学习开发的技巧经验学习方式,也会分享一些学习资料和java架构学习视频在群里给大家下载学习。希望大家能够好好学习,走上架构之路。
相关推荐
【Coding-iPad】是针对iPad平台的编程应用的客户端源代码,开源的特性使得开发者能够深入理解其内部机制,学习并借鉴其中的设计模式和技术实现。这个项目的主要目标是提供一个在平板设备上进行编码的友好环境,使得...
在本节"Go语言基础、进阶、提高课程第二十节 新手代码托管1"中,我们主要讨论了如何将新手程序员的代码托管到云端,以便于协作、备份和分享。Git作为版本控制系统,是代码托管的核心工具。以下是对各知识点的详细...
首先,"Coding iOS客户端"是一个基于Swift构建的App,旨在提供一个平台,让开发者可以方便地进行版本控制、项目协作和代码托管。它的源代码为我们提供了一个鲜活的示例,展示了如何用Swift来构建一个功能丰富的iOS...
Git是一个分布式版本控制系统,而Coding则是一个集成了代码托管、项目管理、质量管理等功能的云端开发平台。本资源旨在帮助用户熟悉如何安装Git,以及如何利用Git和Coding进行项目管理和协同工作。 首先,让我们...
Coding Theory A First Course,作者san ling,chaoping xing 音译。剑桥出版社出版1 Introduction 1 Exercises 4 2 Error detection, correction and decoding 5 2.1 Communication channels 5 2.2 Maximum ...
在压缩包lightview2.8.0中,虽然没有直接提到与Eclipse Zencoding相关的文件,但可以推测这可能是某个项目或库的版本,或许包含了一些示例代码或者相关资源。如果你在使用Eclipse Zencoding时遇到问题,可以尝试在...
这份编码规范,通常被称为"Java Coding编码规范",是每个Java程序员应该熟悉并实践的准则。 1. **命名规范** - 类名:使用大驼峰式命名,所有单词首字母大写,如`ClassName`。 - 方法名:使用小驼峰式命名,第一...
《Game Coding Complete》是一本备受推崇的游戏开发指南,旨在帮助开发者深入理解...总的来说,《Game Coding Complete》的示例源代码是一个宝贵的资源,对于想要提升游戏编程技能的人来说,无疑是一份珍贵的学习资料。
在本话题中,我们将讨论如何在Sublime Text 2中安装和使用JavaScript代码格式化插件——Zen Coding。 Zen Coding,后来更名为Emmet,是前端开发者必备的工具之一,它提供了快速编写HTML和CSS的方法。通过简洁的缩写...
在实际项目中,良好的Phase Coding实践可以提高软件质量,减少错误,便于后期维护和升级。因此,开发人员应注重代码的可读性、可扩展性和可维护性,遵循最佳实践,例如编写注释、进行代码审查和持续集成等。 总结...
多视图对比学习方法中的一种重要技术——Contrastive Multiview Coding(CMC),在学习数据的鲁棒和通用的表示形式方面表现出色。它通过从多个不同视角捕捉信息,促进了学习过程,这样的视角可以是物理的、几何的、...
Alibaba Java Coding Guidelines IDEA插件正是基于这套规范打造的,它集成于IntelliJ IDEA这款强大的Java开发环境之中。安装插件后,IDEA可以在编码过程中实时进行错误和警告提示,帮助开发者及时发现并修正不符合...
这个名为"zencoding小工具"的压缩包文件显然是Zen Coding的一个实现或者扩展,旨在帮助用户快速、便捷地编写代码。下面将详细介绍Zen Coding的核心概念以及如何利用它来提高开发效率。 Zen Coding最初由Dmitry ...
而Coding Block则是SAP ABAP编程中的一项重要概念,它对于理解SAP编程逻辑、提高代码效率和可维护性具有关键作用。这篇文档将深入探讨Coding Block的各个方面,帮助读者掌握这一核心技能。 Coding Block,顾名思义...
在Python的世界里,"coding"不仅仅指的是编写代码,它还涵盖了解决问题、设计算法和创建应用程序等过程。本文档集是针对Python编程的详细资源,旨在帮助开发者更有效地进行开发工作。 **Markdown格式** 文档源代码...
通常它会包含安装步骤、快捷键列表、以及如何自定义和升级ZenCoding的说明。 3. "Zen Coding v.0.7.mxp":这是ZenCoding插件的一个安装包,适用于Adobe Dreamweaver。MXP是Dreamweaver的扩展格式,用户可以通过安装...
Git Coding 使用方法
【coding-interviews源代码】是一个非常有价值的资源,尤其对于准备IT行业面试的求职者而言。这个资源通常包含了各种常见的编程面试题目,旨在帮助开发者熟悉并掌握解决算法和数据结构问题的技巧。以下是对这个资源...
This book is the result of hundreds of emails from all over the world with questions on theory and applications of error correcting coding (ECC), from colleagues from both academia and industry.
它的核心产品包括了敏捷开发管理、代码仓库托管、持续集成与持续部署等,为企业提供了一个从立项到交付的全生命周期的DevOps平台。 在数字化转型的背景下,CODING也积极参与到数字产业生态的建设中,这个生态包括了...