CTO之瞳-后端系统1-代码框架+微服务初探

有了数据库,接下来聊一聊直接操控数据的系统——后端系统。这其实是一个非常大的话题,可以用来写后端系统的开发语言有java/php/nodejs/python/C#/go等,常用的体系有.NET/springboot,加上各类的概念规范,比如MVC/ Servlet,还有层出不穷的框架/库/工具,每当想起这些,总会冒出一个念头:wtf,还是《资治通鉴》容易点。

范围这么广,很多领域都没接触过,那写什么好呢?其实挺简单,打开猎聘找下CTO/架构师/研发总监之类的职位,看看职位要求里都写了些什么,答案就跃然纸上了。

Java,spring boot,基于spring cloud的后端微服务体系,就是此部分的主角了。每一种技术都有自身的优劣势,每一个技术人选择什么技术栈都有自己的判断。重要的不是选了什么,而是选择以后坚持走下去。在技术的领域里,需要广度,同时更需要深度。

注:这里的广度,指的是可以端到端的完成一个业务体系搭建,也就是常说的全栈技术。并不是指什么都摸两下,比如做前端的,今天用vue,明天用react,后天又换成angular;做后端的今天java,明天php,后天nodejs,这不能称之为广度。当然,大神除外。

其实选什么开发语言,用什么技术框架与体系,这些也并不重要;重要的是:按照市场以及用户的需求,在合理的成本与时间范围下,搭建出可持续发展的技术系统。

正式开始吧。

1 代码框架

前面扯了一大段顶层的东西,为什么要从代码框架开始说起?因为良好的代码框架对应的是良好的逻辑分层,而技术里的分层实际上是一种形式的社会分工。亚当斯密在《国富论》里曾经写到过:“劳动生产力最大的改进,以及劳动在任何地方运作或应用中所体现的技能、熟练和判断的大部分,似乎都是劳动分工的结果。”

先上代码(没有代码、系统框图、流程图的技术类文章都是耍流氓,相反的,大段复制粘贴代码的技术类文章也是耍流氓 ^^)。

https://github.com/ctoeyes/basicweb-ui

https://github.com/ctoeyes/basicweb-service

搭建了一个简单的web注册与登录demo,后端基于Springboot,前端基于React+MaterialUI,数据库MySql。可以访问www.ctoeyes.cn查看效果,香港的服务器,速度较慢。主域名www.ctoeyes.com还在备案审核中,一旦审核通过,会把demo迁移过去。

Demo非常简单,没用https,数据库连接的密码是明文存储,API也没防暴力攻击,主要用来说明代码框架。在下一篇后端系统2里,从单体demo改为微服务demo时,这些点都会一一改造。

这个后端service的代码框架长成这样:

CTO之瞳-后端系统1-代码框架+微服务初探

主要由几个部分组成

1. controller:接收前端发来的request并组装相应的response返回

2. service:处理具体的业务逻辑

3. repository:数据仓库,处理DB的CRUD操控

4. pojo:简单对象的定义

  • entity:与数据库表一一对应
  • dto:与业务数据模型一一对应,例如view object,form object
  • enums
  • response:http response的封装

5. common

  • config:配置类
  • utils:工具类
  • const:常量

以上几个部分的具体名称依据个人习惯或者公司规范会有所不同,但大体上都分这几个层级。其中,有两个点较难把握。

1.1 controller和service怎么分?

如果看demo里的代码,controller似乎是多余的,只是简单的把前端的login和register请求做了一次转发。是的,在简单的场景下,完全没有必要照本宣科,把controller与service拆分开来。

但是,如果系统功能进一步迭代,这种分层就有必要了。我们先认识一下MVC(Model/View/Controller)模型(如下图,图片来源于网络),MVC是一种设计规范或者理念,任何复杂的业务都可以拆分成三类模块:Model(Service)、View与Controller。


CTO之瞳-后端系统1-代码框架+微服务初探


View负责页面交互;Controller作为控制层,决定调用哪些服务处理View传来的请求并返回相应的数据;Model,也即Service负责处理具体的业务逻辑,并调用数据库做持久化处理。

以最简单的login登录场景来举例:

1. View负责搭建登录页面,组装页面上用户输入的数据传给Controller,并负责把Controller回传的数据显示在页面上。


2. Controller接收View传来的login请求,在简单的场景下只需把login请求转发给Model(service)层做处理,并接收Model的处理结果回传给View显示,就像demo里那样。

但假设,用户交互的不仅仅有网页浏览器,我们还设计了一个移动端APP,同样也有登录请求,这时Controller就派上用场了。因为屏幕大小的限制,APP登录后页面所展示的用户信息一般与网页端有所不同,偷懒的做法是把数据库里所有用户有关的数据全部回传给前端,由前端选择哪些数据要显示,这显然不符合常规的设计思路。

通常的做法,由后端组装每一个View所需的数据,组成一个VO(View Object)回传给前端。那么既然网页与APP显示的数据不同,很显然我们需要两个不同的VO,这个工作的调度应该在Controller层面完成。

网页端来的登录请求,Controller首先调用AccountService里的login接口,完成登录的验证并拿到登录后获取的uid以及token;接着调用UserInfoService,获取用户的一些基础信息,比如昵称、头像等;再调用HistoryService获取用户最近浏览的几篇文章信息。然后把这些数据打包成一个MainPagePcObject回传给网页端,这样用户在登录后的主页上可以看到个人信息以及最近的浏览记录。

APP来的登录请求,,Controller首先调用AccountService里的login接口,完成登录的验证并拿到登录后获取的uid以及token;接着调用UserInfoService,获取用户的一些基础信息,比如昵称、头像等。然后把这些数据打包成一个MainPageMobileObject回传给APP,这样用户在登录后的主页上可以看到个人信息(由于屏幕大小的限制,APP主页上放不下近期浏览记录,所以Controller并没有调用HistoryService)。

Controller根据不同View的请求,调度不同的Service,组成适当的DTO回传给View显示(以上的代码实现,将在后续讲述前端系统那一篇时加入)。

3. Model(Service)负责处理具体的业务逻辑,比如AcountService负责处理账号的注册/登录/登出等操作。UserInfoService负责新增/修改/删除用户头像、昵称、爱好等个人信息。


1.2 Pojo里的对象有哪些,如何区分?

Pojo = Plain Ordinary Java Object,也就是简单java对象,意味着只有数据不含任何逻辑操作的对象。我们可以把response,enums放在里面,还有与库表一一对应的entity放在里面,接着还有各种眼花缭乱的DTO、VO、FO、DO、PO也可以放在里面。

这些xxO让人不禁联想起公司里的各种CxO,怎么区分呢?按照个人的理解,画了张图,请看。

CTO之瞳-后端系统1-代码框架+微服务初探

层层封装的好处是底层的改动,例如数据库表的变动并不会影响到上层(比如View)的代码。正所谓脏活累活后端都做了,前端只要负责美和优雅。

但是分层太多,也会增加代码的复杂度,所以一般的项目里只会做两层数据封装,一层与库表对应的Entity,另一层与业务对应的DTO。


2 微服务初探

很久很久很久以前(其实也就6-7年前吧,但在技术领域可以算很久远了),用java写的后端系统很多都是一套代码集成在一起,有的甚至与前端页面打成一个war包直接部署。

这种方式,运维起来很简单,但是

- 如果业务代码当中一个service出错,可能会导致整个服务不可用

- 各个service的迭代节奏,相互影响

随着互联网业务体量越来越大,以及涉及的业务种类越来越多,这样的技术架构不能跟上业务的发展需求。回顾文初提过的观点:重要的是按照市场以及用户的需求,在合理的成本与时间范围下,搭建出可持续发展的技术系统。很显然,单体架构无法持续发展下去了,因此微服务架构应运而生。一种技术,必须有旺盛的业务与用户需求,才可能进入快速发展的通道。

微服务的核心概念就是松耦合,把一个业务中相对独立的部分拆出来做成一个独立的服务系统,可以独立开发、独立测试、独立部署。而一套完整的业务系统又由很多个微服务组成。

2.1 Spring & Spring Boot & SpringCloud

Java微服务的框架,必然提到基于Spring体系的Spring Cloud,在此简单阐述一下spring,spring boot,spring cloud的关系。

2.1.1 Spring

更准确的说应该是spring framework,一套基于依赖注入的java开发框架,spring承担的角色是各个java类之间的依赖关系管理者。

2.1.2 Spring boot

基于spring,提供了面向REST(REpresentational State Transfer)设计风格的框架,通过简单的注解,可以快速搭建起一套web服务。

关于REST风格,推荐读一下2000年被首次提出时的论文,传送门如下

英文版

https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf

中文版

https://www.docin.com/p-525700155.html


2000年我在干嘛?2000年北京的夏天,虽然20年过去了,依然难忘。

2.1.3 Spring Cloud

基于spring boot(依赖注入+面向REST),纳入了许多经过生产验证的开源微服务组件,方便使用者快速的搭建一套微服务框架。例如netflix的微服务三大件:服务注册与发现Eureka,网关服务zuul,熔断器Hystrix。

2.2 常用微服务组件与架构

技术上很多设计都来源于生活,例如设计模式里的工厂模式、委派模式等。微服务的组件同样也是来源于我们的日常生活,所以就从生活开始吧。想象一下我们去一间商场买东西的场景,顺着这个场景来介绍Spring Cloud中各个关键组件。

服务网关 – Zuul

走进一间商场或者一个公共设施,第一道关其实是门卫;而离开一间商场,最后一道关也是门卫。平时我们的出入仿佛感受不到门卫的存在,但当你拿着一把刀试图走进商场的时候,门卫必然很快出现并把你摁倒在地;或者当你离开商场时,试图带着并未结账的商品一起离开,门卫也会出现把你摁倒在地。同理,在微服务的体系里也有一个门卫起到网关的作用,Spring Cloud里用的是Zuul(不过已经可以看到spring在推Gateway组件来代替Zuul)。

不论你去商场买什么东西、办什么事,都需要通过门卫这一关,同理所有的服务请求都要通过Zuul。Zuul可以拦截非法的请求,并对请求做统一的鉴权认证。

Zuul还具备负载均衡的作用。Miss1,请问休息间往哪走?往前右转50米。Miss 2,请问休息间怎么走?往前右转50米。Miss 100,请问休息间怎么走?往前右转50米。

休息间保洁员:你md能不能把要休息的引到别处去,我这里排队都绕两圈了。Zuul:抱歉,忘记开启负载均衡了,重新来。

Miss 1,请问休息间往哪走?往前右转50米。Miss 2,请问休息间怎么走?上2楼左转50米。Miss 3,请问休息间往哪走?往前右转50米。Miss 4,请问休息间怎么走?上2楼左转50米。这就是负载均衡。当一个微服务有多台实例时,同一类请求会被均匀分配至不同的实例。

以上负载均衡实现的前提,需要导购妹子Eureka知晓商场里有两个休息间,并且知道它们的具体位置。

服务发现与注册 – Eureka

顺利进入商场后,由于庞大的空间,我们并不知道该去哪里买心仪的东西。于是通常我们都会走到导购台,询问导购妹子:“请问,xx成人用品在哪里有售?”导购妹子白你一眼,但依然会尽职的告知:“请往左走100米进电梯,直达地下18层。”

在微服务的世界里,同样有一个导购台,spring cloud中用的是Eureka,每一个微服务启动时都要向Eureka报备一下:“报告,我是xx成人用品服务,我的位置在地下18层”。这样当Zuul收到访问请求后,Eureka可以告诉Zuul把请求发往哪里,可能是地下18层,也可能是去天台。

断路器 – Hystrix

昨天商场打了个广告,N95口罩有售,5个只要199,于是今天涌入了大量的人群购买口罩。络绎不绝的人流被导购台引导到了口罩专柜,人群把口罩专柜的售卖妹子围在了中间,在七嘴八舌的询问声中,售卖妹子崩溃了,无法再回答任何顾客的提问或是售出口罩。

Hystrix登场,它监控到了以上情况,于是在到口罩专柜的路上,竖了一面告示牌:“N95口罩已售光!明日请早!”于是人群纷纷掉头。

售卖妹子逐渐恢复了常态,处理完了一个个围在柜台前的用户需求。Hystrix于是撤掉了告示牌,有口罩购买需求的客户又继续前往口罩专柜。

当某一个微服务响应异常时,Hystrix对于新入的请求按照事先设定的策略给予立即的回复(一般是拒绝),而不是让请求继续积压形成堰塞湖,使得服务难以回归正常。

消息队列 – Kafka或RabbitMQ

今天商场洗衣机搞活动,顾客买的很多。

第1台:顾客-我要下单;售卖员-秒接单;仓库-库存确认;物流-我查下这个送货时间有没有车,还有送货地址是不是OK,1分钟过去了,OK可以送;顾客-成交。

第2台:顾客-我要下单;售卖员-秒接单;仓库-库存确认;物流-我查下这个送货时间有没有车,还有送货地址是不是OK,1分钟过去了,OK可以送;顾客-成交。

第3台:顾客-我要下单;售卖员-秒接单;仓库-库存确认;物流-我查下先,送货师傅联系不上呀,太累了,先抽根烟,5分钟过去了;顾客-靠,等不了了,不买了。

第4台:顾客-我要下单;售卖员-秒接单;仓库-库存确认;物流-我查下先,啊呀,谁吃香蕉乱扔皮,去医院先,50分钟过去了;顾客-靠,不等了。

加入消息缓存机制之后

第1-n台:顾客-我要下单;售卖员-秒接单;仓库-库存确认;物流(采用消息缓存)-接到送货请求,后续电话联系反馈;顾客-成交。

一个不需要实时反馈的业务点,可以利用消息队列先接受请求,按照先进先出的原则一个个有序的处理,处理完毕后再更新结果(比如各类在线商城里的物流派单)。

业务监控 – Spring Boot Admin

今天顾客特别多,商场巡查溜了一圈。李姐呀,这一楼的休息室卫生没跟上呀,再努努力,不然客户投诉了;导购台貌似一切正常,干的不错;口罩专柜运转正常,不错不错;仓库,咦,仓库里怎么没人?怎么还有烟出来?靠,赶紧打电话报警。

业务系统是否运转正常,需要一些基础的监控点进行监控,一旦有异常立即告警,提醒相关人员介入处理。

以上列举了常用的几个微服务组件或者框架,个人概念中的一个完整中小型业务微服务体系示意图如下(未包含数据分析平台)。

CTO之瞳-后端系统1-代码框架+微服务初探

补充说明,微服务有它的好处,但也一定要根据实际业务来判断是否有必要上微服务体系,否则可能事倍功半。比如,一个十人左右的贸易公司,建一个IT系统跟踪贸易单的状态,此外再无其他IT需求,这种情况直接单体结构服务就好了。

下一篇,写一个简单的微服务系统来阐述以上提到的各个组件与框架。


分享到:


相關文章: