前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何设计运维友好的服务器端系统

如何设计运维友好的服务器端系统

原创
作者头像
韩伟
修改2017-08-03 17:39:25
1.8K0
修改2017-08-03 17:39:25
举报
文章被收录于专栏:韩伟的专栏韩伟的专栏

作者:韩伟,个人公众号:韩大(ID:handa1740168), 以技术提升开发效率

为什么服务器端系统要对运维友好

外网事故,一直以来都是互联网企业力图尽量避免的,也是服务器端程序员最重视的问题之一。然而,如果我们统计一下各种外网事故的原因,会发现一个结论:70%的外网事故原因,都是存在于运维领域的,只有30%左右的事故原因,是由于程序本身的BUG导致。这里说的“运维领域的原因”,包括了由于配置错误、现网操作失误、网络或其他硬件环境变化、硬件故障等等。

在流行“运维”“开发”分离的时代,似乎这些都是运维的锅,但是这锅背了几十年,也没有什么本质上的进步。说明仅仅试图用“分清责任”这种纯管理手段,是解决不了这个技术问题的。

举个例子,当你需要重新部署一个有几百个进程,分为十几不同类型的服务的一个系统时候,你可能要小心翼翼的处理几十上百项配置以及操作命令。这些配置和命令既有大批相似的,也有一些是需要仔细甄别异同的。加上这些配置之中,还有很多互相的关联,你必须理解的非常清楚,加上这些配置和命令的执行顺序也是有严格要求的。部署这一切,就像在操作一个有几百个按钮的机器面板。

最要命的是,如果你搞错了其中任何一项,都可能让系统马上,或者在将来某个不可预料的时间里,造成“外网事故”。这导致了老板就会在半夜三点把你从被窝里扯到电脑前处理这个烂摊子。当然,我说的这个情况并不是每个项目都会发生,但是我们确实是在很多项目中,都不同程度的陷入过类似的陷阱。不知道我们是不是因为都被apache那复杂的httpd.conf所征服过,所以很多程序员都非常热爱配置文件。

“一切都要可配置!”不但成了我们的口号,还成了无数复杂配置项和诡异的工具命令。——但这些东西,在实际的业务运行中,却切切实实的成为了无数颗定时炸弹。

[程序员都爱配置文件]
[程序员都爱配置文件]

程序员都爱配置文件

自动化测试现在已经成为开发的标准流程之一。特别是敏捷开发方式兴起之后,最重要的实践之一就是自动化测试。我们知道,一般我们用来做测试的环境,往往和真实环境不一样。比如我们做功能测试的时候,运行被测试程序的内存和硬盘可能比真正的运行环境要小的多。如果我们的程序因为这些硬件或者其他的诸如IP地址之类的软件差异,都要配置一番才能运行,那么我们的每次测试,都有可能需要手工的去操作一下,这样的测试就不能说是“自动化”了。加上手工操作的错误,更是可能让测试结果严重错漏。

测试工作除了环境上的差异可能导致运维操作,本身测试也需要多套环境的,比如很多系统都有多个分支在同时开发,又或者有内部功能测试、外部邀请用户测试、公共测试等多个测试环境。假设我们的软件,每次部署都要手工进行一大堆的操作,那么面对多个环境,频繁的发布新版本来做测试,部署的工作肯定是非常繁重的,而这些繁重的工作本来是可以尽量的避免。

[美好的CI闭环往往毁于复杂的部署流程]
[美好的CI闭环往往毁于复杂的部署流程]

美好的CI闭环往往毁于复杂的部署流程

快速开发一直是现代软件企业追求的目标。由于需求、市场日新月异,软件产品和应用系统也被迫着每天在更新功能。说到服务器端软件,我们往往需要在开发的过程中,配合其他很多程序一起开发调试,最典型的就是和客户端软件交互。

假设每个参与项目的程序员,都集中连接到一个开发服务器上进行调试,必定产生各种互相影响的事情。而且这种跨机的开发环境,往往也只有一些命令行界面,比不上图形界面的IDE软件使用效率高。假设我们开发的程序,特别是服务器端软件,能直接在开发的工作机上运行和调试,那么除了能响应更快以外,还可以方便的在多个程序同时运行的时候进行调试,这对于常常因为“联调”而头疼的工程师来说,是一个非常有效的提升工作效率的措施。

但上面说的这些措施,都需要我们的服务器端程序,能很方便简单的部署到各种环境上。反过来说,有的服务器系统结构比较复杂,要启动很多进程,配对很多配置文件才能启动,那么大家一定会懒得部署很多套,而是都挤到一个环境上做开发。

分布式系统在运维友好方面的难点

防止资源泄漏。我们知道,服务器端程序需要长期运行,特别害怕资源系列,比如内存漏洞、文件句柄漏洞、网络连接相关漏洞等等。所以很多时候,我们愿意在服务器一启动的时候,就把所有需要的资源,都全部“占用”或“分配”好,然后不管后续来了多少个请求,做什么事情,都完全不需要“分配”,这样就杜绝了一切的“泄漏”。然而,这个方法也导致程序在运维上的复杂程度大大提高。

首先,我们难以明确的硬编码一个程序所运行的硬件资源,而是设计了诸如配置文件、命令行参数这些东西,来根据运行时的环境,来确定可能使用的硬件资源。比如我们会在配置文件中,设计一个“网络协议缓冲区大小”的配置项,根据服务器的内存大小来配置。但是,一个程序中的功能可能是很复杂的,要把所有用到的内存、文件等资源都弄成配置项,这个配置文件必定也同样巨复杂无比。

如果我们指望运维人员去理解这些配置文件,那还不如开发人员自己去做运维,因为开发人员有时候自己都没想清楚这些资源的合理配置应该是怎样——原因是太过于依赖这种“预先申请”的方式,而习惯于把这些难处理的问题,都延后来解决了。防止资源漏洞,固然是一个重要的问题,但是仅仅是简单化的把资源申请都变成配置文件,却同样带来另外一个灾难。

特别要命的是,这种配置文件灾难在多进程协作的系统中,会承几何倍数的增长。这种运维复杂度,在一个系统刚刚上线的时候,似乎都还可以接受,但是随着系统逐步变得更大、更复杂,运维工作的难度就好像温水煮青蛙一样,慢慢的变得完全不可收拾。有些系统随着3-5年的运营,到后来居然发展到完全没有人能从零开始部署一套新环境的地步。

[1497602533175_3164_1497602533321.jpg]
[1497602533175_3164_1497602533321.jpg]

打一成语

快速诊断故障。现在的商业应用系统,往往都不会是一个很简单的功能体,而是会包含了大量相关或者不相关的功能。而我们最害怕的问题,就是这些共同运行的功能,在同时处理成千上万的网络请求的时候,如果因为某一个部分功能代码的BUG,导致整个系统不可用。所以我们往往更希望建立某种隔离体系,比如把不同功能的代码运行在不同的进程里。

这样利用操作系统的工具,就能很快的发现那些出问题的代码。但如果你真的要把一个系统的多个功能分开到不同的进程中运行,首先会碰到的就是进程间通讯的问题。这个问题是现代分布式系统的核心问题之一,无数的开源软件项目都在试图解决这个问题。但不管你是使用开源软件,还是自己写代码解决,这都会让系统的进程变多。特别是我们很喜欢按功能来划分代码和进程,那就是说在运维一个系统的时候,我们需要面对大量“不同种类”的进程。

而我们越是划分功能细致,进程的种类就越多,需要操作和运维的进程就复杂。在管理这些进程的时候,除了前面说到的一些性能参数需要配置,还有巨量的进程间关系需要配置。而这些进程间关系,还会随着业务的变更而变化,对于那些没有具体接触开发需求的运维人员来说,简直是噩梦。

也许有些程序员一开始是在通信企业工作,所以很习惯于按进程划分功能,按通信层次来组织系统,但是随着业务系统的日益复杂化,这种工作习惯带来的是大量的麻烦——每周都有可能需要往系统中加入新进程,或者调整某些进程的通信关系。不同的行业需要不同的技术方案,这才是理性的工程师的想法。

[1497602543504_5652_1497602543630.jpg]
[1497602543504_5652_1497602543630.jpg]

小猫喜欢画地为牢,我们的代码也喜欢用进程边界作牢

负载均衡。现代的服务器端系统,基本上都是分布式系统。也就是由多个服务器、多个进程组合起来提供服务的系统。而为了让这个系统工作稳定,最常见的措施,就是防止过载。在多个进程中防止某一个过载,就需要负载均衡。

而防止所有同类的所有进程过载,则需要过载保护。分布式系统最常见的配置工作之一,就是要配置每类进程启动多少个,每个进程的过载保护阈值。然而,在一个上千个进程,几百个服务器的系统中,要准确无误的填对这些配置,实际上是很难的。特别是这些服务器并不都如提供商说的那样是性能一致的。

假如你需要在集群中加入一些服务器,或者修改(搬迁)某些服务器上的服务,那么更是危险重重,因为稍有不慎,就可能让原来能工作的系统出现故障。然而,和业务需求在不停变化一样,运维环境也是在不停变化,比如搬迁IDC就是最常见的“折腾”。

我们大可以编写很多运维管理的工具,来试图“自动化”这些工作,但是,业务需求也是在不停“折腾”的,而在一些“开发、运维分离”的团队中,开发人员可不太关心运维工具的开发,因为他们已经被市场和业务人员逼得连续加班,只想功能尽快上线拉倒了。由于需要负载均衡,而产生的大量服务器端软件的与我内工作量,由于和集群中巨大服务器的数量相关,所以是最直接体现运维和开发服务器端系统困难的地方。

如何开发运维友好的服务器端系统

为了让服务器端系统能够良好的运行,我们显然应该采取一些开发措施,而不单纯的依靠所谓“运维”甚至更不靠谱的“管理”手段来降低失误和故障。

第一个可供参考的思路,就是“建立具备性能弹性的系统”。所以性能弹性,最简单的是指,我们的服务器进程,可以在各种不同的性能环境下运行,而无需复杂的配置文件或运维操作。这里除了最简单的自己检测机器的IP地址、内存大小等自我配置的功能外,更重要的是我们对于资源管理的思路上的改进。由于一个系统要处理的问题可能比较复杂,需要使用到的资源也会很复杂,比如我们需要用内存来缓冲没收完的网络包,还需要用内存来存放用户的会话数据等等。

如果我们只是把这样一块块内存都提出来配置,就会有一大堆各种内存容量的配置。然而,我们完全可以通过建立一个业务抽象,来简化这种资源模型。比如对于一个在线交互的系统,我们可以把资源管理的单位定义为“会话”——每个会话代表了一次“并发”的服务,每个会话要使用多少资源,是我们可以设计的,然后我们注意管理总的“会话”数量,防止资源泄漏。

当然这种“会话”在不同的业务系统中,其概念和功能可能都不一样,幸好我们还可以用面向对象的思想,来用类和对象封装这些会话及其相关数据。这样我们在性能规划的时候,就不用在程序到处翻找使用“资源”的地方机器配置,而仅仅抓住一个关键变量就可以了。更进一步的是,我们可以对“会话”这类关键指标,采用一种“池”的管理策略,把对这种对象的使用,变成需要“申请/归还”的机制,这样我们放弃在一开始就“分配”大量资源的做法,而是根据实际需要来分配资源,而由于“池”的限制,在资源达到上限的时候,拒绝进一步的服务请求,在防止资源漏洞的同时,解决一些过载保护的问题。

而且,在某些环境下,我们还可以让这个“资源池”变得更智能和弹性,比如我们可以在请求压力接近阈值上限的时候,不是简单的拒绝服务,而是开始启动一些扩容或者报警的工作。又或者我们可以定期的查询被“申请”的资源的处理情况,如果发现占用时间过久,就可以清理掉这些服务请求,这样就有了一定的自我恢复服务的弹性。

如果建立了具备“资源弹性”的系统能力,这样的进程只需要进行很少的配置就能自我管理和运行。从根本上减轻了运维工作的复杂程度,也降低了环境变化对系统的影响程度。同时抽象良好的功能代码,在代码维护和开发上也非常有好处,可谓一举多得。

[1497602554334_6247_1497602554469.jpg]
[1497602554334_6247_1497602554469.jpg]

子弹封装了火药、弹头、底火,所以告别了通马桶式发射

第二个思路,是“在功能容器下运行”。在某个项目实践中,我见过某一个系统,他的每个进程,都包含了整个系统的全部功能代码。通过启动时的命令行参数,可以指定此进程需要提供什么功能。这个系统在运维的便利性上,就远远比需要配置、部署各种不同功能发布包的系统来的简单。而且这个服务器系统,还可以以单进程全功能的形态,用于开发和自动化测试,在开发效率上有着明显的优势。

而在JSP/Servlet技术的使用中,我们往往也是把不同的WebApp部署到不同的Servlet容器(如Tomcat/Resin等)中运行,而不需要完整的配置各种不同的Servlet容器。现在还有一些系统,把主要的业务功能,都用类似python/JS/Lua这类脚本语言来编写,系统中的进程部署,只要完成了脚本容器(引擎)后,基本上就是拷贝脚本文件而已。在容器技术的支持下,我们除了可以简化部署的工作,还可以获得一些“热更新”的好处。

而基于硬件、访问量的运维工作,运维人员可以集中注意力管理好“容器”即可。比如GoogleAppEngine,就是一个高度自动化的Web App容器,使用者甚至完全不需要安装部署任何软件,直接上传一个PHP脚本或者Servlet类文件,就可以开始提供服务。在容器下运行服务器系统,还可以利用容器规定的一些通讯规范,做一些自动化运维的事情,比如自动扩容、缩容、容灾——容器可以自我发现集群的运行状态,加入新的运行资源,剔除有故障(比如访问超时)的运行资源。这也是所谓SOA概念最常见的实现方式。

从另外一个角度说,如果有了容器支持,我们在配置服务器进程的时候,是可以简化对整个集群中各种关系的配置的,因为只要告诉容器,怎样加入一个目标的集群,其他的事情都可以让容器去和其他集群成员协商配置。容器除了提出了统一的功能代码开发环境约束,还规范了运维工作。这对于需要频繁变化服务内容,以及不断改变运行环境的项目来说,是非常有价值的。

在WEB开发领域,容器的概念已经是深入人心了,所以这一类的系统应用比较广泛,而运维工作也能比较专业顺利的开展,但是在诸如网络游戏这种没有“行业标准”的领域,关于功能容器的概念还是没有被很多人接受,很多人还是在质疑为什么要给自己套上这个“枷锁”,却不知道自由从来都是在约束下行走的。

[1497602566382_5556_1497602566517.jpg]
[1497602566382_5556_1497602566517.jpg]

进程需要容器,功能也同样需要容器,有容器比没容器好!

最后,我想说说各种运维工具,不管是Chef,还是各种非通用的运维部署系统,如果仅仅以操作系统提供的能力,就想把所有的系统都统一管理起来,是非常困难的。而如果我们在开发的时候,就充分考虑到系统的运维需求,那么可能只进行了一些简单的约束,都能让运维工作有巨大的改进。我想这也是所谓DevOps流行起来的原因吧。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么服务器端系统要对运维友好
  • 分布式系统在运维友好方面的难点
  • 如何开发运维友好的服务器端系统
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档