一、服务器设计目标
1、高性能(High Preormance):是指对大量的并发请求,能做出快速的响应,这就要求我们的服务器能够最大程度发挥机器的性能,使机器在满负荷的情况下,尽可能多的处理并发请求,并且能及时快速的做出响应。
2、高可用(High Availability):指的是服务器能够7x24小时不间断的提供服务,如果服务器出现了故障,也能够快速的转换到备用机,让备用机工作起来,而不需要人工的干预。这叫failover故障转移。
3、伸缩性(Scalability): 指的是服务器具有良好的框架,分层设计、业务分离并且能够灵活的部署。
比如说某个服务器有两个组件A和B,这两个组件既可以部署在同一台机器上面,也可以部署在两台不同的机器上面,这就要求两个组件之间通信不能用本地进程间通信(比如,共享内存),而应该使用像TCP这样的跨机器之间的进程间通信机制。
二、服务器架构介绍
任何网络系统都可以抽象为C/S结构
B/S 架构也是一个C/S 架构
浏览器是Httpc 客户端软件,服务器是Httpd 服务器端
三、一个典型的服务器结构
这里应用服务器和数据库服务器相连
架构:网络IO + 服务器高性能编程技术 + 数据库
1、这里应用服务器要处理大量的客户请求,大量的网络IO,最高效的网络模型是epoll。
2、超出数据库连接数:假如,数据库并发连接数是10个,应用服务器这边有1000个并发请求,将会有990个请求失败。解决的办法是加队列。
添加一层数据库访问层DAL(Data Access Layer),在访问层排队,可以和应用服务器部署在一台,也可以单独部署一台,应用服务器和数据访问层也是用TCP进行通信的,这也是我们提到的服务器具有伸缩性。
DAL创建一些连接放入连接池,下次访问数据库的时候,就不用创建连接,直接从池里面找一个未用的连接,提高响应速度。
DAL具有队列服务 + 连接池
3、超出时限:数据库并发连接数10个,数据库1秒钟之内最多能处理1000个请求,应用服务器这边有10000个并发请求,会出现0-10秒的等待。如果这个系统规定响应时间是5秒钟,那么这个系统就不能处理10000个并发请求了,因为10000个需要10秒钟,后面的请求就会超时,也就是说这个数据库的能力最多能处理5000个并发请求。那也就是说数据库这边出现了瓶颈,这时候应该减轻数据库的压力。
如何降低数据库的压力呢?
(1)尽可能的将业务逻辑挪到应用服务器这边,数据库尽可能的少做业务处理,数据库只是做辅助业务处理,数据库上面的业务处理不应该太复杂,数据库上面处理逻辑进行计算占用CPU,不如应用服务器在操作系统上进行计算,但是这也很有限的降低数据库压力。
(2)增加缓存,将热点数据存至缓存,下次客户访问就可以从缓存取,降低数据库的压力。
缓存的问题:
1、更新(缓存同步):可以有两种手段,1种是time out 缓存是有时效的,如果缓存失效,那么就要重新从数据库中查询, 实时性比较差。第2种方法应用服务器写数据库,DAL 执行完Updata 后更新cache,这种方法能更新缓存实时性比较好。
2、缓存数据大内存不足,将不活跃的数据唤出内存,缓存换页。唤出算法有:FIFO、LRU(least recently used)(最近最少使用)、LFU(least frequently used)最不频繁使用(操作系统都有介绍)
缓存的更新同步和换页都不需要我们实现,都有开源的产品。NoSql 都有这些功能
nosql key / value 存储,存储一些一致性不是很强的数据
分布式缓存:redis、memcached
如果缓存cache服务器部署在应用服务器上,那么其他应用服务器访问就很麻烦,如果单独部署并且是分布式的缓存服务器,那么应用服务器都可以访问这些缓存服务器,这样就可以减轻数据库的负担。
即便有分布式缓存,如果有大量的客户端请求,数据库仍然是瓶颈,有这种场景,对数据库的写操作把数据库锁住了,其他的请求过来需要等待数据库,继续进行优化
数据库读写分离:数据库的读操作大于写操作,那这时候对数据库进行负载均衡,现在大部分数据库都有replication机制,利用这个机制可以实现数据库的负载均衡。
这时候DAL 就复杂了,对于查询操作就访问slave,更新操作访问master,一旦master数据发生了改变,就同步到slave,这个同步机制就是replication机制,通过日志文件进行复制。这样把一个数据库分拆分成多个数据库就是负载均衡。
这时候数据库的并发能力大大提升了,这时候应用服务器出现了压力,那么我们就增加应用服务器
一个应用服务器可以处理多种应用,app1、app2、app3、app4 我们可以把应用进行分割
那么这时候前端的客户端发起大量的业务请求,就需要负载均衡了,我们最好先经过一个任务服务器,这个任务服务器也能够实现负载均衡,如何实现负载均衡呢?
(1)、任务服务器主动分配任务
任务服务器应该能够监控这些应用服务器的状态,在实现应用服务器的时候应该让应用服务器暴露一些接口,可以使用私有协议也可以用http协议,也就是说应用服务器是http server ,任务服务器是http client, 任务服务器去查询这些应用服务器的状态、负载(CPU高、IO高、内存换页高) ,查询到这些信息之后,选取负载最低的服务器分配任务。这里需要一些算法确定负载最低的。
(2)、应用服务器主动到任务服务器获取任务
一个应用服务器处理完任务主动去任务服务器获取任务,这种分配最科学。例如,有两台服务器A有3个任务,B有5个任务,任务服务器按照算法认为A服务器负载轻,就把任务分配给A,但是任务有自己的特征,有可能B5个任务比A的3个任务更快的处理完了,所以处理完主动获取更加的公平。这些都是架构上的逻辑。
任务服务器负载均衡之后,系统就能处理更多的并发请求了,但是任务服务器有可能出现故障 ,这时候任务服务器应该有故障转移,failover机制,不应该有一台,应该有两台甚至多台,一台出现故障,另一台能立刻投入使用,任务服务器之间通过心跳实现。
数据库还可以进一步优化,对一个大型应用来说,数据是非常庞大的,大到一定程度肯定会影响并发,这时候我们应该对数据库进行数据分区(分库分表)
分库是指数据库可以按照一定的逻辑,把表分散到不同的数据库,也叫垂直分区。例如应用有用户表,业务表,基础表,那么就可以分别放到不同的库里。
有三个这样的主从库,这样分区之后呢,DAL也需要改写
实际上我们更加常用的是水平分区,把一个数据库水平切割若干个数据库,每个数据库的表都一样比方说用户表、业务表、基础表,每个数据库都一样,每个表都有10个记录,那么分配给10个数据库每个数据库一条记录,当然DAL到哪个数据库去访问,也需要改写代码。这样只要数据库出现压力,我们就可以横行的扩展数据库。
这时候可能缓存出现瓶颈,我们就增加缓存服务器,应用服务器出现瓶颈就增加应用服务器,任务服务器出现瓶颈就增加任务服务器,哪一个层面出现瓶颈就在哪个层增加服务器,这就是一个分布式服务器架构的思路。
那么刚才所说的这些架构,实际上我们很多时候都可以用一些开源的软件来搭建。那应用服务器也需要用服务器高性能编程技术,这个就需要我们编码来实现了。
服务器高性能的编程技术有四点:
服务器性能四大杀手(这也是服务器高性能编程所需要具备的知识)
1、数据拷贝:我们应该尽可能地减少数据的拷贝,内存的拷贝(缓存机制,从内核拷贝到应用层)
2、环境切换:主要指线程的切换,服务器到底该不该用多线程,单线程好还是多线程好,如果服务器是单核服务器,多线程服务器未必能提高性能(采用状态机编程,效率最佳)。多核服务器多线程能够充分发挥多核服务器的性能。
单核服务器,即便采用多线程,大量的任务,提交到服务器,增加线程间的切换开销效率反而不高
3、内存分配:增加内存池,减少向操作系统申请内存
4、锁竞争:应该通过逻辑,来避免锁的使用,尽可能避免锁的竞争。