乐蛙小院 - 转载
type: channel
link: https://blog.keer.me/category/reprint/
description: 转载的好文章
items: 【转载】配置 Nginx 反代实现 OSS 内网访问
items: [转载]数字签名是什么？
items: 配置git hook部署项目代码
items: epoll的本质是什么
items: 理解和使用 Go 接口
items: 斐讯K3 固件降级刷官改固件详细图文教程
items: 如果在Ubuntu中切换多个PHP版本
items: 同步、异步、阻塞、非阻塞区别
items: 你真的懂Nginx与PHP的交互？

【转载】配置 Nginx 反代实现 OSS 内网访问
type: item
date: 2025-01-02T09:35:34+08:00
description: 手头有个项目的磁盘占用空间日益见涨，已经逼近 90% 的警戒线。登上去排查了下，一半都是被图片给占用了。既然是图片，我琢磨着是不是可以把这些文件以及相关功能都迁移到云存储上去。服务器是阿里云的，所以这是一份 OSS 的折腾记录。选择同区域 OSS阿里云的 OSS 收费项分的很细，存储是存储的，流量是流量的。存储费按使用的容量来计算，用多少收多少，这个很直观，也比较好掌控。流量费就不好说了，图片一不小心被盗链，或是其他原因流量突然暴增，会带来一些预料之外的开支。好在 OSS 提供了一个比较良心的选择：内网流量免费。通过这个规则，我只需要让访问 OSS 的流量都走内网，就不用担心流量方面的开支了。那么问题来了：如何打通 OSS 的内网访问流量？两个前提：阿里云服务器 1 个。同区域的 OSS 1 个。注意我加粗了的「同区域」。举个例子，如果服务器在杭州，OSS 买到了深圳，按照 OSS 的收费规则，这是跨区域，存在流量费用。如果非要这么安排，但又想减省一点流量费，那就只有在 OSS 所在区域再买一台服务器，然后再通过公网流量连接另一台服务器。这还是会有点得不偿失，因为阿里云加的服务器也不便宜。Nginx 代理配置准备好了同区域的服务器和 OSS 后，接下来就是找个软件，让服务器转发一下访问 OSS 文件的请求。这里我用的是 Nginx。在 Nginx 上要实现这个功能只需要在 server 区域中添加下面几行代理配置：location /upload/ { proxy_pass http://test.oss-cn-hangzhou-internal.aliyuncs.com/; proxy_hide_header Content-Disposition; }注意上面配置中的 test 要替换为自己在 OSS 创建的 Bucket 名称。通过浏览器访问 OSS 上的图片文件时，默认是下载的形式，我希望直接在浏览器显示，proxy_hide_header Content-Disposition; 就是这个用途。完成以上配置后重启 Nginx，通过服务器的地址 + /upload/ + OSS 上存在的文件名，就能走内网方式访问 OSS 的文件了。这一步可能会碰到 OSS 输出 AccessDenied 类似的错误，如果错误消息中有类似 ACL 之类的字眼，可以检查一下创建的 Bucket 是不是「私有」级别权限。这个权限安全级别较高，需要请求中附带一些认证字段才能正常访问。那相应的也需要特殊版本的 Nginx 配合相关插件才能搞定。我不想这么麻烦，所以需要把 Bucket 的 ACL 权限设置为「公共读」。禁止 OSS 直访在使用了「公共读」的权限后，就违背了折腾的初衷。因为这意味着 OSS 上的文件都有了一个公网可访问的地址，这是一个会导致 OSS 流量费的隐患，所以需要堵住这个入口。OSS 支持通过 Referer 方式设置防盗链功能。通过此功能设置一个 IP 白名单，内容就是服务器的访问地址，然后完善一下刚才的 Nginx 代码：location /upload/ { proxy_pass http://test.oss-cn-hangzhou-internal.aliyuncs.com/; proxy_hide_header Content-Disposition; proxy_set_header referer $host; }这样就只能通过服务器的请求来访问 OSS 的文件了。使用 OSS 缩略图功能OSS 自带了缩略图功能，但使用的方式有点特别。是通过在文件地址后面添加处理参数的方式来操作的。类似于这样：/upload/1.jpg?x-oss-process=image/resize,m_fill,w_100,h_100这会输出长宽都是 100 像素的小图。不可否认，这种方式用起来是很方便，但就像我在开头所说的，OSS 的收费项很细，这个图片处理也会产生费用。如果让这个功能直接暴露出来，一样是一个费用隐患。所以我想把这个功能包装起来，让用法看起来如下：/upload/1-100.jpg这要用到 Nginx 的 Rewrite 功能。调整后的 Nginx 配置如下：location /upload/ { rewrite /(.*)-100.(.*) /$1.$2?x-oss-process=image/resize,m_fill,w_100,h_100 break; proxy_pass http://test.oss-cn-hangzhou-internal.aliyuncs.com/; proxy_hide_header Content-Disposition; proxy_set_header referer $…

[转载]数字签名是什么？
type: item
date: 2020-03-18T12:23:53+08:00
description: 今天，我读到一篇好文章。它用图片通俗易懂地解释了，"数字签名"（digital signature）和"数字证书"（digital certificate）到底是什么。我对这些问题的理解，一直是模模糊糊的，很多细节搞不清楚。读完这篇文章后，发现思路一下子就理清了。为了加深记忆，我把文字和图片都翻译出来了。文中涉及的密码学基本知识，可以参见我以前的笔记。====================================================数字签名是什么？作者：David Youd翻译：阮一峰原文网址：http://www.youdzone.com/signature.html1.鲍勃有两把钥匙，一把是公钥，另一把是私钥。2.鲍勃把公钥送给他的朋友们----帕蒂、道格、苏珊----每人一把。3.苏珊要给鲍勃写一封保密的信。她写完后用鲍勃的公钥加密，就可以达到保密的效果。4.鲍勃收信后，用私钥解密，就看到了信件内容。这里要强调的是，只要鲍勃的私钥不泄露，这封信就是安全的，即使落在别人手里，也无法解密。5.鲍勃给苏珊回信，决定采用"数字签名"。他写完后先用Hash函数，生成信件的摘要（digest）。6.然后，鲍勃使用私钥，对这个摘要加密，生成"数字签名"（signature）。7.鲍勃将这个签名，附在信件下面，一起发给苏珊。8.苏珊收信后，取下数字签名，用鲍勃的公钥解密，得到信件的摘要。由此证明，这封信确实是鲍勃发出的。9.苏珊再对信件本身使用Hash函数，将得到的结果，与上一步得到的摘要进行对比。如果两者一致，就证明这封信未被修改过。10.复杂的情况出现了。道格想欺骗苏珊，他偷偷使用了苏珊的电脑，用自己的公钥换走了鲍勃的公钥。此时，苏珊实际拥有的是道格的公钥，但是还以为这是鲍勃的公钥。因此，道格就可以冒充鲍勃，用自己的私钥做成"数字签名"，写信给苏珊，让苏珊用假的鲍勃公钥进行解密。11.后来，苏珊感觉不对劲，发现自己无法确定公钥是否真的属于鲍勃。她想到了一个办法，要求鲍勃去找"证书中心"（certificate authority，简称CA），为公钥做认证。证书中心用自己的私钥，对鲍勃的公钥和一些相关信息一起加密，生成"数字证书"（Digital Certificate）。12.鲍勃拿到数字证书以后，就可以放心了。以后再给苏珊写信，只要在签名的同时，再附上数字证书就行了。13.苏珊收信后，用CA的公钥解开数字证书，就可以拿到鲍勃真实的公钥了，然后就能证明"数字签名"是否真的是鲍勃签的。14.下面，我们看一个应用"数字证书"的实例：https协议。这个协议主要用于网页加密。15.首先，客户端向服务器发出加密请求。16.服务器用自己的私钥加密网页以后，连同本身的数字证书，一起发送给客户端。17.客户端（浏览器）的"证书管理器"，有"受信任的根证书颁发机构"列表。客户端会根据这张列表，查看解开数字证书的公钥是否在列表之内。18.如果数字证书记载的网址，与你正在浏览的网址不一致，就说明这张证书可能被冒用，浏览器会发出警告。19.如果这张数字证书不是由受信任的机构颁发的，浏览器会发出另一种警告。20.如果数字证书是可靠的，客户端就可以使用证书中的服务器公钥，对信息进行加密，然后与服务器交换加密信息。（完）

配置git hook部署项目代码
type: item
date: 2019-09-19T22:22:00+08:00
description: git 的教程可以查看廖雪峰老师的官网：https://www.liaoxuefeng.com/wiki/896043488029600，讲解的非常详细也非常好，基本可以满足正常的工作需求使用。基本流程就是：1、新建本地开发分支（比如 yangzhenhua 分支），提交也是提交的开发分支。2、本地测试无问题之后合并到 master 分支。3、然后 git push origin master 分支到 git 服务器。4、服务器钩子自动 git pull origin master 分支到项目目录。这样我们就在 yangzhenhua 分支工作开发，然后提交也可以提交到 yangzhenhua 分支上，因为服务器钩子虽然是本地提交才会触发，但是钩子是更新的 master 分支，所以提交开发分支并不会影响生产环境项目目录，什么时候想上线了，直接本地分支合并到 master 分支然后 git push origin master 分支就可以了。准备工作1、服务器端安装 git[root@localhost ~]# yum install git [root@localhost ~]# git version2、客户端安装 git我是 windows 环境直接在https://git-scm.com/downloads下载客户端安装，然后使用 Git Bash 作为命令行客户端。安装完成后可以查看下 git 版本。$ git version git version 2.9.2.windows.1搭建 git 服务器1、服务器端创建 git 用户来管理 git 服务[root@localhost ~]# id git id: git: no such user [root@localhost ~]# useradd git [root@localhost ~]# passwd git New password: BAD PASSWORD: The password contains the user name in some form Retype new password: passwd: all authentication tokens updated successfully.2、创建证书登录[root@localhost ~]# cd /home/git/ [root@localhost git]# sudo -u git mkdir .ssh [root@localhost git]# chmod 700 .ssh/ [root@localhost git]# sudo -u git touch .ssh/authorized_keys [root@localhost git]# chmod 600 .ssh/authorized_keys3、服务器端创建 git 服务器[root@localhost ~]# mkdir -p /home/data/git/gittest.git [root@localhost ~]# git init --bare /home/data/git/gittest.git/ Initialized empty Git repository in /home/data/git/gittest.git/ [root@localhost home]# cd /home/data/git/ [root@localhost git]# ll 总用量 4 drwxr-xr-x 7 root root 4096 5月 9 01:17 gittest.git [root@localhost git]# chown -R git:git gittest.git/ [root@localhost git]# ll 总用量 4 drwxr-xr-x 7 git git 4096 5月 9 01:17 gittest.git我们默认设置 /home/data/git/gittest.git 为 git 仓库，当我们执行完 git init --bare /home/data/git/gittest.git/ 命令时，git 就会创建一个裸仓库，裸仓库没有工作区，因为服务器上的 git 仓库只是为了共享，所以不让用户直接登录到服务器上去改工作区，并且服务器上的 git 仓库通常都以 .git 结尾，然后把权限改为 git客户端 clone 远程仓库1、客户端开发目录下打开你的 Git Bash 终端克隆远程仓库$ git clone git@192.168.160.130:/home/data/git/gittest.git www.yanghaihua.com Cloning into 'gittest'... T…

epoll的本质是什么
type: item
date: 2019-07-22T09:37:00+08:00
description: 看到一篇对epoll讲解很到位的文章，转载收藏一下。从事服务端开发，少不了要接触网络编程。epoll 作为 Linux 下高性能网络服务器的必备技术至关重要，nginx、Redis、Skynet 和大部分游戏服务器都使用到这一多路复用技术。epoll 很重要，但是 epoll 与 select 的区别是什么呢？epoll 高效的原因是什么？网上虽然也有不少讲解 epoll 的文章，但要么是过于浅显，或者陷入源码解析，很少能有通俗易懂的。笔者于是决定编写此文，让缺乏专业背景知识的读者也能够明白 epoll 的原理。文章核心思想是：要让读者清晰明白 epoll 为什么性能好。本文会从网卡接收数据的流程讲起，串联起 CPU 中断、操作系统进程调度等知识；再一步步分析阻塞接收数据、select 到 epoll 的进化过程；最后探究 epoll 的实现细节。下边是一个典型的计算机结构图，计算机由 CPU、存储器（内存）与网络接口等部件组成，了解 epoll 本质的第一步，要从硬件的角度看计算机怎样接收网络数据。计算机结构图（图片来源：Linux内核完全注释之微型计算机组成结构）下图展示了网卡接收数据的过程。在 ① 阶段，网卡收到网线传来的数据；经过 ② 阶段的硬件电路的传输；最终 ③ 阶段将数据写入到内存中的某个地址上。这个过程涉及到 DMA 传输、IO 通路选择等硬件有关的知识，但我们只需知道：网卡会把接收到的数据写入内存。网卡接收数据的过程通过硬件传输，网卡接收的数据存放到内存中，操作系统就可以去读取它们。了解 epoll 本质的第二步，要从 CPU 的角度来看数据接收。理解这个问题，要先了解一个概念——中断。计算机执行程序时，会有优先级的需求。比如，当计算机收到断电信号时，它应立即去保存数据，保存数据的程序具有较高的优先级（电容可以保存少许电量，供 CPU 运行很短的一小段时间）。一般而言，由硬件产生的信号需要 CPU 立马做出回应，不然数据可能就丢失了，所以它的优先级很高。CPU 理应中断掉正在执行的程序，去做出响应；当 CPU 完成对硬件的响应后，再重新执行用户程序。中断的过程如下图，它和函数调用差不多，只不过函数调用是事先定好位置，而中断的位置由“信号”决定。中断程序调用以键盘为例，当用户按下键盘某个按键时，键盘会给 CPU 的中断引脚发出一个高电平，CPU 能够捕获这个信号，然后执行键盘中断程序。下图展示了各种硬件通过中断与 CPU 交互的过程。CPU 中断（图片来源：net.pku.edu.cn）现在可以回答“如何知道接收了数据？”这个问题了：当网卡把数据写入到内存后，网卡向 CPU 发出一个中断信号，操作系统便能得知有新数据到来，再通过网卡中断程序去处理数据。了解 epoll 本质的第三步，要从操作系统进程调度的角度来看数据接收。阻塞是进程调度的关键一环，指的是进程在等待某事件（如接收到网络数据）发生之前的等待状态，recv、select 和 epoll 都是阻塞方法。下边分析一下进程阻塞为什么不占用 CPU 资源？为简单起见，我们从普通的 recv 接收开始分析，先看看下面代码：//创建socket int s = socket(AF_INET, SOCK_STREAM, 0); //绑定 bind(s, ...) //监听 listen(s, ...) //接受客户端连接 int c = accept(s, ...) //接收客户端数据 recv(c, ...); //将数据打印出来 printf(...)这是一段最基础的网络编程代码，先新建 socket 对象，依次调用 bind、listen 与 accept，最后调用 recv 接收数据。recv 是个阻塞方法，当程序运行到 recv 时，它会一直等待，直到接收到数据才往下执行。那么阻塞的原理是什么？工作队列操作系统为了支持多任务，实现了进程调度的功能，会把进程分为“运行”和“等待”等几种状态。运行状态是进程获得 CPU 使用权，正在执行代码的状态；等待状态是阻塞状态，比如上述程序运行到 recv 时，程序会从运行状态变为等待状态，接收到数据后又变回运行状态。操作系统会分时执行各个运行状态的进程，由于速度很快，看上去就像是同时执行多个任务。下图的计算机中运行着 A、B 与 C 三个进程，其中进程 A 执行着上述基础网络程序，一开始，这 3 个进程都被操作系统的工作队列所引用，处于运行状态，会分时执行。工作队列中有 A、B 和 C 三个进程等待队列当进程 A 执行到创建 socket 的语句时，操作系统会创建一个由文件系统管理的 socket 对象（如下图）。这个 socket 对象包含了发送缓冲区、接收缓冲区与等待队列等成员。等待队列是个非常重要的结构，它指向…

理解和使用 Go 接口
type: item
date: 2019-05-18T16:38:00+08:00
description: Go 语言也提供了接口类型，使得我们可以面向接口编程，将实现和接口分离。在我看来，软件的抽象之美也应该以此来表达，和 Java 语言不同的是 Go 并不是那么 “强制”，它使用了一种 鸭子类型 的方式让动态类型成为可能。Duck Typing在 Go 中没有 implements 和 extends 这种关键字，这对我们而言反倒轻松了一些，它认为 Go 的接口就像鸭子测试里的描述：当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子，那么这只鸟就可以被称为鸭子。在鸭子类型中，关注点在于对象的行为，能作什么；而不是关注对象所属的类型。例如，在不使用鸭子类型的语言中，我们可以编写一个函数，它接受一个类型为 “鸭子” 的对象，并调用它的 “走” 和 “叫” 方法。在使用鸭子类型的语言中，这样的一个函数可以接受一个任意类型的对象，并调用它的 “走” 和 “叫” 方法。如果这些需要被调用的方法不存在，那么将引发一个运行时错误。任何拥有这样的正确的 “走” 和 “叫” 方法的对象都可被函数接受的这种行为引出了以上表述，这种决定类型的方式因此得名。举个例子我们用 Go 语言来实现一个鸭子类型：type Duck interface { Swim() // 游泳 Feathers() // 羽毛 }这里使用 Go 提供的 interface 关键字定义了一个鸭子接口类型，这个接口中提供了鸭子的两种行为：游泳和羽毛是什么样的，但是没有提供实现。我们见过的鸭子类型可多了去了，下面是你可能见过的：组合接口现在我们给鸭子再添加一种嘎嘎叫的能力，一种方式是在原有的接口上添加 “嘎嘎叫” 方法，这样做的话就表示所有的鸭子都应该拥有此能力，假设我们的玩具鸭并不能开口嘎嘎叫，所以它没有这种能力。这时候我们可以将会嘎嘎叫的鸭子单独定义一种类型，在 Go 可以使用组合的方式来实现：type QuackDuck interface { Quack() // 嘎嘎叫 Duck // 嵌入接口 }这样 QuackDuck 类型就拥有了之前 Duck 提供的两种抽象能力，同时还应该拥有嘎嘎叫的能力。接口实现前面我们只给出了鸭子的能力定义，还没有任何实现，由于 Go 中没有继承和实现的关键字，想成为上述接口的实现非常简单，只要实现它们定义的方法就可以了。// RealDuck - 真正的鸭子 type RealDuck struct { } func (RealDuck) Swim() { fmt.Println("用鸭璞向后划水") } func (RealDuck) Feathers() { fmt.Println("遇到水也不会湿的羽毛") } func (RealDuck) Quack() { fmt.Println("嘎~ 嘎~ 嘎~") } // ToyDuck - 玩具鸭 type ToyDuck struct { } func (ToyDuck) Swim() { fmt.Println("以固定的速度向前移动") } func (ToyDuck) Feathers() { fmt.Println("白色的固定的塑料羽毛") }可以看到我们定义了两种鸭子类型，一种是真正的鸭子，它还多实现了一种嘎嘎叫方法，另一个玩具鸭子只有游泳和羽毛这两种行为。这个编程方式和我们写普通的结构体方法没什么区别，只是对应的方法签名相同，其实这种方式在 Go 语言的标准库中有特别多的应用，比如：io.Reader、io.Writer 和 io.Closer。接口使用接下来我们可以使用一下这个类型了：var duck Duck duck = ToyDuck{} duck.Swim() duck.Feathers()输出以固定的速度向前移动 白色的固定的塑料羽毛由于玩具鸭没有嘎嘎叫的能力，所以如果你这么写编译无法通过我们也可以用一种工厂的方式来进行调用：func Factory(name string) Duck { switch name { case "toy": return &ToyDuck{} case "real": return &RealDuck{} default: panic("No such duck") } } func main() { duck := Factory("toy") duck.Swim() duck.Feathers() }小结其实这就是 Go 中的多态体现，仔细体会其中的味道，在阅读其他源码的时候你会更加熟练。但是也有一个问题当代码工程庞大的时候你很难知道一个接口体到底实现了哪些接口，不过有幸的是我们生在最智能的 IDE 时代，在 GoLand 中可以帮你提示 :D转载于https://blog.biezhi.me/2019/01/le…

斐讯K3 固件降级刷官改固件详细图文教程
type: item
date: 2018-11-24T20:11:00+08:00
description: 简介这篇文章是对原作者文章的一个转载，以防止文章丢失。对应的更新了文中的图片链接，其余内容均未修改。正文在购买了斐讯K3后，第一件事就是刷机，以下是具体操作步骤，建议全程处于 chrome 浏览器的“无痕窗口”下进行。第一部分官方固件降级 58 固件，降级 246 固件输入路由器地址 p.to 或者 192.168.2.1 进入路由器后台页面，找到“功能设置”，点击“系统状态”然后查看“硬件版本”和“软件版本”我这边的“硬件版本”是“A2”，“软件版本”是 V21.6.12.6我们需要将软件版本“V21.6.12.6”先降级为“V21.6.11.58”，在“功能设置”选项中往下滑，找到“手动升级”选项选择固件“K3_V21.6.11.58p.bin”，然后点击“上传升级”然后会提示“正在升级”，K3的屏幕上面会显示“系统升级中”升级完成会自动刷新网页，重新进入“手动升级”页面。，可以看到当前版本为“V21.6.11.58”，然后我们还需要再次降级，降级为“V21.5.37.246”我们再选择“升级文件”，选择“K3_V21.5.37.246.bin”，然后点击“上传升级”，然后会再次进入如上图一样的“正在升级”页面。升级成功后，登录K3，我们打开“功能设置”中的“系统状态”，可以看到固件版本已经降级为了“V21.5.37.246”，然后执行一次重启路由器操作，确保接下来的操作不出问题。第二部分激活 telnet 端口，备份K3激活 telnet 工具地址：http://www.right.com.cn/forum/thread-261028-1-1.html打开 RoutAckProV1B2.exe，“路由器 IP”默认为 192.168.2.1，斐讯默认的路由器地址，不需要更改，然后先点击“打开 Telnet”，然后点击“设置永久标志”，输出信息如下“收到应答信号，打开 Telnet 完成，收到应答信号，设置永久标志完成”，最后关掉软件即可插入 U 盘，然后前往“存储管理”中查看 U 盘是否正常识别打开软件“PuTTY”连接 K3 的 Telnet，“主机名称（或 IP 地址）”处填写路由器地址 192.168.2.1，“连接类型”选择”Telnet”，然后点击“打开”输入命令“mount”查看 U 盘是否挂在成功，成功的话会看到 sda 开头的设备，我这边是显示 sda1输入如下命令进行备份操作cd /tmp/share/sda1 (进入 U 盘,注意 sda1) mkdir backup (建立备份文件夹) cd backup (进入 backup 目录) cat /dev/mtdblock0 > mtdblock0.bin (备份 CFE) cat /dev/mtdblock5 > mtdblock5.bin (备份设备信息) cat /dev/mtdblock6 > mtdblock6.bin (备份固件) ls 备份完成后，进入“存储管理”中点击“安全移除”，如果无法移除，那就直接拔 U 盘，然后将备份的文件拷贝到电脑或者网盘进行二次备份。第三部分刷入官改固件打开”Tftpd64.exe”,”Current Diectory”为路由器固件所在的文件夹，点击“Browes”可修改，一般默认既为 Tftpd64 所在文件夹的路径，“Server interfaces”为连接路由所获取到的本机电脑的 ip 地址，手动选择 ip 地址为“192.168.2.x”，x 为路由自动获取到的地址，可能不一样，我这边的 ip 地址为 192.168.2.100再次打开“PuTTY”，如上次步骤输入 ip 地址为 192.168.2.1，连接类型为“Telnet”，然后单击“开始”进行连接，然后输入如下命令刷入。蓝色字体为固件名称，我这边名称为“k3_v15.bin”；红色字体为本机电脑 ip 地址，我这边 ip 地址为“192.168.2.100”cd /tmp; tftp -g -r k3_v15.bin 192.168.2.100上传成功后输入刷入固件命令cat k3_v15.bin > /dev/mtdblock6 && reboot稍等片刻，会出现如下错误，rmmod dhd failed: No such file or directory，说明路由器已经断开了 PuTTY 的连接，K3 的屏幕上也显示为“系统升级中”，这时候等了 2,3 分钟进入 192.168.2.1 即可。成功后打开 192.168.2.1 登录后台，查看“系统信息”，可以看到已经已经刷入成功，至此刷入完成链接: https://cloud.189.cn/web/share?code=byMVZnriUzEb（访问码：1kvd）文章转载于 http…

如果在Ubuntu中切换多个PHP版本
type: item
date: 2018-11-13T23:17:00+08:00
description: 有时，最新版本的安装包可能无法按预期工作。你的程序可能与更新的软件包不兼容，并且仅支持特定的旧版软件包。在这种情况下，你可以立即将有问题的软件包降级到其早期的工作版本。请参阅我们的旧指南，在这了解如何降级 Ubuntu 及其衍生版中的软件包以及在这了解如何降级 Arch Linux 及其衍生版中的软件包。但是，你无需降级某些软件包。我们可以同时使用多个版本。例如，假设你在测试部署在 Ubuntu 18.04 LTS 中的LAMP 栈的 PHP 程序。过了一段时间，你发现应用程序在 PHP 5.6 中工作正常，但在 PHP 7.2 中不正常（Ubuntu 18.04 LTS 默认安装 PHP 7.x）。你打算重新安装 PHP 或整个 LAMP 栈吗？但是没有必要。你甚至不必将 PHP 降级到其早期版本。在这个简短的教程中，我将向你展示如何在 Ubuntu 18.04 LTS 中切换多个 PHP 版本。它没你想的那么难。请继续阅读。在多个 PHP 版本之间切换要查看 PHP 的默认安装版本，请运行：$ php -v PHP 7.2.7-0ubuntu0.18.04.2 (cli) (built: Jul 4 2018 16:55:24) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.2.7-0ubuntu0.18.04.2, Copyright (c) 1999-2018, by Zend Technologies如你所见，已安装的 PHP 的版本为 7.2.7。在测试你的程序几天后，你会发现你的程序不支持 PHP7.2。在这种情况下，同时使用 PHP5.x 和 PHP7.x 是个不错的主意，这样你就可以随时轻松地在任何支持的版本之间切换。你不必删除 PHP7.x 或重新安装 LAMP 栈。你可以同时使用 PHP5.x 和 7.x 版本。我假设你还没有在你的系统中卸载 PHP 5.6。万一你已将其删除，你可以使用下面的 PPA 再次安装它。你可以从 PPA 中安装 PHP 5.6：$ sudo add-apt-repository -y ppa:ondrej/php $ sudo apt update $ sudo apt install php5.6从 PHP 7.x 切换到 PHP 5.x.首先使用命令禁用 PHP 7.2 模块：$ sudo a2dismod php7.2 Module php7.2 disabled. To activate the new configuration, you need to run: systemctl restart apache2接下来，启用 PHP 5.6 模块：$ sudo a2enmod php5.6将 PHP 5.6 设置为默认版本：$ sudo update-alternatives --set php /usr/bin/php5.6或者，你可以运行以下命令来设置默认情况下要使用的全局 PHP 版本。$ sudo update-alternatives --config php输入选择的号码将其设置为默认版本，或者只需按回车键保持当前选择。如果你已安装其他 PHP 扩展，请将它们设置为默认值。$ sudo update-alternatives --set phar /usr/bin/phar5.6最后，重启 Apache Web 服务器：$ sudo systemctl restart apache2现在，检查 PHP 5.6 是否是默认版本：$ php -v PHP 5.6.37-1+ubuntu18.04.1+deb.sury.org+1 (cli) Copyright (c) 1997-2016 The PHP Group Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies从 PHP 5.x 切换到 PHP 7.x.同样，你可以从 PHP 5.x 切换到 PHP 7.x 版本，如下所示。$ sudo a2enmod php7.2 $ sudo a2dismod php5.6 $ sudo update-alternatives --set php /usr/bin/php7.2 $ sudo systemctl restart apach…

同步、异步、阻塞、非阻塞区别
type: item
date: 2018-03-12T23:02:00+08:00
description: 一直不能很好的理解这4者之间的关系和区别，下面是在知乎上看到严肃的一个回答，感觉说的比较不错。而且比较容易理解，所以转载过来记录一下。阻塞与非阻塞与同步与异步 不能简单的从字面理解，提供一个从分布式系统角度的回答。同步与异步同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)所谓同步，就是在发出一个调用时，在没有得到结果之前，该调用就不返回。但是一旦调用返回，就得到返回值了。换句话说，就是由调用者主动等待这个调用的结果。而异步则是相反，调用在发出之后，这个调用就直接返回了，所以没有返回结果。换句话说，当一个异步过程调用发出后，调用者不会立刻得到结果。而是在调用发出后，被调用者通过状态、通知来通知调用者，或通过回调函数处理这个调用。典型的异步编程模型比如Node.js举个通俗的例子：你打电话问书店老板有没有《分布式系统》这本书，如果是同步通信机制，书店老板会说，你稍等，”我查一下"，然后开始查啊查，等查好了（可能是5秒，也可能是一天）告诉你结果（返回结果）。而异步通信机制，书店老板直接告诉你我查一下啊，查好了打电话给你，然后直接挂电话了（不返回结果）。然后查好了，他会主动打电话给你。在这里老板通过“回电”这种方式来回调。阻塞与非阻塞阻塞和非阻塞关注的是程序在等待调用结果（消息，返回值）时的状态.阻塞调用是指调用结果返回之前，当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前，该调用不会阻塞当前线程。还是上面的例子，你打电话问书店老板有没有《分布式系统》这本书，你如果是阻塞式调用，你会一直把自己“挂起”，直到得到这本书有没有的结果，如果是非阻塞式调用，你不管老板有没有告诉你，你自己先一边去玩了， 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。如果是关心blocking IO/ asynchronous IO, 参考 Unix Network Programming View Book作者：严肃链接：https://www.zhihu.com/question/19732473/answer/20851256来源：知乎注解二老张爱喝茶，废话不说，煮开水。出场人物：老张，水壶两把（普通水壶，简称水壶；会响的水壶，简称响水壶）。老张把水壶放到火上，立等水开。（同步阻塞）老张觉得自己有点傻老张把水壶放到火上，去客厅看电视，时不时去厨房看看水开没有。（同步非阻塞）老张还是觉得自己有点傻，于是变高端了，买了把会响笛的那种水壶。水开之后，能大声发出嘀~~~~的噪音。老张把响水壶放到火上，立等水开。（异步阻塞）老张觉得这样傻等意义不大老张把响水壶放到火上，去客厅看电视，水壶响之前不再去看它了，响了再去拿壶。（异步非阻塞）老张觉得自己聪明了。所谓同步异步，只是对于水壶而言。普通水壶，同步；响水壶，异步。虽然都能干活，但响水壶可以在自己完工之后，提示老张水开了。这是普通水壶所不能及的。同步只能让调用者去轮询自己（情况2中），造成老张效率的低下。所谓阻塞非阻塞，仅仅对于老张而言。立等的老张，阻塞；看电视的老张，非阻塞。情况1和情况3中老张就是阻塞的，媳妇喊他都不知道。虽然3中响水壶是异步的，可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的，这样才能发挥异步的效用。作者：愚抄链接：https://www.zhihu.com/question/19732473/answer/23434554来源：知乎

你真的懂Nginx与PHP的交互？
type: item
date: 2018-03-08T22:14:00+08:00
description: Nginx是俄国人最早开发的Webserver，现在已经风靡全球，相信大家并不陌生。PHP也通过二十多年的发展来到了7系列版本，更加关注性能。这对搭档在最近这些年，叱咤风云，基本上LNMP成了当下的标配。可是，你用了这么多年的Nginx+PHP的搭配，你真正知道他们之间是怎么交互怎么通信的么？作为一道常常用来面试的考题，从过往经验看，情况并不乐观。更多的同学是知道PHP-FPM、知道FastCGI，但不晓得Nginx、PHP这对老搭档具体的交互细节。那么，今天我们就来一起学习一下，做一回认真的PHP工程师。前菜为了讲解的有理有据，我们先来准备一个纯净精简的Nginx+PHP环境，这里我们使用Docker拉取Centos最新版本环境，来快速通过编译安装方式搭建一个Nginx+PHP环境。有了Linux环境，我们来源码编译安装Nginx、PHP，这个过程网络里有很多的教程，我们就不细说了。当然你也可以安装lnmp一键安装包来快速搭建。通过安装nginx、php，我们的Linux环境里就有了今天的这两位主角了。我们稍加配置，让Nginx可以接收请求并转发给PHP-FPM，我们目标是输出一个phpinfo()的信息。我们通过对Nginx新增Server配置实现了nginx与PHP的一次通信，配置文件非常简单，如下图有了上面的一个sample示例，我们开始深入Nginx与FastCGI协议。主食从上图的Nginx配置中可以注意到 fastcgi 开头的一些配置，以及引入的 fastcgi.conf 文件。其实在fastcgi.conf中，也是一堆fastcgi的配置项，只是这些配置项相对不常变，通常单独文件保管可以在多处引用。可以看到在fastcgi.conf中，有很多的fastcgi_param配置，结合nginx server配置中的fastcgi_pass、fastcgi_index，通常我们的同学已经能够想到Nginx与PHP之间打交道就是用的FastCGI，但再深问FastCGI是什么？它起到衔接Nginx、PHP的什么作用？等等深入的问题的时候，很多同学就卡壳了。那么，我们就来一探究竟。CGI是通用网关协议，FastCGI则是一种常住进程的CGI模式程序。我们所熟知的PHP-FPM的全称是PHP FastCGI Process Manager，即PHP-FPM会通过用户配置来管理一批FastCGI进程，例如在PHP-FPM管理下的某个FastCGI进程挂了，PHP-FPM会根据用户配置来看是否要重启补全，PHP-FPM更像是管理器，而真正衔接Nginx与PHP的则是FastCGI进程。如上图所示，FastCGI的下游，是CGI-APP，在我们的LNMP架构里，这个CGI-APP就是PHP程序。而FastCGI的上游是Nginx，他们之间有一个通信载体，即图中的socket。在我们上文图3的配置文件中，fastcgi_pass所配置的内容，便是告诉Nginx你接收到用户请求以后，你该往哪里转发，在我们图3中是转发到本机的一个socket文件，这里fastcgi_pass也常配置为一个http接口地址（这个可以在php-fpm.conf中配置）。而上图5中的Pre-fork，则对应着我们PHP-FPM的启动，也就是在我们启动PHP-FPM时便会根据用户配置启动诸多FastCGI触发器（FastCGI Wrapper）。对FastCGI在Nginx+PHP的模式中的定位有了一定了解后，我们再来了解下Nginx中为何能写很多fastcgi_*的配置项。这是因为Nginx的一个默认内置module实现了FastCGI的Client。关于Module ngx_http_fastcgi_module的详细文档可以查看这里: http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html 。我们关心一下我们图4中的这些fastcgi_param都是些什么吧，详细描述见下图。从图6中可以看到，fastcgi_param所声明的内容，将会被传递给“FastCGI server”，那这里指的就是fastcgi_pass所指向的server，也就是我们Nginx+PHP模式下的PHP-FPM所管理的FastCGI进程，或者说是那个socket文件载体。这时，有的同学会问：“为什么PHP-FPM管理的那些FastCGI进程要关心这些参数呢？”，好问题，我们一起想想我们做PHP应用开发时候有没有用到 $_SERVER 这个全局变量，它里面包含了很多服务器的信息，比如包含了用户的IP地址。同学们不想想我们的PHP身处socket文件之后，为什么能得到远端用户的IP呢？聪明的同学应该注意到图4中的一个f…