一、软件架构
1、C/S架构
C/S架构是第一种比较早的软件架构,主要用于局域网内。也叫 客户机/服务器模式。
它可以分为客户机和服务器两层:第一层是在客户机系统上结合了界面显示与业务逻辑,第二层是通过网络结合了数据库服务器。简单的说就是第一层是用户表示层,第二层是数据库层。
这里需要补充的是,客户端不仅仅是一些简单的操作,它也是会处理一些运算,业务逻辑的处理等。也就是说,客户端也做着一些本该由服务器来做的一些事情
如图所示:
C/S架构软件有一个特点,就是如果用户要使用的话,需要下载一个客户端,安装后就可以使用。比如QQ,OFFICE软件等。
2、B/S架构
B/S架构即浏览器(Browser)和服务器(Server)架构模式,是随着Internet技术的兴起,对C/S(C-Client,客户端)架构的一种改进的架构。
2.1 B/S架构的几种形式
2.1.1 第一种:客户端-服务器-数据库
这个应该是我们平时比较常用的一种模式:
1、客户端向服务器发起Http请求
2、服务器中的web服务层能够处理Http请求
3、服务器中的应用层部分调用业务逻辑,调用业务逻辑上的方法
4、如果有必要,服务器会和数据库进行数据交换. 然后将模版+数据渲染成最终的Html, 返送给客户端
2.1.2 第二种:客户端-web服务器-应用服务器-数据库
类似于第一种方法,只是将web服务和应用服务解耦
- 客户端向web服务器发起Http请求
- web服务能够处理Http请求,并且调用应用服务器暴露在外的RESTFUL接口
- 应用服务器的RESTFUL接口被调用,会执行对应的暴露方法.如果有必要和数据库进行数据交互,应用服务器会和数据库进行交互后,将json数据返回给web服务器
- web服务器将模版+数据组合渲染成html返回给客户端
2.1.3 第三种方法:客户端-负载均衡器(Nginx)-中间服务器(Node)-应用服务器-数据库
这种模式一般用在有大量的用户,高并发的应用中。
1、整正暴露在外的不是真正web服务器的地址,而是负载均衡器器的地址
2、客户向负载均衡器发起Http请求
3、负载均衡器能够将客户端的Http请求均匀的转发给Node服务器集群
4、Node服务器接收到Http请求之后,能够对其进行解析,并且能够调用应用服务器暴露在外的RESTFUL接口
5、应用服务器的RESTFUL接口被调用,会执行对应的暴露方法.如果有必要和数据库进行数据交互,应用服务器会和数据库进行交互后,将json数据返回给Node
6、Node层将模版+数据组合渲染成html返回反向代理服务器
7、反向代理服务器将对应html返回给客户端
Nginx的优点有:
1、它能够承受、高并发的大量的请求,然后将这些请求均匀的转发给内部的服务器,分摊压力.
2、反向代理能够解决跨域引起的问题,因为Nginx,Node,应用服务器,数据库都处于内网段中。
3、Nginx非常擅长处理静态资源(img,css,js,video),所以也经常作为静态资源服务器,也就是我们平时所说的CDN
比如:前一个用户访问index.html, 经过Nginx-Node-应用服务器-数据库链路之后,Nginx会把index.html返回给用户,并且会把index.html缓存在Nginx上,
下一个用户再想请求index.html的时候,请求Nginx服务器,Nginx发现有index.html的缓存,于是就不用去请求Node层了,会直接将缓存的页面(如果没过期的话)返回给用户。
2.2 B/S与C/S比较
2.2.1 C/S和B/S结构的比较
1.C/S结构。(Client/Server)客户机和服务器结构。
它是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统的通讯开销。目前大多是应用软件系统都是Client/Server形式的两层结构,由于现在的软件应用系统正在向分布式的Web应用发展,因此,内部和外部的用户都可以访问新的和现有的应用系统。通过现有应用系统中的逻辑可以扩展出新的应用系统。这也就是目前应用系统的发展方向。
传统的C/S体系结构虽然采用的是开放模式,但这只是系统开发一级的开放性,在特定的应用中,无论是Client端还是Server端都还需要特定的软件支持。由于没能提供用户真正期望的开放环境,C/S结构的软件需要针对不同的操作系统开发不同版本的软件,加之产品的更新换代十分快,已经很难适应百台电脑以上局域网用户同时使用,而且代价高,效率低。
2.B/S结构。(Browser/Server)浏览器和服务器结构
它是随着Internet技术的兴起,对C/S结构的一种变化或者改进的结构。在这种结构下,用户工作界面是通过WWW浏览器来实现,极少部分事务逻辑在前段(Browser)实现,但是主要事务逻辑在服务器端(Server)实现,形成所谓三层3-tier结构。这样就大大简化了客户端电脑载荷,减轻了系统维护与升级的成本和工作量,降低了用户的总体成本(TCO),以目前的技术看,局域网建立B/S结构的网络应用,并通过Internet/Intranet模式下数据库应用,相对易于把握,成本也是较低的。它是一次性到位的开发,能实现不同的人员,不同的地点,以不同的接入方式(比如LAN,WAN,Internet/Intranet等)访问和操作共同的数据库;它能有效的保护数据平台和管理访问权限,服务器数据库也很安全。特别是在java这样的跨平台语言出现之后,B/S架构管理软件更是方便,快捷,高效。
3.管理软件主流技术
与管理思想一样,也是经历了三个发展时期。首先,界面技术从上个世纪DOS字符界面,到Windows图形界面(或图形用户界面GUI),只是Browser浏览器界面三个不同的发展时期。其次,今天所有电脑的浏览器界面,不仅直观和易于使用,更主要的是基于浏览器平台的任何应用软件其风格都是一样的,使用人对操作培训的要求不高,而且软件可操作性强,易于识别;再者,平台体系结构也从过去单用户发展到今天的文件/服务器(F/S)体系,客户机/服务器(C/S)体系和浏览器/服务器(B/S)体系。
2.1.2 C/S和B/S体系的比较
C/S是美国Borland公司最早研发,B/S是美国微软公司研发。
1.B/S架构软件的优势和劣势。
(1) 应用服务器运行数据负荷较轻。最简单的C/S体系结构的数据库应用有两部分组成,即:客户应用程序和数据库服务器程序。二者可分别称为前台程序和后台程序。
(2) 数据的存储管理功能较为透明。在数据库应用中,数据的存储管理功能,是由服务器程序和客户应用程序分别独立进行的,前台应用可以违反的规则,并且通常把那些不同的(不管是已知还是未知的)运行数据,在服务器程序中不集中实现。例如访问者权限,编号可以重复,必须有客户才能建立订单这样的规则。所有这些,对于工作在前台程序上的最终用户,是“透明”的,他们无需过问(通常也无法干涉)背后的过程,就可以完成自己的一切工作。在客户服务器架构的应用中,前台程序不是非常“瘦小”,麻烦的事情都交给服务器和网络,在C/S体系下,数据库不能真正成为公共,专业化的仓库,它受到独立的专门管理。
(3) C/S架构的劣势是高昂的维护成本且投资大。首先,采用C/S架构,要选择适当的数据库平台来实现数据库数据的真正“统一”,使分布于两地的数据同步完全交由数据库系统去管理,但逻辑上两地的操作者要直接访问同一个数据库才能有效实现,有这样一些问题,如果需要建立“实时”的数据同步,就必须在两地间建立实时的通讯连接,保持两地的数据库服务器在线运行,网络管理工作人员既要对服务器维护管理,又要对客户端维护和管理,这需要高昂的投资和复杂的技术支持,维护成本很高,维护任务量大。其次,传统的C/S结构的软件需要针对不同的操作系统系统开发不同版本的软件,由于产品的更新换代十分快,代价高和低效率已经不适应工作需要,在Java这样的跨平台语言出现之后,B/S架构更是猛烈冲击C/S,并对其形成威胁和挑战。
2. B/S架构软件的优势与劣势。
(1) 维护和升级方式简单。B/S架构的软件,系统管理人员只需要管理服务器,所有的客户端只是浏览器,不需要做任何的维护。无论用户规模有多大,多少分支机构都不会增加任何维护升级的工作量,所有的操作只需要针对服务器进行。如果是异地,只需要把服务器连接专网即可,实现远程维护,升级和共享。所以客户机越来越“瘦”,而服务器越来越“胖”时将来信息化发展的主流方向。因此,维护和升级革命的方式是“瘦”客户机,“胖”服务器。
(2) 降低成本,选择更多。现在的趋势是,凡是使用B/S架构的应用管理软件,只需安装在Linux服务器上即可,而且安全性高,所以服务器操作系统的选择是很多的,不管选用那种操作系统,都可以让大部分人使用windows作为桌面操作系统电脑不受影响。
(3) 应用服务器运作数据负荷较重。由于B/S架构管理软件只安装在服务器端(server)上,网络管理人员只需要管理服务器就行了,用户界面主要事务逻辑在服务器(server)端完全通过WWW浏览器实现,极少部分事务逻辑在前端(Browser)实现,所有的客户端只有浏览器,网络管理人员只需要做硬件维护。但是应用服务器运行数据负荷较重,一旦发生服务器“崩溃”等问题,后果不堪设想。因此,许多单位都备有数据库存储服务器,以防万一。
—————————————-
参考文献:
- MVC,MVP 和 MVVM 的图示:http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html
软件架构入门:http://www.ruanyifeng.com/blog/2016/09/software-architecture.html - 两种架构对比:https://www.cnblogs.com/xianyulaodi/p/5986748.html
- B/S与C/S比较—《JAVA与模式》:https://bbs.csdn.net/topics/70352588
二、系统集成
1、统一身份认证平台
1.1 OAuth2、CAS单点登录
1.1.1 OAuth
OAuth 是一个关于授权(authorization)的开网络标准(规范)
OAuth2: 解决的是不同的企业之间的登录,本质是授权,如论坛与QQ
要能访问各种资源重点是要获取令牌(token),但根据令牌的获取方式不同,又会有四种授权方式
- 授权码(authorization-code)
- 隐藏式(implicit)
- 密码式(password)
- 客户端凭证(client credentials)
授权码:这是最常用的一种方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌,项目中用的就是这种
隐藏式:允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)”隐藏式”(implicit),一般应用于纯前端项目
密码式:直接通过用户名和密码的方式申请令牌,这方式是最不安全的方式
凭证式:这种方式的令牌是针对第三方应用,而不是针对用户的,既某个第三方应用的所有用户共用一个令牌,一般用于没有前端的命令行应用
授权码授权流程:
第一步,A 网站提供一个链接,用户点击后就会跳转到 B 网站(权限验证系统)
http://b.com/oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
第二步,用户跳转后,B 网站如果没有登录会要求用户登录,然后询问是否同意给予 A 网站授权。用户表示同意,这时 B 网站就会跳回redirect_uri参数指定的网址,并附加授权码code
http://a.com/callback?code=AUTHORIZATION_CODE
第三步,A 网站拿到授权码以后,在后端,向 B 网站请求令牌。
http://b.com/oauth/token?
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=CALLBACK_URL
上面 URL 中,client_id参数和client_secret参数用来让 B 确认 A 的身份(client_secret参数是保密的,因此只能在后端发请求),grant_type参数的值是AUTHORIZATION_CODE,表示采用的授权方式是授权码,code参数是上一步拿到的授权码,redirect_uri参数是令牌颁发后的回调网址。
第四步,B 网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri指定的网址,发送一段 JSON 数据。
{
“access_token”:”ACCESS_TOKEN”,
“info”:{…}
}
接下来用户就可以根据这个access_token来进行访问了,
如A网站拿着token,申请获取用户信息,B网站确认令牌无误,同意向A网站开放资源。
对于第三方网站来说 可分为3部分
1、申请code
2、申请token
3、带着token去请求资源(如:申请获取用户信息)
1.1.2 单点
单点是解决企业内部的一系列产品登录问题,安全信任度要比oauth2高。
(一)session-cookie机制
1、session-cookie机制出现的根源, http连接是无状态的连接
———— 同一浏览器向服务端发送多次请求,服务器无法识别,哪些请求是同一个浏览器发出的
2、为了标识哪些请求是属于同一个人 ————— 需要在请求里加一个标识参数
方法1—————-直接在url里加一个标识参数(对前端开发有侵入性),如: token
方法2—————-http请求时,自动携带浏览器的cookie(对前端开发无知觉),如:jsessionid=XXXXXXX
3、浏览器标识在网络上的传输,是明文的,不安全的
—————-安全措施:改https来保障
4、cookie的使用限制—-依赖域名
——————— 顶级域名下cookie,会被二级以下的域名请求,自动携带
——————— 二级域名的cookie,不能携带被其它域名下的请求携带
5、在服务器后台,通过解读标识信息(token或jsessionid),来对应会话是哪个session
———————- 一个tomcat,被1000个用户登陆,tomcat里一定有1000个session ———-》存储格式map《sessionid,session对象》
———————- 通过前端传递的jsessionid,来对应取的session ——— 动作发生时机request.getsession
(二)session共享方式,实现的单点登陆
- 多个应用共用同一个顶级域名,sessionid被种在顶级域名的cookie里
- 后台session通过redis实现共享(重写httprequest、httpsession 或使用springsession框架),即每个tomcat都在请求开始时,到redis查询session;在请求返回时,将自身session对象存入redis
- 当请求到达服务器时,服务器直接解读cookie中的sessionid,然后通过sessionid到redis中查找到对应会话session对象
- 后台判断请求是否已登陆,主要校验session对象中,是否存在登陆用户信息
- 整个校验过程,通过filter过滤器来拦截切入,如下图:
6. 登陆成功时,后台需要给页面种cookie方法如下:
response里,反映的种cookie效果如下:
7. 为了request.getsession时,自动能拿到redis中共享的session,
我们需要重写request的getsession方法(使用HttpServletRequestWrapper包装原request)
1.1.3 cas单点登陆方案
- 对于完全不同域名的系统,cookie是无法跨域名共享的
- cas方案,直接启用一个专业的用来登陆的域名(比如:cas.com)来供所有的系统登陆。
- 当业务系统(如b.com)被打开时,借助cas系统来登陆,过程如下:
cas登陆的全过程:
(1) b.com打开时,发现自己未登陆 ——》 于是跳转到cas.com去登陆
(2) cas.com登陆页面被打开,用户输入帐户/密码登陆成功
(3) cas.com登陆成功,种cookie到cas.com域名下 —————-》把sessionid放入后台redis《ticket,sesssionid》—-页面跳回b.com
String ticket = UUID.randomUUID().toString(); redisTemplate.opsForValue().set(ticket,request.getSession().getId(),20, TimeUnit.SECONDS);//一定要设置过期时间 CookieBasedSession.onNewSession(request,response); response.sendRedirect(user.getBackurl()+”?ticket=”+ticket);
(4) b.com重新被打开,发现仍然是未登陆,但是有了一个ticket值
(5) b.com用ticket值,到redis里查到sessionid,并做session同步 ——— 》种cookie给自己,页面原地重跳
(6) b.com打开自己页面,此时有了cookie,后台校验登陆状态,成功
(7) 整个过程交互,列图如下:
- cas.com的登陆页面被打开时,如果此时cas.com本来就是登陆状态的,则自动返回生成ticket给业务系统
整个单点登陆的关键部位,是利用cas.com的cookie保持cas.com是登陆状态,此后任何第三个系统跳入,都将自动完成登陆过程
- 本示例中,使用了redis来做cas的服务接口,请根据工作情况,自行替换为合适的服务接口(主要是根据sessionid来判断用户是否已登陆)
- 为提高安全性,ticket应该使用过即作废(本例中,会用有效期机制)
2、基于AD账号的LDAP身份认证方式
2.1 LDAP
轻型目录访问协议(英文:Lightweight Directory Access Protocol,缩写:LDAP,/ˈɛldæp/)是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息。
2.1.1 什么是LDAP?
(一)在介绍什么是LDAP之前,我们先来复习一个东西:“什么是目录服务?”
- 目录服务是一个特殊的数据库,用来保存描述性的、基于属性的详细信息,支持过滤功能。
- 是动态的,灵活的,易扩展的。
如:人员组织管理,电话簿,地址簿。
(二)了解完目录服务后,我们再来看看LDAP的介绍:
LDAP(Light Directory Access Portocol),它是基于X.500标准的轻量级目录访问协议。
目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。
目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。
LDAP目录服务是由目录数据库和一套访问协议组成的系统。
(三)为什么要使用
LDAP是开放的Internet标准,支持跨平台的Internet协议,在业界中得到广泛认可的,并且市场上或者开源社区上的大多产品都加入了对LDAP的支持,因此对于这类系统,不需单独定制,只需要通过LDAP做简单的配置就可以与服务器做认证交互。“简单粗暴”,可以大大降低重复开发和对接的成本。
2.1.2 LDAP的主要产品
细心的朋友应该会主要到,LDAP的中文全称是:轻量级目录访问协议,说到底LDAP仅仅是一个访问协议,那么我们的数据究竟存储在哪里呢?
来,我们一起看下下面的表格:
厂商 | 产品 | 介绍 |
---|---|---|
SUN | SUNONE Directory Server | 基于文本数据库的存储,速度快 。 |
IBM | IBM Directory Server | 基于DB2 的的数据库,速度一般。 |
Novell | Novell Directory Server | 基于文本数据库的存储,速度快, 不常用到。 |
Microsoft | Microsoft Active Directory | 基于WINDOWS系统用户,对大数据量处理速度一般,但维护容易,生态圈大,管理相对简单。 |
Opensource | Opensource | OpenLDAP 开源的项目,速度很快,但是非主 流应用。 |
没错,这就是正常存储数据的地方,而访问这些数据就是通过我们上述所说的LDAP。相信到这里大家应该了解两者之间的关系了吧!
2.1.3 LDAP的基本模型
每一个系统、协议都会有属于自己的模型,LDAP也不例外,在了解LDAP的基本模型之前我们需要先了解几个LDAP的目录树概念:
(一)目录树概念
1. 目录树:在一个目录服务系统中,整个目录信息集可以表示为一个目录信息树,树中的每个节点是一个条目。
2. 条目:每个条目就是一条记录,每个条目有自己的唯一可区别的名称(DN)。
3. 对象类:与某个实体类型对应的一组属性,对象类是可以继承的,这样父类的必须属性也会被继承下来。
4. 属性:描述条目的某个方面的信息,一个属性由一个属性类型和一个或多个属性值组成,属性有必须属性和非必须属性。
(二)DC、UID、OU、CN、SN、DN、RDN
关键字 | 英文全称 | 含义 |
---|---|---|
dc | Domain Component | 域名的部分,其格式是将完整的域名分成几部分,如域名为example.com变成dc=example,dc=com(一条记录的所属位置) |
uid | User Id | 用户ID songtao.xu(一条记录的ID) |
ou | Organization Unit | 组织单位,组织单位可以包含其他各种对象(包括其他组织单元),如“oa组”(一条记录的所属组织) |
cn | Common Name | 公共名称,如“Thomas Johansson”(一条记录的名称) |
sn | Surname | 姓,如“许” |
dn | Distinguished Name | “uid=songtao.xu,ou=oa组,dc=example,dc=com”,一条记录的位置(唯一) |
rdn | Relative dn | 相对辨别名,类似于文件系统中的相对路径,它是与目录树结构无关的部分,如“uid=tom”或“cn= Thomas Johansson” |
2.1.4、LDAP的使用
那我们是如何访问LDAP的数据库服务器呢?
统一身份认证主要是改变原有的认证策略,使需要认证的软件都通过LDAP进行认证,在统一身份认证之后,用户的所有信息都存储在AD Server中。终端用户在需要使用公司内部服务的时候,都需要通过AD服务器的认证。
那么程序中是如何访问的呢? 我们以PHP脚本作为例子:
$ldapconn = ldap_connect(“10.1.8.78”) $ldapbind = ldap_bind($ldapconn, ‘username’, $ldappass); $searchRows= ldap_search($ldapconn, $basedn, “(cn=*)”); $searchResult = ldap_get_entries($ldapconn, $searchRows); ldap_close($ldapconn);
1. 连接到LDAP服务器;
2. 绑定到LDAP服务器;
3. 在LDAP服务器上执行所需的任何操作;
4. 释放LDAP服务器的连接;
——————-
参考文献:
- OAuth2、CAS单点登录:https://www.cnblogs.com/aligege/p/13712399.html
- OAuth2、CAS、SSO单点登录详解:https://blog.csdn.net/qq_40136782/article/details/108223526
- LDAP概念和原理介绍:https://www.cnblogs.com/wilburxu/p/9174353.html
三、API
1、 RESTful
理解RESTful架构:https://www.ruanyifeng.com/blog/2011/09/restful.html
四、未来主题规划:
- 云计算技术:
- 存储技术:
- 大数据分析与处理技术
- 异构系统
- spark 任务调度(前后端分离、任务调度)
- docker +k8s
- 数据处理(流批处理)
软件分层设计
分层设计是什么?有什么好处?
分层设计将软件划分成若干层,每一层只解决一部分问题,通过所有层的协作来完成整体的目标。一个复杂的问题通过分解成一系统子问题,这样就有效的降低了每个子问题的规模与复杂度。
分层设计带来的好处是:
- 降低了系统软件的复杂度。将一个复杂问题通过分解,分而治之。
-
计算机语言的发展
机器语言
- 早期的软件开发是机器语言,直接使用二进制 0 和 1 表示机器可以识别的指令和数据,看起来像这样:0010000100100011这就是计算机 CPU 唯一可以理解的语言。
- 对人类为说,二进制的程序是不可读的。
- 汇编语言
- 为了解决语言可读性的问题,汇编程序诞生了。汇编程序是人类可读的机器代码。它又称为‘符号语言’,使用助记符来代替机器的操作码。
- 汇编语言是二进制的文本形式,与 CPU 的指令是一一对应的关系。而我们不同的 CPU 体系结构(比如 PC 的 x86,嵌入式的 ARM)是不同的,面向机器的语言 带来的问题就是:对于不同的 CPU 体系架构,就需要不同的汇编语言。
- 高级语言
- 为了解决语言对机器的无关性,高级语言诞生了。一条高级语言通常由若干条机器语言实现的,并且不具有对应性。
- 高级语言让开发者不需要关注底层 CPU 体系结构与指令,只关注业务即可。
计算机语言的发展就是不断的抽象,只有通过抽象,将一个复杂的的系统变成一层层的接口集合,让我们每次只需要考虑关注当前层集合内的逻辑,而不用去考虑当前层次以上或者以下的复杂度,才有可能让我们从复杂系统中解放出来,逐步理解以及构造一个复杂系统。
Linux 内核
内核功能层与内核硬件层
操作系统内核简化理解成三大层:
- 内核接口层
- 向上对用户态应用程序提供一套接口子集。开发者使用的系统调用 APIs。
- 内核功能层
- 这一层完成各种实际的功能,我们知道 OS 主要负责资源管理,内存,进程这些资源。物理内存如何申请,释放,进程如何调度。具体来说进程管理,内存管理,中断管理,设备管理。
- 内核硬件层
- 分离硬件的相关性,我们知道一个 OS 可以运行不同的指令集,也就是运行在不同的硬件平台。不管是 ARM 体系结构,还是 x86,选择一个进程调度的算法是可以相同的,需要改变的进程切换相关代码,因为不同的硬件平台的上下文是不同的,CPU 的寄存器也不同。这时候最好的设计是分层,当操作系统运行在不同的硬件平台时,就只需要修改硬件平台相关层代码,实现操作系统的高可移植性。
操作系统有两个关键设计:
- 内核接口层区分用户态与内核态,来保护硬件资源受限访问。
- 内核硬件层分离多种硬件平台相关性。这种分层的架构,极大提升了系统的稳定性和扩展性。
MMU 抽象层
操作系统负责管理物理内存,而用户进程使用虚拟内存。操作系统呈现给用户进程的是连续的虚拟空间,但不一定是连续的物理空间,因为物理内存被整个 OS 共享。
- 什么是 MMU 呢?它是硬件,即内存管理单元,它对 CPU 发出的访存地址进行映射与检查,可以让处理器发出的访存地址访问不同的物理内存单元。
- 如果将计算机上有限的物理内存分配给多个应用程序使用,如果让应用程序直接访问物理内存,如果没有 MMU 这层抽象呢?带来的问题是每个应用程序地址空间不隔离,内存使用率低,程序运行地址也无法固定。
- 解决的问题:虚拟内存 VA 与物理内存 PA 的映射。
- 通过在 CPU 与内存之间加入 MMU 抽象层,让 CPU 在运行指令时发出的 VA 虚拟地址通过 MMU 转换后变成 PA 物理地址,然后再去访问物理内存。
MMU 引入带来的好处是:
- 权限控制。可以对一些虚拟地址进行访问控制,比较代码段为只读,用户程序代可写。
- 提升内存使用率:物理内存按需申请。fork 子进程的对应的物理空间是能过写时复制才进行真正的物理内存分配。
- 不同进程之间可以使用相同的虚拟内存地址空间,而进程的物理内存又可以隔离。
- 系统运行多个进程,所分配的内存之和可以大于实际物理内存大小。
CPU 与外设的通信
CPU 访问外设有两种方法:
- IO 与内存统一编址
- IO 与内存的独立编址
外设接口中的 IO 寄存器(即 IO 端口)与主存单元一样看待,每个端口占用一个存储单元的地址,将主存的一部分划分出来用作 IO 的地址空间。
- 把外设的寄存器当做是一个内存地址,从而 CPU 以类似访问内存相同的方式来操作外设。
- 对 IO 外设的端口映射到一个物理内存单元地址,在 CPU 与外设之间的‘内存’抽象层,带来好处是访问内存一样去访问外设。
小结:
Linux 中的内核硬件层设计、MMU、CPU 与 IO 外设通信设计处处体现了分层/中间层的设计思想。TCP/IP 网络协议堆栈
从最底层的物理链路层层层向上封装抽象,解决了复杂的网络通信的问题。同样的,任何复杂的问题,通过分层最终总能够回归最本质。
这个分层架构,对所有开发者而言,再熟悉不过,它的引入是想与后续介绍的 Netty 形成对比。这里先卖个关子,后面解开谜底。
举例说明:
来自杭州西湖区某个小区的商务人士来京出差后,被确诊新冠肺炎,实施在京隔离措施,同时北京将此报告先发给浙江省,接着浙江省发给杭州市政府,然后市政府再向西湖区发送,最后到达某小区。这个发送报告过程也是分层报告思想。
DNS 中间层
DNS(Domain Name System)是域名系统,是用来将主机转换为 IP 地址的服务。我们有至少三种方式在互联网上标识一台主机,主机名、IP 地址以及 MAC 地址。为什么有引入 DNS 中间抽象层呢?
主要是主机名便于记忆,而 IP 地址方便于在计算机网络设备的处理,因此需要设计出一个 DNS 协议(中间层)来做主机名到 IP 地址的转换。
ARP 中间层
ARP(Address Resolution Protocol)是地址解析协议,它根据 IP 地址来获取物理地址。上面也谈到,MAC 与 IP 都可以用来标识一台主机。那这二者区别是什么?
- 同一个局域网中的一台主机和另一台主机通信的时候,需要通过 MAC 地址进行定位,之后才能进行数据包的传送。
- 而在网络层和传输层中,主机之间是通过 IP 地址来定位的,对应的数据包中必须携带目标主机的 IP 地址,而没有 MAC 地址。
因此,ARP 协议(中间层)用来实现从 IP 到 MAC 地址的转换。
Netty
Netty 提供了异步的,基于事件驱动的网络应用程序框架。目前分布式搜索引擎,Spark 框架底层是扩展使用 Netty 框架。
Netty 本身的架构理解有些曲线,为了讲清楚,我还是希望循序渐进方式,通过它的发展历史来一步步介绍。先铺垫再介绍,大家需要一些耐心。
传统阻塞 IO 服务模型
思路:
- 采用阻塞 IO 模式获取输入数据。
- 每个连接都需要独立的线程完成数据的输入,业务的处理和数据返回。
问题:
思路:
- 通过引入 Selector 事件选择器来监听多路连接的请求。
- Reactor 对象通过 Selector 监控客户端请求事件后,通过 Dispatch 进行分发。
- 如果建立连接请求事件,则由 Acceptor 负责建立一个连接,然后创建一个 Handler 对象处理连接完成后的业务处理。
问题:
- 模型简单,没有多线程,资源竞争的问题。所以工作在一个线程完成。
- 性能问题,一个线程,无法发挥多核 CPU 的性能。
- 可靠性问题,线程 crash,会导致整个系统不可用。
主从 Reactor 多线程
主 React 处理所有 socket 连接事件的监听和响应,而从 React 处理所有 socket 的读写事件的监听与响应。主从 React 都在多线程中运行。
Netty 模型
Netty 主要基于主从 Reactor 多线程模型发展出来的。
Netty 逻辑架构
前面 Netty 的发展阶段都是铺垫,Nettty 逻辑架构为典型网络分层架构设计,从下到上分别为网络通信层、事件调度层、服务编排层。
- 网络通信层:它执行网络 I/O 操作,核心组件包含 BootStrap、ServerBootStrap、Channel。
- Channel 通道,提供了基础的 API 用于操作网络 IO,比如 bind、connect、read、write、flush 等等。它以 JDK NIO Channel 为基础,提供了更高层次的抽象,同时屏蔽了底层 Socket 的复杂性。
- Channel 有多种状态,比如连接建立、数据读写、连接断开。随着状态的变化,Channel 处于不同的生命周期,背后绑定相应的事件回调函数。
- 事件调度层:它的核心组件包含 EventLoopGroup、EventLoop。
- EventLoop 本质是一个线程池,主要负责接收 Socket I/O 请求,并分配事件循环器来处理连接生命周期中所发生的各种事件。
- 服务编排层:它的职责实现网络事件的动态编排和有序传播。
- ChannelPipeline 基于责任链模式,方便业务逻辑的拦截和扩展。
- 本质上它是一个双向链表将不同的 ChannelHandler 链接在一块,当 I/O 读写事件发生时,会依次调用 ChannelHandler 对 Channel(Socket)读取的数据进行处理。
ChannelPipeline 私有协议栈 vsTCP/IP 协议栈
前面铺垫这么久,就是为了自然过渡到上面的图,请务必与 TCP/IP 协议栈进行对比。
- socket。read 经过 TCP/IP 协议栈后,进入 Netty 的网络通信层,事件调度层,最后来到服务编排层。而服务编排层的 ChannelPipeline 的设计也是一个 upstream/downstream 的 stack,一进一出的两个 Pipeline,负责处理流入/流出的数据包。
上面的 stack 就非常类似 TCP/IP 协议栈。根据公司组织的需要可以定制分层的私有协议栈,比如 authentication-handler、message-validation-handler、message-encode-handler、message-decoder-handler。
微服务分层
gRPC-Gateway
- 它是一个开源框架,读取 Protobuf 接口定义并生成一个反向代理服务器,此时服务器一步将 RESTful API 转换成 gRPC 服务。
- Middleware
- 实现鉴权功能,比如哪些 URL 需要权限检验
- Handler 通用处理层
- 参数检验:Handler 层负责执行与客户端约定参数的检验,检验通过后再组装成后端服务需要的数据结构发往后端。
- 接口聚合/组合服务:Handler 层可以根据业务需要,调用多个后端服务的 endpoint 来组合实现一个新的接口,同时将下层返回的数据进行聚合处理。
- service/model 业务逻辑层
- 对业务逻辑的封装,负责将多个 DAO 数据结构转换和封装成一个有逻辑意义的模型。
- 可以引入缓存策略,优化数据存取效率。
- DAO 层
- 数据访问层,主要负责操作 DB 中某张表并映射到内存中某个 DAO 模型。
- 与数据表结构一一对应,通过 DAO 内存模型向上层传递数据源的对象。
- 数据访问层 DAL
- 对底层的数据源做统一的抽象,屏蔽数据库。如果没有 DAL 的存在,那么几乎所有的业务逻辑层都会去与具体的数据库存储强绑定,耦合性就很高。
还有一个补充点:
业务逻辑层中的服务在实际场景中不可避免的会出现互相调用的场景,这种情况往往需要将耦合/公共的功能进行下沉。比如数据请求下沉为数据访问层服务,而业务下沉为稳定的通用业务服务,被其它服务稳定依赖。
Rails On Rack
熟悉 Ruby On Rails Web 应用框架的开发者,肯定知道 Rack 是如何成为应用容器(Web Server)和应用框架之间的桥梁。
Rack 在 Web Server 和应用框架之间提供了一套最小的 API 接口,如果 Web Server 都遵循 Rack 提供的这套规则,那么所有的框架都能通过协议任意地改变底层。
Rack 分层设计非常类似 Decorate pattern 或者 Chain-of-responsibility pattern。
总结
本文作者结合自身工作经验,总结一些典型分层设计案例:
- 计算机语言的发展。
- Linux 内核设计(内核功能层与内核硬件层,MMU 抽象层,CPU 与外设的通信)。
- TCP/IP 网络协议堆栈(DNS 和 ARP 协议)。
- Netty 框架发展以及分层私有协议栈分析。
- 微服务分层。
- 应用框架 Rails on Rack。
这些案例充分说明了计算机系统本身就是通过一层一层抽象构造出来的。
- 硬件方面是从一个个小的晶体管,抽象成一个个门电路,再到 CPU 器件,最后抽象组成计算机。
- 软件设计也是由一个层次一个层次的功能完善叠加的,无论是自顶向下还是自底向上。