maksim 发布的文章

什么是算法?

算法理解起来其实非常简单,就是是用于解决特定问题的一系列的执行步骤。

// 计算 a 跟 b 的和
public  static int plus(int a, int b ) {
        return a + b;
}

上面的代码很简单,但是它其实也是一个算法,因为它解决了加法运算。

// 计算 1 + 2 +3 ... +n 的和

public static int sum(int n ) {
        int result =0;
    for (int i = 1; i <= n; i++) {
                result += i
        }
        return result;
}

sum 也是一个算法,它解决了从 1 加到 n 的问题,这两个我们都可以称之为算法,算法其实就是你能解决问题就可以了,小到一行代码,大到上万行都可以称之为算法。

但是当我们使用不同额算法,解决同一个问题,有时效率可能相差非常大。因为我们都知道当我们解决一个问题的时候,解决方案可能会有很多,写代码也是如此,不同算法之间执行效率可能会相差很多。

public static int sum2(int n) {
        return (1 + n) * n / 2;
}

就拿上面从 1 到 n 计算累加的算法,其实让我们如果使用数学公式解决的话,一行代码就搞定了,根本不需要使用循环。

再举个例子:求第 n 个斐波拉切(fibonacci number)

求斐波拉切的方式有很多,在这里我们用两种方案来实现:

  • 递归
public static int fib(int n) {
        if (n <= 1) {
                return n;
        }
        return fib(n - 1) + fib(n - 2);
}

这是一种解决方案,肯定能够解决我们的问题,但是这个算法有性能问题,当我们把 n 写成 64 的时候,这个算法的执行效率就开始降低了,需要执行很久才能够返回。原因我们在后面说,我们先来换一个算法。

  • 动态规划

我们将斐波拉切数列展开:

0 1 1 2 3 5 8 13 ....

我们发现,其实最终值就是前两个值加在一起,那么我们完全可以将前面的值保存起来。


public staitc int fib (int n) {
      if (n <= 1) {
              return n;
      }
   
    int first = 0;
    int second = 1;

    for (int i =0; i < n - 1; i++ ) {
                 int sum = first + second;
        first = second;
            second = sum;
    }
        return second;
}

当我们使用 fib(64) 的时候几乎是秒出结果。

这也就证明了,当我们以后解决问题的时候会有很多种算法,但是算法之间效率相差非常大。这两个算法的差距我们该如何测量呢?

我们可以计算它们给出结果花费时间。

package com.mj;

import java.text.SimpleDateFormat;
import java.util.Date;

public class Times {
    private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS");
    
    public interface Task {
        void execute();
    }
    
    public static void test(String title, Task task) {
        if (task == null) return;
        title = (title == null) ? "" : ("【" + title + "】");
        System.out.println(title);
        System.out.println("开始:" + fmt.format(new Date()));
        long begin = System.currentTimeMillis();
        task.execute();
        long end = System.currentTimeMillis();
        System.out.println("结束:" + fmt.format(new Date()));
        double delta = (end - begin) / 1000.0;
        System.out.println("耗时:" + delta + "秒");
        System.out.println("-------------------------------------");
    }
}

Times 其实就是一个代理,它可以执行满足 Task 的接口的类,然后在执行前后加入时间计数。这样我们就可以统计算法的执行时长了

int = 45
TimeTool.check("fib1", new Task() {
        public void execute() {
        System.out.println(fib1(n));
        }
});

exec

我们对比两个算法之间的差距,可以发现采用动态规划的的方案,随着数字的增大执行时间并不是很大,但是采用递归的方案,时间就慢慢的增加了。

exec

当我们执行 n = 46 的时候,采用递归的方案时间已经达到了 9.44 秒。

如何评判算法的好坏?

还是拿上面的两个案例来举例,我们可以拿代码越短就证明代码越好吗?

肯定不行,在累加中的确是利用公式更好,因为它代码足够简洁,但是在斐波拉切中,反倒是代码更多的执行效率更好。

如果单从执行效率上进行评估,你可能会得到这么一个方案,比较不同算法对同一组输入的执行处理时间这种方案也叫做:事后统计法。

但是这种方案是有以下缺点:

  • 执行时间严重依赖硬件以及运行时各种不确定的因素环境,例如 CPU 更好,当前进程数量比较少等等。
  • 必须编写相应的测试代码
  • 测试数据的选择比较难保证公正性

一般从以下维度来评估算法的优劣

  • 正确性、可读性、健壮性(对不合理输入的反应能力和处理能力)
  • 时间复杂性(time complexity):估算程序指令的执行次数(执行时间)
  • 空间复杂度(space complexity):估算所需占用的存储空间

算法的评估

我们来算一下面这个函数需要执行多少条指令

public static void test1(int n) {
        // 汇编指令
        
        // 1
        if (n > 10) { 
            System.out.println("n > 10");
        } else if (n > 5) { // 2
            System.out.println("n > 5");
        } else {
            System.out.println("n <= 5"); 
        }
        
        // 1 + 4 + 4 + 4
        for (int i = 0; i < 4; i++) {
            System.out.println("test");
        }
        
        // 140000
        // O(1) 时间复杂度
        // O(1) 空间复杂度
    }

判断的执行指令次数我们假定是 1,这样算其实不太严谨,但是没有关系,我们先按照这个规则计算。

我们继续向下,到了 for 循环的时候 int i = 0,我们算一个指令,i++ 会执行 4 次,i < 4 会被执行 4 次,for 循环中的打印输出也会执行四次,这样就是 1 + 4 + 4+ 4。总的算下来 就是14。

public static void test2(int n) {
        // O(n)
        // 1 + 3n
        for (int i = 0; i < n; i++) {
            System.out.println("test");
        }
}

int = 0 是 1 次, i < n 执行 n 次, i++ 执行 n 次,输出执行 n 次,那么执行指令的次数就是 1 + 3 * n 次。

public static void test3(int n) {
        // 1 + 2n + n * (1 + 3n)
        // 1 + 2n + n + 3n^2
        // 3n^2 + 3n + 1
        // O(n^2)
        
        // O(n)
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                System.out.println("test");
            }
        }
    }
public static void test5(int n) {
        // 8 = 2^3
        // 16 = 2^4
        
        // 3 = log2(8)
        // 4 = log2(16)
        
        // 执行次数 = log2(n)
        // O(logn)
        while ((n = n / 2) > 0) {
            System.out.println("test");
        }
    }

    public static void test6(int n) {
        // log5(n)
        // O(logn)
        while ((n = n / 5) > 0) {
            System.out.println("test");
        }
    }

    public static void test7(int n) {
        // 1 + 2*log2(n) + log2(n) * (1 + 3n)
        
        // 1 + 3*log2(n) + 2 * nlog2(n)
        // O(nlogn)
        for (int i = 1; i < n; i = i * 2) {
            // 1 + 3n
            for (int j = 0; j < n; j++) {
                System.out.println("test");
            }
        }
    }

    public static void test10(int n) {
        // O(n)
        int a = 10;
        int b = 20;
        int c = a + b;
        int[] array = new int[n];
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i] + c);
        }
    }

上面的这些函数计算方式都差不多,我就直接列在这里了。

大 O 表示法

我们已经算出了执行指令的大概次数,但是看上去还是很复杂。这个时候我们就要用大 O 表示法来描述复杂度,它表示的是数据规模 N 的一应的复杂度。

规则如下:

  • 忽略常数、系数、低阶

    • 9 >> O(1)
    • 2n + 3 >> O(n)
    • n^2 + 2n + 6 >> o(n^2)
    • 4n^3 + 3n^2 + 22n + 100 >> o(n^3)

上一节中的函数对应的大 O 表示法,在注释中,可以翻回去看一下。大 O 表示法仅仅是一种粗略的分析模型,是一种估算帮助我们短时间内了解一个算法的执行效率。

对数阶的细节

  • 对数阶一般省略底数

常见的复杂度

table

数据规模对比

数据规模较小时候

数据规模较小

数据规模较大时

数据规模较大

这个对比就已经很明显了。

斐波拉切的时间复杂度

斐波拉切的时间复杂度

斐波拉切的时间复杂度

他们的差距有多大?

  • 如果是一台 1GHz 的普通计算机,运算速度 10^9 次每秒(n 为 64)
  • O(n)大约耗时 6.4 * 18^-18 秒
  • O(2^n)大约耗时 584.94 年
  • 有时候算法之间的差距,往往比硬件方面的差距还大

算法的优化方向

  • 用尽量少的存储空间
  • 用尽量少的执行步骤(执行时间)
  • 根据情况:

    • 空间换时间
    • 时间换空间

多个数据规模的情况

public function void test (int n, int k) {
        for (int i = 0; i < n; i++){
                System.out.println("test")
        }

        for (int i = 0; i < k; i++){
                System.out.println("test")
        }
}

这个函数的时间复杂度是由 n 和 k 决定的,O( n + k) 这两个都不能被忽略,因为是两个数据规模

更多的知识

更多的复杂度相关知识,会在后续讲解数据额机构、算法的过程中穿插

  • 更好、更坏复杂度
  • 均摊复杂度
  • 复杂度震荡
  • 平均复杂度

为什么要学数据结构与算法

说到数据结构相信大家的第一反应就是复杂、深奥、难学,至于大家为什么会这么觉得,我认为是没有找对学习资料,只要找对了学习资料相信大家都是可以掌握的。

另外可能会觉着数据结构与算法并不常用,一个产品从开发到上线压根就没有用到数据结构和算法,好像就算我们不懂数据结构与算法也不耽误我们完成研发任务,拿很高的薪资啊,那为什么还要学习数据结构与算法呢?

其实有一个很重要的原因就是我们要应付面试,数据结构和算法可以说是民企面试必考的内容,也就是说国内外一线的大型互联网公司,它在面试的过程中都会多多少少问一些数据结构与算法的问题,而且规模越大的公司,越注重数据结构预算法,甚至现在连一些中小型公司的面试都可能开始问算法题了。

随着时间的推移,面试的难度肯定会越来越高的,这个时候可能会觉着很奇怪,工作中根本用不到算法,那为什么面试的时候还会经常问呢?

这个时候可能会有人觉着委屈,命名研发能力很优秀,但就是因为不会数据结构预算法那大公司就进不了,被大公司拒之门外,但是它整体的综合能力可能会比大公司的一些人要强,这样的情况其实有很多。

在 2015 年 homebrew 的作者去 Google 面试却没有通过面试。

Max Howel

这是他的一条吐槽,内容大概是我们公司百分之九十的人都在用你写的工具,但是你却不能白板编程二叉树的翻转。

但是后来硅谷的大公司都在抢着要他,所以公司面试死盯着算法这一项的确是很容易会误伤很多人才,那为什么要好这么做呢?

其实无论是什么公司都想要招聘优秀的人才,但是你在短短的几个小时面试过程中想要了解一个人的话,其实是真的很困难的,所以很多公司招聘的第一步就是卡学历,因为从概率上讲高学历出现优秀人才的几率肯定会更大一点。

举个例子,一个是计算机硕士但是毫无开发经验,一个是大专学历,有三年开发经验,大公司很有可能会招聘这位硕士,因为大公司会看一个人的长期潜力,如果你不会这个技术,没关系,公司可以进行培训,花时间去培养。

除了通过学历来过滤人才,在面试的时候去考察数据结构和算法也是在短时间内考察一个人长期潜力对的一个捷径。因为数据结构算法功底扎实的程序员,技术实力、业务能力、咨询能力一般都不会差。

还有如果我们问的都是平时工作中常用的技术点,那大家的答案其实都会差不多,而且大家在面试前其实都回去背一下题目,这个时候企业无法去区分人才,算法题就不一样了,这个没法背,考验的是一个人的功底和长期积累,所以通过算法题就可以将人才进行再次区分,进而挑选出更优秀的人才。

所以大公司面试的时候考察这个数据结构和算法不是乱来的,也是有一定的道理的,如果你想要进入更大的公司,数据结构和算法是必须要掌握的。

数据结构与算法的应用

databases

我们平时用的各种数据库在底层肯定用到了数据结构,例如 B 树,hash 表等等这些数据结构,我们平时用数据库的时候直接写 SQL 语句就好了,如果你想重新开发一套数据库,那就必须要会数据结构与算法了。

这些数据库大多数都是由大公司开发出来的,所以大公司对于数据结构与算法的要求肯定会非常高的。

梦幻西游地图

这张截图来自于梦幻西游,我们的游戏人物香葱长安走到阴曹地府,这个时候你就会发现这个游戏会帮助角色规划路线,从长安出发途径建业、东海湾最后到阴曹地府。

规划路线肯定会用到图这种结构,还会用到最短路径算法,游戏开发里面肯定会存在大量的数据结构与算法。

区块链

比特币

区块链和比特币底层也会用到大量的数据结构比如说:链表、二叉树、哈希函数等。

人工智能、VR、AR、自动驾驶

人工智能、VR、AR,无人驾驶肯定是基于数据结构和算法的,可以看出来数据结构和算法可以说是无处不在。

在我们平时的开发中用不到数据结构和算法的原因是因为我们直接使用了很多第三方框架来完成任务,实际上很多第三方框架内部又用到了大量的数据结构与算法,所以如果你懂数据结构和算法的话就可以更好的去阅读这些框架的源码,也更能体会作者的设计思想,更好的使用框架,可以把框架的价值发挥到最大。

你有没有考虑过一个问题,问什么比人能够写出这么优秀的框架?

其实不仅仅是开发第三方框架需要用到算法,你的工作中其实也需要用到数据结构和算法,比如说你的用户量达到上百万,千万,上亿的时候,你要处理海量数据的时候,你怎么可能用不到数据结构算法呢?

如果你一直没用过数据结构和算法有可能你做的一直都是小项目,当你进入更大的平台,更大的用户量的时候,你肯定必然要用到数据结构与算法,所以希望大家再也不要去说数据结构不常用,数据结构没有用的话。

说直接一点,并非是用不上数据结构,而是你的层次还不够。另外计算机编程领域数据结构与算法的应用无处不在的,我们刚才举例的几个场景其实都会应用到大量的数据结构与算法,如果我们的数据结构预算法功底比较扎实的话,我觉着是可以让我们站在更高的角度去思考代码的,写出性能够高的程序。而且也能让我们更快的去上手各种新技术。

学习数据结构预算法也能够让我们打开一扇全新的大门。因为我们这样能够进入更高的编程领域。

为什么有的人学新技术那么快?

比如说人工智能,区块链一出来,很多人已经搞的很厉害了,因为他们的数据结构预算法功底扎实,学的快是很正常的。

接下来的画面可能会很扎心。

都挺好截图

随着年龄的增长,我们的学习能力,体力的确会所有下降,所以趁着脑子还没有生锈,攻克它,一次掌握,终生受益。

Pascal 之父 Nicklaus Wirth 凭借一个公式获得了图灵奖:

算法 + 数据结构 = 程序

可见数据结构与算法在编程领域有多么重要,要不他凭什么一个公式就拿到了图灵奖呢?

课程地址:18个Linux Shell脚本经典案例【共20课时】_自动化运维课程-51CTO学堂

服务器系统配置初始化

当我们拿到一台崭新的服务器时候需要对其进行初始化,一般的步骤如下:

  1. 安装系统新能分析工具已经其他的工具
  2. 设置时区并同步时间
  3. 禁用selinux
  4. 清空防火墙默认策源
  5. 历史命令显示操作时间
  6. 禁止root远程登录
  7. 禁止定时任务发送邮件
  8. 设置最大打开文件数
  9. 减少Swap使用
  10. 系统内核参数的优化

当我们的服务器只有一台的时候其实还好,但如果是 10 台,100 台呢?我们不可能还是手动的去一台一台的设置,我们肯定要借助脚本来自动完成。

#/bin/bash
# 安装系统性能分析工具及其他
yum install gcc make autoconf vim sysstat net-tools iostat iftop iotp wget lrzsz lsof unzip openssh-clients net-tool vim ntpdate -y

# 设置时区并同步时间
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
if ! crontab -l |grep ntpdate &>/dev/null ; then
    (echo "* 1 * * * ntpdate time.windows.com >/dev/null 2>&1";crontab -l) |crontab 
fi

# 禁用selinux
sed -i '/SELINUX/{s/permissive/disabled/}' /etc/selinux/config

# 关闭防火墙
if egrep "7.[0-9]" /etc/redhat-release &>/dev/null; then
    systemctl stop firewalld
    systemctl disable firewalld
elif egrep "6.[0-9]" /etc/redhat-release &>/dev/null; then
    service iptables stop
    chkconfig iptables off
fi

# 历史命令显示操作时间
if ! grep HISTTIMEFORMAT /etc/bashrc; then
    echo 'export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S  `whoami` "' >> /etc/bashrc
fi

# SSH超时时间
if ! grep "TMOUT=600" /etc/profile &>/dev/null; then
    echo "export TMOUT=600" >> /etc/profile
fi

# 禁止root远程登录 切记给系统添加普通用户,给su到root的权限
sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config

# 禁止定时任务向发送邮件
sed -i 's/^MAILTO=root/MAILTO=""/' /etc/crontab 

# 设置最大打开文件数
if ! grep "* soft nofile 65535" /etc/security/limits.conf &>/dev/null; then
cat >> /etc/security/limits.conf << EOF
    * soft nofile 65535
    * hard nofile 65535
EOF
fi

# 系统内核优化
cat >> /etc/sysctl.conf << EOF
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_tw_buckets = 20480
net.ipv4.tcp_max_syn_backlog = 20480
net.core.netdev_max_backlog = 262144
net.ipv4.tcp_fin_timeout = 20  
EOF

# 减少SWAP使用
echo "0" > /proc/sys/vm/swappiness

发送告警邮件

安装软件

yum install mailx -y

进入qq邮箱首页,点击设置>账户,然后找到下图截取的地方(需要设置的,如图)

设置完之后呢,就要把生成的授权码作为邮箱的password的啦~

配置/etc/mail.rc文件【下面的配置qq是假的,别用】

#设置发件人名称
set [email protected]
#设置邮件服务器
set smtp=smtp.qq.com
#填写自己邮箱地址
set [email protected]
#输入邮箱验证码
set smtp-auth-password=pfljngafoqaxecff
#smtp的认证方式,默认是login
set smtp-auth=login

测试【已经完成】

 echo "admin ,文件内容" | mail -s "标题" 你的[email protected]

一键查看服务器利用率

#!/bin/bash
function cpu(){
    
    util=$(vmstat | awk '{if(NR==3)print $13+$14}')
    iowait=$(vmstat | awk '{if(NR==3)print $16}')
    echo "CPU -使用率:${util}% ,等待磁盘IO相应使用率:${iowait}:${iowait}%"
 
}
function memory (){
 
    total=`free -m |awk '{if(NR==2)printf "%.1f",$2/1024}'`
    used=`free -m |awk '{if(NR==2) printf "%.1f",($2-$NF)/1024}'`
    available=`free -m |awk '{if(NR==2) printf "%.1f",$NF/1024}'`
    echo "内存 - 总大小: ${total}G , 使用: ${used}G , 剩余: ${available}G"
}
disk(){
    
    fs=$(df -h |awk '/^\/dev/{print $1}')
    for p in $fs; do
        mounted=$(df -h |awk '$1=="'$p'"{print $NF}')
        size=$(df -h |awk '$1=="'$p'"{print $2}')
        used=$(df -h |awk '$1=="'$p'"{print $3}')
        used_percent=$(df -h |awk '$1=="'$p'"{print $5}')
        echo "硬盘 - 挂载点: $mounted , 总大小: $size , 使用: $used , 使用率: $used_percent"
    done
 
}
function tcp_status() {
    summary=$(ss -antp |awk '{status[$1]++}END{for(i in status) printf i":"status[i]" "}')
    echo "TCP连接状态 - $summary"
}
cpu
memory
disk
tcp_status

cover

PHP 是无处不在的,可以说是互联网 Web 应用上使用最广泛的语言。

然而,它的高性能并不为人所知,尤其是在涉及到高并发系统时。这就是为什么对于这样特殊的用例,正在被 Node (是的,我知道,它不是一种语言)、Go 和 Elixir 等语言接管。

也就是说,您可以做很多事情来改进服务器上的 PHP 性能。本文主要关注 php-fpm 方面的内容,如果您使用 Nginx,这是在服务器上的默认配置。

如果你知道 php-fpm 是什么,请直接跳到优化部分。

什么是 php-fpm?

许多开发人员对 DevOps 方面的知识不太感兴趣,即使是那些对此感兴趣的开发人员,也极少有人知道它的底层原理。有趣的是,当浏览器发送一个请求到运行 PHP 的服务器上时,PHP 也不是最先进行处理请求的服务;而是,HTTP 服务器,Apache 和 Nginx 是其中最主要的两个。「web 服务器」决定如何与 PHP 进行通信,然后传递请求的类型,数据和头部信息到 PHP 进程。

php-fpm

上图是 PHP 项目的请求 - 响应生命周期(图片来源: ProinerTech)

在现代 PHP 应用中,「find file」部分即为 index.php 文件,它是在服务器配置文件中配置的用于处理所有请求的代理。

如今,Web 服务器究竟如何连接 PHP 正在进化,如果我们要深入研究所有细节,这篇文章的长度将激增。但粗略来说,在 Apache 作为 Web 服务器首选的时间段,PHP 是作为包含在服务器内部的模块。

所以每当一个请求被接收,服务器将开启一个新的进程,它将自动包含 PHP 和执行请求。这个方法被称作 mod_php,“PHP 作为一个模块” 的缩写。这种方法有其局限性,而 Nginx 和 php-fpm 克服了它。

在 php-fpm 中,管理 PHP 的责任在于服务器内部的 PHP 程序。换言之,Web 服务器 (Nginx, 在本例中), 不在乎 PHP 在哪和怎样运行的,只要它知道如何发送和接收数据即可。如果需要,在这种情况下,您可以将 PHP 视为另一台服务器,它管理传入请求的某些子 PHP 进程(因此,我们将请求送到服务器,该请求由服务器接收并传递到服务器 — — 太疯狂了!:-P)。

如果你用过 Nginx, 你会看到这些代码:

location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/run/php/php7.2-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

对于这一行:fastcgi_pass unix:/run/php/php7.2-fpm.sock;,它告诉 Nginx 通过 php7.2-fpm.sock 的 socket 与 php
进程通信。因此,对于每个传入的请求,Nginx 都通过这个文件写入数据,在接收到输出后,将其发送回浏览器。

我必须再次强调,对于如何运行这不是最完整或者最准确的,但对于大多数 DevOps 任务是完全准确的。

除此之外,让我们回顾一下到目前为止所学到的东西:

  • PHP 不会直接接收浏览器发送的请求。像 Nginx 这种 Web 服务器首先会拦截它。
  • Web 服务器知道如何连接到 PHP 进程,并将所有请求数据(粘贴所有内容)传递到 PHP 上。
  • PHP 完成其职责后,会将响应发送回 Web 服务器,然后将其发送回客户端(在大多数情况下为浏览器)。

流程图如下:

php nginx php-fpm 流程图

PHP 和 Nginx 如何协同工作? (图片来源:数据狗)

到目前为止都不错,那么关键问题来了:PHP-FPM 到底是什么呢?

PHP 中的 FPM 代表 「快速进程管理器」, 花式解释就是说,在服务器上运行的 PHP 并不是单个进程,而是由这个 FPM 进程管理器派生、控制和终止的一些 PHP 进程。web 服务器将请求传递给的就是这个进程管理器。

PHP-FPM 本身就是一个完整的兔子洞,所以如果您愿意,可以随意探索,但是对于我们的目的,这些解释就足够啦。 ?

为什么要优化 php-fpm?

一般在正常运行的情况下,为什么要考虑优化呢? 为什么不将事物保持原样。

具有讽刺意味的是,一般我为大多数用例提供建议的话。 如果您的设置运行良好,并且没有特殊用例,请使用默认设置。 但是,如果您希望扩展一台机器之外的能力,那么从一台机器中挤出最大的处理能力是必不可少的,因为它可以将您服务器的花费减少一半(甚至更多!)。

要说明的另一件事情是,Nginx 是为处理巨大的工作负载而构建的。 它能够同时处理成千上万的连接,但是如果您的 PHP 设置不合理,那么您将浪费很多资源,因为 Nginx 必须等待 PHP 完成当前处理之后才可以接受下一个请求,最终 Nginx 不能为您的服务提供任何优势!

所以,接下来让我们看看尝试优化 php-fpm 时我们到底要优化什么。

如何优化 PHP-FPM ?

php-fpm 的配置文件在不同服务器上的位置可能不同,因此您需要做一些调查来确定它的位置。在 UNIX 上,你可以使用 find 命令。在我的 Ubuntu 上,它的路径是 /etc/php/7.2/fpm/php-fpm.conf 。当然,7.2 是我正在运行的 PHP 版本。
下面是这个文件的前几行代码:

;;;;;;;;;;;;;;;;;;;;;
; FPM Configuration ;
;;;;;;;;;;;;;;;;;;;;;

; All relative paths in this configuration file are relative to PHP's install
; prefix (/usr). This prefix can be dynamically changed by using the
; '-p' argument from the command line.

;;;;;;;;;;;;;;;;;;
; Global Options ;
;;;;;;;;;;;;;;;;;;

[global]
; Pid file
; Note: the default prefix is /var
; Default Value: none
pid = /run/php/php7.2-fpm.pid

; Error log file
; If it's set to "syslog", log is sent to syslogd instead of being written
; into a local file.
; Note: the default prefix is /var
; Default Value: log/php-fpm.log
error_log = /var/log/php7.2-fpm.log

很明显:这一行 pid = /run/php/php7.2-fpm.pid 告诉我们哪个文件包含了 php-fpm 进程的进程 id。

我们还看到 /var/log/php7.2-fpm.log 是 php-fpm 存储日志的地方。

在这个文件中,像下面这样添加三个变量:

emergency_restart_threshold 10
emergency_restart_interval 1m
process_control_timeout 10s

前两个设置是警告性的,它们告诉 php-fpm 进程,如果 10 个子进程在一分钟内失败,主 php-fpm 进程应该重新启动自己。
这听起来可能不够稳健,但是 PHP 是一个短暂的进程,它会泄漏内存,所以在出现高故障时重新启动主进程可以解决很多问题。
第三个选项是 process_control_timeout,它告诉子进程在执行从父进程接收到的信号之前需要等待这么长的时间。这个设置是非常有用的。例如,当父进程发送终止信号时,子进程正在处理某些事情的时候。十秒的时间,他们会有一个更好的机会完成任务并且优雅地退出。

令人惊讶的是,这 不是 php-fpm 的核心配置!这是因为,为了 web 请求服务,php-fpm 创建了一个新的进程池,它将具有一个单独的配置。在我的例子中,进程池的名称是 www,我想编辑的文件是 /etc/php/7.2/fpm/pool.d/www.conf。
让我们来看看文件的内容:

; Start a new pool named 'www'.
; the variable $pool can be used in any directive and will be replaced by the
; pool name ('www' here)
[www]

; Per pool prefix
; It only applies on the following directives:
; - 'access.log'
; - 'slowlog'
; - 'listen' (unixsocket)
; - 'chroot'
; - 'chdir'
; - 'php_values'
; - 'php_admin_values'
; When not set, the global prefix (or /usr) applies instead.
; Note: This directive can also be relative to the global prefix.
; Default Value: none
;prefix = /path/to/pools/$pool

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
user = www-data
group = www-data

快速浏览一下上面代码片段的末尾,您就会明白为什么服务器进程以 www-data 的形式运行了。如果您在设置网站时遇到文件权限问题,您可能要将目录的所有者或组更改为 www-data,从而允许 PHP 进程写入日志文件和上传文档等。
最后,我们到达了问题的根源,流程管理器 (pm) 设置。一般情况下,默认值是这样的:

pm = dynamic
pm.max_children = 5
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 200

那么,这里的 「dynamic(动态)」是什么意思呢?我认为官方文档最好地解释了这一点 (我的意思是,这应该已经是您正在编辑的文件的一部分,但是我在这里复制了它,以防它不是):

; Choose how the process manager will control the number of child processes.
; Possible Values:
;   static  - a fixed number (pm.max_children) of child processes;
;   dynamic - the number of child processes are set dynamically based on the
;             following directives. With this process management, there will be
;             always at least 1 children.
;             pm.max_children      - the maximum number of children that can
;                                    be alive at the same time.
;             pm.start_servers     - the number of children created on startup.
;             pm.min_spare_servers - the minimum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is less than this
;                                    number then some children will be created.
;             pm.max_spare_servers - the maximum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is greater than this
;                                    number then some children will be killed.
;  ondemand - no children are created at startup. Children will be forked when
;             new requests will connect. The following parameter are used:
;             pm.max_children           - the maximum number of children that
;                                         can be alive at the same time.
;             pm.process_idle_timeout   - The number of seconds after which
;                                         an idle process will be killed.
; Note: This value is mandatory.

由此可见,有三个可用值:

  • Static: 无论什么情况,都会保持一个固定的 PHP 进程数量。
  • Dynamic: 我们需要指定 php-fpm 在任何给定时间点会保持活动的最小以及最大进程数量。
  • ondemand: 按照需求创建和销毁进程。

那这些设置有什么影响呢?

简而言之,如果你有个小流量的网站,“dynamic” 设置在大多数时间内都是一种资源的浪费。假设你的 pm.min_spare_servers 设置成了 3,那会有三个 PHP 进程会被创建并保持运行,甚至是网站没有流量时。这种情况下,“ondemand” 就是个更好的选择,可以让系统决定何时启动新的进程。

另一方面,大流量 或者必须快速响应的网站将在这种情况下被惩罚。 最好避免创建新的 PHP 进程的额外开销,使其成为池的一部分并对其进行监控。

使用 pm = static 固定子进程的数量,使最大的系统资源用于服务请求而不是管理 PHP。假如你确定走这条路,注意它有其指导方针和陷阱。关于它的一篇相当密集但非常有用的文章是 这篇 。

写在最后

由于有关网络性能的文章可能会引发争论或使人们感到困惑,因此在结束本文之前,我觉得需要讲几句话。 性能调优既涉及系统知识,也涉及猜测和技巧。

即使您完全了解 php-fpm 的所有设置,也无法保证成功。 如果您不了解 php-fpm 的存在,那么您就不必浪费时间担心它。 继续做您已经在做的事情并继续下去。

同时,尽可能不让结果变得很戏剧性。 是的,您可以通过从头开始重新编译 PHP 并删除所有不需要的模块来获得更好的性能,但是这种方法在生产环境中不够明智。 优化某些内容的整个想法是查看您的需求是否与默认值不同(它们很少这样做!),并根据需要进行较小的更改。

课程地址:https://edu.51cto.com/course/15585.html

背景

新购买10台服务器并已安装Linux操作系统

需求:

  1. 安装系统新能分析工具已经其他的工具
  2. 设置时区并同步时间
  3. 禁用selinux
  4. 清空防火墙默认策源
  5. 历史命令显示操作时间
  6. 禁止root远程登录
  7. 禁止定时任务发送邮件
  8. 设置最大打开文件数
  9. 减少Swap使用
  10. 系统内核参数的优化

脚本编写

#/bin/bash
# 安装系统性能分析工具及其他
yum install gcc make autoconf vim sysstat net-tools iostat iftop iotp wget lrzsz lsof unzip openssh-clients net-tool vim ntpdate -y

# 设置时区并同步时间
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
if ! crontab -l |grep ntpdate &>/dev/null ; then
    (echo "* 1 * * * ntpdate time.windows.com >/dev/null 2>&1";crontab -l) |crontab 
fi

# 禁用selinux
sed -i '/SELINUX/{s/permissive/disabled/}' /etc/selinux/config

# 关闭防火墙
if egrep "7.[0-9]" /etc/redhat-release &>/dev/null; then
    systemctl stop firewalld
    systemctl disable firewalld
elif egrep "6.[0-9]" /etc/redhat-release &>/dev/null; then
    service iptables stop
    chkconfig iptables off
fi

# 历史命令显示操作时间
if ! grep HISTTIMEFORMAT /etc/bashrc; then
    echo 'export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S  `whoami` "' >> /etc/bashrc
fi

# SSH超时时间
if ! grep "TMOUT=600" /etc/profile &>/dev/null; then
    echo "export TMOUT=600" >> /etc/profile
fi

# 禁止root远程登录 切记给系统添加普通用户,给su到root的权限
sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config

# 禁止定时任务向发送邮件
sed -i 's/^MAILTO=root/MAILTO=""/' /etc/crontab 

# 设置最大打开文件数
if ! grep "* soft nofile 65535" /etc/security/limits.conf &>/dev/null; then
cat >> /etc/security/limits.conf << EOF
    * soft nofile 65535
    * hard nofile 65535
EOF
fi

# 系统内核优化
cat >> /etc/sysctl.conf << EOF
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_tw_buckets = 20480
net.ipv4.tcp_max_syn_backlog = 20480
net.core.netdev_max_backlog = 262144
net.ipv4.tcp_fin_timeout = 20  
EOF

# 减少SWAP使用
echo "0" > /proc/sys/vm/swappiness