回顾互联网的发展史,服务端开发和客户端开发的边界曾一度趋于模糊。2010年,互联网尚处于全面爆发前夕,短、平、快,贴近用户需求进行极限开发的思潮开始萌芽,紧随其后,敏捷(Agile)开发迅速在互联网领域兴起,极限编程、结对编程随之在软件工程界得以推广。然而,一段时间后,互联网技术风向标便开始转向倡导“全栈”,对软件研发工程师的要求也越来越高。期间,具备客户端、服务端全栈开发能力成为一项基本要求,就连测试、运维也转向服务化。
2012年前后,随着移动设备硬件性能的快速提升,移动互联网开始崛起,前端三剑客Vue、AngularJS、React引领大旗,将那些一度被服务端模板化渲染夺走的技术阵地一一收复。原本计划奔赴全栈的程序员们,又重新被划分为客户端开发人员和服务端开发人员,但此时两者的职责与最初已经相去甚远。服务端开发更加注重对业务的理解和抽象,致力于系统的高可用、高并发和高性能,而客户端开发则更偏向于交互和体验。
本章主要围绕“服务端开发”展开,分别介绍服务端开发的定义、职责、技术栈、核心流程及进阶路径。通过学习本章,读者可以对服务端开发有一个较为全面的理解。
在互联网领域,服务端的全称为“服务器端”,通常也被称为“后端”,与之对应的客户端则被称为“前端”。服务端和客户端既相互独立,又相辅相成。
在正式介绍服务端开发的定义之前,我们先直观地感受一下服务端和客户端。如图1-1所示,客户端通常直接面向用户,如个人计算机的Web浏览器、手机App,而服务端则通常是部署于机房的一台计算机或一个计算机集群,对用户不可见。客户端和服务端可以基于多种互联网通信协议进行通信。
图1-1 客户端与服务端的关系示意图
(1)什么是客户端
客户端(Client)是指运行于普通客户机(如个人计算机、手机、平板)上的程序,用于收集用户意图、为用户展现服务结果,定位是与用户交互。除了一些只在客户机本地运行的应用程序之外,其他客户端均需要与服务端互相配合运行。
(2)什么是服务端
服务端(Server)是指运行在服务器端的程序,为客户端提供API(Application Programming Interface)服务。服务的内容包括向客户端提供资源、存储客户端数据及进行数据计算等。
(3)客户端与服务端如何协作
目前,软件客户端和服务端的协作分为C/S(Client/Server)和B/S(Browser/Server)两种模式。相应的,客户端程序可分为通用客户端和特定客户端两种。Web浏览器就是一种通用客户端,其主要功能是将用户向服务端请求的资源呈现在浏览器窗口中。资源通常有html、pdf、jpg等格式。主流的Web浏览器有微软的IE、Mozilla的Firefox、苹果公司的Safari、Google的Chrome及Opera软件公司的Opera。手机客户端则是一种特定客户端,是指可以在手机终端上运行的软件,如现在流行的各类App。这类客户端依赖客户端应用程序调用操作系统组件,收集用户意图,为用户展现服务结果,在用户本地处理用户提交的或服务端返回的数据。
(4)什么是服务端开发
服务端开发,简单理解,即开发运行在服务器端的程序。无论B/S模式还是C/S模式,都需要有相应的服务器端程序提供服务。在一些简单的应用场景中,服务端开发主要围绕数据库做CRUD(Create、Retrieve、Update、Delete)的编排服务,但在头部互联网企业的应用场景中,要求则高得多,编码的同时还需要与网络、容器、中间件、数据、搜索、算法、运维、测试、安全等一系列技术体系打交道。
在互联网行业,软件开发项目通常以需求为单位。如图1-2所示,一个完整的软件开发项目涉及需求评审、软件设计、开发、联调、测试、上线等诸多环节。参与角色一般包括产品经理、项目经理、运营、交互、视觉、算法、服务端开发、客户端开发、测试开发等人员,重点项目可能还有法务、财务、风控等相关人员。由于在大多数场景中,核心业务逻辑由服务端实现,因此在项目的整个生命周期中,服务端开发工程师通常要全程参与。
图1-2 互联网行业软件开发项目相关流程
自2012年起,移动互联网高速发展,大量新奇的应用逐渐渗透到人们生活和工作的方方面面。目前,资讯、社交、游戏、消费、出行等丰富多彩的移动互联网应用发展迅猛,正深刻改变着信息时代的社会生活。通过移动互联网,人们可以使用手机、平板、智能手表等移动终端设备浏览新闻、在线聊天、玩网游、看电视、在线阅读、收听及下载音乐等。
随着移动应用的功能越来越丰富,需要服务端处理的业务逻辑的复杂度也越来越高,互联网企业对服务端开发工程师提出了更高的要求。服务端开发工程师的岗位职责远不局限于“开发运行于服务器端的程序”,而是要求工程师同时具备业务分析、产品设计、架构设计、技术攻关、团队协作、文档编写、系统维护等综合能力。事实上,这些能力正是服务端开发的不同阶段所需的。具体而言,服务端开发的阶段可分拆为需求分析、抽象建模、系统设计、数据设计、非功能性设计、编码实现及发布运维,每个阶段都需要相应的能力作为支撑。
在传统软件开发中,工程师的主要职责是根据需求文档进行系统设计和编码实现,侧重于技术而轻业务。但当今的互联网企业则要求服务端工程师“技术与业务并行”。简而言之,服务端工程师不仅要知道“怎么做”,而且要知道“为什么要做”以及“如何做得更好”。
正如前文提及的那样,现阶段的服务端开发早已不再是简单地围绕着数据库编排CRUD服务。很多时候,服务端应用的第一行代码尚未写就,工程师便需要与产品、运营、法务等人员及网络、中间件、操作系统、搜索、数据、算法、运维、安全等技术体系打交道,互联网企业对工程师的要求越来越高。
单从技术栈来看,服务端开发涉及编程语言、开发工具、开发框架、数据库与数据存储、中间件、操作系统、应用部署、运维监控等知识体系。
对服务端开发来说,编程是最基本的能力。目前,全球已经投入使用的编程语言超过50种,其中多数可用于服务端开发,但术业有专攻,不同语言的流行度和学习成本不一样,各自的特性也有较大差异。在选择开发语言时,可以参考TIOBE排行榜。TIOBE排行榜是基于业界资深软件工程师、课程和第三方厂商的数量,使用搜索引擎和第三方数据统计出的排名,每月更新一次。排名反映的是编程语言的热度。工程师可以用它来评估自身编程技能是否跟上了趋势,也可以通过它来了解世界范围内开发语言的走势。
1.选择编程语言的一般原则
在实践中,如何选择服务端编程语言呢?易学习、功能强大、生态丰富是朴素而直接的判断要素,但与此同时也充满争议,因为这些要素缺乏具备公信力的评判标准,感性评估则往往因人而异。在软件工程师圈子里谈及编程语言时,假设笔者说一句“PHP是世界上最好的语言”,则必然会引起工程师们的激烈讨论,众多Java、Python、C/C++、Go等语言的爱好者会一起“群殴”笔者的观点。那么,关于编程语言的选择难道就没有可遵循的原则吗?当然不是,通常有4个选择依据。
(1)编程语言须服务于系统架构
合适的编程语言应根据具体的场景来选择,而不是基于工程师自身的偏好。简而言之,即根据场景选择语言,而不是以语言去应付场景。例如引擎层是以C/C++为主,而算法层则是以Python为主。在一些特定的领域,Lisp、Scala等冷门语言亦有应用。
(2)尽量复用前人积累
软件工程一旦发展到比较庞大的规模,即使采用了先进的编程语言,如果不能很好地复用前人的积累,而不得不重新造轮子,也必然会导致效率降低。
(3)尽量避免冷门语言
一门编程语言的兴起通常都是从一个典型的软件产品开始的。比如日本人设计的Ruby语言,它的代表作是Gitlab。商业公司如果选用它,就不得不面对冷门语言工程师招聘困难的问题,即便招聘到了资深的冷门语言专家,软件的开发、维护,以及工程师的发展也将是问题。
(4)尽量选择生态丰富的语言
微服务开始起步后,给更多小众语言提供了更好的生存环境。Service Mesh技术让小众语言也可以通过接口或者系统调用获得更多中间件体系提供的便利,但当前尚处于前沿,还远未到完全成熟应用的阶段。在微服务之下的SOA(Service-Oriented Architecture,面向服务的架构),大多数还是建立在传统语言之上。比如阿里坚持深耕Java体系多年,一个重要的原因是其他语言短期内难以比拟Java的生态优势。事实上,不少语言在编程效率上优于Java,但一旦用来做大型工程,在达到一定规模时通常会陷入自洽性矛盾:要么放弃语言的灵活性,要么放弃工程的可持续性。
2.互联网企业常用的服务端编程语言
可用于服务端开发的编程语言很多,企业在选择编程语言时会根据自身的实际情况权衡,分出主次,即以1种编程语言为主要开发语言,1~3种编程语言为辅。这不难理解,选择一种编程语言为主,持续深耕,对于软件开发、维护,技术积累、传承,都是非常有利的,但同时,一种编程语言很难满足众多复杂业务场景的需要,因此需要选择几种辅助编程语言。在此,笔者列举了部分互联网企业服务端开发的主流编程语言,如表1-1所示,供读者参考。
表1-1 部分互联网企业服务端开发的主流编程语言
工欲善其事,必先利其器。从事服务端开发工作,选择称手的工具很重要。常用的开发工具包括集成开发环境、代码管理工具及建模工具。
1.集成开发环境
集成开发环境(Integrated Development Environment,IDE)是指用于提供程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面等工具。它是集成了代码编写功能、分析功能、编译功能、调试功能等于一体的开发软件服务套件,所有具备这一特性的软件或者软件套(组)都可以叫集成开发环境。如微软的Visual Studio系列、JetBrains的IDE系列等。IDE可以独立运行,也可以和其他程序并用。
IDE众多,如何选择呢?针对服务端主流编程语言,笔者推荐两个系列的IDE给大家。
(1)VSCode
VSCode(Visual Studio Code)是由微软开发的一款功能强大的现代化轻量级IDE,社区版免费。VSCode具有强大的插件扩展能力,几乎支持所有主流语言(C++、Java、Go、Python等)的项目开发。该IDE支持语法高亮、代码自动补全(又称IntelliSense)、代码重构等功能,并且内置了命令行工具和Git版本控制系统。用户可以更改主题和键盘快捷方式实现个性化设置,也可以通过内置的扩展程序商店安装扩展以拓展软件功能。
(2)JetBrains系列
JetBrains是一家捷克的软件开发公司,该公司出品了支持Java、C++、Python、Go等主流编程语言的系列知名IDE,堪称IDE界的集大成者。其中最具代表性的是IntelliJIDEA,在业界被公认为最好的JavaIDE,它在智能代码助手、代码自动提示、重构、JavaEE支持、版本工具(Git、SVN等)、JUnit、CVS整合、代码分析、GUI设计等方面表现优异,深受Java工程师认可。此外,JetBrains为Go语言提供了GoLand,为Python语言提供了PyCharm,这两款IDE也广受工程师赞誉,长期位于最受欢迎的IDE榜单前列。
2.代码管理工具Git
在实践中,一个软件项目通常由多名工程师协作完成。工程师各自开发自己所负责部分的同时,还需兼顾整个项目。由几个人协同开发的小项目尚可通过人力管理来应对,但几十人、几百人协作的项目呢?如果没有一个强大的工具支撑,那将无疑是人力“黑洞”。著名的版本控制软件Git便是在这种背景下诞生的。
Git最初是Linus Torvalds为了帮助管理Linux内核而开发的一个开放源码的版本控制软件。它是一个开源的分布式版本控制系统,可用于敏捷、高效地处理任何或小或大的项目。通过Git,工程师可以方便地创建代码仓、创建开发分支、合并代码、提交代码、解决冲突、查看提交记录等。
3.建模工具Visual Paradigm
服务端开发工程师的日常工作并不止于“编写运行于服务器端的程序”。根据笔者的经验,编程占据工作时间的比例通常不到30%,那么,其他时间在做什么呢?主要是做设计。设计的本质其实就是“与自己的沟通”“与合作伙伴的沟通”。通过沟通帮助自己厘清思路,同时让合作者理解你的思路。在实际工作中,笔者将至少40%的时间用在沟通上,比如业务对焦、需求评审、服务端方案评审、客户端方案评审、系统重构、风险评估等,沟通对象包括产品、运营、技术、质量、视觉、交互等。在跨团队合作、跨部门合作甚至对外合作的场景中,“文山会海”往往使得沟通更加低效。
为了提升设计环节的沟通效率,在较大的项目中,业界一般采用建模利器Visual Paradigm辅助。Visual Paradigm是一款UML建模工具,可以支持多种图表类型,如类图、业务用例图、时序图、状态机图、动态图、组件图、部署图、对象图、场景交互图、领域模型图、业务框架图、组件关系图等。
通过Visual Paradigm将需求分析、抽象建模、系统设计等环节有机地链接起来,循序渐进,从具体的业务用例到抽象的领域模型,再到技术实现方案,便于不同角色理解,提升沟通效率的同时提升设计质量、减少后续维护成本。Visual Paradigm在服务端开发生命周期中的应用如图1-3所示。
图1-3 Visual Paradigm在服务端开发生命周期中的应用
1.什么是框架
在软件工程中,框架(Framework)的定义为:整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法。一个框架是一个可复用的设计构件,它规定了应用的体系结构,阐明了整个设计、协作构件之间的依赖关系、责任分配和控制流程,为构件复用提供了上下文关系。
框架是实现了某应用领域通用功能的底层服务,软件开发者可以在通用功能已经实现的基础上开始具体的系统开发。框架提供了所有应用期望的默认行为的类集合,具体的应用通过重写子类或组装对象来支持应用专用的行为。通俗地说,框架是完成了某种应用的半成品,它可以提供一些常用的工具类和基础通用化的组件,基于此,软件开发者可以专注于自身业务的开发。
2.为什么要使用框架
软件系统发展到今天已经很复杂了,特别是服务器端软件,它涉及资源、网络、中间件、离线计算、搜索引擎、实时计算、运维、测试、安全等一系列技术体系。因此,为开发出完善、健壮的软件,对工程师的要求将会非常高。如果采用成熟、稳健的框架,那么一些基础的通用工作(如事务处理、安全性、数据流控制等)就可以交给框架处理,工程师只需要专注于完成具体的业务逻辑设计,就可大大降低开发难度。从软件工程师的角度看,使用框架最明显的好处是复用,包括设计复用和分析复用。
❑设计复用:框架提供可复用的抽象算法及高层设计,并能将大系统分解成更小的构件,而且能描述构件间的内部接口。这些标准接口使得在已有构件的基础上通过组装建立各种各样的系统成为可能。只要符合接口定义,新的构件就能插入框架中,构件设计者就能复用构架的设计。
❑分析复用:框架的使用者若按照框架的思想和规范来分析、描述、设计软件工程,就可以将软件工程划分为符合一定标准的构件,那么使用同一框架的工程师之间就能进行高效的沟通。简言之,统一了语言之后,使用同一框架的工程师就可采用统一的工程语言来沟通。
一种技术的终极目标是为业务发展而服务。从业务的角度来讲,首先,框架服务于企业的业务发展和战略规划;其次,框架可提高企业的竞争力,包括降低研发成本、提高质量、缩短周期等;最后,框架实现上述目标的方式是进行有效的知识积累。软件开发是一种知识活动,因此知识的高效积累至关重要。框架能够采用一种结构化的方式对某个特定的业务领域进行描述,也就是将这个领域相关的技术以代码、文档、模型等方式固化下来。综合来看,合理地使用框架可以带来以下诸多好处。
❑显著提升代码的可复用性,从而提高软件生产效率和质量。
❑软件代码结构规范化,从而降低工程师之间的沟通成本和后期维护成本。
❑提高知识积累效率,通过设计框架和领域构件使得知识得以体系化积累。
❑降低软件研发难度,工程师可更专注于业务领域,使需求分析更充分。
❑提高协同工作效率,基于统一的规范和原型,有助于多人协同工作。
❑大粒度地复用可降低平均研发费用,缩短开发周期,减少开发人员,降低维护成本。
3.框架的分类
框架与类库是不同的,框架强调的是软件的设计重用性和系统的可扩充性,以缩短大型应用软件系统的开发周期,提高开发质量。相较于传统的基于类库的面向对象重用技术,应用框架更注重于面向专业领域的软件重用。应用框架具有领域相关性,构件根据框架进行复合而生成可运行的系统。框架的粒度越大,其中包含的领域知识就越完整。考虑到面向的领域和编码实现,软件开发框架一般可分为如下3类。
1)基础类库。基础类库是一种最基础的框架,涵盖多数项目所需要的基础类。例如,Java集合框架主要包括两种类型的容器:一种是集合(Collection),存储一个元素集合;另一种是图(Map),存储键/值对映射。Collection接口又分List、Set和Queue三种子类型,再下面是一些抽象类,最后是具体实现类,常用的有ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap等。
2)基础框架。基础框架一般是针对于某特定领域,实现特定领域所需要的常用功能。例如Hibernate,它是一个对象关系映射框架,支持几乎所有主流的关系型数据库,如MySQL、Oracle、SQL Server和DB2等,开发者只须在配置文件中指定好当前使用的数据库,而不必关注不同数据库之间的差异。Hibernate提供了一系列数据访问接口,工程师可以通过这些接口轻松地使用面向对象思想对数据库进行操作。
3)平台框架。平台框架一般需整合或实现某种编程语言开发所需要的常用功能。例如Spring,它是一个综合型框架,致力于J2EE应用的各层的解决方案,而不是仅仅专注于某一层的解决方案。一些场景下,Spring可以作为应用开发的“一站式”选择,并贯穿表现层、业务层及持久层。Spring功能强大,可提供IOC、AOP及Web MVC等功能。Spring可以单独应用于构建应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用。
4.常用框架举例
针对部分主流服务端编程语言,笔者列举了对应的常用框架,如表1-2所示。
表1-2 部分编程语言的常用框架举例
信息时代,数据已悄然成为企业的核心资产。由于数据库是数据唯一的持久层,几乎所有的业务流程最终都依赖数据库中的数据,因此作为服务端开发工程师,掌握数据库及数据存储技术尤为重要。本节将着重介绍数据与数据库的基本概念、分类以及常用数据库的特点和选型指标等。关于数据库及数据存储实战,笔者将在后文中专门介绍。
1.数据与数据库
数据是数据库中存储的基本对象,是按一定顺序排列组合的物理符号。数据有多种表现形式,可以是数字、文字、图像,甚至是音频或视频,它们都可以经过数字化后存入计算机。数据库是数据的集合,具有统一的结构形式并存放于统一的存储介质内,是多种应用数据的集成,并可被各个应用程序所共享。
数据库是数据管理的有效技术,是由一批数据构成的有序集合,被存放在结构化的数据表里。数据表之间相互关联,反映客观事物间的本质联系。数据库能有效地帮助一个组织或企业科学地管理各类信息资源。
2.数据分类
对于用户而言,在通过个人计算机、手机等设备使用软件应用时,接触到的都是图形化界面,无法直接接触到数据库。信息时代,数据库无处不在,生活的诸多方面都是建立在数据库的基础上的。例如,登录App时,用户的账号和密码都存储在服务器的数据库中;购买火车票、机票时,余票信息、个人购票记录也存储在数据库中;网上购物、订外卖、订酒店、订电影票等信息也都存储在数据库中。
数据库是存储数据的,没有数据的数据库,就不能称为数据库,个性推荐、运营分析等也需要大量的数据。那么数据从哪里来呢?在互联网领域,数据按来源大致可以分为以下两类。不同类型的数据,收集方式也存在差异。
(1)用户必要数据
比如电商平台的用户购物记录、收藏记录,这些数据需要存储到服务端的数据库中,当用户查看记录时,前端发起请求,经由服务端从数据库中获取对应数据,并显示出来。
(2)运营、分析数据
这类数据通常对用户不可见,而是企业为了实现个性推荐、运营分析所使用的数据。获取这类数据的方式主要是埋点,即在产品流程(用户与软件客户端交互流程)的关键环节植入相关统计代码,用来追踪用户的行为,并上传到服务器,通过数据库存储起来。比如,为了实现个性化推荐,电商平台会统计用户的浏览时间、点击商品等关键信息,基于这些数据来实现。
3.客户端、服务端和数据库
软件客户端和服务端的协作都需要有相应的服务器端程序提供服务。在简单的应用场景中,服务端主要围绕数据库编排CRUD服务。客户端、服务端和数据库的关系如图1-4所示。
图1-4 客户端、服务端和数据库的关系
4.数据库分类
数据库大致可以分为两大类,即SQL数据库和NoSQL数据库。SQL(Structured Query Language)数据库指关系型数据库,主要代表有SQL Server、Oracle、MySQL、PostgreSQL、SQLite。NoSQL(Not Only SQL)泛指非关系型数据库,主要代表有MongoDB、Redis、HBase、Memcached。SQL、NoSQL数据库在存储数据类型和存储方式上差异较大。
关系型数据库适合存储结构化数据,如用户的账号、积分、等级、注册时间等。这些数据通常需要做结构化查询,比如过滤出积分大于1000的所有用户,使用SQL查询就非常方便。在这类场景下,关系型数据库就略胜一筹。
随着互联网的发展,海量数据场景越来越多,如发微博、发微信、发评论等。一方面,这些数据规模大,增长速度难以预计;另一方面,这些数据类型比较复杂,可能同时包括文字、图片、音频、视频等,使用关系型数据库无法直接存储。关系型数据库在应对这些场景时显得有些力不从心,逐渐暴露出许多难以克服的难题,因此出现了针对大规模数据场景,以性能卓越和应用便捷为目标的数据库产品—NoSQL数据库。NoSQL数据库是“非关系实体模型”的数据库,NoSQL的意思是“不只是SQL”(Not only SQL),而不是“不是SQL”(No SQL)。显然,NoSQL数据库的出现并不是要完全否认或替代关系型数据库,而是作为传统关系型数据库的一个合理补充。
5.服务端常用数据库
(1)常用关系型数据库
1)Oracle。甲骨文公司开发的商业数据库,不开源,支持所有主流平台,性能好,功能强,稳定性好,安全性好,支持大数据量。在很长的一段时期,甲骨文凭借其在服务器、数据库软件、存储设备上的优势,几乎垄断了全球商用数据库系统的市场。但是,Oracle数据库非常复杂,收费高,以至于在互联网飞速发展、数据量爆炸式增长的背景下,这种昂贵、扩展性不足的商业数据库已不再是企业的优选方案。2009年年底,阿里开始“去IOE”,其中“O”指的便是Oracle数据库,即用MySQL和自研数据库替代Oracle。目前,头部互联网企业已经很少大规模使用Oracle。
2)SQL Server。SQL Server最初由Microsoft、Sybase和Ashton-Tate三家公司共同研发,后来由Microsoft独立研发。作为一款商业数据库,SQL Server具有易用性、可伸缩性,以及优秀的数据管理、分析功能,是一款高性价比的商业数据库。但是,它开放性较差,仅支持Windows平台。
3)MySQL。MySQL最初由瑞典的DataKonsultAB公司研发,该公司被Sun公司收购,后来Sun公司又被Oracle公司收购,因此MySQL目前属于Oracle旗下产品。MySQL软件采用了双授权政策,分为社区版和商业版。由于体积小、速度快、总体拥有成本低,MySQL通常是中小型网站的数据库首选。
4)PostgreSQL。PostgreSQL使用BSD协议的完全开源的、免费的关系型数据库管理系统,支持多种操作系统,功能强大,可以和多种开源工具配合。PostgreSQL不仅仅是关系型数据库,还同时支持JSON数据、全文检索以及其他扩展。
(2)常用非关系型数据库
1)Redis。Redis是现在最受欢迎的NoSQL数据库之一,具备如下特性:基于内存运行,性能高;支持分布式,理论上可以无限扩展;是键值对存储系统;使用ANSI C语言编写,遵守BSD协议,支持网络,是可持久化的日志型数据库,提供多种语言的API。
相比于其他数据库类型,Redis具备的优势是:读写速度极快、支持丰富的数据类型、操作具有原子性等。由于Redis性能出色,已被Twitter、阿里、百度等公司在开源版本的基础上继续深度开发与定制。
2)HBase。HBase(Hadoop Database)是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,它本质上是一个数据模型,可以提供快速随机访问海量结构化数据的能力。HBase的适用场景如下:
第一,写密集型应用。HBase支持每天写入量巨大,而读数量相对较小的应用,比如IM的历史消息、游戏的日志等。
第二,不需要复杂查询条件来查询数据的应用。HBase只支持基于rowkey的查询。对于HBase来说,单条记录或者小范围的查询是可以接受的,大范围的查询由于分布式的原因,在性能上可能有点影响,而对于像SQL的join等查询,HBase无法支持。
第三,对性能和可靠性要求非常高的应用。由于HBase本身没有单点故障,因此可用性非常高。
6.数据库特点及选型指标
前文已经提及不同数据库的特点和适用场景,此外简要概括一下关系型数据库和非关系型数据库的特点。
(1)关系型数据库的主要特点
❑表结构较严格,支持行列式存储结构化数据。
❑需要预定义数据类型。
❑部分支持事务特性,可保证较强的数据一致性。
❑支持SQL语言,增删改查功能强大,大都支持多表join操作。
❑较为通用,技术较成熟。
❑不适合处理大数据,当数据读写量较大时,通常需要分库分表。
❑高并发性能不足,扩展较为复杂。
(2)非关系型数据库的主要特点
❑表结构较灵活,如列存储、键值对存储、文档存储、图形存储;支持非结构化数据。
❑部分不需要预定义数据类型,甚至不需要预定义表。
❑不支持事务特性,数据一致性能力弱。
❑非SQL查询语言或类SQL查询语言,但功能都比较弱;通常不支持多表join操作,或有限支持。
❑支持大数据量,且多数支持分布式。
❑高并发性能较强,易扩展。
数据库选型一般需要考虑的指标有数据量、并发量、实时性、一致性要求、读写分布、读写类型、安全性、运维成本。当然,不同的应用场景需要关注的指标不同,例如“双11”大促活动,数据量和并发量是最重要的两个指标;而对于银行转账等金融业务,一致性要求是最关键的指标。
中间件(Middleware)是一种应用于分布式系统的基础软件。从纵向层次来看,中间件位于各类应用、服务与操作系统、数据库系统以及其他系统软件之间,主要解决分布式环境下数据传输、数据访问、应用调度、系统构建、系统集成和流程管理等问题。目前,中间件并没有很严格的定义,但业界普遍接受IDC的定义:中间件是一种独立的系统软件服务程序,管理计算资源和网络通信,帮助分布式应用软件在不同的技术之间共享资源。从这个意义上可以用一个等式来表示中间件:中间件=平台+通信。这也就限定了只有用于分布式系统中才能叫中间件,同时也把它与支撑软件和实用软件区分开来。
为了让读者直观地感受中间件与应用软件、支撑软件(如操作系统)的关系,我们来看一个例子,如图1-5所示:客户端请求传递到服务端,不是直接由应用承接,而是要经过负载均衡中间件(如Nginx)进行处理后,才由具体的应用进一步处理,而应用之间的协作一般也需要借助中间件;当然,中间件和应用都需要服务器的操作系统和硬件支撑。
图1-5 中间件与应用软件、支撑软件(如操作系统)的关系
中间件在过去的十年间大放异彩,庞大的中间件群体带来了巨大的效率提升。如果说开发框架使软件研发工程师能够通过代码掌控一切,那么,中间件则通过集中式服务来降低工程师的编程工作量。常用的中间件有消息中间件、事务中间件、数据中间件等。
1.消息中间件
消息中间件也称消息队列,是分布式系统中重要的组件,主要解决应用耦合、异步消息、流量削峰等问题。它可以实现高性能、高可用、可伸缩和最终一致性架构,是大型分布式系统不可缺少的中间件。消息队列在电商系统、消息通信、日志收集等应用中发挥着关键作用。以阿里为例,它研发的消息队列(RocketMQ)在历次天猫“双十一”活动中支撑了万亿级的数据洪峰,为大规模交易提供了有力保障。
作为提升应用性能的重要手段,分布式消息队列技术在互联网领域得到了越来越广泛的关注。常用的分布式消息队列开源软件有Kafka、ActiveMQ、RabbitMQ及RocketMQ。
为了便于读者理解消息中间件,我们以简化版电商架构为例进行具体介绍。如图1-6所示,在传统强耦合订单场景中,客户在电商网站下订单,订单系统接收到请求后,立即调用库存系统接口扣减库存。
图1-6 商品下单简化流程
上述模式存在如下巨大风险:
❑假如库存系统无法访问(升级、业务变更、故障等),则订单减库存将失败,从而导致订单失败;
❑短时间内大量的请求、频繁查询库存、修改库存等,库存系统负载极大。
我们引入消息中间件,解除强耦合性,处理流程又会怎样呢?如图1-7所示,订单系统中,用户下单后,订单系统完成持久化处理,将消息写入消息中间件,返回用户订单,此时客户可以认为下单成功。消息中间件提供异步的通信协议,消息的发送者将消息发送到消息中间件后可以立即返回,不用等待接收者的响应。消息会被保存在队列中,直到被接收者取出。库存系统中,从消息中间件中获取下单信息,库存系统根据下单信息进行操作。
图1-7 引入消息中间件后的商品下单简化流程
2.事务中间件
事务中间件又称事务处理管理程序,是当前用得最广泛的中间件之一,主要用于解决分布式环境下的事务一致性问题。在单机数据库下,维持事务的ACID(Atomicity、Consistency、Isolation、Durability)特性很容易,但在分布式系统中并不容易,而分布式事务中间件可以保证分布式系统中的分布式事务的ACID特性。
通常,分布式事务中间件可支持DRDS、RDS、MySQL等多种数据源,并兼容消息队列实现事务消息。通过各种组合,可以轻松实现分布式数据库事务、多库事务、消息事务、服务链路级事务等多种业务需求。常用的分布式事务中间件有GTS、TXC、Seata等。
3.数据中间件
数据中间件处于底层数据库和应用系统之间,主要用于屏蔽异构数据库的底层细节,是客户端与后台的数据库之间进行通信的桥梁。数据中间件一般用于解决海量请求下数据访问瓶颈及数据库的容灾问题,具备分布式数据库全生命周期的运维管控能力,支持分库、分表、平滑扩容、结果集合并、SQL解析、数据库容灾和分布式事务等特性。开源的数据中间件有Vitess、MyCat、Atlas、OneProxy等。
总体来看,目前开源的数据中间件比较少,广受认可的更是寥寥无几,大型互联网企业几乎都自研数据中间件,如阿里的DRDS、蚂蚁的Zdal、京东数科的CDS、美团点评的Zebra等。
操作系统是管理计算机硬件与软件资源的计算机程序。操作系统需要管理和配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。在计算机中,操作系统是最基本,也是最重要的基础性系统软件。
1.Linux系统的优势
对于服务端开发工程师,学习并掌握操作系统知识极为重要。通常,服务端程序几乎都是部署在Linux操作系统的服务器上的。为什么是Linux而不是常见的Windows或其他操作系统呢?主要有以下因素。
(1)开源
源代码开放使得企业可以获取整个操作系统的源码,并根据自己的需求对操作系统进行二次开发,甚至打造“定制”的操作系统。同时,源码开放使企业可以洞悉操作系统的实际运转情况,而诸如Windows、MacOS这种闭源商用操作系统,则很难掌控。
(2)免费
Linux开源意味着企业不用为操作系统支付任何费用,而Windows是商用操作系统,大规模使用成本高昂。很多时候,成本是企业考量的最重要的因素之一。毕竟企业要生存、发展,在满足需要的前提下节约成本,何乐不为呢?
(3)稳定
Linux系统以其稳定性而闻名,这也是企业非常注重的一个因素。企业里有很多服务器要求7×24小时不间断稳定运行,而这更是Linux最擅长的地方。Linux更新升级或者配置某一项操作的时候,只须重新启动对应的服务即可,无须重启服务器。
(4)生态
在Linux开源之后,一批技术专家迅速聚集起来,他们不求回报地为Linux提供代码、修复bug,提出新的想法,帮助Linux成长,直到如今形成了一个庞大的开源社区。现在开发者想要学习或者获取Linux的最新版本,都可以在开源社区上找到自己所需要的资料,在开发过程中遇到的问题也可以上社区和同行交流并寻求帮助。
2.Linux常用命令和操作
Linux系统相关的知识点非常多,市面上介绍Linux系统的书籍基本都是“大部头”,通篇学习实属不易,效果也很难保证。对于服务端开发工程师来说,可重点学习一些常用的命令和操作。
(1)Linux基础
如图1-8所示,Linux基础知识包括Linux版本和基础概念两个部分。Red Hat、Ubuntu、CentOS都是Linux的常见版本。
图1-8 Linux基础知识
(2)常用帮助命令
Linux系统的命令数量有上千个,每个命令又有若干个甚至数十个适配不同情景的参数,单纯通过记忆掌握这些命令是非常困难的。因此,Linux系统为使用者提供了帮助命令,只需要正确使用Linux的帮助命令,就能够快速地定位到自己想要的命令和参数。Linux系统常用的帮助命令有3个,即man、help、info,基础用法如表1-3所示。
表1-3 Linux系统常用帮助命令
(3)文件和目录管理
文件和目录管理命令是最基础的Linux命令,基于这两类命令,用户可以在Linux系统下创建文件和目录,也可以对已有文件和目录进行查看、删除、复制等操作。如表1-4所示,笔者列举了几个常用的文件和目录管理命令。
表1-4 Linux系统常用的文件和目录管理命令
(4)Vim文本编辑
与Windows操作系统不同,在安装Linux系统的服务器上,为了节省内存、提高效率等,通常不会安装图形界面,而只能通过命令行来进行各种操作。因此,当我们在命令行下新增文件、更改文件、编写脚本时,不可避免地要用到文本编辑器。目前,可供选择的文本编辑器有很多,如Vim、emacs、pico、nano等,作为服务端开发工程师,应熟悉至少一款Linux文本编辑器的用法。
就受工程师欢迎的程度来看,Vim可以说是无出其右。Vim是一个高效、功能强大、可扩展的编辑器。Vim有自己的脚本语言,称为Vim脚本(也称为VimScript或VimL),用户可以通过多种方式使用它来增强Vim,例如为其他编程语言启用语法高亮、自动化语法检查等。此外,Vim还具有高度可配置性,包含Vim核心全局设置(称为vimrc)的文件可以在各个Vim安装之间共享。
(5)文件系统与文件查找
作为服务端开发工程师,在日常定位问题时,最常用的手段是日志,因此掌握搜索、查看服务端运行日志的技能十分重要。在Linux系统上进行文件查找的命令主要有两个:grep和find。这两个命令的用法非常多,在此,笔者仅列举几个基础用法,如表1-5所示。
表1-5 grep和find命令的基础用法
除了上面列举的知识点外,系统管理、磁盘分区、逻辑卷、shell、文本操作等也有必要学习掌握。
工程师开发的代码需要经过编译、打包等流程,并最终部署到服务器上,才能运行并对外提供服务。在2014年前,生产环境应用部署一般是通过工程师编写脚本实现的,而开发环境则基本是手动部署,效率普遍较低。经过多年的发展,目前一些头部互联网企业的研发平台和流程已经非常完善,不仅可以支持不同研发环境下自动编译、部署,而且能提供场景化分析、定制化质量和风险控制能力。研发平台化、流程标准化虽然使研发更加简单、高效、可靠,但对工程师屏蔽了背后的技术细节,某种程度上对初入职场的服务端开发工程师是不利的,应用部署不应该成为一个“黑盒”。鉴于此,在本节中,笔者将着重介绍一下互联网企业生产环境的应用部署。应用部署的发展历程大致可分为物理机部署、虚拟机部署和容器化部署3个阶段。
1.物理机部署
物理机部署,顾名思义,就是将应用直接部署在物理机器上,如图1-9所示。
在早期,物理机部署几乎是部署应用的唯一方式,这种部署方式存在一些弊端。
(1)硬件资源浪费
图1-9 物理机部署应用
当时服务器普遍采用高性能计算机,造价高昂。如果一台物理机只部署一个应用,则硬件资源难以被充分利用,造成资源浪费。此外,在部署异构系统(不同架构的两个或多个系统之间通常无法直接通信,也不能部署在同类服务器上)时须重新采购物理资源。
(2)进程间资源抢占
为了充分利用服务器资源,一种方案是将多个应用进程、数据库、缓存进程等都部署在同一台物理机上。这种部署方案固然能高效地利用昂贵的物理机,但有一个显著的缺点是进程间资源抢占。例如,如果某个进程占用了100%的CPU资源,那么其他进程将无法提供服务。
2.虚拟机部署
物理机部署存在进程间资源抢占问题,其根本解决方案是实现进程间硬件资源隔离。虚拟机技术的出现使得这一问题得到了很好的解决。虚拟机技术的本质是硬件虚拟化,即每台虚拟机事先从物理机分配好CPU核数、内存、磁盘等资源,每台虚拟机通常只部署一个应用,不同的进程在不同的虚拟机上运行,从而解决了进程间资源隔离的问题。如图1-10所示,一台物理机会部署多个虚拟机,物理机上的所有虚拟机则依靠虚拟机管理系统进行管理。
图1-10 虚拟机部署
虚拟机的出现使得用户在一台物理机上能够独立运行多个相互隔离的系统,通过对资源的抽象化使得主机资源能够被有效复用,这对于企业降低成本十分有益。然而,虚拟机也会带来一些问题。
(1)额外开销
虚拟机部署的主要目的之一是减少物理服务器的数量,但它通常会导致采用更多的虚拟服务器。随着虚拟机数量的增加,大量独立系统开始运行,从而带来许多额外开销。IT生态系统中的其他组件(如存储和网络)也将受到新增容量的影响。此外,每当运行新的虚拟机时,都需要重新配置一遍环境,与在物理机上的情况基本无差异。重复的环境配置操作则会消耗开发和运维人员的工作时间。
(2)资源争用
基于虚拟机管理系统,虽然可以对每个虚拟服务器进行资源分配调整,但如果其中一个虚拟机负担过重,则仍然会影响运行在同一物理服务器上的其他虚拟机。例如对CPU周期、内存和带宽等资源的争用可能会严重影响系统响应。即使有足够的资源,某些工作负载在虚拟机上的性能也可能不如在专用硬件服务器上运行时那么好。
(3)版本冲突
大应用集群的虚拟机第一次安装时基本可以保障软件的版本和库依赖统一。但随着时间的推移,开源软件需要逐步升级,这个过程中,批量升级可能出现遗漏或升级失败。此外,工程师可能会登录服务器修改软件的版本或配置,以满足特定的需求。一段时间后,一个应用集群的虚拟机的软件版本和配置就可能出现差异,导致线上故障。
3.容器化部署
为了解决虚拟机部署的不足,容器技术应运而生。容器化部署的本质是构建一个完整、独立的运行环境,包含3个关键因素:环境隔离、资源控制和文件系统。2013年发布的Docker便是容器化部署的佼佼者,目前已成为首屈一指的容器平台。它能提供轻量的虚拟化和一致性环境,允许将应用及其依赖的运行环境打包在一起,打包好的“集装箱”(镜像)能够被分发到任何节点上执行,无须再进行配置环境的部署。如此一来就解决了开发和部署应用时环境配置的问题,规范了应用交付和部署,降低了部署测试的复杂度以及开发运维的耦合度,极大提升了容器移植的便利性,便于构建自动化的部署交付流程。
容器和虚拟机都是资源虚拟化发展的产物,但二者在架构上又有区别。虚拟机通过虚拟机管理系统(如Hypervisor)虚拟化主机硬件资源,然后构建客户机操作系统,由宿主机进行程序管理。容器则直接运行于主机内核中,如图1-11所示,应用在主操作系统的用户空间上执行独立任务,不需要从操作系统开始构建环境,赋予了应用从交付到部署再到运维的独立性。
图1-11 容器化部署
相较于虚拟机部署,容器化部署有以下优点。
(1)资源隔离
容器的本质是宿主机上的一个进程。Docker通过namespace实现了资源隔离,通过cgroups实现了资源限制。其中cgroups是Control Groups的缩写,是Linux内核提供的一种可以限制、记录、隔离进程组所使用的物理资源(如CPU、内存、I/O等)的机制。通过对资源进行隔离与限制,容器可以精确地为应用分配CPU、内存等资源,保证了应用间不会相互影响。
(2)高资源利用
相比于虚拟机,Docker不但启动速度更快,所需的计算开销更小,显著提高了服务器的效率,而且迁移时更加轻量,得益于分层文件系统,开发者共享代码更方便、快捷。容器没有管理程序的额外开销,它与底层共享操作系统,性能更加优异,系统资源负载更低,相比于传统虚拟机,在同等条件下可以运行更多的应用实例,而且可以更充分地利用系统资源。
(3)高研发效率
容器化部署将应用相关的代码与运行所需的全部环境、配置文件、依赖关系和库等打包在一起。基于容器提供的环境一致性和标准化,一旦出现故障,系统可以快速回滚。相比于虚拟机镜像,容器的压缩和备份速度更快,镜像可像普通进程一样快速启动。此外,工程师可以在一台机器上启动数百个相互隔离的容器,模拟现实场景进行测试。同时使用容器化部署,可以很方便地针对同一个项目生成多套不同的构建环境。
(4)版本控制
在应用集群部署时,每台机器首先会拉取指定版本的镜像文件,由于所有机器的镜像文件一样,因此容器的软件版本相同。即使开发或运维中途修改了容器的软件版本,但当容器销毁时,软件的改动也会随容器的销毁一起湮灭。当应用用已有的镜像文件重新部署时,生成的容器与修改之前的容器完全一样。容器如果要升级软件版本,那就修改镜像文件,这样部署时集群内所有的机器重新拉取新的镜像,软件也跟着一起升级。软件版本混乱的问题就得到了完美的解决。
(5)微服务架构基石
容器技术有助于将一个复杂的巨型应用拆分成一系列可以组合的松耦合服务。每个容器都可以被看作一个不同的微服务,可以独立升级,而不需要考虑它们的同步。基于微服务架构,工程师可专注于其所负责的微服务,而无须关注整个项目,降低了复杂度。与此同时,拆分开来的服务可重用,有助于提升研发效率,降低成本。此外,Docker支持多种编程语言,技术栈灵活,相对独立的微服务还可按需扩缩容、多节点部署。
(6)可移植与易管理
由于镜像不依赖主机的操作系统,因此具备良好的可移植性和跨平台运行能力。在迁移的时候,无须重复安装环境依赖。目前,越来越多的云平台开始支持容器,使得应用程序在公有云、私有云中进行混合部署成为可能。借助于容器编排平台如Kubernetes,容器化工作负载和服务的安装、更新、调试、扩展、监控与日志等流程都可以实现自动化。
运维监控对应用稳定运行、业务效果感知十分重要。日常运维监控一方面有助于预警、定位问题,从而令问题得以快速解决,避免影响扩大化,另一方面还可以帮助研发人员洞悉业务效果和发展趋势,实现业务决策。
在互联网企业,对于一个上线运行的产品,运维监控在其整个生命周期将一直存在。当产品用户规模较小或业务场景较少时,运维监控通常由产品和研发兼任,甚至完全由研发负责;当产品的用户量增长到一定规模或业务场景增加时,运维监控通常会逐步独立出来,由专职人员负责。这类专职人员通常称为SRE(Site Reliability Engineer,网站可靠性工程师)。对于服务端开发工程师,在运维监控方面,一般需要关注以下几个方面。
(1)基础监控
基础监控即监控服务端应用部署的机器(通常是服务器集群)的系统指标,如CPU、负载、硬盘、内存、网络等。监控这些指标并设置预警阈值,可以帮助工程师直观地了解所负责应用系统的运行状态,及时发现瓶颈并解决。
(2)服务监控
服务监控即对核心服务进行监控,重点指标包括平均耗时、成功率、调用来源、错误码、调用量等。有了这些指标,一旦出现问题(如成功率下跌、耗时上涨等),工程师可以快速将问题排查范围缩小到服务(接口)维度,此外还可以指导技术优化方向(如减少耗时、提高成功率等)。
(3)业务监控
业务监控即监控业务相关流程的关键节点(如商品查看、下单、退单等)和指标(如下单成功率、订单总量等)。这类指标既有利于我们跟踪业务发展趋势,也有助于我们对业务进行相关分析并做出决策。
在实践中,如何开展运维监控呢?基础监控(CPU、负载、硬盘、内存、网络等)一般可借助专业的监控工具(如Anturis、SeaLion、Icinga、Munin等),并非服务端开发工程师专长。事实上,对于服务端开发,最重要的依托是日志,如图1-12所示。基于规范的日志可以快速构建、感知常用服务指标。站在业务角度,合理打印日志不仅能辅助定位问题原因,还可以多维度聚合分析各类场景。某种程度上,排查问题的过程,其本质就是日志结构化还原的过程。如果日志能结构化还原特定场景,那么该场景就能快速被定位。
图1-12 基于日志监控可感知的信息
软件开发一般是以项目为单位进行的。从技术的视角看,一个软件项目从提出需求到落地,通常要经历需求评审、系统设计、开发、联调、测试、验收、上线等诸多环节,如图1-13所示。
图1-13 软件项目流程
服务端开发是核心岗位,仅仅具备“开发运行于服务器端的程序”的能力是远远不足以胜任的。在头部互联网企业对该岗位的职责定义中,普遍要求服务端开发工程师需要具备业务分析、产品设计、架构设计、技术攻关、团队协作、文档编写、系统维护等综合能力。读者可能会认为如此高要求完全是“内卷”的结果,但从笔者的工作经验来看,这些能力其实是服务端开发的不同阶段所需的,具体而言,服务端开发可分为需求分析、抽象建模、系统设计、数据设计、非功能性设计、编码实现及发布运维等阶段,每个阶段都需要相应的能力作为支撑,核心流程如图1-14所示。
图1-14 服务端开发核心流程
在实践中,根据业务复杂度的不同,有时候可能只需要画个简单的流程图便可梳理清楚系统,但是复杂系统的设计则困难得多。以12306为例,2020年春运期间高峰日点击量达1495亿次/天,约170万次/秒,单从点击量数据看,项目的设计、实现就不容易,在此,读者不妨设想一下,如果你是12306项目的设计师,你准备如何设计这个系统呢?你的设计方法是怎样的?
在笔者看来,不论采用何种设计方案,首先需要明确一个问题:你了解铁路票务吗?如果根本不了解铁路票务,那又怎么可能设计出满足铁路票务业务需求的系统呢?在实践中,设计并开发一个软件系统并不单纯是技术层面的问题,首要任务是深入了解其业务。而要了解业务,则必须要经过需求分析。
(1)需求分析的定义
需求分析也称为软件需求分析、系统需求分析或需求分析工程,是软件工程师经过深入细致的调研和分析,准确理解业务方和产品经理的具体要求,将非形式化的需求表述转化为完整的需求定义,从而确定软件系统必须做什么的过程。
需求分析是软件计划阶段的重要活动,也是软件生存周期中的一个重要环节。该阶段是分析系统在功能上需要实现什么,而不是考虑如何去实现。需求分析的目标是把业务方和产品经理对待开发软件提出的“要求”或“需要”进行分析与整理,确认后形成描述完整、清晰、规范的文档,确定软件需要实现哪些功能、完成哪些工作。
(2)需求和业务的关系
很多工程师认为理解需求就是理解业务,这是一种误解。需求其实是业务经过产品经理消化后的产物,通常已经经过产品经理的演绎和拆解,因此并不是业务本身。当然,了解的需求越多,对业务的全貌就越清楚。那么,什么是业务呢?业界对“业务”有多种定义,但其主要思想基本一致:业务是指商业(或非商业)组织及其运作的活动流程。直白点说,业务就是一系列人通过一系列活动完成某一任务的过程,业务可大、可小、可拆分,比如支付宝的支付业务,往下可拆分为花呗、余额宝、银行卡等。
(3)为什么要进行需求分析
在互联网行业,一个完整的需求通常参与者众多,包括业务方(如运营)、产品经理、服务端开发、客户端开发、测试、交互、视觉、项目管理等人员。正如上文所述,需求的本质是业务方原始诉求经过产品经理演绎、拆解的产物,一些问题由此而生:需求文档所述与业务方的核心诉求可能不一致;产品设计难免存在遗漏和缺陷。因此,作为工程师,拿到需求后并不是立即投入研发,而是进一步分析需求。
(4)需求分析阶段需要厘清的内容
❑业务背景:即业务当前状况及对业务发展、变化起重要作用的客观因素。例如,某虚拟电商平台用户数量达2亿,客单价约140元。
❑业务问题:即结合业务当前状况和客观因素,分析与业务预期之间的差距。接续上面的例子,用户规模、客单价都是当前的状况,而经过调研发现,竞争对手的客单价已达200元。为什么差距这么大呢?业务分析认为,关键因素是自身平台营销活动数量少于竞争对手,这就是业务面临的问题。
❑业务诉求:即业务想到达到的目标。接续上面的例子,诉求是丰富营销活动模式,增加活动频次,比如每月开展一次“满减活动”(如“每消费满50元减5元”),促使用户凑单,从而提升客单价。
❑业务价值:即预期可以产生的收益。接续上面的例子,营销活动促进客单价提升,由GMV(Gross Merchandise Volume)=成交用户数×客单价可知,客单价提升,GMV增长,进而实现利润增加。
❑产品方案:即基于业务诉求形成的产品方案。接续上面的例子,支持如“满50减5”的营销策略,需要一个运营后台,支持运营灵活地配置营销活动(满减门槛、优惠额度、预算控制等),同时须支持将活动定向投放给指定的人群(精细化运营,针对不同的人群投放不同的活动)。
❑评估指标:即评估产品落地后实际效果的客观指标。接续上面的例子,评估指标如GMV、客单价、满减活动参与用户数等。
❑技术现状:结合上述内容,站在技术角度初步评估当前技术架构、系统容量、风控能力等是否可支持业务诉求落地和未来发展。
需求分析要义: 作为工程师,不要急于给出“解决方案”,而应带着问题分析需求,大胆假设,小心求证。例如,需求所述的问题是问题吗?对于问题根因,业务方或产品经理能分析准确吗?产品方案能解决问题吗?产品方案落地成本可接受吗?解决问题的效果可评估吗?
这一阶段需要基于需求分析阶段的知识储备,进一步提炼、建立可以刻画业务本质特征的模型。抽象建模实际上包含抽象和建模两个部分,由于两者通常同步进行,因此归纳为抽象建模。
1.抽象
抽象在中文里可作为动词,也可作为名词。作为动词的抽象是指一种行为,这种行为的结果,就是作为名词的抽象。百度百科上是这么定义抽象的:人们在实践的基础上,对于丰富的感性材料通过去粗取精、去伪存真、由此及彼、由表及里地加工制作,形成概念、判断、推理等思维形式,以反映事物的本质和规律的方法。如图1-15所示,对现实中的公牛进行抽象,用简单的线条勾勒出公牛的本质特征(如牛尾、牛角、牛鞭等),抽象牛具备更好的泛化能力,不再局限于具体品种的公牛,而是可以实例化为几乎所有品种的公牛。
图1-15 抽象示意图
事实上,日常生活中抽象无处不在。例如数字,人类初期并没有数字这一概念,原始人或许能够理解3个苹果和3只鸭子,但他们的脑海里不存在数字“3”这个概念,在他们的意识里,3个苹果和3只鸭子是没有任何联系的。人类进化到一定阶段后发现了这两者之间存在的一种共性,即是数字“3”,于是数字这个概念就逐渐形成了。此后,人们就开始用数字对各类事物进行计数。
软件开发本身就是一个不断抽象的过程。软件工程师把业务需求抽象成数据模型、模块、服务和系统,面向对象开发时抽象出类和对象,面向过程开发时抽象出方法和函数。换言之,上面提到的模型、模块、服务、系统、类、对象、方法、函数等,都是一种抽象。由此可见,抽象对软件开发非常重要。
2.建模
业务需求大都是以具象的现实世界事物概念来描述的,依附于自然语言体系,距离软件工程非常“远”。为了将需求落地,工程师需要开展一系列的工作,其中建模尤为重要。建模的过程实际上就是从业务领域里找出反映业务本质的事物、规则和结构,并将其抽象化,进而描述业务运行的基本原理、交互机制及用户的首要利益。从某种意义上说,建模的过程就是系统地实施抽象的过程。
目前,服务端开发常用的建模方法主要有3种,即用例建模法、服务建模法和事件建模法,在实践中需要根据业务场景的特点和复杂度选型。关于建模方法详见第3章。
系统设计又称为系统架构。在系统设计阶段,需要将抽象建模的成果有条不紊地映射到具体的技术实现中,要通盘考虑、权衡取舍。工程师须具备一定的技术深度、技术视野,同时充分理解业务。如果在抽象建模阶段做得足够好,建模方法选型与业务特点匹配,且抽象出的模型可以准确刻画业务的本质特征,那么系统设计将是一件相对轻松的事情。
1.设计和划分功能域
在互联网领域,一些业务的复杂度是非常高的。图1-16所示为阿里的电商业务摘要架构图。该架构自顶向下划分为前台、移动中台、业务中台、PaaS、IaaS,而业务中台又可细分为会员中心、商品中心、交易中心、支付中心、评价中心和订单中心,这其实就是一种高层次的功能域设计和划分。
对于复杂的业务,首先应设计和划分功能域,以降低复杂度。具体而言,在完成抽象建模后,可基于模型将系统拆分为不同的功能域,各个功能域相互协作,共同实现业务需求。需要特别注意的是,不同功能域之间必须有清晰的职责边界,同时单个功能域的复杂度不能过高。我们可以将业务中台的会员中心进一步拆解,划分为积分、权益、淘气值等功能域,如图1-17所示。
2.设计功能域之间的协作
经过多年的发展,互联网领域的“高增长”时代已经过去,正大步迈入“存量”时代。由于大多数业务已经相当成熟,通用基础能力亦趋于完善,因此业务需求的复杂度通常不会高到需要步骤1中的设计和功能域划分。
设计功能域之间的协作,可视为一种“粗粒度”的服务编排。具体而言,借助已有功能域提供的服务,目标功能域(新建或者在已有功能域的基础上扩展)可以快速实现新的功能,满足业务需要。在实践中,功能域之间协作的关键在于充分、合理地利用已有公共基础服务。如图1-18所示,会员中心的权益投放通常有个性化推荐(算法推荐)和运营定向投放(运营针对特定人群,如新用户,定向投放特殊权益)两种策略。这两种投放策略可以通过不同的功能域协作实现。
图1-16 阿里的电商业务摘要架构图
图1-17 会员中心功能域
图1-18 功能域协作示意图
3.确定功能域之间的数据边界
功能域之间的协作设计完成后,整个系统的上下游依赖也就清楚了,接下来我们需要进一步明确功能域之间的数据边界。以微服务架构为例,一个功能域可作为一个应用,不同功能域之间通过服务调用来协作,对于服务,通常用请求(服务调用方发起请求)和响应(服务提供方响应请求返回结果)的数据模型来描述边界。
如果有多名服务端工程师(甚至团队)参与产品需求的开发,在确定数据边界之后,不同功能域的职责也就进一步明确了,工程师可以专注于其所负责功能域的内部设计和开发。
4.功能域内部设计
通过步骤3,功能域的技术目标得以明确,接下来便是通过设计去实现这些技术目标。在功能域内部,为了降低问题的规模和复杂度,同时增强系统的可扩展性和可维护性,一般采用如图1-19所示的“分层架构”。
在分层的同时,很自然会产生一些模块。需要注意的是,在本步骤中虽然通过分层将功能域进一步细化到了“模块粒度”,但并不涉及模块实现细节,这是下一个阶段要做的事情。
图1-19 功能域内部分层设计示意图
5.详细设计
详细设计是系统设计的最后一个环节,对大多数服务端开发工程师而言,这个环节应该是最为熟悉和擅长的,毕竟在一名工程师的职业生涯中,大多数时间都在从事子系统/模块的设计和开发。子系统/模块的设计是一种详细设计,需要从细节层面考量问题,所做的设计用于直接指导开发。在这一步中,设计结果一般包括以下内容。
❑相关模型:如领域模型图、类图、实体关系图、数据表清单等。其中领域模型图一般在抽象建模阶段完成,而数据表清单则属于数据设计范畴。
❑上下游交互:大多数业务场景或多或少存在上下游交互,交互一般通过数据库、接口、消息等方式。如果选择通过接口交互,须写明类名、方法名、入参、出参、结果码等;如果选择通过消息交互,须写明消息Topic、Group、消息体结构等;如果选择通过数据库交互,须写明表名、索引、QPS等。
❑方案描述:当业务较为复杂的时候,文字描述难以直观地反映设计方案的细节,也不便于后续评审和实施,因此,一般通过流程图、时序图来描述。
需要注意的是,详细设计环节通常是包含数据设计的,但考虑到数据设计的重要性和复杂性,笔者会进行详细介绍。
谈及数据设计,大多数IT从业者的第一反应是数据库设计。这其实是片面的。事实上,数据设计的内涵非常丰富,数据库选型、表结构设计、字段设计、索引设计、缓存设计、数据核对、数据监控等都属于数据设计的范畴。
如图1-20所示,完整的数据设计一般包含3个环节:领域概念模型设计、逻辑数据模型设计和物理存储模型设计。不过,落实到具体的业务需求,这3个环节并不是必需的。例如在一些相对简单的业务场景中,根本不涉及领域概念模型,也无须领域概念模型设计。本节重点介绍一下数据库选型和存储方案设计,其他数据设计相关的内容在后文再展开详细介绍。
图1-20 数据设计主要环节
1.数据库选型
目前,常见的数据库类型如表1-6所示,数据库类型不同,其特性和适用的场景也存在较大差异。服务端开发工程师需要根据业务场景自身的特点,结合数据库的性能、存储成本、容量、一致性、读写偏好、稳定性等指标综合评估选型。
表1-6 常见的数据库类型
(续)
2.存储方案设计
数据库选型确定后,接下来需要根据数据库进一步设计存储方案。为了便于读者理解,笔者以MySQL和HBase为例介绍存储方案设计。如图1-21所示,对于MySQL数据库,存储方案的核心是字段设计和索引设计;而对于HBase,核心则是RowKey设计。
图1-21 存储方案设计示例
业务方提出的需求和产品经理设计的产品方案大都聚焦于业务功能描述,在验收时,通常也只是验证要求实现的功能是否符合预期,极少考虑稳定性、兼容性、安全性、异常补偿等非功能性问题。然而,很多时候,非功能性问题往往事关项目成败,因此必须根据业务场景谨慎评估非功能性问题并设计相应的解决方案。
1.稳定性设计
在互联网领域,稳定性设计是最重要的非功能性设计。根据阿里官方公布的数据,在2020年的“双11”大促活动中,天猫平台订单创建峰值达58.3万笔/秒。如此巨大的流量,若没有稳定、可靠系统和服务,业务便是空中楼阁,随时有崩塌的可能。那么,在互联网企业,可以通过哪些具体的措施来保障稳定性呢?如图1-22所示,在稳定性保障的流程中,容量评估、压测验证、限流/预案等环节都是需要服务端工程师来保障的。
图1-22 保障稳定性的一般流程
2.可测试性设计
与客户端不同,服务端对用户来说是不可见的,测试工程师无法直接通过UI(User Interface)界面来验证服务端的复杂逻辑,因此,服务端开发工程师在进行非功能性设计时,需要充分考虑可测试性。
❑功能可测试:对于客户端不直接可见的功能(如异步处理、定时任务补偿等),服务端可采用在关键链路打印摘要日志等方式来帮助测试人员识别不可见逻辑是否正确执行。
❑支持压测:压测数据(如用户账号、场景等)通常为虚构数据,在服务端强校验逻辑下(如账号校验)无法直接跑通全链路。因此,服务端需要识别压测数据,并支持压测标识传递,对于不支持压测的特殊环节还需要支持约束跳跃。
❑灰度可测试:重大变更通常需经过充分灰度测试才能对外切流。在灰度环节,服务端需要支持多种流量控制策略,如白名单、百分比、万分比等。
3.其他非功能性设计
非功能性设计涉及面广,除了前面介绍的稳定性和可测试性,还有应用安全、异常处理、扩展性、兼容性等方面,如图1-23所示。
图1-23 部分非功能性设计内容
在互联网行业,很多时候加班对工程师来说是家常便饭,同时互联网领域技术又日新月异,要求工程师有很强的学习能力。如何在繁忙的工作中做好积累,构建个人核心竞争力,相信是很多工程师都在思考的问题。
近些年,中年危机几乎是绕不开的话题,为何会有中年危机呢?其根源在于职业本身的特点。事实上,不只是互联网领域,在其他领域,具有以下特点的职业,同样不可避免地会面临中年危机:
❑能力习得速度快的行业,后辈更容易挑战前辈,形成中年危机。
❑技能演进速度快的行业,手里的秘籍容易成为废纸,形成中年危机。
互联网领域的技术演进日新月异,很多时候,先入行者不但没有先发优势,反而可能还有巨大的劣势:或许你是一位中年JavaScript“艺术家”,老一套已经信手拈来,可新来的工程师直接上React秒杀你;或许你花了十年时间把C++玩得滚瓜烂熟,可面对现代大型分布式项目,年轻人用Scala处理得又快又好。现实就是如此残酷,你会的技术,别人一学也会;你掌握的利器,过两天可能就成了钝铁。如此种种,让互联网行业呈现出一边倒的年轻化特征,在大多数非创新岗位上也不存在老专家这么一说。因此,某种意义上,软件工程师的中年危机是由其职业属性决定的,不可避免。
当然,不可否认的是,互联网领域也有像Jeff Dean那样的一群人,对所谓的中年危机完全“免疫”。但不要忘了,这样的大牛毕竟是少数派。既然危机将不可避免地到来,对于软件工程师来说,最好的应对方式就是想办法解决,规划一条进阶路径,进而构筑自己的能力护城河,最大限度降低危机的影响和冲击。
在进阶之路上,为提升自身的核心竞争力、构建能力护城河,首先需要打造具有竞争力的能力模型。如图1-24所示,处于中心的是行为处事的准则,也就是正确的思想观念。其中最核心的观念就是要把职业生涯当作自己的事业,为自己而工作,把提升自身能力作为事业的目标。围绕着这一核心的还包括主动承担责任、团队协作等。为了达成这些能力,需要养成良好的习惯并掌握相关技能。习惯和技能相辅相成,影响着平时的一言一行,通过规范言行来帮助将这些观念深化为本能。而为了保证上述的方案能够落地执行,需要执行详细的规划并反复实践,并通过复盘等手段总结得失,查漏补缺。
此外,大厦需要有坚实的基础,否则只能是空中楼阁。改变现状的勇气、优秀的执行力和健康的身体便是这一切的基础。
图1-24 能力模型示意图
本节所述的知识特指技术知识和业务知识,基于这两者,形成服务端开发工程师的专业技能和业务技能。在互联网领域,技术和业务相辅相成,技术服务于业务,业务促进技术更迭,两者并行,因此一般将技术知识和业务知识统称为领域知识。
从事服务端开发,领域知识是最基础却又最重要的能力。但是,只有当领域知识形成体系时,才可以成为真正的核心竞争力。当遇到问题时,如果连相关的知识储备都没有,将很难解决问题;而如果你有相应的知识储备,则可以将知识迁移到问题场景中,辅以推理寻找解决问题的方法。为了让领域知识和逻辑推理更好地结合,我们要系统地学习知识,专注于某些技术方向的同时,有意识地向周边拓展。对于服务端开发工程师,除了掌握服务端开发技术栈和相关业务知识,也应了解一些客户端的原理,知道服务端和客户端之间如何联系。
那么,如何才能使自己的领域知识系统化呢?很多时候,领域知识点本身是可以相互联系起来的,但前提是你对这些点都理解透彻。在工作中学习,最常陷入的困境就是学习时间过于碎片化,学了后面的,就忘了前面的,不得要领。出现这种情况的一个很重要的原因在于:领域知识宏观层面的整体大图没有形成,关键知识点理解深度不够、不透彻。这些关键点就是这个领域的骨架、支点。缺了骨架,自然难以体系化;缺了宏观大图,容易误入歧途。
读者是否曾有过这样的体验:在某个领域学习知识特别快,但换个领域就总是不得要领。想象一下,为什么你对家所在的小区周边特别了解,不会迷路?本质上就是因为你脑中已经对小区周边形成了整体大图,并对关键节点了然于胸。把你放到陌生的小区,你可能就迷路了,关键节点、整体大图均没有,胡乱摸索,即便你把摸索路上所见到的每一个小餐馆的情况都了解了,也没什么意义,再过几条街你就忘了。
鉴于上面抛出的问题,在学习领域知识的时候,特别是进入一个新领域时,首先要厘清整体大图和关键知识点,然后循着大图和关键点通过实践进一步强化。
知识通常是抽象的,离开校园后,静下心来看书或研究源码是一件比较困难的事情,因此需要掌握正确的学习方法。
1.场景式学习
场景式学习有助于克服“学习困难症”。具体而言,就是带着问题去学习,在学习某方面知识的时候,先列一个问题表,带着这些问题逐一寻解。举个例子,在入门Spring框架的时候,我最初是看书,看到四十多页就犯困了,似懂非懂;后来我换了一种方式,列了一系列问题:Bean到底是什么?Bean会被垃圾回收(Garbage Collection,GC)吗?等等。基于这些问题,我逐一去查资料、跑demo,比直接看文献轻松很多。
当然,再好的方法也需要执行力支撑。在学习的过程中,我们会遇到很多问题,而解决这些问题通常需要你去做很多事,比如下载源代码(找不到网址,放弃)、编译(还要去学习那些编译参数,放弃)、搭建环境(太麻烦,放弃)。这中间九九八十一难,放弃任何一难都取不了真经。这也是为什么同样的学习、同样的问题,优秀的人能学会、能解决,有些人却不可以。
2.站在“巨人”的肩膀上
古人云:“三人行,必有我师焉。”在学习的道路上,要善于向身边优秀的人学习。很多时候,大牛的指点可以让你事半功倍。举个例子,笔者曾遇到过这样一个问题:在应用刚启动时,连接数据库特别慢(非慢查询)。百思不得其解,于是向一位经验丰富的同事请教,他仅凭我的描述便给出了原因和解法:创建连接时,MySQL会进行反向域名解析,这一过程比较耗时,通过配置参数skip-name-resolve跳过即可解决。
大型企业通常都有较为完善的技术传承体系,以帮助员工成长,提升研发效率。以蚂蚁集团为例,不仅有针对新员工的“精武门”“青年近卫军”等内部培训,还有“般若堂”“奇点学堂”等形式多样的内部技术分享。把握住这些向大牛学习的机会,定会有所收获。
在互联网行业,工程师大都在技术领域很有追求,但部分工程师却轻视业务,甚至嗤之以鼻。这种现象多与工程师的职场经历相关。通常工程师是和产品经理沟通需求的,而不是直接与真正的业务方沟通,导致很多工程师不了解业务全貌。同时,工程师专注于技术,为各种需求所累,业务意识不足,更不会主动去思考业务痛点和了解业务策略。笔者初入职场时,做的是比较偏中台技术的工作,对业务感知比较少,也不感兴趣,那时候比较崇拜技术大牛,根本没有意识去了解业务相关的信息。
在互联网领域,特别是在阿里、腾讯这类商业科技公司里,一个不理解业务的工程师与流水线的工人是没有很大区别的。在阿里,工程师评价指标分为三大板块:业务理解、项目管理和专业技术。其中业务理解是非常重要的,层级越高,业务理解的占比越大。一个不懂业务、不了解业务的工程师,很难设计出好的系统。比如12306这样的系统,如果不了解它的业务,何谈设计?它的业务有多复杂呢?此处就其SKU(Stock Keeping Unit)来进行分析。
1)SKU总数多。对于普通商品,一个SKU对应一种商品,每卖出一件,库存减1。而列车票则是一种动态SKU,以上海到成都的G1974次列车为例,共有20站、3种座位,粗略一看似乎只有3种SKU(商务座、一等座、二等座),实际上G1974有570个SKU,始发站上海与后面的19站便有19×3个SKU,以此类推共计(19+18+17+…+1)×3=570个SKU。
2)SKU计算复杂。G1974总共有570个SKU,但是SKU的数量和库存却都是动态的,库存不仅会减少,还会增加。比如列车驶出上海站,SKU会减少19×3个,对应的库存也会清零;与此同时,上海与第三站及其后面的站对应的18×3个SKU可能未卖完,这些座位自然不会浪费,因此,第二站苏州北到后面各站的SKU库存则可能会增加。如果再考虑座位存在位置差异(A、B、C、D、F),计算量会更大。
铁路交通领域的票务系统非常复杂,在系统设计之前,首先需要对铁路交通领域的业务进行充分调研,深刻理解业务场景和运作模式,在此前提下再谈设计。
通过上面的例子,相信读者对于业务的重要性已经有所感悟。事实上,一个公司通常也是先有业务模式,然后才去招兵买马,组建研发团队的,皮之不存毛将焉附。业务发展的好坏往往决定了公司的营收和前途,也决定了研发的效益和去留。
根据工程师对业务的影响程度,可划分6个阶段,如图1-25所示,层层递进,越往上对业务的影响越大。在具体的业务中,不论做前端开发、服务端开发,还是质量保障,最终目标都是促进业务发展,只是担当的角色不同,影响程度不同。工程师在打磨技术的同时应关注业务,促进业务发展,这样可以让技术的价值得以更好地体现。
图1-25 工程师对业务的影响程度示意图
不要随心所欲地生活,也不要随遇而安地行走在职业生涯的漫漫长路上。没有明确的方向,你走的每一步可能都是徒劳的。对工程师来说,没有以不变应万变的方法,唯一不变的就是改变,树立长远的目标,持之以恒,踏实前行,方能达到最终的目标。
1.关于工作
你不是为老板工作,也不是为公司工作,而是为自己工作。工作是属于公司的,而职业生涯却是属于你自己的。当把这件事情想明白的时候,你的职业发展将会焕发新的青春。在这个过程中,你应学习如何像公司一样思考,如何提升自己的技能,从而让公司持续地购买你的服务。
在工作中,切忌无意义的抱怨、愤怒,这些不成熟的行为可能会让你痛快一时,但更多的是证明你的无能,于现状而言没有任何帮助。吐槽、抱怨、愤怒很容易,而提出解决方案才有价值,应争取成为后者。
2.关于责任
责任与重要性成正比。当你的责任越大,承担的事情越多时,公司对你的依赖也就越重,也更容易让你脱颖而出,得到更好的资源和机会。所以,需要主动地承担更多的责任,不要退缩,敢于顶上去,做出引人注目的成绩,成为问题的解决者,并不断提升自己。
人都是有惰性的,倾向于在自己熟悉的领域行动,心理学上称之为“安全区间”,对于有挑战的事情,内心往往因“缺乏安全感”而不愿意尝试。但是,你要明白,挑战所在常常也是机会所在,敢于承担责任,赢取机会,才能不断突破自己的舒适区,拓宽自己的能力域,使自己更有价值。
3.关于技术
要揽瓷器活,得有金刚钻。只有形成自己的技术影响力,当出现某类问题时,公司才会首先想到你。同时,要有技术广度,对自己所从事的领域相关的技术都有一定的了解,具备较为完善的技术栈。扩展广度要注意范围,最好沿着自己所从事的领域向外扩展,而不是征服不成体系的技术知识点。
4.关于协作
在互联网领域,不要试图单打独斗,要注重团队协作。人多力量大的前提是:创建一个好的环境,制订合理的激励措施,规划好成长路线,让每个人都能被激发并释放自己的能量,让优秀的人脱颖而出。在进阶的路上,即便你专注于技术,也不可能事必躬亲,团队协作的力量不可或缺。