系统架构概念及思想1
分布式系统
特点:
1.系统各组件分布于网络上多个计算机上
2.我们部署的各功能组件彼此之间仅仅通过消息传递来通信,达到协调行动的目的。
满足这两点,你的系统就是分布式系统
分布式系统存在的意义:
向上扩展达到瓶颈:
1.即单机上无论你增加内存,磁盘,CPU个数,其性价比很难再提不上去了。
2.性能达到临界点后,性能将不升反降。
3.单点故障无法避免,故障后一切扩展都将不可用。
基于冯诺依曼的计算机架构,对于当前来说已经无法从单机上得到更好的整体系统性能。摩尔定律说计算机硬件技术会每18个月更新一次,在当今世界也在改变,硬件的提升会逐渐达到提升瓶颈,而数据则会在18个月后翻一倍,所以我们已经进入一个大数据的时代。另外,摩尔定义也向我们说明了一个问题,今天你花多大的价钱买的硬件,在一年半(18个月)后都会落后。
多CPU:
硬件技术进步,让单机计算能力出现了太多空闲,因此多线程编程技术就成为了最佳的提高性价比的方式。
多线程编程模型:
1. 互不通信的线程模型: 这些模型的性能是最好的,执行效率也是最高效的,但实用性可能不高。
2. 基于共享容器协同工作的模型:
A线程运行过程中,需要从B线程哪里获取一个结果,才能继续运行,若此时A线程直接去启动B线程,
这其实是相当麻烦的事,若借助中间容器,这里指队列,A线程通过将请求发给队列,然后,B线程
从队列中获取请求,执行结束后将结果再放回队列中,A线程从队列中获取结果后,就可以继续执行了。
这种就是共享容器模型,它可以实现让多个线程并行协同运行。
为了使这种共享容器模型在多线程并行时,访问数据更加安全,它又分为两种类型:
a. 线程安全
指A线程修改了共享容器中的A数据,而C去访问A数据或修改A数据也一样可以,它们之间都是
互相独立,互不干扰的,这种就称为线程安全的模型。
b. 线程不安全
指A线程修改了共享容器中的A数据,而C线程此时也去修改A数据,就可能导致A数据损坏,这种
就称为线程不安全。 对于线程不安全的类型,通常使用加锁的机制 或 Copy on Write(COW)
的方式来保障数据安全,在使用加锁的机制时,若并发线程数很多时,通常会使用互斥锁。
这种方式中线程之间是需要互相通信的,因此就会造成等待结果的一方将不能继续运行,所以这其实
就会降低多线程并行执行的效率了。
3.通过事件协调的多线程模型
B的某个执行流程依赖于A完成了某种特定操作之后才能进行。那么A就需要在某个特定操作完成之后,
触发一个事件,这个事件可以通知给B,所以B拿到这个事件后,就可以继续运行了,所以在A触发这个
事件之前,B就只能处于等待事件的状态。
多线程 和 多进程模型的区别:
线程之间是可共享父进程内部的很多资源,如:父进程打开的文件句柄,内部信号等等。
进程之间是互相隔离的,它们仅能通过进程间通信模式进行交互通信,所以多进程模型比多线程模型,在资源控制方面更为简单。
这些模型的出现,主要是为了使用多CPU,因为,单个CPU已经不能通过提升频率来提升其性能,目前芯片厂商都是通过增加CPU的核心数,借助于多线程编程,来解决单机上面性能的提升问题。但多线程编程又非常复杂,若程序员不能很好的利用多核心来编程,对于多核来说效益是很小的。
网络I/O:
1.通过阻塞模型,即一个请求过来,在处理它时,其它请求都被阻塞等待第一个请求处理完才能被接入进来进行处理,所以这种模型目前已不在使用。
2.多进程模型,即每个进程响应一个请求;
3.多线程,多进程,即每个进程生成多个线程,每个线程响应一个用户请求。
4.多线程,每个线程直接响应多个请求。
但是无论是那种方式,每一个请求进来都需要有一个对应的套接字。
基于socket实现网络通信开发,其实现方式主要有三类:
BIO: Blocking IO,即阻塞式IO:一个Socket套接字需要一个进程或一个线程来处理,那么连接的建立读数据写数据的过程都有可能产生阻塞,这种机制就是每进程 或 每线程响应一个请求的模式。
缺点:每个连接都要占用一个套接字. 【注:套接字有两种:连接和监听套接字】
NIO: Nonblocking IO,非阻塞I/O,它是基于事件驱动(Linux是epoll实现事件驱动的.)的思想,采用Reactor模式,实现响应。
优点: 一个套接字分配个一个线程,而一个线程可以处理多个套接字相关的工作。
AIO:异步IO模式,它也是基于事件驱动的思想,但它采用Proactor模式
如何把应用从单机扩展到多机?
输入、输出设备如何变化?
控制器如何变化?
实现的模式:
透明代理: 即用户访问时,已经给我们响应的就是服务器,其实可能是以下这些,这种就属于对用户透明的.
而且中间的转发控制器就成为网络的中心。如下:
LVS的NAT模型,
Haproxy, Nginx的反向代理
旁路代理
LVS的DR模型
名称服务模式:
DNS: 用户仅第一次访问时,需要查询,DNS通过名称给客户端反馈不同的解析IP,实现调度控制。
规则服务模式:
规则服务器:在数据库服务中,当数据库很大时,需要分库,分表时,就需要规则服务器在中间做为一个向导,
告诉第一次访问数据的客户端,你需要的数据是分散在多个主机上,还是一个节点上。
Master/Slave机制:
Master能够处理所有请求,Slave仅做为运算器分担部分读运算任务,Master需要控制给Slave发送数据,
所以Master也是一种控制器。
运算器的变化: 【暂时理解不深】
存储器的变化: 【暂时理解不深】
分布式系统实现的难点:
1.缺乏全局时钟: 在单机中,CPU的每秒中能产生很多个时钟频率,但是其它部件的功能频率相对与CPU来说是极慢的,为了达到步调一致,单机内部通过中断等机制实现协同工作。
2.面对故障时的独立性
3.处理单点故障
4.事务处理:
ACID
2PC(两段式提交)、BASE、CAP、Paxos(帕克索斯:希腊神话中的天马)
大型网站站点的架构演进方式:
LAMT, LNMT(Tomcat)
应用从资源占用的角度分两类:
CPU Bound: CPU密集型,ApplicationServer对CPU占用多。
IO Bound: IO密集型, 数据库对IO占用多。
网站从最开始一台主机上运行一个Nginx代理 + Tomcat应用服务器 + MySQL数据库.
当网站访问量增大时,首先出现告警的可能是MySQL数据库,因为磁盘I/O天生是系统的瓶颈,因此数据库先被分离出来,这样数据库压力就变小了。
引入MySQL主从面临的问题:
1. 数据复制的问题
2. 应用选择数据源的问题
接着网站访问量继续增大,Tomcat这类应用服务器,Nginx代理等都属于CPU密集型,它们在单机上出现了CPU资源争用的问题,这仅是小问题,更重要的是应用服务器出现了数据访问的热区,基本上会遵循二八定律,这时就必须分离,并且增加应用服务器,你的系统就变成了两台应用服务器,这时前端必须有一个反向代理,所以加上MySQL就是四台主机.但应用服务器分开后,就需要用户访问的会话保存问题,有以下三种解决方案:
Session sticky:会话粘性,存在单点故障,一定其中一台Server宕机,会话将丢失。
ip based:
cookie based:它带来的额外问题是,带宽占用将非常巨大,若一个cookie仅50字节,但是若一天一亿个请求,就有一亿个50字节,有多大?
Session replication: 会话复制,不适合大规模使用,因为一方面会消耗Server的内存,另一方会带来大量的内网同步Session的流量,影响整体网络环境。
Session server:通过Memcached来集中存储用户会话,扩展性好,适合大规模扩展。
若考虑到网站在一年内可能出现几何增长的可能,如:从200并发到过万的并发,这时使用前两种会话保存就不合适了,就必须使用第三种方案。
引入搜索引擎实现全文搜索:
网站演变到这里,我们需要知道问题的核心在哪里,数据库中的数据最终是要让用户可以查询到的,因为 用户不能在你的网站一个一个网页去翻,然后去找到自己需要的商品,通常都是通过搜索定位到他们需要的商品,那这时仅靠MySQL很难实现了,因为我们的电商网站的数据库是需要支持事务,但MyISAM存储引擎支持全文索引,但不支持事务,所以它并非最佳途径,我们就需要另拿一台服务器专门安装MySQL使用支持全文索引的存储引擎,将现有MySQL的Slave节点上将数据都读出来,在它上面做全文索引,然后,前端自己开发的索引引擎,通过搜索全文索引数据库,来响应用户的搜索请求,所以Slave读取Master上数据的及时性就显得很重要了,因为新上架是商品能否让用户看到,取决于搜索库中有没有从Slave库中读到新商品数据。
引入缓存
当我们的网站中应用服务器的压力主要集中在对很多静态内容的处理上时,那我们就需要使用动静分离技术,但比它更使用的此时应该是缓存,在负载均衡器后面第一级,先引入缓存,把不变化的静态内容直接缓存在应用服务器的前端缓存服务器中,甚至还可以使用AJAX技术实现缓存网页中的一部分数据,另外一部分变化的数据,到后端去计算后得到,比如一个淘宝页面,第一用户看商品页面和第二个用户看的商品页面是一样的,这时就可以缓存这个页面,但有可能第二个用户看时,这个商品已经卖出去了2个,所以仅需要从后端应用服务器上获取这部分变化的库存量信息就可以了,这样可以更大程度减少后端的压力。
引入缓存要考虑两个方面的问题:
1.页面缓存
varnish, squid
2.数据库缓存
因为数据库节点可能也会逐渐增加,而查询缓存仅在数据库本地有效,为了提高命中率,增加数据库缓存也是必须的。
对于数据库缓存,要使用Key-Value类型的存储,而Memcached是其中最具代表性的。
若引入Memcached,将会引入新问题:
a. 需要自行开发程序调用Memcached的API实现存储。
b. 需要自行管理其数据有效期。
注意:
引入缓存,可能不会只有一台,或者说会逐渐增加成多台,这时我们就可以称这种缓存架构为分布式缓存,在分布式缓存中,要提高数据的命中率,就必须使用哈希算法,来提高命中率。
当网站规模继续增大时,接下来面临压力的一定就是数据库的写库。
对于数据库写库的分担,只有数据库拆分这一条路.
垂直拆分:把数据库中不同的业务的数据拆分到不同的数据库中,它缓解的是读压力.
假如以电商网站来说:将它的用户表,交易表,商品表分别分到三个库,但我们知道,
我们到淘宝上买的东西,我们会去搜索用户吗?会搜索其他人的交易信息吗?
不会,甚至也不允许,所以商品表被读的压力就会很大,因此它是一定要做主从的,
其它库可以从数据安全性角度来说,也可以做主从。
由于数据库级别拆分成多个了,这时我们访问数据库就必须要知道我们访问的数据在哪里
所以前端应该还要有规则服务器来告知前端应用数据在那台后端数据库上。
静态内容: 如用户证书,用户生物信息,交易历史记录,商品图片等
FTP上传: 如: 商品图片,用户身份证图片,文档等
水平拆分:把一个单独的表中的数据拆分到多个不同的数据库服务器中,它缓解的是写压力。
假如马上要到剁手节,将产生大量用户产生交易信息,交易表可能受不了,那怎么办?
这就需要分担对交易表的写压力,对交易表我们可以根据用户年龄来拆分,将年轻群体
中消费能力弱的18-20的分到一张表,消费能力较强的21-25分一张表,26-29分一张表,
等等,来拆分表,但是表拆分完了后,该怎么去访问?前端应用怎么知道我要访问的数据
在哪里存储?由此规则服务就更加重要了。
引入NoSQL: 非关系型数据库
文档数据库
列式数据库
....
DFS: 非结构化数据
TFS, MogileFS: 适用于海量小文件
HDFS, GFS: 少量大文件
CDN的使用:
越向后,系统的访问量越大,这时内部整个机房的扩容若在继续扩大,不在是解决问题最有效的手段,而且主机数量也总有上限,但是前端的压力已经让负载和缓存吃不消了,在这时最佳的手段就是引入CDN,目前国内比较知名的CDN厂家有 BAT,蓝汛等。 CDN它能够实现让用户直接在家门口的缓存服务器上获取要我们的网站数据,这样就有效的分散了到达我们主站的流量压力,当用户访问我们网站时,智能DNS通常会,根据用户所在地区为用户解析域名,它会依据缓存服务器距离用户地理位置最近,或带宽最大等信息,做智能判断并返回用户一个域名,这个域名通常是一个负载均衡器的域名,用户进行二次解析后,得到距离自己最近的缓存集群,该缓存集群的负载均衡器将用户请求接入进来,在后端缓存服务器上查询,若命中,则直接返回数据给用户,若未命中,则向最近的缓存节点查询,无数据则由该缓存节点,向我们网站发起请求,网站响应该请求时,先查询本地缓存,命中则直接返回,未命中则继续向下,到应用服务器,由应用服务器处理此请求,并通过查询数据库等一系列业务逻辑处理,返回结果给CDN的缓存节点,由该节点再向用户返回数据。
应用拆分:
根据业务特性拆分
根据用户拆分
如: 用户注册应用
用户登录应用
用户信息维护应用
根据对底层应用的调用进行拆分
异步: 解耦
消息中间件:
它是协调多个应用之间,通过消息传递的方式来实现其功能协调的机制,它的整个工作过程是
通过异步方式实现的。所以它在分布式系统中,是消息接收和发送的基础性软件。
它最大的特点就是:异步和解耦。
MOM:Message-oriented middleware.
现有的比较知名的消息中间件:
RabbitMQ, ActiveMQ, ZeroMQ(ZMQ)
注: 它们仅能工作于它们支持的模型中。
模块化:
服务化:
当网站的结构发展到服务化后,每个层都可能出现大量重复代码,但这是不可避免的。
一旦架构进入这样的级别,无论是垂直还是水平拆分都会面临ACID被打破的问题,此时只有两种选择,放弃事务 或 引入分布式事务!
在进行Join查询时,也将变得异常困难,原来依赖与外键实现的约束将无从保证等等问题,都将面临巨大的挑战。
面对这些挑战,我们唯一能做到就是从理论出发,结合实际去构建适合自己实际需求的解决方案。