大并发服务器开发(实战)

大并发服务器开发(实战)

 

P1: 大并发服务器架构介绍

任何网路系统都可以抽象为C/S结构。

请求超过最大数量限制的时候,需要考虑队列

DAL:数据访问层,有队列服务 + 连接池

 

 

一个典型的服务器结构:

 

 

减少数据库的压力的方法:增加队列服务 + 连接池;主要业务处理放在应用服务器处理,数据库只做辅助的业务处理,可以有限减少数据库的压力;增加缓存(cache)有效减少数据库的压力,如果缓存里面有数据,则不去数据库查询,减少了数据库的压力,但是缓存的更新、同步问题。

 

 

缓存更新(缓存同步)的解决办法:缓存time out (超时),如果缓存失效,就需要重新去数据库查询,然后更新缓存,使得缓存和数据库的信息相同,但是缓存和数据库的信息不同步,实时性差;先将一些热点数据存至缓存,如果要更新数据库的数据,我们直接对数据库的数据进行改写操作(update),然后回到缓存中将相应的信息更新,实时性比较高,也就是一旦数据库中的数据更新了,立即通知前端的缓存更新,实时性比较高,实现起来麻烦一些。

 

缓存换页的定义:内存不够,将不活跃的数据换出内存。

缓存换页的方法:FIFO,LRU(least recently used)(最近最少使用),LFU(least frequently used)(最不频繁使用),在操作系统里面有介绍这些方法。

 

nosql(反sql):基于key-value存储非关系的数据。例如分布式的缓存:redis,memcached,都是开源的软件。

 

如果缓存和APP1部署在一台机器上面,则这个缓存不是全局的缓存,而是局部的缓存,仅仅缓存在APP1上面。假设APP有两台,那么另一台APP2(应用服务器)没办法访问部署在APP1上面的缓存,或者访问比较麻烦;如果缓存是部署在独立的机器上面,而且使用的是分布式缓存机制(有很多机器都有独立的缓存,这些缓存是全局缓存),那么各个APP(应用服务器)都可以访问缓存,减少对服务器的访问,减轻对服务器的负担。如图:

 

 

正在对数据库进行写操作,如果此时业务服务器发来多个读请求,那么数据会锁(读写锁)。所以需要进行数据读写分离

数据库的读操作(查询)比写操作(更新、删除)难得多,需要对数据库进行负载均衡。现在主流的数据库都具有replication机制,这个机制可以使得数据库具有负载均衡。

主从机制:Master主数据库,进行write操作,slave从数据库,进行read操作。当更新、删除操作在主库的时候,信息也要同步到从库。这个同步,就是replication机制,通过一些日志文件进行控制。

 

 

任务服务器的负载均衡实现:

 

方案1:增加一个任务服务器来实现,任务服务器可以监视应用服务器的负载,CPU高、IO高、并发高、内存换页高。因为任务服务器可以监控业务服务器的状态,那么在实现应用服务器的时候,可以暴露一个http协议的接口,让任务服务器查询到应用服务器的CPU高、IO高、并发高、内存换页高等信息,查询到这些信息之后,选取负载最低的服务器分配任务。会有一些算法来确定哪个服务器的负载最低,方案1中,任务服务器主动把任务分配给应用服务器,应用服务器被动的接受任务。那么能不能应用服务器主动地获取任务呢?能。方案2:应用服务器主动到任务服务器接受任务进行处理。好处是:当应用服务器处于空闲状态的时候,主动去任务服务器取任务,这样是更加科学的。更科学,更公平的原因如图:

 

但是方案2也有缺点:如果某个应用服务器只能处理某些特定的应用,那么就会增加任务服务器实现的复杂度。如果不同应用服务器处理的任务相同,那么采用方案2,会更加公平一些。

 

任务服务器有2台或者多台,保证服务器的高可用性,当一个服务器出现故障,可以另一个服务器进行工作。

 

数据库的优化:除了数据读写分离,还可以数据分区(分库和分表),和水平分区。

数据分区,一般称为垂直分区。分库,指数据库可以按照一定的逻辑,把表分散到不同数据。表,有用户表、业务信表、基础信息表。用户表可以组成用户的数据库,业务表可以组成业务数据库,基础信息表组成基础信息的数据库。

更常用的是水平分区,把数据库水平切割为n个数据库,每个数据库都有用户表、业务表、基础信息表,只是把每个表的记录平均分配给各个数据库。比如,用户表的10条记录,可以平均分配给5个数据库,每个数据库有用户表的2条记录。

 

服务器性能四大杀手:第一个杀手,数据拷贝(办法:缓存),需要减少数据拷贝,需要有缓存来解决。第二个杀手,环境切换(办法:理性创建线程),该不该用多线程,单线程好,还是多线程好。如果是单核服务器,采用状态机编程的效率是最高的。大量的任务提交到服务器不能并行地处理,如果使用了多线程,会增加线程间的切换开销,类似操作系统的进程切换。如果是多核服务器,多线程能够充分发挥多核服务器的性能,并且线程不是越多越好,会增加线程上下文切换(环境切换)。第三个杀手,内存分配,增加内存池,减少对内存的分配,减少向操作系统分配内存;第四个杀手,锁竞争,减少锁的竞争。

 

P2:大型网站架构演变过程

Step1:Web动静资源分离

 

Step2:缓存处理:

 

Step3:web server集群+读写分离

负载均衡:

 

Step4:CDN、分布式缓存、分库分表

CDN是内容分发的

垂直分区:

水平分区:

 

Step5:多数据中心+分布式存储与计算

 

P3:第三章,poll

I/O复用,select,poll,epoll,其中epoll的效率最高。

Poll使用基本流程:

 

signal(SIGPIPE, SIG_IGN);

Linux网络编程,第12讲 tcp 11种状态中

如果客户端关闭套接字close,而服务器调用了一次write,服务器会接收一个RST segment(TCP传输层)

如果服务器再次调用了write,这个时候就会产生SIGPIPE信号。

TIME_WAIT状态 对大并发服务器的影响

应尽可能在服务器端避免出现 TIME_WIAT状态

如果服务器端 主动断开连接(先与client调用 close),服务端就会进入TIME_WAIT

协议设计上,应该让客户端主动断开连接,这样就把TIME_WAIT状态分散到大量的客户端。

如果客户端不活跃了,一些客户端不断开连接,这样子就会占用服务器端的连接资源。

服务器端也要有个机制来踢掉不活跃的连 close

 

Nonblocking socket + I/O复用

&*pollfds.begin();

C++ 11

Pollfds.data();

 

数据包:一个数据包,两次read。

Read 可能并没有把connfd所对应的接收缓冲区的数据都读完,那么connfd仍然是活跃的。

我们应该将读到的数据保存在connfd的应用层接收缓冲区

 

Read发送缓冲区满了的话,那么write调用可能并不能把所有的数据都发送出去,而且还需要弄成非阻塞模式,非阻塞套接字。

write发送的时候,我们也应该有一个应用层发送缓冲区,

POLLOUT事件 触发条件, connfd的发送缓冲区不满(可以容纳数据)。

当内存缓冲区的数据在满了的时候,才关注connfd的POLLOUT事件。

忙等待。

poll模型是电平触发(LT)模式,不支持边沿触发(ET)。

epoll的电平触发模式和poll的电平触发模式相同,但是还多了边沿触发模式。

 

accept(2)返回EMFILE的处理方法:

1、调高进程文件描述符数目(治标不治本,因为我们是做大并发)

2、死等(效率也比较低)

3、退出程序(是暂时性的,有点小题大做,,不满足系统7*24小时不间断的服务)

4、关闭监听套接字。那什么时候重新打开呢?(这种方法也不太现实)

5、如果是epoll模型,可以改用edge trigger(边沿触发)。

6、准备一个空闲的文件描述符。(推荐)

 

Cmake

Ubuntu系统:

Sudo apt-get install make

 

P4:epoll

Epoll三个函数:

Epoll_create()

Epoll_ctl()

Epoll_wait()

 

EPOLLIN事件

内核中的接收缓冲区 为空  低电平

内核中的接收缓冲区 不为空 高电平

 

EPOLLOUT事件

内核中的socket发送缓冲区不满    高电平

内核中的socket发送缓冲区满      低电平

 

LT 电平触发

高电平触发

 

ET边沿触发

低电平-》高电平 触发

高电平-》低电平 触发

 

三种I/O复用方式的使用情况:

Poll select:适用于已连接套接字不太大,并且这些套接字非常活跃。

Epoll:适用于一次性遍历返回,活跃的文件描述符。因为epoll内部的实现更复杂,更复杂的代码逻辑,适用于处理大量连接的时候。

 

参考链接:https://www.bilibili.com/video/BV11b411q7zr?from=search&seid=18026742472358864629&spm_id_from=333.337.0.0

 

posted @ 2022-01-06 17:38  白雪儿  Views(104)  Comments(0Edit  收藏  举报