操作系统相关优化思考(一)

日常工作中遇到的操作系统层的一些问题和读到的文章,总结下来持续更新,并没有什么条理,随想随写

Posted by NianShen Blog on June 21, 2017
原则和目的

提高服务器性能有很多方法,比如按照服务器的功能性,区分web服务器,数据库服务器,应用中间件服务器,数据中间价服务器。在硬件资源有限的情况下,最大的提升服务器的性能和资源利用率,提高服务器的并发处理能力,提高服务器的安全性。通过调整系统参数来提高系统内存、CPU、内核资源的使用率,通过禁用不必要的服务、端口,来提高系统的安全性,更好的发挥系统的可用性。总的来说通过对OS的一些调整,可以在不提高成本的前提下,完成一些力所能及的安全加固,性能优化。

何为高性能

我的理解就是在资源一定的情况下服务的响应延迟低,并发高,吞吐量大。瓶颈本质上来说就是:系统资源一定无法提升,但请求的处理却还不够快,无法支撑更多的请求。

负载是什么

平均负载是指单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数。可运行状态的进程,是指正在使用 CPU 或者正在等待 CPU 的进程,ps 命令看到的,处于 R 状态(Running 或 Runnable)的进程。不可中断状态的进程则是正处于内核态关键流程中的进程,并且这些流程是不可打断的,比如最常见的是等待硬件设备的 I/O 响应,也就是我们在 ps 命令中看到的 D 状态(Uninterruptible Sleep,也称为 Disk Sleep)的进程。一句话概括:负载就是活跃进程数的平均值。

负载高低的阈值是什么

当平均负载高于CPU数量70%的时候服务器可能就是出现了过载,举个例子,我有一台服务器cpu数量为4核,如果查看服务器的负载高于4 * 70% = 2.8说明我的服务器可能出现了过载。这是一个经验值,最优的方式时做一个长期的监控,观察机器一段时间内的负载情况变化,做环比。uptime或者top的方式在看系统负载的时候又三个值分别是1,5,15三个时间端内的负载值,具体看那个要根据实际情况分析,如果三个值差别不大说明系统负载稳定,如果1分钟的大于15分钟的说明系统的负载正在逐渐上来,如果15分钟的大于一分钟的说明在15分钟之内出现了系统负载的波动,曾经系统负载高过,逐渐降了下来。

什么是CPU密集型和I/O密集型的
  • CPU密集型:CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。 在多重程序系统中,大部份时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部份时间用在三角函数和开根号的计算,便是属于CPU bound的程序。 CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。
  • IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。 I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。
  • 第一种任务是计算密集型:计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,想要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。 计算密集型任务由于主要消耗CPU资源,所以代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
  • 第二种任务的类型是IO密集型:涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。 IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。 总之,计算密集型程序适合C语言多线程,I/O密集型适合脚本语言开发的多线程。
平均负载和CPU使用率

平均负载的判定不只是包含了正在使用CPU的进程还包括等待CPU和等待I/O的进程。所以平均负载高的话有三种情况。

  • CPU密集型任务,这时候CPU的使用率和负载是一致的
  • I/O密集型任务,等待I/O操作也会导致负载高,但CPU使用率并不一定高,可能是磁盘或者网卡的吞吐量不够或者
  • 大量等待CPU的进程调度
stress和systat

stress命令主要用来模拟系统负载较高时的场景,mpstat多CPU环境下显示各个可用CPU的状态信息,pidstat监控全部或指定进程的cpu、内存、线程、设备IO等系统资源的占用情况

  • stress -c 1 –timeout 600 产生1个进程,每个进程都反复不停的计算随机数的平方根,执行600s后退出,模拟CPU密集型进程
  • stress -i 1 –timeout 600 产生1个进程,每个进程反复调用sync()将内存上的内容写到硬盘上,模拟I/O密集型进程
  • stress -c 4 –timeout 600 产生4个进程,由于我使用的1core的服务器,所以会出现等待CPU的进程,模拟大量进程的场景
  • mpstat -P ALL 5 关于CPU的详细信息,-P ALL 表示监控所有CPU,后面数字5表示间隔5秒后输出一组数据
  • pidstat -u 5 1 关于运行中的进程/任务、CPU、内存等的统计信息,间隔5秒后输出一组数据,-u表示CPU指标
什么是CPU上下文

Linux是一个多任务操作系统,它支持远大于CPU数量的任务同时运行。这些任务实际上并不是真的在同时运行,而是因为系统在很短的时间内,将CPU 轮流分配给它们,造成多任务同时运行的错觉。CPU 寄存器,是CPU内置的容量小、但速度极快的内存。程序计数器是用来存储CPU正在执行的指令位置或者即将执行的下一条指令位置。它们都是CPU 在运行任何任务前必须的依赖环境,因此也被叫做CPU上下文。在每个任务运行前CPU都需要知道任务从哪里加载、哪里开始运行,也就是说,需要系统事先帮它设置好CPU寄存器和程序计数器。

CPU的上下文切换

CPU上下文切换过程:记录当前任务的上下文(即寄存器和计算器等所有的状态)————>找到新任务的上下文并加载————>切换到新任务的程序计算器位置 这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来,这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。

进程和线程

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。由于每个进程至少要干一件事,所以,一个进程至少有一个线程。像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。

CPU上下文切换之进程上下文切换
经历过几次安全问题
  • 服务器使用弱密码被入侵,当时使用的是AWS,采用的流量计价方式是带宽不设封顶,按照流量计费,因使用弱密码被入侵成为DDos别人的肉鸡,几天时间产生了大量的流量费用。
  • php应用被挂码,植入挖矿程序,cpu mem爆表,正常业务无法运行。
  • 被友商DDos直接把官网干挂。
  • 使用未知非官方镜像,容器内有挖矿程序,服务器cpu 内存使用率只用到79%,隐藏深操作系统层的监控不报警,察觉困难。
  • 爬虫,很多人不会遵循robots协议,随意的爬,有时候会导致后端服务器的压力增加,影响正常业务。
ssh的优化
  • 修改sshd默认22端口,启用一个非1-1023的随机端口,一般选择在50000以上,可以通过/etc/services查看常见的协议与port的对应记录
  • 禁止使用root直接进行登录
  • 禁止使用密码登陆 PasswordAuthentication no
  • 禁止Root用户直接登陆 PermitRootLogin no 可以添加一个普通用户并为此普通用户赋权可以sudo -i
  • 禁用DNS反向解析 UseDNS no
  • 使用堡垒机或跳板机,后端所有服务器只允许通过某一台或几台进行登陆跳转,无法直连后端服务器
  • 打开并记录secure日志,可以收集到elk中,根据失败次数统计并报警。或者使用shell实现,自动添加secure日志中的失败ip到hosts.deny中,也可以使用denyhosts,Fail2Ban工具
    [root@zzr-blog shell]# cat secure.sh 
    #! /bin/bash
    cat /var/log/secure|awk '/Failed/{print $(NF-3)}'|sort|uniq -c|awk '{print $2"="$1;}' > /opt/shell/black.list
    for i in `cat  /opt/shell/black.list`
    do
    IP=`echo $i |awk -F= '{print $1}'`
    NUM=`echo $i|awk -F= '{print $2}'`
    if [ ${#NUM} -gt 1 ]; then
      grep $IP /etc/hosts.deny > /dev/null
      if [ $? -gt 0 ];then
        echo "sshd:$IP:deny" >> /etc/hosts.deny
      fi
    fi
    done
    
  • 使用hosts.allow 和hosts.deny 限制可以使用ssh协议的ip范围,一般只放开需要ssh堡垒机的ip地址,或部分内网IP地址段
/etc/hosts.allow文件中
sshd:ip:allow (也可以是ip段/子网掩码,例如192.168.0.0/255.255.255.0)

配合/etc/hosts.deny使用
sshd:all:deny

这两个文件支持多种服务例如httpd vsftpd等,服务进程名:主机列表:当规则匹配时可选的命令操作 server_name:hosts-list[:command]
  • 设置密码复杂度过期天数等,如果能使用堡垒机或者禁用密码登陆会更好
[root@zzr-blog ~]# cat /etc/login.defs | grep "PASS_MAX_DAYS"
PASS_MAX_DAYS	99999  #设置成想要的天数

[root@zzr-blog ~]# authconfig --passminclass=2 --update
[root@zzr-blog ~]#  grep "^minclass" /etc/security/pwquality.conf
minclass = 2  #设置密码复杂度,包括大写字母/小写字母/数字/特殊字符

[root@zzr-blog ~]# vim /etc/security/pwquality.conf
maxsequence = 3 #加到最后一行,在新密码中设置单调字符序列的最大长度,比如说abcde 12345 qwer等
limits.conf的优化

limits.conf文件是Linux PAM(插入式认证模块,Pluggable Authentication Modules)中pam_limits.so的配置文件,突破系统的默认限制,对系统访问资源有一定保护作用。 limits.conf和sysctl.conf区别在于limits.conf是针对用户,而sysctl.conf是针对整个系统参数配置。limits.conf文件格式:

username|@groupname   type    resource    limit

username|@groupname 设置需要被限制的用户名,组名前面加@和用户名区别,也可用通配符*来做所有用户的限制 type 类型有soft,hard 和 - 软限制 硬限制 同时设置 soft的数值不能大于hard的数值两者可以相同 resource 需要限制的资源类型 limit 限制的值是多大