提高服务器性能的建议

        因为自己对于Linux内核和web服务器很感兴趣,所以最近看了一些书,也搜集了一些资料,先整理一下吧!记录我的成长。 性能对于服务器来说是十分的重要,毕竟每个客户都希望自己的请求能够得到快速的响应。

1.池
根据不同的资源类型,池可以分为多种,常见的有内存池,进程池,线程池和连接池。

2.数据复制

    高性能的服务器应该避免不了不必要的数据的复制 尤其是数据复制发生在内核态和用户态之间的时候。
如果内核可以直接处理从socket或者文件读入的数据,则应用程序就没必要将这些数据从内核缓冲区复制到应用程序缓冲区中。 这里说的“直接处理”指的是应用程序不关心这些数据的内容,不需要对它们做任何的分析,比如ftp服务器,当客户请求一个文件的时候, 服务器只需要检测目标文件是否存在,以及客户端是否有读取他的权限,绝对不会关心文件的具体内容。这样的话,ftp服务器就无须把目标文件的内容 读入到应用程序的缓冲区中,并调用send函数来进行发送,而是可以使用“零拷贝”函数sendfile来直接将其发送给客户端。


在apache,nginx,lighttpd等web服务器当中,都有一项sendfile相关的配置,在一些网上的资料都有谈到sendfile会提升文件传输性能,那sendfile到底是什么呢?它的原理又是如何呢?

在传统的文件传输里面(read/write方式),在实现上其实是比较复杂的,需要经过多次上下文的切换,我们看一下如下两行代码:

  1. read(file, tmp_buf, len);
  2. write(socket, tmp_buf, len);

以上两行代码是传统的read/write方式进行文件到socket的传输。

当需要对一个文件进行传输的时候,其具体流程细节如下:

 1、调用read函数,文件数据被copy到内核缓冲区

 2、read函数返回,文件数据从内核缓冲区copy到用户缓冲区

 3、write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。

 4、数据从socket缓冲区copy到相关协议引擎。

以上细节是传统read/write方式进行网络文件传输的方式,我们可以看到,在这个过程当中,文件数据实际上是经过了四次copy操作:

硬盘—>内核buf—>用户buf—>socket相关缓冲区—>协议引擎

 而sendfile系统调用则提供了一种减少以上多次copy,提升文件传输性能的方法。Sendfile系统调用是在2.1版本内核时引进的:

  1. sendfile(socket, file, len);

运行流程如下:

 1、sendfile系统调用,文件数据被copy至内核缓冲区

 2、再从内核缓冲区copy至内核中socket相关的缓冲区

 3、最后再socket相关的缓冲区copy到协议引擎

 相较传统read/write方式,2.1版本内核引进的sendfile已经减少了内核缓冲区到user缓冲区,再由user缓冲区到socket相关 缓冲区的文件copy,而在内核版本2.4之后,文件描述符结果被改变,sendfile实现了更简单的方式,系统调用方式仍然一样,细节与2.1版本的 不同之处在于,当文件数据被复制到内核缓冲区时,不再将所有数据copy到socket相关的缓冲区,而是仅仅将记录数据位置和长度相关的数据保存到 socket相关的缓存,而实际数据将由DMA模块直接发送到协议引擎,再次减少了一次copy操作。

3.上下文切换和锁

    并发程序必须考虑上下文的切换(context switch)的问题,即进程切换或者线程切换导致的系统开销。即使是I/O密集型的服务器,也不应该开启过多的工作线程(或者是工作进程),否则线程间的切换将占用大量的CPU时间,服务器真正处于处理业务逻辑的CPU时间的比重就显得不足了。因此,为每一个客户连接都创建一个工作线程的服务器模型是不可取的。
接下来我们来讨论一下进程切换和线程切换
    进程切换比线程切换开销大是因为进程切换时要切页表,而且往往伴随着页调度,因为进程的数据段代码段要换出去,以便把将要执行的进程的内容换进来。本来进程的内容就是线程的超集。而且线程只需要保存线程的上下文(相关寄存器状态和栈的信息)就好了,动作很小

引起上下文切换的原因
  1. 时间片用完,CPU正常调度下一个任务
  2. 被其他优先级更高的任务抢占
  3. 执行任务碰到IO阻塞,调度器挂起当前任务,切换执行下一个任务
  4. 用户代码主动挂起当前任务让出CPU时间
  5. 多任务抢占资源,由于没有抢到被挂起
  6. 硬件中断
关于linux内核实现上下文切换的解释:
    在linux中一个叫做task_struct结构体代表一个线程,linux调度器会对一个结构体:sched_entity结构体感兴趣并对其进行调度,而它正好嵌入到task_struct中。因此对可以看出linux调度是线程级的。那具体怎么调度呢?
Linux用红黑树存所有可运行的进程(注意是可运行,也就是就绪态的进程),使用等待队列wait_queue记录休眠(被阻塞)线程。用一个例子来介绍调度和上下文切换的细节,例如网卡产生一个中断通知有网络数据,执行中的线程阻塞(从执行状态剥离并放入等待队列),然后再到红黑树里面选一个来执行。这个过程的详细过程是:虚拟内存映射和处理器状态均要切换到新线程,前一个线程寄存器、栈信息还有其他状态信息被保存。而新线程的栈信息和寄存器信息被恢复,刚好是反操作。我们把上述过程叫做上下文切换。等到网络数据读取就绪,在等待队列中的线程又被唤醒,接着放入红黑树中,成为可执行态,等待被执行。
    并发程序的另一个需要考虑的问题就是共享资源的加锁保护。锁通常被认为导致服务器效率低下的一个因素;因为它引入的代码不仅不处理任何业务逻辑,而且还需要访问内核的资源。如果服务器必须使用锁可以考虑减小锁的粒度.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值