Gitlib

天下武功 唯快不破


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

推荐文章索引

发表于 2018-08-08

杂谈

  • 寒冬已至,我们还能活多久?
  • 技术管理的三个层次
  • 技术文摘精选

PHP

  • PHP底层运行机制和原理
  • PHP框架常见URL模式
  • PHP运行模式(SAPI)
  • PHP性能优化之Opcache
  • PHP安全编程
  • PHP标准规范PSR
  • PHP类的自动加载
  • Composer自动加载原理
  • PHP-FPM使用指南
  • PHP反射模拟实现注解路由
  • PHP协程
  • PHP写时拷贝技术
  • PHP垃圾回收机制
  • PHP的Session运行机制
  • PHP错误和异常处理机制详解
  • PHP依赖注入和控制反转
  • PHP扩展开发入门
  • PHP安全配置总结
  • PHP微服务开发指南

Python

  • Python高频模块汇总
  • Python之线程池
  • Python之装饰器
  • Python使用lambda表达式

前端开发

  • PureMVC(JS版)源码解析:总结
  • Javascript垃圾回收机制
  • Javascript实现双向数据绑定
  • 浏览器资源缓存机制总结
  • 深入理解Javascript异步原理

Lua

  • Lua语法总结
  • Lua版MVC框架

Mysql

  • Mysql基础操作
  • Mysql binlog详解
  • Mysql主从复制
  • SQL执行原理
  • Mysql常见优化技巧
  • Mysql存储引擎比较
  • Mysql核心知识汇总
  • 深入理解Mysql复制机制
  • 深入理解Mysql事务
  • 深入理解Mysql索引
  • MySql表锁、行锁、共享锁、排他锁、乐观锁、悲观锁
  • 谈一谈Mysql分库分表
  • Mysql中间件MyCat使用
  • CAS-乐观锁的一种实现方式

NoSQL

  • Redis发布订阅
  • Redis管道技术
  • Redis事务机制
  • Redis持久化和数据数据恢复
  • Redis主从模式搭建及应用
  • Redis缓存穿透、缓存击穿、缓存雪崩
  • Redis集群及高可用实现
  • Redis数据过期和淘汰策略
  • Redis中BitMap使用
  • 基于Redis使用令牌桶算法实现流量控制
  • MongoDB入门实践
  • MongoDB副本集及分片
  • Redis和Memcache对比

大数据

  • 大数据开发入门之hadoop介绍
  • 大数据开发入门之hadoop单机版部署
  • 大数据开发入门之WordCount开发实践

Docker

  • Docker入门总结
  • Dockerfile设置默认时区
  • Docker使用技巧
  • Dockerfile中CMD与ENTERPOINT差异比较

架构设计

  • LNMP架构下各项配置优化总结
  • HAProxy实践详解
  • P3P协议详解
  • 大型网站架构设计总结
  • ELK日志分析系统入门
  • Nginx高级用法总结
  • Nginx+Keepalived搭建高可用集群
  • Keepalived的部署及应用
  • 常见负载均衡算法
  • Nginx常见问题之Location优先级
  • Nginx与HAProxy负载均衡比较
  • Nginx实战灰度发布

队列/MQ

  • 简单队列服务:HTTPSQS
  • Kafka基础入门
  • 深入浅出RabbitMQ
  • 基于RabbitMQ消息延时队列方案
  • RabbitMQ集群实践

分布式

  • Zookeeper基础入门
  • 分布式系统中CAP理论
  • 分布式唯一ID实现方案
  • Redis和Zookeeper分布式锁实现
  • 走进Gossip协议
  • 服务降级、熔断和限流
  • 深入理解一致性Hash原理

微服务

  • 微服务设计思想和目标

RPC

  • RPC的简单实现
  • Protobuf:高效数据结构化工具
  • gRPC入门及应用

API网关

  • OpenID Connect协议
  • OpenResty实现API网关
  • Kong API网关使用入门
  • 基于JWT实现Token认证

CI/CD

  • GitFlow在团队协作中的实践
  • 基于CodePipeline实现CI/CD

Linux基础

  • 磁盘IO与swap分区
  • Page Cache与Page回写
  • 从TCP/IP协议谈Linux内核参数优化
  • 深入理解IO模型
  • IO多路复用之select、poll、epoll详解
  • 长、短链接与连接池

数据结构和算法

  • 数据结构之单向链表
  • 数据结构之双向链表
  • 数据结构之栈
  • 数据结构之队列
  • 数据结构之集合
  • 数据结构之二叉树
  • 常见二叉树结构
  • 数据结构之B树、B+树、B*树
  • 二叉搜索树转双向链表
  • 数据结构之图
  • Times33哈希算法
  • 常见排序算法实现
  • 常见搜索算法实现
  • 布隆过滤,实现亿级数据快速查找
  • LRU算法实现
  • 深入理解一致性Hash原理
  • 深入理解递归算法

深入理解Javascript异步原理

发表于 2019-11-12 | 分类于 Javascript

众所周知,Javascript是单线程运行的,那既然是单线程运行又是如何实现异步非阻塞操作的呢?下面带大家从Javascript原理层面,一步步剖析这个问题。

JS单线程问题

我们经常说JS 是单线程执行的,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程?

官方的说法是:进程是 CPU资源分配的最小单位;线程是 CPU调度的最小单位。这两句话并不好理解,我们先来看张图:

Javascript异步

  • 进程好比图中的工厂,有单独的专属自己的工厂资源。
  • 线程好比图中的工人,多个工人在一个工厂中协作工作,工厂与工人是 1:n的关系。也就是说一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
  • 工厂的空间是工人们共享的,这象征一个进程的内存空间是共享的,每个线程都可用这些共享内存。
  • 多个工厂之间独立存在。

多进程与多线程

  • 多进程:在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。
  • 多线程:程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

以Chrome浏览器中为例,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程(下文会详细介绍),比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。

浏览器内核

简单来说浏览器内核是通过取得页面内容、整理信息(应用CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  • GUI 渲染线程

  • JavaScript引擎线程

  • 定时触发器线程

  • 事件触发线程

  • 异步http请求线程

GUI渲染线程

  • 主要负责页面的渲染,解析HTML、CSS,构建DOM树,布局和绘制等。
  • 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
  • 该线程与JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起,当任务队列空闲时,主线程才会去执行GUI渲染。

JS引擎线程

  • 该线程当然是主要负责处理 JavaScript脚本,执行代码。
  • 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS引擎线程的执行。
  • 当然,该线程与 GUI渲染线程互斥,当 JS引擎线程执行 JavaScript脚本时间过长,将导致页面渲染的阻塞。

定时器触发线程

  • 负责执行异步定时器一类的函数的线程,如: setTimeout,setInterval。
  • 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待JS引擎线程执行。

事件触发线程

主要负责将准备好的事件交给 JS引擎线程执行。 比如 setTimeout定时器计数结束, ajax等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS引擎线程的执行。

异步http请求线程

  • 负责执行异步请求一类的函数的线程,如: Promise,axios,ajax等。
  • 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待JS引擎线程执行。

Javascript引擎

Google V8引擎是目前最流行的Javascript引擎之一,它使用在Chrome浏览器和Node.js中。下面是V8引擎一个简化的视图:

Javascript异步

V8引擎主要包含两个部分:

  • Memory Heap — 分配内存将会在这里发生
  • Call Stack — 回调函数将会在这里执行

Runtime

有一些APIs被开发者在浏览器中经常使用到(如:“setTimeout”),然而这些APIs也许并不是由Javascript引擎提供的。

Javascript异步

诸如DOM、AJAX、setTimeout等其它是由浏览器提供的,我们称之为WEB APIs。

接下来,我们将谈谈非常流行的callback queue和event loop。

Call Stack

Javascript是一种单线程的编程语言,这导致它只有单一的Call Stack。因此在某一时刻,他只能做一件事。

Call Stack是一种数据结构,他主要是记录Javascript整个执行过程。当Javascript的虚拟机执行一个函数,就会把这个函数推送到Call Stack中。当这个函数返回值或是执行完毕后,这个函数就会从Call Stack删除。

如以下示例:

1
2
3
4
5
6
7
8
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);

当Javascript引擎在执行这段代码的前一刻,Call Stack是空的。然后Call Stack将会按照下图发生变化。

Javascript异步

看下面的代码,这段代码模拟在Call Stack中出现异常后的全过程。

1
2
3
4
5
6
7
8
9
10
function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();

假设这段代码在foo.js中,foo.js在chrome浏览器执行后将会出现下面的堆栈追踪记录。

Javascript异步

堆栈溢出:Javascript引擎产生的堆栈超过Javascript运行环境所提供的最大数量。这种异常在代码中存在递归但没有设置递归结束的条件时,尤其容易产生。

下面就是这种类型的代码:

1
2
3
4
function foo() {
foo();
}
foo();

Javascript引擎执行这段代码是从foo函数开始,在这个函数中不断调用自己并没有设置终止条件,从而产生无限循环。每一次执行foo,Call Stack都会添加一次函数。这就像下面显示的那样:

Javascript异步

当Javascript引擎中的Call Stack的长度,超过Javascript执行环境中Call Stack的实际长度时,Javascript执行环境(Chrome浏览器或Node)就会抛出下面的异常。

Javascript异步

在多线程环境中,要考虑诸如死锁等复杂执行过程。单线程的环境中相比较要简单很多,但是单线程同样有它的限制。Javascript单线程的执行环境中,如何应对复杂的调用,单线程会不会限制程序的性能。

并发(concurrence)

当在你的Call Stack中存在一个需要占用相当大执行时间的函数时,将会发生什么。例如在浏览器中通过Javascript传输一个比较大的image文件时,你会怎么做?

你也许会问这怎么也算是一个问题。当Call Stack有待执行的函数时,浏览器会阻塞在这里,并不做其它的任务。这也意味着你不可能在app中呈现流畅复杂的UI。

问题不仅仅如此,一旦Call Stack中等待执行的任务很多时,浏览器要在很长的时间内都不能回应其它事件。许多浏览器这时都会抛出一个提示信息,征求你是否要关闭页面。

Javascript异步

这样必然将导致非常差的用户体验。

Event loop

浏览器端事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。宏任务队列可以有多个,微任务队列只有一个。

  • 见的 macro-task 比如:setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等
  • 常见的 micro-task 比如: new Promise().then(回调)、MutationObserver(html5新特性) 等。

过程解析

一个完整的 Event Loop 过程,可以概括为以下阶段:

Javascript异步

导图要表达的内容用文字来表述的话:

  • 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数。
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

我们不禁要问了,那怎么知道主线程执行栈为空啊?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

总结

事件的执行顺序,是先执行宏任务,然后执行微任务,这个是基础,任务可以有同步任务和异步任务,同步的进入主线程,异步的进入Event Table并注册函数,异步事件完成后,会将回调函数放入Event Queue中(宏任务和微任务是不同的Event Queue),同步任务执行完成后,会从Event Queue中读取事件放入主线程执行,回调函数中可能还会包含不同的任务,因此会循环执行上述操作。

Javascript异步


参考文章:

  • 浏览器与Node的事件循环(Event Loop)有何区别?

Mysql中分表和分区差异

发表于 2019-10-23 | 分类于 Mysql

日常开发中我们经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表。这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性能会更加糟糕。分表和分区的目的就是减少数据库的负担,提高数据库的效率,通常点来讲就是提高表的增删改查效率。

分区

分区是根据一定的规则,把一个表分解成多个更小的、更容易管理的部分。就访问数据库应用而言,逻辑上就只有一个表或者一个索引,但实际上这个表可能有N个物理分区对象组成,每个分区都是一个独立的对象,可以独立处理,可以作为表的一部分进行处理。分区对应用来说是完全透明的,不影响应用的业务逻辑。

分区有利于管理非常大的表,它采用分而治之的思想,分区引入了分区键的概念,分区键用于根据某个区间值(或者范围值)、特定值列表或者hash函数值执行数据的聚集,让数据根据规则分布在不同的分区中,让一个大对象碧昂城一些小对象。

阿里开源ETL工具:dataX上手指南

发表于 2019-10-12 | 分类于 大数据开发

DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL、Oracle、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、DRDS 等各种异构数据源之间高效的数据同步功能。

开源地址:https://github.com/alibaba/DataX

设计思想

为了解决异构数据源同步问题,DataX将复杂的网状的同步链路变成了星型数据链路,DataX作为中间传输载体负责连接各种数据源。当需要接入一个新的数据源的时候,只需要将此数据源对接到DataX,便能跟已有的数据源做到无缝数据同步。

DataX

数据交换通过DataX进行中转,任何数据源只要和DataX连接上即可以和已实现的任意数据源同步

DataX

核心组件:

  • Reader:数据采集模块,负责从源采集数据
  • Writer:数据写入模块,负责写入目标库
  • Framework:数据传输通道,负责处理数据缓冲等

以上只需要重写Reader与Writer插件,即可实现新数据源支持d支持主流数据源,详见:https://github.com/alibaba/DataX/blob/master/introduction.md

核心架构

DataX 3.0 开源版本支持单机多线程模式完成同步作业运行,本小节按一个DataX作业生命周期的时序图,从整体架构设计非常简要说明DataX各个模块相互关系。

DataX

从一个JOB来理解datax的核心模块组件:

  • datax完成单个数据同步的作业,称为Job,job会负责数据清理、任务切分等工作;
  • 任务启动后,Job会根据不同源的切分策略,切分成多个Task并发执行,Task就是执行作业的最小单元;
  • 切分完成后,根据Scheduler模块,将Task组合成TaskGroup,每个group负责一定的并发和分配Task;

入门

环境要求

  • Linux
  • JDK(1.6以上,推荐1.6.x)
  • Python(推荐Python2.6.X)
  • Apache Maven 3.x (若不编译DataX源码,则不需要)

工具部署

方法一:安装包安装

直接下载DataX工具包:DataX,下载后解压至本地某个目录,进入bin目录,即可运行同步作业

1
2
$ cd {YOUR_DATAX_HOME}/bin
$ python datax.py {YOUR_JOB.json}

同步作业配置模板,请参考DataX各个插件配置模板和参数说明

方法二:编译安装

下载DataX源码,自己编译:DataX源码

  • 下载DataX源码:
1
$ git clone git@github.com:alibaba/DataX.git
  • 通过maven打包:
1
2
$ cd {DataX_source_code_home}
$ mvn -U clean package assembly:assembly -Dmaven.test.skip=true

打包成功,日志显示如下:

1
2
3
4
5
6
[INFO] BUILD SUCCESS
[INFO] ————————————————————————————————-
[INFO] Total time: 08:12 min
[INFO] Finished at: 2015-12-13T16:26:48+08:00
[INFO] Final Memory: 133M/960M
[INFO] ————————————————————————————————-

打包成功后的DataX包位于 {DataX_source_code_home}/target/datax/datax/ ,结构如下:

1
2
3
$ cd {DataX_source_code_home}
$ ls ./target/datax/datax/
bin conf job lib log log_perf plugin script tmp

配置示例

示例:从stream读取数据并打印到控制台

第一步、创建创业的配置文件(json格式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#stream2stream.json
{
"job": {
"setting": {
"speed": {
"channel": 5
}
},
"content": [
{
"reader": {
"name": "streamreader",
"parameter": {
"sliceRecordCount": 10,
"column": [
{
"type": "long",
"value": "10"
},
{
"type": "string",
"value": "hello,你好,世界-DataX"
},
{
"type": "double",
"value": "3.141592653"
},
{
"type": "bytes",
"value": "image"
},
{
"type": "bool",
"value": "true"
},
{
"type": "bool",
"value": "5678true"
},
{
"type": "date",
"value": "2014-10-10",
"dateFormat": "yyyy-MM-dd"
}
]
}
},
"writer": {
"name": "streamwriter",
"parameter": {
"encoding": "UTF-8",
"print": true
}
}
}
]
}
}

第二步:启动DataX

1
2
$ cd {YOUR_DATAX_DIR_BIN}
$ python datax.py ./stream2stream.json

同步结束,显示日志如下:

1
2
3
4
5
6
7
8
2015-12-17 11:20:25.263 [job-0] INFO JobContainer -
任务启动时刻 : 2015-12-17 11:20:15
任务结束时刻 : 2015-12-17 11:20:25
任务总计耗时 : 10s
任务平均流量 : 205B/s
记录写入速度 : 5rec/s
读出记录总数 : 50
读写失败总数 : 0

其他reader与writer插件的配置文档直接点击对应的文件夹进入doc即可。

DataX

k8s入门实践之环境搭建

发表于 2019-09-19 | 分类于 Docker

LNMP架构下各项配置优化总结

发表于 2019-09-07 | 分类于 Linux

从事PHP开发的都知道,LNMP一般是指Linux+Nginx+MySQL+PHP组合,也是日常开发和线上环境中最简单的Web服务器架构。为尽可能的提升服务器响应速度,LNMP的配置优化是十分关键的步骤。下面分别总结一下LNMP各组件的配置优化方法。

Linux

关于Linux优化,我们这次主要从内核配置方面去讲(硬件优化增加投入即可)。内核配置优化主要围绕如何提供更好更稳定的TCP/IP服务为主,可以查看这篇文章:从TCP/IP协议谈Linux内核参数优化, 这里不在单独写了。

工作进程数量

Nginx运行工作进程个数,建议按照cpu 数目来指定,一般为它的倍数 (如,2个四核的cpu计为8)。

1
worker_processes 8;

CPU亲和力

worker_cpu_affinity 为每个进程分配cpu,一般情况下一个进程分配一个cpu,例如8cpu:

1
2
worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;

最大打开文件数

1
worker_rlimit_nofile 65535;

这个指令是指当一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(ulimit -n)与nginx进程数相除,但是nginx分配请求并不是那么均匀,所以最好与ulimit -n的值保持一致。

注:文件资源限制的配置可以在/etc/security/limits.conf设置,针对root/user等各个用户或者*代表所有用户来设置。

1
2
* soft nofile 65535
* hard nofile 65535

用户重新登录生效。

Nginx事件处理模型

1
2
3
events {
use epoll;
}

nginx采用epoll事件模型,处理效率高。

工作进程连结束

1
worker_connections 65535;

设置每个进程允许的最多连接数, 理论上每台nginx 服务器的最大连接数为worker_processes*worker_connections,一般设置为65535。

开启Gzip压缩

使用gzip压缩功能,可能为节约带宽,加快传输速度。

一般我们需要压缩的内容有:文本,js,html,css,对于图片,视频,flash什么的不压缩,使用gzip的功能是需要消耗CPU的。

1
2
3
4
5
6
7
8
9
gzip on;
gzip_min_length 2k;
gzip_buffers 4 32k;
gzip_http_version 1.1;
gzip_comp_level 6;
gzip_typestext/plain text/css text/javascriptapplication/json application/javascript application/x-javascriptapplication/xml;
gzip_vary on;
gzip_proxied any;
gzip on; #开启压缩功能

参数说明:

  • gzip_min_length 1k :设置允许压缩的页面最小字节数,页面字节数从header头的Content-Length中获取,默认值是0,不管页面多大都进行压缩,建议设置成大于1K,如果小与1K可能会越压越大。
  • gzip_buffers 4 32k :压缩缓冲区大小,表示申请4个单位为32K的内存作为压缩结果流缓存,默认值是申请与原始数据大小相同的内存空间来存储gzip压缩结果。
  • gzip_http_version 1.1 :压缩版本,用于设置识别HTTP协议版本,默认是1.1,目前大部分浏览器已经支持GZIP解压,使用默认即可。
  • gzip_comp_level 6 :压缩比例,用来指定GZIP压缩比,1压缩比最小,处理速度最快,9压缩比最大,传输速度快,但是处理慢,也比较消耗CPU资源。
  • gzip_types text/css text/xml application/javascript :用来指定压缩的类型,‘text/html’类型总是会被压缩。默认值: gzip_types text/html (默认不对js/css文件进行压缩)
  • 压缩类型,匹配MIME型进行压缩;
  • 不能用通配符 text/*;
  • text/html默认已经压缩 (无论是否指定);
  • 设置哪压缩种文本文件可参考 conf/mime.types。
  • gzip_vary on :varyheader支持,改选项可以让前端的缓存服务器缓存经过GZIP压缩的页面,例如用Squid缓存经过nginx压缩的数据。

连接超时设置

1
2
3
4
5
6
keepalived_timeout 65;
client_header_timeout 30;
client_body_timeout 30;
sned_timeout 60;
proxy_send_timeout 300;
reset_timedout_connection on;

参数说明:

  • keepalived_timeout :客户端连接保持会话超时时间,超过这个时间,服务器断开这个链接,对于一些请求比较大的内部服务器通讯的场景,适当加大为120s或者300s,具体根据不同场景,默认值是60秒。
  • client_header_timeout : 客户端向服务器发送一个完整的request header的超时时间,如果客户端在此时间内没有发送一个完整的request header,那么Nginx返回HTTP 408错误(Request Timed Out),默认值是60秒。
  • client_body_timeout: 客户端与服务器建立连接后发送request body的超时时间,如果客户端在此时间内没有发送任何内容,那么Nginx返回HTTP 408错误(Request Timed Out),默认值是60秒。
  • reset_timeout_connection :告诉nginx关闭不响应的客户端连接。这将会释放那个客户端所占有的内存空间。
  • send_timeout :发送数据至客户端超时时间,默认60s,如果连续的60s内客户端没有收到1个字节,连接关闭。
  • proxy_send_timeout:发送请求给upstream服务器的超时时间,超时设置不是整个发送期间, 而是在两次write操作期间, 如果超时后,upstream没有收到新的数据,nginx会关闭连接。

Buffer缓解后端的负载

在大部分场景下,利用 Nginx 的 buffer(缓冲) 和 cache(缓存) 能力,可以大大地减轻负担。

1
2
client_body_buffer_size 16K
client_header_buffer_size 1K
  • client_body_buffer_size:允许客户端请求的最大单个文件字节数,在32位系统上默认是8k,在64位系统上默认是16k。可以在http, server 和 location模块中指定
  • client_header_buffer_size:用于设置客户端请求的Header头缓冲区大小,大部分情况1KB大小足够, 默认的值是1k

开启高效传输模式

1
2
3
4
5
6
7
8
http {
include mime.types;
default_type application/octet-stream;
……
sendfile on;
tcp_nopush on;
……
}
  • sendfile on:开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
  • tcp_nopush on:必须在sendfile开启模式才有效,防止网路阻塞,积极的减少网络报文段的数量(将响应头和正文的开始部分一起发送,而不一个接一个的发送。)

expires 缓存调优

缓存,主要针对于图片,css,js等元素更改机会比较少的情况下使用,特别是图片,占用带宽大,我们完全可以设置图片在浏览器本地缓存365d,css,js,html可以缓存个10来天,这样用户第一次打开加载慢一点,第二次,就非常快了!缓存的时候,我们需要将需要缓存的拓展名列出来, Expires缓存配置在server字段里面。

1
2
3
4
5
6
location ~* \.(ico|jpe?g|gif|png|bmp|swf|flv)$ {
expires 30d;
}
location ~* \.(js|css)$ {
expires 7d;
}

fastcgi 调优

1
2
3
4
5
6
7
8
9
10
fastcgi_connect_timeout 600;
fastcgi_send_timeout 600;
fastcgi_read_timeout 600;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;
fastcgi_temp_path/usr/local/nginx1.10/nginx_tmp;
fastcgi_intercept_errors on;
fastcgi_cache_path/usr/local/nginx1.10/fastcgi_cache levels=1:2 keys_zone=cache_fastcgi:128minactive=1d max_size=10g;
  • fastcgi_connect_timeout 600 :指定连接到后端FastCGI的超时时间。
  • fastcgi_send_timeout 600 :向FastCGI传送请求的超时时间。
  • fastcgi_read_timeout 600 :指定接收FastCGI应答的超时时间。
  • fastcgi_buffer_size 64k :指定读取FastCGI应答第一部分需要用多大的缓冲区,默认的缓冲区大小为。fastcgi_buffers指令中的每块大小,可以将这个值设置更小。
  • fastcgi_buffers 4 64k :指定本地需要用多少和多大的缓冲区来缓冲FastCGI的应答请求,如果一个php脚本所产生的页面大小为256KB,那么会分配4个64KB的缓冲区来缓存,如果页面大小大于256KB,那么大于256KB的部分会缓存到fastcgi_temp_path指定的路径中,但是这并不是好方法,因为内存中的数据处理速度要快于磁盘。一般这个值应该为站点中php脚本所产生的页面大小的中间值,如果站点大部分脚本所产生的页面大小为256KB,那么可以把这个值设置为“8 32K”、“4 64k”等。
  • fastcgi_busy_buffers_size 128k :建议设置为fastcgi_buffers的两倍,繁忙时候的buffer。
  • fastcgi_temp_file_write_size 128k :在写入fastcgi_temp_path时将用多大的数据块,默认值是fastcgi_buffers的两倍,该数值设置小时若负载上来时可能报502BadGateway。
  • fastcgi_temp_path :缓存临时目录。
  • fastcgi_intercept_errors on :这个指令指定是否传递4xx和5xx错误信息到客户端,或者允许nginx使用error_page处理错误信息。注:静态文件不存在会返回404页面,但是php页面则返回空白页!
  • fastcgi_cache_path /usr/local/nginx1.10/fastcgi_cachelevels=1:2 keys_zone=cache_fastcgi:128minactive=1d max_size=10g :fastcgi_cache缓存目录,可以设置目录层级,比如1:2会生成16*256个子目录,cache_fastcgi是这个缓存空间的名字,cache是用多少内存(这样热门的内容nginx直接放内存,提高访问速度),inactive表示默认失效时间,如果缓存数据在失效时间内没有被访问,将被删除,max_size表示最多用多少硬盘空间。
  • fastcgi_cache cache_fastcgi :#表示开启FastCGI缓存并为其指定一个名称。开启缓存非常有用,可以有效降低CPU的负载,并且防止502的错误放生。cache_fastcgi为proxy_cache_path指令创建的缓存区名称。
  • fastcgi_cache_valid 200 302 1h :#用来指定应答代码的缓存时间,实例中的值表示将200和302应答缓存一小时,要和fastcgi_cache配合使用。
  • fastcgi_cache_valid 301 1d :将301应答缓存一天。
  • fastcgi_cache_valid any 1m :将其他应答缓存为1分钟。
  • fastcgi_cache_min_uses 1 :该指令用于设置经过多少次请求的相同URL将被缓存。
  • fastcgi_cache_key http://$host$request_uri :该指令用来设置web缓存的Key值,nginx根据Key值md5哈希存储.一般根据$host(域名)、$request_uri(请求的路径)等变量组合成proxy_cache_key 。
  • fastcgi_pass :指定FastCGI服务器监听端口与地址,可以是本机或者其它。

禁用访问日志文件

这一点影响较大,因为高流量站点上的日志文件涉及大量必须在所有线程之间同步的IO操作。

1
2
3
access_log off;
log_not_found off;
error_log /var/log/nginx-error.log warn;

若你不能关闭访问日志文件,至少应该使用缓冲:

1
access_log /var/log/nginx/access.log main buffer=16k;

关闭版本显示

1
server_tokens off;

server_tokens并不会让nginx执行的速度更快,但它可以关闭在错误页面中的nginx版本数字,这样对于安全性是有好处的。

MySql

innodb_file_per_table

表的数据和索引存放在共享表空间里或者单独表空间里。我们的工作场景安装是默认设置了innodb_file_per_table = ON,这样也有助于工作中进行单独表空间的迁移工作。MySQL 5.6中,这个属性默认值是ON。

innodb_flush_log_at_trx_commit

默认值为1,表示InnoDB完全支持ACID特性。当你的主要关注点是数据安全的时候这个值是最合适的,比如在一个主节点上。但是对于磁盘(读写)速度较慢的系统,它会带来很巨大的开销,因为每次将改变flush到redo日志都需要额外的fsyncs。

如果将它的值设置为2会导致不太可靠(unreliable)。因为提交的事务仅仅每秒才flush一次到redo日志,但对于一些场景是可以接受的,比如对于主节点的备份节点这个值是可以接受的。如果值为0速度就更快了,但在系统崩溃时可能丢失一些数据:只适用于备份节点。说到这个参数就一定会想到另一个sync_binlog。

innodb_flush_method

这项配置决定了数据和日志写入硬盘的方式。一共有三种方式,我们默认使用O_DIRECT 。O_DIRECT模式:数据文件的写入操作是直接从mysql innodb buffer到磁盘的,并不用通过操作系统的缓冲,而真正的完成也是在flush这步,日志还是要经过OS缓冲。

innodb_log_buffer_size

这项配置决定了为尚未执行的事务分配的缓存。其默认值(1MB)一般来说已经够用了,但是如果你的事务中包含有二进制大对象或者大文本字段的话,这点缓存很快就会被填满并触发额外的I/O操作。看看Innodb_log_waits状态变量,如果它不是0,增加innodb_log_buffer_size。

innodb_buffer_pool_size

这个参数应该是运维中必须关注的了。缓冲池是数据和索引缓存的地方,它属于MySQL的核心参数,默认为128MB,正常的情况下这个参数设置为物理内存的60%~70%。(不过我们的实例基本上都是多实例混部的,所以这个值还要根据业务规模来具体分析。)

innodb_log_file_size

这是redo日志的大小。redo日志被用于确保写操作快速而可靠并且在崩溃时恢复。如果你知道你的应用程序需要频繁地写入数据并且你使用的是MySQL 5.6,那么你可以一开始就把它这是成4G。(具体大小还要根据自身业务进行适当调整)

innodb_support_xa

innodb_support_xa可以开关InnoDB的XA两段式事务提交。默认情况下,innodb_support_xa=true,支持XA两段式事务提交。由于XA两段式事务提交导致多余flush等操作,性能影响会达到10%,所有为了提高性能,有些DBA会设置innodb_support_xa=false。这样的话,redolog和binlog将无法同步,可能存在事务在主库提交,但是没有记录到binlog的情况。这样也有可能造成事务数据的丢失。

innodb_additional_mem_pool_size

该参数用来存储数据字段信息和其他内部数据结构。表越多,需要在这里分配的内存越多。如果InnoDB用光了这个池内的内存,InnoDB开始从操作系统分配内存,并且往MySQL错误日志写警告信息,默认8MB。一般设置16MB。

max_connections

MySQL服务器默认连接数比较小,一般也就100来个最好把最大值设大一些。一般设置500~1000即可每一个链接都会占用一定的内存,所以这个参数也不是越大越好。有的人遇到too many connections会去增加这个参数的大小,但其实如果是业务量或者程序逻辑有问题或者sql写的不好,即使增大这个参数也无济于事,再次报错只是时间问题。在应用程序里使用连接池或者在MySQL里使用进程池有助于解决这一问题。

server-id

复制架构时确保 server-id 要不同,通常主ID要小于从ID。

log_bin

如果你想让数据库服务器充当主节点的备份节点,那么开启二进制日志是必须的。如果这么做了之后,还别忘了设置server_id为一个唯一的值。就算只有一个服务器,如果你想做基于时间点的数据恢复,这(开启二进制日志)也是很有用的:从你最近的备份中恢复(全量备份),并应用二进制日志中的修改(增量备份)。

二进制日志一旦创建就将永久保存。所以如果你不想让磁盘空间耗尽,你可以用 PURGE BINARY LOGS 来清除旧文件,或者设置expire_logs_days 来指定过多少天日志将被自动清除。记录二进制日志不是没有开销的,所以如果你在一个非主节点的复制节点上不需要它的话,那么建议关闭这个选项。

skip_name_resolve

当客户端连接数据库服务器时,服务器会进行主机名解析,并且当DNS很慢时,建立连接也会很慢。因此建议在启动服务器时关闭skip_name_resolve选项而不进行DNS查找。唯一的局限是之后GRANT语句中只能使用IP地址了,因此在添加这项设置到一个已有系统中必须格外小心。

sync_binlog

sync_binlog 的默认值是0,像操作系统刷其他文件的机制一样,MySQL不会同步到磁盘中去而是依赖操作系统来刷新binary log。

当sync_binlog =N (N>0) ,MySQL 在每写N次二进制日志binary log时,会使用fdatasync()函数将它的写二进制日志binary log同步到磁盘中去。当innodb_flush_log_at_trx_commit和sync_binlog 都为 1 时是最安全的,在mysqld服务崩溃或者服务器主机crash的情况下,binary log只有可能丢失最多一个语句或者一个事务。但是鱼与熊掌不可兼得,双1会导致频繁的IO操作,因此该模式也是最慢的一种方式。出于我们的业务考虑在业务压力允许的情况下默认的都是双1配置。

log_slave_update

当业务中需要使用级联架构的时候log_slave_update = 1这个参数必须打开,否者第三级可能无法接收到第一级产生的binlog,从而无法进行数据同步。

tmpdir

如果内存临时表超出了限制,MySQL就会自动地把它转化为基于磁盘的MyISAM表,存储在指定的tmpdir目录下.因此尽可能将tmpdir配置到性能好速度快的存储设备上。

慢日志相关

1
slow_query_log = 1 #打开慢日志

PHP

进程数

1
2
3
4
5
6
7
8
9
10
pm = dynamic
#pm参数指定了进程管理方式,有两种可供选择:static或dynamic,从字面意思不难理解,为静态或动态方式。如果是静态方式,那么在php-fpm启动的时候就创建了指定数目的进程,在运行过程中不会再有变化(并不是真的就永远不变);而动态的则在运行过程中动态调整,当然并不是无限制的创建新进程,受pm.max_spare_servers参数影响;动态适合小内存机器,灵活分配进程,省内存。静态适用于大内存机器,动态创建回收进程对服务器资源也是一种消耗
pm.max_children = 24
#static模式下创建的子进程数或dynamic模式下同一时刻允许最大的php-fpm子进程数量
pm.start_servers = 16
#动态方式下的起始php-fpm进程数量
pm.min_spare_servers = 12
#动态方式下服务器空闲时最小php-fpm进程数量
pm.max_spare_servers = 24
#动态方式下服务器空闲时最大php-fpm进程数量

一般php-fpm进程占用20~30m左右的内存就按30m算。如果单独跑php-fpm,动态方式起始值可设置物理内存Mem/30M。

最大处理请求数

1
pm.max_requests = 10240

最大处理请求数是指一个php-fpm的worker进程在处理多少个请求后就终止掉,master进程会重新respawn一个新的,这个配置的主要目的是避免php解释器或程序引用的第三方库造成的内存泄露。

最长执行时间

1
2
max_execution_time = 20
request_terminate_timeout = 20

这个是用来处理因为PHP执行时间超长而报502错误的解决。这个时长配置可以在php.ini(max_execution_time)或php-fpm.conf中配置均可,为了不影响全局配置,可在php-fpm.conf中实现

大型网站架构设计总结

发表于 2019-08-23 | 分类于 架构设计

大型网站架构设计是一个循序渐进的过程,围绕“性能、可用性、伸缩性、扩展性、安全性”展开,下面是个人关于网站架构方面的一些总结,不到之处请大家多多批评。

前端

  • CDN加速:CSS/JS/图片等静态资源使用CDN加速,设置缓存时间、Referer限制(防盗链)等进行流量优化;
  • 减少HTTP请求:将CSS/JS/图片等静态资源合并,可以利用webpack等前端构建工具进行处理
  • 启用浏览器缓存和文件压缩:压缩图片、JS/CSS混淆压缩、Web服务器开启Gzip压缩&设置文件expire缓存时间;
  • 异步加载:动态接口通过Ajax异步加载,减少网络请求(可以通过JSONP或者设置Access-Control-Allow-Origin进行跨域);
  • 使用验证码:使用短信或图像验证码,提高验证码的复杂度及多样性,缓解羊毛党带来的流量冲击;
  • 减少Cookie传输:Cookie包含在每次请求和响应中,太大的Cookie会严重影响数据传输,例如针对CDN采用独立域名,可以减少静态资源加载携带Cookie信息;

缓存

关于缓存,永远要记住二八定律:80%的业务访问集中在20%的数据上。将热点数据进行缓存,可以降低网络I/O和磁盘I/O,极大提升响应速度,除了前端本地资源缓存外,服务端缓存常见做法如下:

  • 页面静态化:缓存整个页面,或者局部缓存,减少数据读取和运算频率;
  • 数据缓存:充分利用NoSQL数据库,例如memcached、redis、mongodb等,可以部署集群或分布式缓存,提高缓存命中率,减少数据访问的压力,可以进行缓存预热、预先加载热点数据,同时需要防止缓存穿透;

消息队列

  • 异步解耦:利用Httpsqs、RabbitMQ、Kafka等队列或消息中间件,将耗时/非即时性操作通过队列进行异步处理,提高服务器响应速度,以此降低对资源的并发访问。遵循一个原则:任何可以晚点做的事情都应该晚点再做。
  • 削峰填谷:由于流量是波动变化的,高峰和低谷差距很大,可以将一些操作存储到MQ队列中,消费端通过拉取的方式,并且拉去速度有消费端来控制,则就可以控制流量趋于平稳,达到了削峰填谷的目的,或者说起到了流控的目标。

数据库

使用缓存后,大部分数据读取操作都不用通过数据库完成,但是在缓存不命中、缓存过期和全部的写操作时需要访问数据库。当用户达到一定规模后,数据库会因为负载压力过高而成为瓶颈,常见做法如下:

  • 读写分离:利用Mysql主从复制机制搭建读写分离集群,在客户端实现或者服务端利用中间件(例如MyCAT)实现读写分离;
  • 分库分表:针对不同业务类型,进行分库,部署到不同服务器上,减少单服务器压力,同时针对大表,根据一定条件(如用户ID取模、ID范围)进行水平分表,减少单表读写压力;
  • 数据库连接池:充分利用连接复用,解决数据库连接过程需要占用资源,影响响应速度等问题,PHP本身没有连接池,可以利用第三方框架实现(例如SMProxy);
  • 硬件优化:利用磁盘阵列(RAID)提升数据可靠性,资金充足的话可以换SSD硬盘;

负载均衡

  • 业务拆分:根据URL拆分业务,分发流量到不同的服务器组,防止单一功能模块卡住,而影响整个业务,主要遵循SOA的架构思路,将业务模块打散(鸡蛋不要放在一个篮子里面)
  • 反向代理负载均衡:充分利用Nginx/Haproxy/LVS反向代理负载均衡,搭建服务器集群,提升并发处理能力;
  • DNS负载均衡:DNS解析可以依据不同网络运营商、地区进行分别解析、从而实现DNS层面负载均衡;

代码

  • 多线程:充分利用多CPU优势进行数据批处理
  • 锁:在高并发情况下,对统一资源读写访问容易出现脏读、幻读,这个时候需要对核心资源枷锁,利用Redis或者Zookeeper等可以实现分布式锁;
  • 设计模式:充分利用设计模式,实现逻辑解耦、分层和资源服用,例如单利模式、观察者模式等;
  • 数据结构和算法:好的数据结构和算法可以给程序性能带来很大提升、例如链表查找、快速排序等;
  • 垃圾回收:良好的编程习惯,例如即时清理内存占用大的变量、避免操作的数据量大等都可以避免出现内存泄漏(OOM)的情况发生。
  • SQL查询优化:SQL执行效率一般是影响响应速度的关键,可以开启SQL慢查询日志,对执行效率慢的SQL语句进行优化;
  • GIT多分支:良好的分支管理对于CI/CD、版本测试有很好的作用,建议参考git-flow进行日常Git协作。
  • 日志:统一的日志输出标准,有利于进行数据追踪和问题排查。

冗余

  • 数据定期备份:定期全量备份+主从同步增量备份,例如MySQL/Redis主从复制进行增量备份、Mysqldump全量备份、Redis利用RDB或者AOF进行备份;
  • 集群:一定数量的备用服务器,可以保障系统的高可用,防止单点故障,可以结合Keepalived+LVS/HAProxy/Nginx等实现数据库/Web服务器/缓存/消息中间件高可用集群;

自动化

  • CI/CD:利用成熟的CI/CD机制实现自动化测试、代码检测,自动化发布,例如可采用Git+Jenkins+Docker搭建CI/CD工作流。
  • 自动化部署:针对超过30台的服务器部署,可以采用ansible进行批量管理
  • 自动监控/报警:监控用户行为日志、服务器日志等 ,可结合zabbix+ELK进行实施;
  • 自动降级和资源调度:微服务架构范畴,作者也在努力学习中。

安全

  • 二次验证:充分手机验证/人脸识别进行二次验证,保障操作的真实性;
  • web防火墙:传统防火墙仅限于包过滤,网络和端口地址转换(NAT)和VPN等功能。它根据端口,协议和IP地址做出决策;Web防火墙(WAF)则提供了HTTP/HTTPS访问请求监控、自定义过滤规则、Web攻击防护、安全合规等功能;可以利用阿里云等提供的WAF服务或者基于Nginx+lua等实现的类似API网关实施类似功能;
  • 内外网隔断:暴露出来的IP和端口越少,安全系数越高;例如数据库服务、缓存服务、中间件等尽量只允许内外访问,如果确实需要可以通过路由转发或反向代理实现;
  • 数据加密:充分利用非对称加密,例如启用https, rsa加密等,保障数据传输的安全性;
  • 网络攻击:需要防止DDOS攻击、XSS攻击、SQL注入、CSRF等,常见手段CDN加速、高仿IP、Linux内核优化、数据输入输出过滤、Referer限制、表单添加随机token/验证码等;
  • 信息安全:对于垃圾信息、敏感信息可以采用第三方解决方案(例如网易云盾、百度AI等)对文本、图片、音频、视频等进行过滤和审核。

总结

架构的演变,遵循着”分层->分割->分布式”的思路不断深入,是随着业务的增长,不断积累经验、优化、改良的过程。业务发展是架构发展的主要力量,架构的核心价值是服务业务的灵活发展。所有的架构设计必须以了解业务特点作为出发点,需要考虑互联互通、负载均衡、网络、开发、缓存、存储、数据库、安全性等层面,这些层面看似一个整体,任何一个环节出问题都可能导致整个崩溃,所以一个高可用、高并发的平台还少不了监控、开发、运维等角色通力协作。

大型网站的架构设计,作者也在不断的学习和实践,特别是微服务架构和K8S的运用,必然是未来主流的架构思想,作者正在努力学习。关于本文内容,欢迎大家积极补充。

服务降级、熔断和限流

发表于 2019-08-21 | 分类于 Linux

在高并发系统开发时有三把利器用来保护系统:缓存、降级和限流。缓存的目的是提升系统访问速度和增大系统能处理的容量,是抗高并发流量的重要手段;而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流。

降级

降级也就是服务降级,当我们的服务器压力剧增为了保证核心功能的可用性,而选择性的降低一些功能的可用性,或者直接关闭该功能。这就是典型的丢车保帅了。

就比如贴吧类型的网站,当服务器吃不消的时候,可以选择把发帖功能关闭,注册功能关闭,改密码,改头像这些都关了,为了确保登录和浏览帖子这种核心的功能。

熔断

降级一般而言指的是我们自身的系统出现了故障而降级。而熔断一般是指依赖的外部接口出现故障的情况断绝和外部接口的关系。

例如你的A服务里面的一个功能依赖B服务,这时候B服务出问题了,返回的很慢。这种情况可能会因为这么一个功能而拖慢了A服务里面的所有功能,因此我们这时候就需要熔断!即当发现A要调用这B时就直接返回错误(或者返回其他默认值啊啥的),就不去请求B了。如果是微服务体系,各系统之间互相调用,一环扣一环,出问题不熔断,那就回拖垮整个系统,造成雪崩。

有人说熔断属于降级的一种,但是熔断和降级还是有些区别的:

  • 触发原因不太一样:服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
  • 管理目标的层次不太一样:熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
  • 实现方式不太一样:服务降级具有代码侵入性(由控制器完成/或自动降级),熔断一般称为自我熔断。

限流

一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。

PHP微服务开发指南

发表于 2019-08-12 | 分类于 PHP

开发框架

目前PHP相关的微服务框架不多,基本上都是基于Swoole开发, 下面介绍几种网络上信息相对较多的几款PHP微服务开发框架。

腾讯Tars

Tars是基于名字服务使用Tars协议的高性能RPC开发框架,同时配套一体化的服务治理平台,帮助个人或者企业快速的以微服务的方式构建自己稳定可靠的分布式应用。

  • 官网:https://tars.tencent.com/base/tars_index/cn/index.html
  • 项目地址:https://gitee.com/TarsCloud/TarsPHP
  • 产品介绍:https://www.oschina.net/news/108987/tars-php-the-road

PHP-msf

PHP-msf是Camera360社区服务器端团队基于Swoole自主研发现代化的PHP协程服务框架,是Swoole的工程级企业应用框架,经受了Camera360自拍相机亿级用户高并发大流量的考验。

  • 产品说明:https://www.oschina.net/p/php-msf
  • 项目地址:https://github.com/pinguo/php-msf-docs

Swoft

Swoft是基于swoole协程2.x的高性能PHP微服务框架,内置http服务器。框架全协程实现,性能优于传统的php-fpm模式。

  • 官网:https://www.swoft.org/
  • 文档:https://www.swoft.org/docs
  • 项目地址:https://github.com/swoft-cloud/swoft

Hyperf

Hyperf是基于 Swoole 4.4+ 实现的高性能、高灵活性的PHP协程框架,内置协程服务器及大量常用的组件,性能较传统基于PHP-FPM的框架有质的提升。

框架组件库除了常见的协程版的 MySQL 客户端、Redis 客户端,还提供了协程版的 Eloquent ORM、WebSocket 服务端及客户端、JSON RPC 服务端及客户端、GRPC 服务端及客户端、Zipkin/Jaeger (OpenTracing) 客户端、Guzzle HTTP 客户端、Elasticsearch 客户端、Consul 客户端、ETCD 客户端、AMQP 组件、Apollo 配置中心、阿里云 ACM 应用配置管理、ETCD 配置中心、基于令牌桶算法的限流器、通用连接池、熔断器、Swagger 文档生成、Swoole Tracker、Blade 和 Smarty 视图引擎、Snowflake 全局ID生成器 等组件,省去了自己实现对应协程版本的麻烦。

  • 官网:https://www.hyperf.io/
  • 文档:https://doc.hyperf.io
  • 项目地址:https://github.com/hyperf-cloud/hyperf

开发体系

最后,总结附一张PHP微服务开发体系思维导图,因为作者也在学习微服务架构,所以不是很全面,仅供参考: PHP微服务

IO多路复用之select、poll、epoll详解

发表于 2019-07-26 | 分类于 Linux

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

select

监视多个文件句柄的状态变化,程序会阻塞在select处等待,直到有文件描述符就绪或超时。

1
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 函数监视的文件描述符分3类,writefds(写状态), readfds(读状态), exceptfds(异常状态)。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可)。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。

缺陷:

  • 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;(在linux内核头文件中,有这样的定义:#define __FD_SETSIZE 1024);
  • 内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销;
  • select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;

以select模型为例,假设我们的服务器需要支持100万的并发连接,则在__FD_SETSIZE 为1024的情况下,则我们至少需要开辟1k个进程才能实现100万的并发连接。除了进程间上下文切换的时间消耗外,从内核/用户空间大量的无脑内存拷贝、数组轮询等,是系统难以承受的。因此,基于select模型的服务器程序,要达到10万级别的并发访问,是一个很难完成的任务。

poll

与select轮询所有待监听的描述符机制类似,但poll使用pollfd结构表示要监听的描述符,poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他select的缺点依然存在

1
int poll (struct pollfd *fds, unsigned int nfds, int timeout);

pollfd结构:

1
2
3
4
5
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};

pollfd结构包括了events(要监听的事件)和revents(实际发生的事件)。而且也需要在函数返回后遍历pollfd来获取就绪的描述符。

epoll

epoll的实现机制与select/poll机制完全不同,相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

epoll在Linux内核中申请了一个简易的文件系统,通过三个函数epoll_create、epoll_ctl, epoll_wait实现调度:

1
2
3
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

调用过程如下:

  • 调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源
  • 调用epoll_ctl向epoll对象中添加连接的套接字
  • 调用epoll_wait收集发生的事件的连接

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关,eventpoll结构体如下所示:

IO

每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。

而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示: IO

当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

IO

通过红黑树和双链表数据结构,并结合回调机制,造就了epoll的高效。

如此一来,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制所有连接的句柄数据,内核也不需要去遍历全部的连接。

12…23

Ravior

222 日志
32 分类
56 标签
RSS
GitHub Gitee
Links
  • Redis 命令参考
  • OpenResty 最佳实践
  • Nginx中文文档
  • Vue中文文档
  • Kafka中文文档
  • RabbitMQ中文文档
  • gRPC中文文档
  • Scrapy入门教程
© 2020 Ravior