面试问题记录

高并发环境下,怎么提升服务器的一个性能?

(1)优化套接字缓冲区

如果服务器的网络套接字缓冲区太小,就会导致应用程序读写多次才能将数据处理完,这会大大影响我们程序的性能。如果网络套接字缓冲区设置的足够大,从一定程度上能够提升我们程序的性能。

(2)优化TCP连接

TCP的连接需要经过“三次握手”和“四次挥手”的,还要经过慢启动、滑动窗口、粘包算法等支持可靠性传输的一系列技术支持。虽然,这些能够保证TCP协议的可靠性,但有时这会影响我们程序的性能。

  • 关闭粘包算法(纳格尔算法):如果用户对于请求的耗时很敏感,我们就需要在TCP套接字上添加tcp_nodelay参数来关闭粘包算法,以便数据包能够立刻发送出去。此时,我们也可以设置net.ipv4.tcp_syncookies的参数值为1。

  • 增大线程池和数据库连接池的大小。

  • 避免重复发送数据包。

  • 增大服务器文件描述符数量:在Linux操作系统中,一个网络连接也会占用一个文件描述符,连接越多,占用的文件描述符也就越多。如果文件描述符设置的比较小,也会影响我们服务器的性能。此时,我们就需要增大服务器文件描述符的数量。

  • 避免TIME_WAIT过多

(3)、数据库集群,读写分离
(4)、使用缓存机制

为什么使用数据库?

使用数据库主要是实现登录的功能。

linux中目录、文件、文件夹的查询?文件内容的关键字查询?

文件、文件夹的查询

(1)find命令

find   【查找路径】 -type  f/d   -name   "文件名"
f:表示仅查找文件
d:表示仅查找文件夹
【查找路径】:不写就默认查询所有文件

(2)locate命令

locate  /home/ect/*conf*   查找文件或者文件夹

(3)grep命令

grep命令主要过滤给定文本和文件内容,但是我们可以使用它来查找文件和文件夹

ls -R -l | grep 文件名         递归使用ls命令,并grep我们要查找的文件和文件夹
查找文件中的关键字

1)、如果是用vi或vim打开文件了,查找方法是:

在命令行模式下输入"/关键字"

2)、如果文件是在当前文件夹目录下,且没有打开,查找方法是:

cat 文件名 | grep "关键字"

3)、 如果是在某个目录下的多个文件中查找内容中包含的关键字,查找方法是:

grep -r "关键字" 目录

说一说数据库的左连接、右连接?

索引用过吗,索引的底层数据结构?为什么使用B+树?

因为B树不管叶子节点还是非叶子节点,都会保存数据,这样导致存储非叶子节点的页面所能存储的指针数量就会变少,导致IO操作变多,查询性能变低。

B树是专门为外部存储器设计的,如磁盘,它对于读取和写入大块数据有良好的性能,所以一般被用在文件系统及数据库中。

B树是一棵自平衡的搜索树,它类似普通的平衡二叉树,不同的一点是B树允许每个节点有更多的子节点。

B树有如下特点:

  1. 所有键值分布在整颗树中(索引值和具体data都在每个节点里);

  2. 任何一个关键字出现且只出现在一个结点中;

  3. 搜索有可能在非叶子结点结束(最好情况O(1)就能找到数据);

  4. 在关键字全集内做一次查找,性能逼近二分查找;

先来看看为什么会出现B树这类数据结构。

传统用来搜索的平衡二叉树有很多,如 AVL 树,红黑树等。这些树在一般情况下查询性能非常好,但当数据非常大的时候它们就无能为力了原因当数据量非常大时,内存不够用,大部分数据只能存放在磁盘上,只有需要的数据才加载到内存中。一般而言内存访问的时间约为 50 ns,而磁盘在 10 ms 左右。速度相差了近 5 个数量级,磁盘读取时间远远超过了数据在内存中比较的时间。这说明程序大部分时间会阻塞在磁盘 IO 上。那么我们如何提高程序性能?减少磁盘 IO 次数,像 AVL 树,红黑树这类平衡二叉树从设计上无法“迎合”磁盘。

平衡二叉树是通过旋转来保持平衡的,而旋转是对整棵树的操作若部分加载到内存中则无法完成旋转操作。其次平衡二叉树的高度相对较大为 log n(底数为2),这样逻辑上很近的节点实际可能非常远,无法很好的利用磁盘预读(局部性原理),所以这类平衡二叉树在数据库和文件系统上的选择就被 pass 了。

B树的每个节点,都是存多个值的,不像二叉树那样,一个节点就一个值,B树把每个节点都给了一点的范围区间,区间更多的情况下,搜索也就更快了,比如:有1-100个数,二叉树一次只能分两个范围,0-50和51-100,而B树,分成4个范围 1-25, 25-50,51-75,76-100一次就能筛选走四分之三的数据。所以作为多叉树的B树是更快的B+ 树

B+树概述

B+树是B-树的变体,也是一种多路搜索树, 它与 B- 树的不同之处在于:

  1. 所有关键字存储在叶子节点出现,内部节点(非叶子节点) 并不存储真正的 data

  2. 为所有叶子结点增加了一个链指针

因为内节点并不存储 data,所以一般B+树的叶节点和内节点大小不同,而B树的每个节点大小一般是相同的,为一页。

为了增加 区间访问性,一般会对B+树做一些优化。

B树和B+树的区别

1.B+树内节点不存储数据,所有 data 存储在叶节点导致查询时间复杂度固定为 log n。而B-树查询时间复杂度不固定,与 key 在树中的位置有关,最好为O(1)。

2. B+树叶节点两两相连可大大增加区间访问性,可使用在范围查询等,而B树每个节点 key 和 data 在一起,则无法区间查找。

根据空间局部性原理:如果一个存储器的某个位置被访问,那么将它附近的位置也会被访问。

B+树可以很好的利用局部性原理,若我们访问节点 key为 50,则 key 为 55、60、62 的节点将来也可能被访问,我们可以利用磁盘预读原理提前将这些数据读入内存,减少了磁盘 IO 的次数。当然B+树也能够很好的完成范围查询。比如查询 key 值在 50-70 之间的节点。

3.B+树更适合外部存储。由于内节点无 data 域,每个节点能索引的范围更大更精确

这个很好理解,由于B-树节点内部每个 key 都带着 data 域,而B+树节点只存储 key 的副本,真实的 key 和 data 域都在叶子节点存储。前面说过磁盘是分 block 的,一次磁盘 IO 会读取若干个 block,具体和操作系统有关,那么由于磁盘 IO 数据大小是固定的,在一次 IO 中,单个元素越小,量就越大。这就意味着B+树单次磁盘 IO 的信息量大于B-树,从这点来看B+树相对B-树磁盘 IO 次数少。

点评:由于B树的节点都存了key和data,而B+树只有叶子节点存data,非叶子节点都只是索引值,没有实际的数据,这就时B+树在一次IO里面,能读出的索引值更多。从而减少查询时候需要的IO次数!

InnoDB中主键索引B+树是如何组织数据、查询数据的,我们总结一下:

1、InnoDB存储引擎的最小存储单元是页(每页的大小是16K),页可以用于存放数据也可以用于存放键值+指针,在B+树中叶子节点存放数据,非叶子节点存放键值+指针。

2、索引组织表通过非叶子节点的二分查找法以及指针确定数据在哪个页中,进而在去数据页中查找到需要的数据;

在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。

在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据

介绍一下IP协议?

IP协议是TCP/IP协议族的核心协议,也是socket网络编程的基础之一。

IP协议是TCP/IP协议族的基石,它为上层提供无状态、无连接、不可靠的服务

IP协议的主要功能:在相互连接的网络之间传递IP数据报

一个类只含有一个int,多大,含有virtual多少;

在32位环境下,只有一个int的类对象大小为4字节,加上virtual后大小为8字节

在64位环境下,只有一个int的类对象为4字节,加上virtual后为16字节

主函数main(argc,**argv)中两个参数的用法

main函数中的第一个参数argc代表的是向main函数传递的参数个数,实际上它要比你在命令行里输入的数据多一个,因为第一个参数它保存了该程序的路径名, 也就是说如果你向命令行中输入2个数,则argc 实际上等于3, 第二个参数 argv保存的命令面板中的输入的参数值,第一个值是该应用程序的路径名,后面依次是输入的数据的值,注意的是最后一个值是null

C++自定义比较函数?

算法:sort,容器:priority_queue、map、set

在使用STL中很多涉及比较的函数(比如:sortpriority_queuemapset)的时候,我们都可以自定义比较函数,具体来说大致有下面几种方法:

  1. 重载运算符

  2. 重写函数对象,也就是仿函数,使用时要加括号

  3. 函数指针

  4. lambda表达式

怎样使用gdb调试段错误?

产生段错误就是访问了错误的内存段,一般是没有权限,或者根本就不存在对应的物理内存,尤其常见的是访问0地址.

在编程中以下几类做法容易导致段错误,基本是是错误地使用指针引起的1)访问系统数据区,尤其是往 系统保护的内存地址写数据最常见就是给一个指针以0地址2)内存越界(数组越界,变量类型不一致等) 访问到不属于你的内存区域

用gdb和core dumped快速定位到错误位置

一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB中)。

默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。

开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件

1.core文件是内核生成的,那某一个进程因为段错误而崩溃的时候的内存映像很大,那必然会生成一个很大的core文件,我们可以使用umilit -a显示当前shell下的各种软硬件的上限。

我们也可以通过ulimit命令来设置生成core文件的大小,

例如:

(1). ulimit -c unlimited,这里就是设置生成的core文件无大小限制。

(2). ulimit -c [size],表示设置生成的core文件的最大上限为size。

使用gdb和core dumped调试该文件

gdb 程序名 core.xxx,在进入gdb之后可以使用where查找出错的位置也可以使用core-file core.xxx来查找错误的位置。

  • 使用core-file core.xxx的形式定位错误

  • 在gdb状态下使用where来定位错误

编程题

随机输出0-99,不能有重复数值

思路:先预定义一个数组,第一次使用rand()函数获取0-99中的随机下标,将下标处的值与nums[99]进行交换,下一次获取0-98的随机下标,并将下标处的值与nums[98]进行交换。。。。。

class Solution{
public:
    vector<int> slove(vector<int>nums){
        for(int i=0;i<99;i++){
            int index=rand()%(100-i);
            swap(nums[i],nums[100-1-i]);
        }
        return nums;
    }
}

数据库的锁机制?怎么加锁?

数据库查询,一个表,有三列,分别为姓名,科目,成绩,求一个人的总成绩,输出总成绩和姓名?

vector与list的区别?

Http是基于什么传输层协议?TCP

HTTP无状态什么意思?采用什么方法解决?

什么是负载均衡?

当一台服务器的性能达到极限时,我们可以使用服务器集群来提高网站的整体性能。那么,在服务器集群中,需要有一台服务器充当调度者的角色,用户的所有请求都会首先由它接收,调度者再根据每台服务器的负载情况将请求分配给某一台后端服务器去处理。

​ 那么在这个过程中,调度者如何合理分配任务,保证所有后端服务器都将性能充分发挥,从而保持服务器集群的整体性能最优,这就是负载均衡问题。

负载均衡分类

负载均衡可以根据网络协议的层数进行分类,我们这里以ISO模型为准,从下到上分为:

物理层,数据链路层,网络层,传输层,会话层,表示层,应用层。

当客户端发起请求,会经过层层的封装,发给服务器,服务器收到请求后经过层层的解析,获取到对应的内容。

二层负载均衡

​ 二层负载均衡是基于数据链路层的负载均衡,即让负载均衡服务器和业务服务器绑定同一个虚拟IP(即VIP),客户端直接通过这个VIP进行请求,那么如何区分相同IP下的不同机器呢?没错,通过MAC物理地址,每台机器的MAC物理地址都不一样,当负载均衡服务器接收到请求之后,通过改写HTTP报文中以太网首部的MAC地址,按照某种算法将请求转发到目标机器上,实现负载均衡。

​ 这种方式负载方式虽然控制粒度比较粗,但是优点是负载均衡服务器的压力会比较小,负载均衡服务器只负责请求的进入,不负责请求的响应(响应是有后端业务服务器直接响应给客户端),吞吐量会比较高。

三层负载均衡

​ 三层负载均衡是基于网络层的负载均衡,通俗的说就是按照不同机器不同IP地址进行转发请求到不同的机器上。

这种方式虽然比二层负载多了一层,但从控制的颗粒度上看,并没有比二层负载均衡更有优势,并且,由于请求的进出都要经过负载均衡服务器,会对其造成比较大的压力,性能也比二层负载均衡要差。

四层负载均衡

​ 四层负载均衡是基于传输层的负载均衡,传输层的代表协议就是TCP/UDP协议,除了包含IP之外,还有区分了端口号,通俗的说就是基于IP+端口号进行请求的转发。相对于上面两种,控制力度缩小到了端口,可以针对同一机器上的不用服务进行负载。

这一层以LVS为代表。

七层负载均衡

七层负载均衡是基于应用层的负载均衡,应用层的代表协议有HTTP,DNS等,可以根据请求的url进行转发负载,比起四层负载,会更加的灵活,所控制到的粒度也是最细的,使得整个网络更"智能化"。例如访问一个网站的用户流量,可以通过七层的方式,将对图片类的请求转发到特定的图片服务器并可以使用缓存技术;将对文字类的请求可以转发到特定的文字服务器并可以使用压缩技术。可以说功能是非常强大的负载。

这一层以Nginx为代表。

在普通的应用架构中,使用Nginx完全可以满足需求,对于一些大型应用,一般会采用DNS+LVS+Nginx的方式进行多层次负债均衡,以上这些说明都是基于软件层面的负载均衡,在一些超大型的应用中,还会在前面多加一层物理负载均衡,比如知名的F5。

负载均衡算法

负载均衡算法分为两类:

一种是静态负载均衡,一种是动态负载均衡。

静态均衡算法:

1、轮询法

将请求按顺序轮流地分配到每个节点上,不关心每个节点实际的连接数和当前的系统负载。

优点:简单高效,易于水平扩展,每个节点满足字面意义上的均衡;

缺点:没有考虑机器的性能问题,根据木桶最短木板理论,集群性能瓶颈更多的会受性能差的服务器影响。

2、随机法

将请求随机分配到各个节点。由概率统计理论得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配,也就是轮询的结果。

优缺点和轮询相似。

3、源地址哈希法

原理:源地址哈希的思想是根据客户端的IP地址,通过哈希函数计算得到一个数值,用该数值对服务器节点数进行取模,得到的结果便是要访问节点序号。

采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会落到到同一台服务器进行访问。

优点:相同的IP每次落在同一个节点,可以人为干预客户端请求方向,例如灰度发布;

缺点:如果某个节点出现故障,会导致这个节点上的客户端无法使用,无法保证高可用。当某一用户成为热点用户,那么会有巨大的流量涌向这个节点,导致冷热分布不均衡,无法有效利用起集群的性能。所以当热点事件出现时,一般会将源地址哈希法切换成轮询法。

4、加权轮询法

​ 不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同给配置高、负载低的机器配置更高的权重,让其处理更多的请求;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求 顺序且按照权重分配到后端。

加权轮询算法要生成一个服务器序列,该序列中包含n个服务器。n是所有服务器的权重之和。在该序列中,每个服务器的出现的次数,等于其权重值。并且,生成的序列中,服务器的分布应该尽可能的均匀。比如序列{a, a, a, a, a, b, c}中,前五个请求都会分配给服务器a,这就是一种不均匀的分配方法,更好的序列应该是:{a, a, b, a, c, a, a}。

加权轮询算法使用场景是建立在每个节点存储的数据都是相同的前提。所以,每次读数据的请求,访问任意一个节点都能得到结果。

但是,加权轮询算法是无法应对「分布式系统(数据分片的系统)」的,因为分布式系统中,每个节点存储的数据是不同的。

优点:可以将不同机器的性能问题纳入到考量范围,集群性能最优最大化;

缺点:生产环境复杂多变,服务器抗压能力也无法精确估算,静态算法导致无法实时动态调整节点权重,只能粗糙优化。

5、加权随机法

与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。

6、键值范围法

根据键的范围进行负载均衡比如0到10万的用户请求走第一个节点服务器,10万到20万的用户请求走第二个节点服务器……以此类推。

优点:容易水平扩展,随着用户量增加,可以增加节点而不影响旧数据;

缺点:容易负债不均衡,比如新注册的用户活跃度高,旧用户活跃度低,那么压力就全在新增的服务节点上,旧服务节点性能浪费。而且也容易单点故障,无法满足高可用。

动态均衡算法:

1、最小连接数法

根据每个节点当前的连接情况,动态地选取其中当前积压连接数最少的一个节点处理当前请求,尽可能地提高后端服务的利用效率,将请求合理地分流到每一台服务器。俗称闲的人不能闲着,大家一起动起来。

优点:动态,根据节点状况实时变化;

缺点:提高了复杂度,每次连接断开需要进行计数;

实现:将连接数的倒数当权重值,连接数越多权值越小,每次选择权值最大的节点连接。

2、最快响应速度法

根据请求的响应时间,来动态调整每个节点的权重,将响应速度快的服务节点分配更多的请求,响应速度慢的服务节点分配更少的请求,俗称能者多劳,扶贫救弱。

优点:动态,实时变化,控制的粒度更细,跟灵敏;

缺点:复杂度更高,每次需要计算请求的响应速度;

实现:可以根据响应时间进行打分,计算权重。

3、观察模式法

观察者模式是综合了最小连接数和最快响应度,同时考量这两个指标数,进行一个权重的分配。

负载均衡的四种实现方式
1、HTTP重定向实现负载均衡
1.1 过程描述

​ 当用户向服务器发起请求时,请求首先被集群调度者截获;调度者根据某种分配策略,选择一台服务器,并将选中的服务器的IP地址封装在HTTP响应消息头部的Location字段中,并将响应消息的状态码设为302,最后将这个响应消息返回给浏览器。

​ 当浏览器收到响应消息后,解析Location字段,并向该URL发起请求,然后指定的服务器处理该用户的请求,最后将结果返回给用户。

​ 在使用HTTP重定向来实现服务器集群负载均衡的过程中,需要一台服务器作为请求调度者。用户的一项操作需要发起两次HTTP请求,一次向调度服务器发送请求,获取后端服务器的IP,第二次向后端服务器发送请求,获取处理结果。

1.2 调度策略

​ 调度服务器收到用户的请求后,究竟选择哪台后端服务器处理请求,这由调度服务器所使用的调度策略决定。

随机分配策略 当调度服务器收到用户请求后,可以随机决定使用哪台后端服务器,然后将该服务器的IP封装在HTTP响应消息的Location属性中,返回给浏览器即可。

轮询策略(RR) 调度服务器需要维护一个值,用于记录上次分配的后端服务器的IP。那么当新的请求到来时,调度者将请求依次分配给下一台服务器。

由于轮询策略需要调度者维护一个值用于记录上次分配的服务器IP,因此需要额外的开销;此外,由于这个值属于互斥资源,那么当多个请求同时到来时,为了避免线程的安全问题,因此需要锁定互斥资源,从而降低了性能。而随机分配策略不需要维护额外的值,也就不存在线程安全问题,因此性能比轮询要高。

1.3 优缺点分析

​ 采用HTTP重定向来实现服务器集群的负载均衡实现起来较为容易,逻辑比较简单,但缺点也较为明显。

​ 在HTTP重定向方法中,调度服务器只在客户端第一次向网站发起请求的时候起作用。当调度服务器向浏览器返回响应信息后,客户端此后的操作都基于新的URL进行的(也就是后端服务器),此后浏览器就不会与调度服务器产生关系,进而会产生如下几个问题

  • 由于不同用户的访问时间、访问页面深度有所不同,从而每个用户对各自的后端服务器所造成的压力也不同。而调度服务器在调度时,无法知道当前用户将会对服务器造成多大的压力,因此这种方式无法实现真正意义上的负载均衡,只不过是把请求次数平均分配给每台服务器罢了。

  • 若分配给该用户的后端服务器出现故障,并且如果页面被浏览器缓存,那么当用户再次访问网站时,请求都会发给出现故障的服务器,从而导致访问失败。

2、DNS负载均衡
2.1 DNS是什么?

​ 在了解DNS负载均衡之前,我们首先需要了解DNS域名解析的过程。

我们知道,数据包采用IP地址在网络中传播,而为了方便用户记忆,我们使用域名来访问网站。那么,我们通过域名访问网站之前,首先需要将域名解析成IP地址,这个工作是由DNS完成的,也就是域名服务器。

我们提交的请求不会直接发送给想要访问的网站,而是首先发给域名服务器,它会帮我们把域名解析成IP地址并返回给我们。我们收到IP之后才会向该IP发起请求。

那么,DNS服务器有一个天然的优势,如果一个域名指向了多个IP地址,那么每次进行域名解析时,DNS只要选一个IP返回给用户,就能够实现服务器集群的负载均衡。

2.2 具体做法

首先需要将我们的域名指向多个后端服务器(将一个域名解析到多个IP上)再设置一下调度策略,那么我们的准备工作就完成了,接下来的负载均衡就完全由DNS服务器来实现。

当用户向我们的域名发起请求时,DNS服务器会自动地根据我们事先设定好的调度策略选一个合适的IP返回给用户,用户再向该IP发起请求。

2.3 调度策略

​ 一般DNS提供商会提供一些调度策略供我们选择,如随机分配轮询根据请求者的地域分配离他最近的服务器

2.4 优缺点分析

DNS负载均衡最大的优点就是配置简单。服务器集群的调度工作完全由DNS服务器承担,那么我们就可以把精力放在后端服务器上,保证他们的稳定性与吞吐量。而且完全不用担心DNS服务器的性能,即便是使用了轮询策略,它的吞吐率依然卓越。

此外,DNS负载均衡具有较强了扩展性,你完全可以为一个域名解析较多的IP,而且不用担心性能问题。

但是,由于把集群调度权交给了DNS服务器,从而我们没办法随心所欲地控制调度者,没办法定制调度策略。

DNS服务器也没办法了解每台服务器的负载情况,因此没办法实现真正意义上的负载均衡它和HTTP重定向一样,只不过把所有请求平均分配给后端服务器罢了。

此外,当我们发现某一台后端服务器发生故障时,即使我们立即将该服务器从域名解析中去除,但由于DNS服务器会有缓存,该IP仍然会在DNS中保留一段时间,那么就会导致一部分用户无法正常访问网站。这是一个致命的问题!好在这个问题可以用动态DNS来解决。

2.5 动态DNS

动态DNS能够让我们通过程序动态修改DNS服务器中的域名解析。从而当我们的监控程序发现某台服务器挂了之后,能立即通知DNS将其删掉。

2.6 综上所述

​ DNS负载均衡是一种粗犷的负载均衡方法,这里只做介绍,不推荐使用。

3、反向代理负载均衡
3.1 什么是反向代理负载均衡?

反向代理服务器是一个位于实际服务器之前的服务器,所有向我们网站发来的请求都首先要经过反向代理服务器,服务器根据用户的请求要么直接将结果返回给用户,要么将请求交给后端服务器处理,再返回给用户。

之前我们介绍了用反向代理服务器实现静态页面和常用的动态页面的缓存。接下来我们介绍反向代理服务器更常用的功能——实现负载均衡。

我们知道,所有发送给我们网站的请求都首先经过反向代理服务器。那么,反向代理服务器就可以充当服务器集群的调度者,它可以根据当前后端服务器的负载情况,将请求转发给一台合适的服务器,并将处理结果返回给用户。

3.2 优点

隐藏后端服务器。 与HTTP重定向相比,反向代理能够隐藏后端服务器,所有浏览器都不会与后端服务器直接交互,从而能够确保调度者的控制权,提升集群的整体性能。故障转移 与DNS负载均衡相比,反向代理能够更快速地移除故障结点。当监控程序发现某一后端服务器出现故障时,能够及时通知反向代理服务器,并立即将其删除。合理分配任务 HTTP重定向和DNS负载均衡都无法实现真正意义上的负载均衡,也就是调度服务器无法根据后端服务器的实际负载情况分配任务。但反向代理服务器支持手动设定每台后端服务器的权重。我们可以根据服务器的配置设置不同的权重,权重的不同会导致被调度者选中的概率的不同。

3.3 缺点

调度者压力过大 由于所有的请求都先由反向代理服务器处理,那么当请求量超过调度服务器的最大负载时,调度服务器的吞吐率降低会直接降低集群的整体性能。制约扩展 当后端服务器也无法满足巨大的吞吐量时,就需要增加后端服务器的数量,可没办法无限量地增加,因为会受到调度服务器的最大吞吐量的制约。

3.4 粘滞会话

反向代理服务器会引起一个问题:若某台后端服务器处理了用户的请求,并保存了该用户的session或存储了缓存,那么当该用户再次发送请求时,无法保证该请求仍然由保存了其Session或缓存的服务器处理,若由其他服务器处理,先前的Session或缓存就找不到了。

解决办法1: 可以修改反向代理服务器的任务分配策略,以用户IP作为标识较为合适。相同的用户IP会交由同一台后端服务器处理,从而就避免了粘滞会话的问题。

解决办法2: 可以在Cookie中标注请求的服务器ID,当再次提交请求时,调度者将该请求分配给Cookie中标注的服务器处理即可。

Linux高性能服务器编程解释:反向代理被设置在服务器端,因而客户端无须进行任何设置。反向代理是指用代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从内部服务器上得到的结果返回给客户端。

nginx反向代理服务:浏览器输入网址并回车后,会发起一个http请求给nginx(反向代理服务器),这个请求如果是访问静态文件,那么nginx作为web服务器直接返回请求的内容,如果是访问的后台服务逻辑,那么nginx把请求转发给后端的服务处理。

nginx配置反向代理服务器的好处:

  • 增加安全性,客户端不能直接访问后端服务,多了一个中间的屏障。

  • 提升性能通过异步非阻塞的方式把请求传给后端,提升了并发处理能力。

  • 也可利用缓存、压缩响应提高响应速度

Nginx作静态资源服务器实现动静分离

Nginx本身也是一个静态资源的服务器,当只有静态资源的时候,就可以使用Nginx来做服务器,同时现在也很流行动静分离,就可以通过Nginx来实现。动静分离是让站里的页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。

4、2层or3层做负载均衡
4.1 2层负载均衡

​ 也即在数据链路层做负载均衡。通过修改数据链路层的mac地址,ip使用的是虚拟IP,来实现负载均衡,解决响应数据体量过大效率低的问题。当客户端请求服务器时,负载均衡服务器替换mac地址为计算服务器,替换ip为负载均衡服务器ip,计算服务器直接响应数据到客户端。

这种负载均衡方式吞吐量最高,大型互联网公司都是采用这种负载均衡方式。LVS负载均衡是结合了IP层和数据链路层的负载均衡方式,linux通过配置可以实现这两种负载均衡方式。

4.2 3层负载均衡

​ 网络层负载均衡。对网络层的IP地址进行替换,不需要在http层(应用层)工作,直接在操作系统内核的IP数据包中替换地址,将其指向负载均衡服务器,然后负载均衡服务器通过不同的分配策略将请求转发给不同的服务器,效率比基于HTTP层的反向代理高。但是有个缺点是:请求和响应都需要经过负载均衡服务器进行ip层替换,响应数据会成为后期的瓶颈。

有OSPF负载均衡、RIP负载均衡

服务端开发需要什么技能?

1、熟练使用一门编程语言,并会使用一些库和框架,如C++有Boost库,STL标准库,Qt(跨平台的应用程序和用户界面框架)

2、会在服务器上搭建环境

3、会使用数据库,比如说关系型数据库MySQL

4、了解TCP/IP,HTTP/HTTPS等协议

5、良好的业务设计能力和调优能力

gcc的一些参数介绍一下?

gcc -E 预处理器对程序进行预处理,生成预处理文件,后缀为.i

gcc -s 编译器对程序进行编译,生成汇编文件.s

gcc -c 汇编器对汇编文件进行处理生成目标文件.o

-g:在编译的时候生成调试信息

-DMACRO:定义MACRO宏,等效于在程序中使用#define MACRO

-w:不生成任何警告信息

-l:在程序编译的时候指定使用的库

-O:对程序进行优化编译、链接,采用这个选项,整个源代码会在编译、链接过程中进行优化处理。这样产生的可执行文件的执行效率可以提高,但编译、链接的速度就相应的要慢一些。

-O2:比-O更好的优化编译、链接,当然整个编译、链接过程会非常慢

-l dirname:(大写的i)将dirname所指出的目录加入到程序头文件目录列表中。

-Ldirname:将dirdirname所指出的目录加入到库文件的目录列表中

-lname(小写的L):链接时,装载名字为"libname.a"的函数库,该函数库位于系统预设的目录或者由-L选项确定的目录下。

-Wall:生成所有警告信息

-w:不生成任何警告信息

vim的一些用法?

vim有两种模式:命令模式,编辑模式

输入模式下

退出编辑模式:esc

w 保存输入,仅仅保存。

q 退出

wq 保存并且退出

wq 文件名.格式 (退出并且命名)

q!在不保存的情况下退出

命令模式下

set nu设置行号,set nonumber取消行号

/word就可以找到所有的word,按n查找下一个

hjkl分别表示左下上右:

10h:表示向左10行

10j:向下10行

10k:向上10行

10l:向右10行

dd删除,5dd删除当前行向下的5行

yy复制,5yy复制当前行向下的5行,yy5复制当前行5次

gg快速定位到首行,G快速定位到尾行

分屏

sp上下分屏,Ctrl+w+h/j/k/l        切换上下左右窗口(按顺序分别为左/下/上/右),ctrl+w再按c,关闭当前窗口,ctrl+w再按q,也是关闭当前窗口,ctrl+w再按r,交换上下两个分屏的位置。

vsp左右分屏

sleep() 和 wait() 有什么区别?

sleep()和wait()都是线程暂停执行的方法。

1、这两个方法来自不同的类分别是Thread和Object,sleep方法属于Thread类中的静态方法,wait属于Object的成员方法。2、sleep()是线程类(Thread)的方法,不涉及线程通信,调用时会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复wait()是Object的方法,用于线程间的通信调用时会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才进入对象锁定池准备获得对象锁进入运行状态。

什么是状态机?

状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型

状态机的四大概念。

  1. State ,状态。一个状态机至少要包含两个状态。例如灯泡的例子,有灯泡亮和 灯泡灭两个状态。

  2. Event ,事件事件就是执行某个操作的触发条件或者口令。对于灯泡,“打开开关”就是一个事件。

  3. Action ,动作事件发生以后要执行动作。例如事件是“打开开关”,动作是“开灯”。编程的时候,一个 Action 一般就对应一个函数。

  4. Transition ,变换。也就是从一个状态变化为另一个状态。例如“开灯过程”就是一个变换。

epoll的使用流程?

1、创建epoll实例,即创建一棵监听红黑树 ,使用epoll_create函数创建epoll实例并返回和该实例关联的文件描述符,后续通过这个文件描述符调用epoll,epoll_create函数参数大于0即可

2、通过epoll_ctl函数可以向参数中指向的epoll上下文中添加或者删除文件描述符。

epoll_ctl(int epfd,int op,int fd,struct epoll_event*event);
epfd为epoll实例、op为操作,即是将fd加入epfd还是从epfd上下文中删除,fd是要监听的文件描述符
event为fd指向对象所要监听的事件,如读,写等

3、定义一个struct epoll_event events_[]数组,存放监听到的事件集合,调用函数epoll_wait函数进行监听并将监听到的结果放到数组中

epoll_wait( epfd , &events_[0] , (int)event.size() , timeout )

4、遍历数组,进行处理。

什么叫I/O多路复用?

IO多路复用是指单个进程/线程可以同时处理多个IO请求。实现原理:用户将想要监视的文件描述符(File Descriptor)添加到select/poll/epoll的监听集中,由内核监视,函数阻塞。一旦有文件描述符就绪(读就绪或写就绪),或者超时(设置timeout),函数就会返回,然后该进程/线程可以进行相应的读/写操作。

select/poll/epoll三者的区别?

select:将文件描述符放入一个集合中,调用select时,将这个集合从用户空间拷贝到内核空间(缺点1:每次都要复制,开销大),由内核根据就绪状态修改该集合的内容。(缺点2)集合大小有限制,32位机默认是1024(64位:2048);采用水平触发机制。select函数返回后,需要通过遍历这个集合,找到就绪的文件描述符(缺点3:轮询的方式效率较低),当文件描述符的数量增加时,效率会线性下降;poll:和select几乎没有区别,区别在于文件描述符的存储方式不同,poll采用链表的方式存储,没有最大存储数量的限制;epoll:通过内核和用户空间共享内存,避免了不断复制的问题;支持的同时连接数上限很高(1G左右的内存支持10W左右的连接数);文件描述符就绪时,采用回调机制,避免了轮询(回调函数将就绪的描述符添加到一个链表中,执行epoll_wait时,返回这个链表);支持水平触发和边缘触发,采用边缘触发机制时,只有活跃的描述符才会触发回调函数。总结,区别主要在于:1、一个线程/进程所能打开的最大连接数2、文件描述符传递方式(是否复制)3、水平触发 or 边缘触发4、查询就绪的描述符时的效率(是否轮询)

对于select和poll来说所有文件描述符都是在用户态被加入其文件描述符集合的,每次调用都需要将整个集合拷贝到内核态;epoll则将整个文件描述符集合维护在共享内存,每次添加文件描述符的时候都需要执行一个系统调用。系统调用的开销是很大的,而且在有很多短期活跃连接的情况下,epoll可能会慢于select和poll由于这些大量的系统调用开销。

select使用线性表描述文件描述符集合,文件描述符有上限;poll使用链表来描述epoll底层通过红黑树来描述并且维护一个ready list,将事件表中已经就绪的事件添加到这里,在使用epoll_wait调用时,仅观察这个list中有没有数据即可。

select和poll的最大开销来自内核判断是否有文件描述符就绪这一过程:每次执行select或poll调用时,它们会采用遍历的方式,遍历整个文件描述符集合去判断各个文件描述符是否有活动;epoll则不需要去以这种方式检查当有活动产生时,会自动触发epoll回调函数通知epoll文件描述符,然后内核将这些就绪的文件描述符放到之前提到的ready list中等待epoll_wait调用后被处理

select和poll都只能工作在相对低效的LT模式下,而epoll同时支持LT和ET模式。

select、poll、epoll优缺点

select优缺点

缺点:监听上限受文件描述符限制。 最大 1024。调用select函数后常见的针对所有文件描述符的循环语句,且每次调用select函数时都需要向该函数传递监视对象信息。

优点:    跨平台。

select代码里有个可以优化的地方,用数组存下文件描述符,这样就不需要每次扫描一大堆无关文件描述符了,只需要扫描监听套接字和通信套接字就行了

poll优缺点

优点:自带数组结构。 可以将监听事件集合和返回事件集合分离。拓展监听上限。 超出1024限制。

缺点:不能跨平台。 Linux无法直接定位满足监听事件的文件描述符, 编码难度较大。

epoll优缺点

优点:高效。突破1024文件描述符。缺点:不能跨平台。 Linux。

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

什么时候使用select/poll,什么时候使用epoll?
  • 当连接数较多并且有很多的不活跃连接时,epoll的效率比其它两者高很多;

  • 但是当连接数较少并且都十分活跃的情况下,由于epoll需要很多回调,因此性能可能低于其它两者。

什么是文件描述符?

文件描述符在形式上是一个非负整数。实际上,它是一个索引值指向内核为每一个进程所维护的打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。内核通过文件描述符来访问文件。文件描述符指向一个文件。

什么是水平触发?什么是边缘触发?

水平触发(LT,Level Trigger)模式下,只要一个文件描述符就绪,就会触发通知,如果用户程序没有一次性把数据读写完,下次还会通知;边缘触发(ET,Edge Trigger)模式下,只当描述符从未就绪变为就绪时通知一次,之后不会再通知,直到再次从未就绪变为就绪(缓冲区从不可读/写变为可读/写)。区别:边缘触发效率更高,减少了被重复触发的次数,函数不会返回大量用户程序可能不需要的文件描述符。为什么边缘触发一定要用非阻塞(non-block)IO:避免由于一个描述符的阻塞读/阻塞写操作让处理其它描述符的任务出现饥饿状态,因为如果都阻塞在了读或者写操作上,将没有进程/线程处理其他任务。

有哪些常见的IO模型?

1、同步阻塞IO(Blocking IO):用户线程发起IO读/写操作之后,线程阻塞,等待内核准备好数据,再将数据从内核拷贝到用户内存;对CPU资源的利用率不够;2、同步非阻塞IO(Non-blocking IO):发起IO请求之后可以立即返回,如果没有就绪的数据,需要不断地发起IO请求直到数据就绪;不断重复请求消耗了大量的CPU资源;

用户进程发出read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。

3、IO多路复用

select/epoll的好处就在于单个进程/线程就可以同时处理多个网络连接的IO。

它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程

4、异步IO(Asynchronous IO):用户线程发出IO请求之后,继续执行,由内核进行数据的读取并放在用户指定的缓冲区内,在IO完成之后通知用户线程直接使用。

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

对于一个network IO ,它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。

当一个read操作发生时,它会经历两个阶段:

1)等待数据准备 ,这是阻塞和非阻塞区别的阶段

2)将数据从内核拷贝到用户内存中,这是同步和异步区别的阶段  

记住这两点很重要,因为这些IO模型的区别就是在两个阶段上各有不同的情况

为什么使用状态机?

因为请求报文主要分为三个部分,分别为请求行,请求头,请求体,每个部分的数据的形式不相同,所以在解析请求报文时,要采用不同的方法进行解析,也就是说针对于解析这个过程来说,他会有多个状态,状态机解决的问题就是当某种模型的状态变更比较比较复杂,且状态比较多, 那么我们有必要将这些状态变化的逻辑抽象出来,做成一个可以统一调用的算法,这样封装出来的代码就比较好维护,同时可读性也很强。

说一说TCP三次握手、四次挥手?

1、首先客户端发起连接请求,即SYN报文并随机生成一个序号作为TCP报文段的序列号,此时客户端进入SYN_SENT

2、服务器端收到客户端发来的SYN连接请求报文,如果同意建立连接,则会生成一个自己的序列号并将客户端的序列号加一作为确认号,向客户端发送SYN_ACK报文,此时服务器端进入SYN_RECV状态

3、客户端收到服务器端的应答报文后,向服务器端发送确认报文,进入ESTABLISHED状态,服务端收到客户端发来的确认报文也进入ESTABLISHED状态,至此三次握手结束。

四次挥手:

1、客户端发送FIN报文请求断开连接,表示自己不会再发送数据了,其进入FIN_WAIT1状态

2、服务器端接收到断开连接报文,此时自己还有数据要发送,则发送ACK报文,同意断开连接,进入CLOSE_WAIT状态,客户端收到之后,进入FIN_WAIT2状态

3、服务器端数据发送完毕,向客户端发送断开连接请求,进入LAST_ACK状态

4、客户端收到之后进入TIME_WAIT状态,并向服务器发送确认报文,服务器端收到之后进入CLOSE状态,客户端等待2MSL时间就进入CLOSE状态。

互斥量和信号量的区别

1. 互斥量用于线程的互斥,信号量用于线程的同步。

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

2. 互斥量值只能为0/1,信号量值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

  1. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

常见的Reactor模型

在reactor pattern处理模式中定义以下三种角色:

Reactor 将I/O事件分派给对应的Handler

Acceptor 处理客户端新连接,并分派请求到处理器链中

Handlers 执行非阻塞读/写 任务

常见的Reactor模型分为三种:

1、单reactor单线程模型

这是最基本的单Reactor单线程模型。其中Reactor线程,负责多路分离套接字,有新连接到来触发connect 事件之后,交由Acceptor进行处理,有IO读写事件之后交给hanlder 处理。

2、单reactor多线程模型

相对于第一种单线程的模式来说,在处理业务逻辑,也就是获取到IO的读写事件之后,交由线程池 thread pool来处理,这样可以减小主reactor的性能开销,从而更专注的做事件分发工作了,从而提升整个应用的吞吐。

3、多reactor多线程模型

第三种模型比起第二种模型,是将Reactor分成两部分,

mainReactor负责监听server socket,用来处理新连接的建立,将建立的socketChannel指定注册给subReactor。subReactor维护自己的selector, 基于mainReactor 注册的socketChannel多路分离IO读写事件,读写网络数据,对业务处理的功能,另其扔给worker线程池来完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值