一、基础概念
分布式处理是将不同地点的,或具有不同功能的,或拥有不同数据的多台计算机通过通信网络连接起来,在控制系统的统一管理控制下,协调地完成大规模信息处理任务的计算机系统。
1 分布式性能指标
1.QPS每秒查询率(Query Per Second)
QPS:Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够接受相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
qps越高,说明每秒查询的数量越高,当一个系统的qps过高时,可以考虑系统是否能承受这么高的qps.
2.TPS:是TransactionsPerSecond的缩写,也就是事务数/秒。
它是软件测试结果的测量单位。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。
tps越高,说明每秒处理的事务数更高,系统处理能力越强,系统性能也就越好。
3.吞吐量:吞吐量是指对网络、设备、端口、虚电路或其他设施,单位时间内成功地传送数据的数量(以比特、字节、分组等测量)。
我自己的理解为单位时间系统所承受的请求到响应的数目。
吞吐量越高,系统性能越好。
1.系统吞吐量、TPS(QPS)、用户并发量、性能测试概念和公式
https://my.oschina.net/wugong/blog/1633176
思考
1.机器数量选型
按二八定律来看,如果每天 80% 的访问集中在 20% 的时间里,这 20% 时间就叫做峰值时间。
- 公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS)
- 机器:峰值时间每秒QPS / 单台机器的QPS = 需要的机器
1、每天300w PV 的在单台机器上,这台机器需要多少QPS?
( 3000000 * 0.8 ) / (86400 * 0.2 ) = 139 (QPS)
2、如果一台机器的QPS是58,需要几台机器来支持?
139 / 58 = 3
2.最佳线程数选型
单线程QPS公式:QPS=1000ms/RT
对同一个系统而言,支持的线程数越多,QPS越高。
假设一个RT是80ms,则可以很容易的计算出QPS,QPS = 1000/80 = 12.5
多线程场景,如果把服务端的线程数提升到2,那么整个系统的QPS则为 2*(1000/80) = 25
可见QPS随着线程的增加而线性增长,那QPS上不去就加线程呗,听起来很有道理,公司也说的通,但是往往现实并非如此。
3.高并发QPS的定义
首先是无状态前端机器不足以承载请求流量,需要进行水平扩展,一般QPS是千级。
然后是关系型数据库无法承载读取或写入峰值,需要数据库横向扩展或引入nosql,一般是千到万级。
之后是单机nosql无法承载,需要nosql横向扩展,一般是十万到百万QPS。
最后是难以单纯横向扩展nosql,比如微博就引入多级缓存架构,这种架构一般可以应对百万到千万对nosql的访问QPS。
当然面向用户的接口请求一般到不了这个量级,QPS递增大多是由于读放大造成的压力,但也属于高并发架构考虑的范畴。
相关联的领域概念
1.微服务
参考我写的微服务文章:
微服务架构
https://blog.csdn.net/sinat_34814635/article/details/79931252
1.3 技术选型
1.方向代理:nginx
2.网关
3.rpc服务器内部调用:dubbo
4.消息队列mq:rabbitmq
5.存储:mysql,es,redis,ck
6.服务监控:cat,
7.分布式链路追踪:Pingpoint
二、核心理论
1. 同步和异步通讯选型
1.同步通讯
同步通讯就像打电话,需要实时响应,而异步通讯就像发邮件,不需要马上回复。各有千秋,我们很难说谁比谁好。但是在面对超高吐吞量的场景下,异步处理就比同步处理有比较大的优势了,这就好像一个人不可能同时接打很多电话,但是他可以同时接收很多的电子邮件一样。
同步调用虽然让系统间只耦合于接口,而且实时性也会比异步调用要高,但是我们也需要知道同步调用会带来如下几个问题。
- 同步调用需要被调用方的吞吐不低于调用方的吞吐。否则会导致被调用方因为性能不足而拖死调用方。换句话说,整个同步调用链的性能会由最慢的那个服务所决定。
- 同步调用会导致调用方一直在等待被调用方完成,如果一层接一层地同步调用下去,所有的参与方会有相同的等待时间。这会非常消耗调用方的资源。因为调用方需要保存现场(Context)等待远端返回,所以对于并发比较高的场景来说,这样的等待可能会极度消耗资源。
- 同步调用只能是一对一的,很难做到一对多。
- 同步调用最不好的是,如果被调用方有问题,那么其调用方就会跟着出问题,于是会出现多米诺骨牌效应,故障一下就蔓延开来。
2.异步通讯
异步通讯相对于同步通讯来说,除了可以增加系统的吞吐量之外,最大的一个好处是其可以让服务间的解耦更为彻底,系统的调用方和被调用方可以按照自己的速率而不是步调一致,从而可以更好地保护系统,让系统更有弹力。
异步通讯的三种方式
从实现复杂度分析,一下三种由易到难。
1.请求响应式
在这种情况下,发送方(sender)会直接请求接收方(receiver),被请求方接收到请求后,直接返回——收到请求,正在处理。
对于返回结果,有两种方法,一种是发送方时不时地去轮询一下,问一下干没干完。
另一种方式是发送方注册一个回调方法,也就是接收方处理完后回调请求方。这种架构模型在以前的网上支付中比较常见,页面先从商家跳转到支付宝或银行,商家会把回调的 URL 传给支付页面,支付完后,再跳转回商家的 URL。
这种方式比较适合和外部公司的一种异步通讯方式,因为与外部公司的对决不太可能用mq,因为成本太大了。
很明显,这种情况下还是有一定耦合的。是发送方依赖于接收方,并且要把自己的回调发送给接收方,处理完后回调。
2.通过订阅的方式
这种情况下,接收方(receiver)会来订阅发送方(sender)的消息,发送方会把相关的消息或数据放到接收方所订阅的队列中,而接收方会从队列中获取数据。
这种方式下,发送方并不关心订阅方的处理结果,它只是告诉订阅方有事要干,收完消息后给个 ACK 就好了,你干成啥样我不关心。这个方式常用于像 MVC(Model-View-Control)这样的设计模式下
这就好像下订单的时候,一旦用户支付完成了,就需要把这个事件通知给订单处理以及物流,订单处理变更状态,物流服务需要从仓库服务分配相应的库存并准备配送,后续这些处理的结果无需告诉支付服务。
为什么要做成这样?好了,重点来了!前面那种请求响应的方式就像函数调用一样,这种方式有数据有状态的往来(也就是说需要有请求数据、返回数据,服务里面还可能需要保存调用的状态),所以服务是有状态的。如果我们把服务的状态给去掉(通过第三方的状态服务来保证),那么服务间的依赖就只有事件了。
你知道,分布式系统的服务设计是需要向无状态服务(Stateless)努力的,这其中有太多的好处,无状态意味着你可以非常方便地运维。所以,事件通讯成为了异步通讯中最重要的一个设计模式。
就上面支付的那个例子,商家这边只需要订阅一个支付完成的事件,这个事件带一个订单号,而不需要让支付方知道自己的回调 URL,这样的异步是不是更干净一些?
但是,在这种方式下,接收方需要向发送方订阅事件,所以是接收方依赖于发送方。这种方式还是有一定的耦合。
这种方式一般适用于进程内的解耦合,进程间一般通过mq的方式。
3.通过 Broker 的方式
所谓 Broker,就是一个中间人,发送方(sender)和接收方(receiver)都互相看不到对方,它们看得到的是一个 Broker,发送方向 Broker 发送消息,接收方向 Broker 订阅消息。如下图所示。
这是完全的解耦。所有的服务都不需要相互依赖,而是依赖于一个中间件 Broker。这个 Broker 是一个像数据总线一样的东西,所有的服务要接收数据和发送数据都发到这个总线上,这个总线就像协议一样,让服务间的通讯变得标准和可控。
在 Broker 这种模式下,发送方的服务和接收方的服务最大程度地解耦。但是所有人都依赖于一个总线,所以这个总线就需要有如下的特性:
- 必须是高可用的,因为它成了整个系统的关键;
- 必须是高性能而且是可以水平扩展的;
- 必须是可以持久化不丢数据的。
要做到这三条还是比较难的。当然,好在现在开源软件或云平台上 Broker 的软件是非常成熟的,所以节省了我们很多的精力。
3.异步通讯的设计重点
首先,我们需要知道,为什么要异步通讯。
- 异步通讯最重要的是解耦服务间的依赖。最佳解耦的方式是通过 Broker 的机制。
- 解耦的目的是让各个服务的隔离性更好,这样不会出现“一倒倒一片”的故障。
- 异步通讯的架构可以获得更大的吞吐量,而且各个服务间的性能不受干扰相对独立。
- 利用 Broker 或队列的方式还可以达到把抖动的吞吐量变成均匀的吞吐量,这就是所谓的“削峰”,这对后端系统是个不错的保护。
- 服务相对独立,在部署、扩容和运维上都可以做到独立不受其他服务的干扰。
但我们需要知道这样的方式带来的问题,所以在设计成异步通信的时候需要注意如下事宜。
- 用于异步通讯的中间件 Broker成为了关键,需要设计成高可用不丢消息的。另外,因为是分布式的,所以可能很难保证消息的顺序,因此你的设计最好不依赖于消息的顺序。
- 异步通讯会导致业务处理流程不那么直观,因为像接力一样,所以在 Broker 上需要有相关的服务消息跟踪机制,否则出现问题后不容易调试。
- 因为服务间只通过消息交互,所以业务状态最好由一个总控方来管理,这个总控方维护一个业务流程的状态变迁逻辑,以便系统发生故障后知道业务处理到了哪一步,从而可以在故障清除后继续处理。这样的设计常见于银行的对账程序,银行系统会有大量的外部系统通讯,比如跨行的交易、跨企业的交易,等等。所以,为了保证整体数据的一致性,或是避免漏处理及处理错的交易,需要有对账系统,这其实就是那个总控,这也是为什么银行有的交易是 T+1(隔天结算),就是因为要对个账,确保数据是对的。
- 消息传递中,可能有的业务逻辑会有像 TCP 协议那样的 send 和 ACK 机制。比如:A 服务发出一个消息之后,开始等待处理方的
ACK,如果等不到的话,就需要做重传。此时,需要处理方有幂等的处理,即同一件消息无论收到多少次都只处理一次。
2.3 幂等性
1. 接口幂等性
简而言之,就是同样的请求对接口发起的一次调用和多次调用,所产生的结果都是一致的。
下面是一些典型场景:
- 订单创建接口,第一次调用超时了,然后调用方重试了一次。是否会多创建一笔订单?
- 订单创建时,我们需要去扣减库存,这时接口发生了超时,调用方重试了一次。是否会多扣一次库存?
- 当这笔订单开始支付,在支付请求发出之后,在服务端发生了扣钱操作,接口响应超时了,调用方重试了一次。是否会多扣一次钱?
如果不控制幂等,那么:
比如订单提交的过程中,用户点了一次提交,但是由于网络等原因,导致后端处理延时,客户就连 续点击了多次,在没有幂等性的条件下,那么就会造成订单的重复提交。
解决思路:在保存订单的时候,根据生成的系统全局唯一ID(这里订单号+业务类型),并且把该唯一ID 调用 redis 的setnx命令保存起来,在第一次保存的时候,由于redis中没有该key,那么就会 把全局唯一ID 进行设置上,此时订单就会保存成功。
这个时候若出现前端重复点击按钮, 由于第一步已经 setnx上了 就会阻止后面的保存。
即分布式锁的思想
2. mq消息幂等性
第一步:消息生产者向Mq服务端发送消息
第二步:mq 服务端把消息进行落地
第三步:消息服务端向消息生产者发送ack
第四步:消息消费者消费消息
第五步:消费者发送ack
第六步: mq服务将落地消息删除
消息重复发送的原因
为了保障消息的百分之百的投递,我们使用了消息重发-确认机制,导致消息可能被重复发送,由上图可知道,由于网络原因,第三步的上半场ack丢失还是第五步的下半场ack丢失 都会导致消息重复发送
mq服务端(broker)是如何保证幂等性的?
消息队列的服务中,对每一条消息都会生成一个全局唯一的与业务无关的ID(inner_msg_id)。
当mq_server 接受到消息的时候,先根据inner_msg_id 是否需要重复发送给消费端,再决定消息是否落DB ,这样保证每条消息都只会落一次DB。
即broker可以判断inner_msg_id是否重复来判断是否持久化到硬盘里。即通过DB来控制幂等。
消费端如何来做到幂等性的?
还是把对每条消息做生成一个唯一性的ID 通过redis的来setnx命令来保证幂等性。
即通过分布式锁控制幂等。
3.幂等性解决方案
1 分布锁
适用于高并发场景,基于redis实现唯一键的读写,性能快支撑大流量高并发
2 DB自带的唯一索引机制
3.前端控制不让重复提交
上面三种方案并不是互斥的,而是相互结合的。首先前端控制不让重复提交是第一道防线,分布式锁解决高并发场景是第二道防线,当前面两种控制都失效,则DB唯一建控制则是最后一道防线了,他能保证数据的绝对唯一性。
2.4 轮询
1.短轮询
定义:其实就是普通的轮询。指在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客户端的浏览器。如果服务端没有数据,将一直重试请求。
类似于Java中自旋锁方式。
应用场景:传统的web通信模式。后台处理数据,需要一定时间,前端想要知道后端的处理结果,就要不定时的向后端发出请求以获得最新情况。
优点:前后端程序编写比较容易。
缺点:请求中有大半是无用且难于维护,浪费带宽和服务器资源;
响应的结果没有顺序(因为是异步请求,当发送的请求没有返回结果的时候,后面的请求又被发送。而此时如果后面的请求比前面的请 求要先返回结果,那么当前面的请求返回结果数据时已经是过时无效的数据了)。
实例:适于小型应用。
2.长轮询
定义:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
类似于Java中wait于notify模型。
优点:在无消息的情况下不会频繁的请求,耗费资源小。
缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。
实例:WebQQ、Hi网页版、Facebook IM。
3.长轮询和短轮询比较
两者相同点:
可以看出 http 长轮询和 http 短轮询的都会 hold 一段时间;
两者不同点:
间隔发生在服务端还是浏览器端: http 长轮询在服务端会 hold 一段时间, http 短轮询在浏览器端 “hold”一段时间;
总结:可以使用超时机制权衡短轮询和长轮询
2.7 通信协议
1.RESTful
REST可以看着是http协议的一种直接应用,默认基于json作为传输格式,使用简单,学习成本低,效率高,但是安全性较低.
RESTful是一种架构的规范与约束原则,符合这种规范的架构就是RESTful架构。
RESTful大部分协议都是基于HTTP协议的。
@see RESTful 架构详解 https://www.runoob.com/w3cnote/restful-architecture.html
2.webservice
基于xml为格式的传输协议。在安全方面是通过使用XML-Security和XML-Signature两个规范组成了WS-Security来实现安全控制的。
3.RPC
是一种允许分布式应用程序调用网络上不同计算机的可用服务的机制,一般通过tcp协议进行传输。
通常和分布式框架结合使用如dubbo,通常包含了负载均衡,集群容错等高可用,高性能特点但学习和维护难度大。
思考
1.http和tcp的区别?
1、性质不同:
TCP对应与传输层、而HTTP对应于应用层,所以HTTP协议是建立在TCP协议之上的;
2、TCP是网络传输协议, HTTP是超文本传输协议;
TCP是底层协议,定义的是数据传输和连接方式的规范。
HTTP是应用层协议,定义的是传输数据的内容的规范。
3.HTTP是无状态的短链接,而TCP是有状态的长连接;
总结:
Http协议是建立在TCP协议基础之上的。当浏览器需要从服务器 获取网页数据的时候,会发出一次http请求。
Http通过TCP建立起一个到服务器的通道。
当一个网页完成之后,客户端和服务器端之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个页面时,会继续使用这一条已经建立的连接Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件中设定这个时间,
10 长连接和短连接
短连接:
例如普通的web请求,在三次握手之后建立连接,发送数据包并得到服务器返回的结果之后,通过客户端和服务端的四次握手进行关闭断开。
长连接:
区别于短连接,由于三次握手链接及四次握手断开,在请求频繁的情况下,链接请求和断开请求的开销较大,影响效率。采用长连接方式,执行三次握手链接后,不断开链接,保持客户端和服务端通信,直到服务器超时自动断开链接,或者客户端主动断开链接。
三、SOA(Service Oriented Ambiguity)面向服务架构
SOA 的全称是 Service Oriented Architecture,中文翻译为“面向服务的架构”,诞生于上世纪 90 年代,1996 年 Gartner 的两位分析师 Roy W. Schulte 和 Yefim V. Natis 发表了第一个 SOA 的报告。
SOA:它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。我的理解就是在如单体架构中MVC的Service层。只是将各个服务器看成一个整体充当Service层即对外提供服务。
1.使用场景
SOA 更多是在传统企业(例如,制造业、金融业等)落地和推广,在互联网行业并没有大规模地实践和推广。
SOA 出现 的背景是企业内部的 IT 系统重复建设且效率低下,主要体现在:
企业各部门有独立的 IT 系统,比如人力资源系统、财务系统、销售系统,这些系统可能都涉及人员管理,各 IT 系统都需要重复开发人员管理的功能。例如,某个员工离职后,需要分别到上述三个系统中删除员工的权限。
各个独立的 IT 系统可能采购于不同的供应商,实现技术不同,企业自己也不太可能基于这些系统进行重构。
随着业务的发展,复杂度越来越高,更多的流程和业务需要多个 IT 系统合作完成。由于各个独立的 IT 系统没有标准的实现方式(例如,人力资源系统用 Java 开发,对外提供 RPC;而财务系统用 C# 开发,对外提供 SOAP 协议),每次开发新的流程和业务,都需要协调大量的 IT 系统,同时定制开发,效率很低。
SOA 架构是比较高层级的架构设计理念,一般情况下我们可以说某个企业采用了 SOA 的架构来构建 IT 系统,但不会说某个独立的系统采用了 SOA 架构。
例如,某企业采用 SOA 架构,将系统分为“人力资源管理服务”“考勤服务”“财务服务”,但人力资源管理服务本身通常不会再按照 SOA 的架构拆分更多服务,也不会再使用独立的一套 ESB,因为这些系统本身可能就是采购的,ESB 本身也是采购的,如果人力资源系统本身重构为多个子服务,再部署独立的 ESB 系统,成本很高,也没有什么收益。
SOA 解决了传统 IT 系统重复建设和扩展效率低的问题,但其本身也引入了更多的复杂性。SOA 最广为人诟病的就是 ESB,ESB 需要实现与各种系统间的协议转换、数据转换、透明的动态路由等功能。
2.核心架构
为了应对传统 IT 系统存在的问题,SOA 提出了 3 个关键概念。
1.服务
所有业务功能都是一项服务,服务就意味着要对外提供开放的能力,当其他系统需要使用这项功能时,无须定制化开发。
服务可大可小,可简单也可复杂。例如,人力资源管理可以是一项服务,包括人员基本信息管理、请假管理、组织结构管理等功能;而人员基本信息管理也可以作为一项独立的服务,组织结构管理也可以作为一项独立的服务。到底是划分为粗粒度的服务,还是划分为细粒度的服务,需要根据企业的实际情况进行判断。
2.ESB
ESB 的全称是 Enterprise Service Bus,中文翻译为“企业服务总线”。从名字就可以看出,ESB 参考了计算机总线的概念。计算机中的总线将各个不同的设备连接在一起,ESB 将企业中各个不同的服务连接在一起。因为各个独立的服务是异构的,如果没有统一的标准,则各个异构系统对外提供的接口是各式各样的。SOA 使用 ESB 来屏蔽异构系统对外提供各种不同的接口方式,以此来达到服务间高效的互联互通。
例如在新零售行业中,可能存在订单,商品,采购,库存等服务,那么将服务之间彼此联系起来的服务总线就是MQ或者RPC等交互协议中间件。
3.松耦合
松耦合的目的是减少各个服务间的依赖和互相影响。因为采用 SOA 架构后,各个服务是相互独立运行的,甚至都不清楚某个服务到底有多少对其他服务的依赖。如果做不到松耦合,某个服务一升级,依赖它的其他服务全部故障,这样肯定是无法满足业务需求的。
但实际上真正做到松耦合并没有那么容易,要做到完全后向兼容,是一项复杂的任务。
典型的 SOA 架构样例如下
3.微服务与 SOA 的关系
1.拆分粒度维度
整体上来说,SOA 的服务粒度要粗一些,而微服务的服务粒度要细一些。例如,对一个大型零售企业来说,“商品供应链系统”就是一个 SOA 架构中的服务;而如果采用微服务架构,则“商品供应链系统”会被拆分为更多的服务,比如“订单服务”“商品服务”“采购服务”和“库存服务”等更多服务。
又比如对应一个大型在线教育企业来说,可能会拆分为高端ViP的服务系统,普通用户的服务系统,底层基础数据的服务系统等。而高端ViP的服务系统可能又包括积分,视频,做题等相关服务,然后服务之间又通过轻量级的通信协议来彼此连接起来。
即微服务是SOA架构体系中服务的一个子集。
2.服务间交互维度
SOA 采用了 ESB 作为服务间通信的关键组件,负责服务定义、服务路由、消息转换、消息传递,总体上是重量级的实现。微服务推荐使用统一的协议和格式,例如,RESTful 协议、RPC 协议,无须 ESB 这样的重量级实现。
3.应用场景维度
SOA 更加适合于庞大、复杂、异构的企业级系统,这也是 SOA 诞生的背景。这类系统的典型特征就是很多系统已经发展多年,采用不同的企业级技术,有的是内部开发的,有的是外部购买的,无法完全推倒重来或者进行大规模的优化和重构。因为成本和影响太大,只能采用兼容的方式进行处理,而承担兼容任务的就是 ESB。
微服务更加适合于快速、轻量级、基于 Web 的互联网系统,这类系统业务变化快,需要快速尝试、快速交付;同时基本都是基于 Web,虽然开发技术可能差异很大(例如,Java、C++、.NET 等),但对外接口基本都是提供 HTTP RESTful 风格的接口,无须考虑在接口层进行类似 SOA 的 ESB 那样的处理。
综合上述分析,我将 SOA 和微服务对比如下:
分布式难点
1.数据一致性
跨域
什么是跨域
浏览器对于javascript的同源策略的限制,例如a.cn下面的js不能调用b.cn中的js,对象或数据(因为a.cn和b.cn是不同域),所以跨域就出现了.
上面提到的,同域的概念又是什么呢??? 简单的解释就是相同域名,端口相同,协议相同。
如何跨域
1.jsonp跨域
凡是拥有scr这个属性的标签都可以跨域例如
jsonp:
jsonp 全称是JSON with Padding,是为了解决跨域请求资源而产生的解决方案,是一种依靠开发人员创造出的一种非官方跨域数据交互协议。
思考
如何设计一个良好的API
自己总结:
1.入参使用对象的形式接受参数,这样往里面加参数时,不会影响其他接口。对参数作校验,对应批量接口,要限制个数。
2.返回参数也使用对象形式,不是使用map,便于冗余数据
3.对应无法共用的接口,注释需要标明调用业务方,底层的能尽量共用,上层可以渐渐分化。
4.返回状态码约定
5.身份认证避免非法连接
如何设计一个良好的消费API的消费端
自己总结:
1.异常捕获,并打印关键日志。不能因为调用异常,而中断整个服务
2.网路超时
3.条件允许加入熔断机制
参考资料
1.什么是分布式系统,如何学习分布式系统:https://www.cnblogs.com/xybaby/p/7787034.html
2.深入浅出SOA https://www.cnblogs.com/renzhitian/p/6853289.html
3.初步理解一下:SOA, SOAP, Web Service, WSDL等https://www.cnblogs.com/yuxiang204/archive/2012/10/16/2726006.html
4.如何正确理解CAP理论?https://www.jdon.com/bigdata/how-to-understand-cap.html
5.从分布式一致性谈到CAP理论、BASE理论https://www.cnblogs.com/szlbm/p/5588543.html