MySQL & NoSQL – Memcached 插件

大多数人都已经听说 NoSQL 数据库,其中使用最广泛的工具是 Memcached,你们通过 Memcached 再应用层和数据库之间添加一个缓存层。从 MySQL 5.6 开始,你可以获得一个全新的插件,这个插件把 MySQL 和 Memcached 集成起来。在此文中,我们将学习怎样在 Linux 中 安装这个插件,怎样做一些基础的配置。

MySQL & NoSQL – Memcached 插件
MySQL & NoSQL – Memcached 插件

先决条件

安装 libevent。

译者注:以下命令由译者提供。

命令如下:

yum install libevent -y
yum install php -y
yum install policycoreutils-python -y

译者注:我的操作系统版本是 RHEL 6.5,而作者的 OS 版本为 CentOS。以下是我的 MySQL 版本。

mysql> SHOW VARIABLES LIKE '%version%';
+-------------------------+------------------------------+
| Variable_name           | Value                        |
+-------------------------+------------------------------+
| innodb_version          | 5.6.21                       |
| protocol_version        | 10                           |
| slave_type_conversions  |                              |
| version                 | 5.6.21                       |
| version_comment         | MySQL Community Server (GPL) |
| version_compile_machine | x86_64                       |
| version_compile_os      | Linux                        |
+-------------------------+------------------------------+
7 rows in set (0.00 sec)

安装

安装 Memcached 支持,我们需要创建一些为 MySQL 和 Memcached 集成服务的表。MySQL 已经包含了创建这些表的文件(innodb_memcached_config.sql),你可以在你的 basedir 子目录中找到这个文件。为了找到你的 basedir 在什么地方,运行如下命令:

mysql> SHOW VARIABLES LIKE 'basedir';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| basedir       | /usr  |
+---------------+-------+
1 row in set (0.00 sec)

如果你通过发行版仓库安装 MySQL,这个文件的路径如下:$basedir/share/mysql/innodb_memcached_config.sql

如果你使用 MySQL 二进制版本,这个文件的路径如下:$basedir/share/innodb_memcached_config.sql

现在,我们将运行这个 SQL 文件。默认情况下,这个脚本在 test 数据库中创建一个 test 表,但是在我们的测试中,我们将使用 memcached 数据库。

译者注:操作日志中的时间可能跟原文不同,以下日志来自自己的实验。

mysql> CREATE DATABASE IF NOT EXISTS test;
Query OK, 1 row affected (0.00 sec)

mysql> source /usr/share/mysql/innodb_memcached_config.sql
Query OK, 1 row affected (0.00 sec)

Database changed
Query OK, 0 rows affected (0.14 sec)

Query OK, 0 rows affected (0.02 sec)

Query OK, 0 rows affected (0.02 sec)

Query OK, 1 row affected (0.01 sec)

Query OK, 1 row affected (0.00 sec)

Query OK, 1 row affected (0.01 sec)

Query OK, 1 row affected (0.01 sec)

Database changed
Query OK, 0 rows affected (0.03 sec)

Query OK, 1 row affected (0.00 sec)

mysql>

现在,让我们创建我们自己的表,用于存放 Memcached 数据:

mysql> CREATE DATABASE IF NOT EXISTS memcached;
Query OK, 1 row affected (0.00 sec)

mysql> CREATE TABLE memcached.dados LIKE test.demo_test;
Query OK, 0 rows affected (0.02 sec)

mysql> UPDATE innodb_memcache.containers SET db_schema = 'memcached',
    -> db_table = 'dados' WHERE name = 'aaa' LIMIT 1;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> DROP DATABASE test;
Query OK, 1 row affected (0.09 sec)

下一步是在 MySQL 中安装 Memcached 插件。为了实现这个功能,我们将会使用INSTALL PLUGIN 命令:

mysql> INSTALL PLUGIN daemon_memcached soname "libmemcached.so";
Query OK, 0 rows affected (0.03 sec)

验证此插件是否成功安装,我们可以运行如下命令:

mysql> ! netstat -tunap | grep LIST | grep mysql
tcp        0      0 0.0.0.0:11211               0.0.0.0:*                   LISTEN      1858/mysqld
tcp        0      0 :::11211                    :::*                        LISTEN      1858/mysqld
tcp        0      0 :::3306                     :::*                        LISTEN      1858/mysqld

配置和使用

现在,我们将会通过一种编程语言——PHP,比如使用这种方式:

[root@mysql memcache]# cat test1.php

译者注:原文是 new Memcached(),此处改为 new Memcache(),以下的 PHP 脚本均为 new Memcache()。

addServer('localhost', 11211);
    $m->set('key1', 'Testing memcached');
    echo 'Value of key1 is:' . $m->get('key1') . "n";
?>
[root@mysql memcache]# php test1.php
Value of key1 is:Testing memcached

现在,让我们看看在 MySQL 中存储了些什么?

mysql> SELECT * FROM memcached.dados;
+------+-------------------+------+------+------+
| c1   | c2                | c3   | c4   | c5   |
+------+-------------------+------+------+------+
| key1 | Testing memcached |    0 |    1 |    0 |
+------+-------------------+------+------+------+
1 row in set (0.00 sec)

如果我们在 MySQL 中手动更改一个记录,会发生什么?

mysql> UPDATE memcached.dados
    -> SET c2 = 'Entry modified  directly on MySQL';
Query OK, 1 row affected (0.03 sec)
Rows matched: 1  Changed: 1  Warnings: 0
[root@mysql memcache]# cat test2.php
addServer('localhost', 11211);
    echo 'Value of key1 is:' . $m->get('key1') . "n";
?>
[root@mysql memcache]# php test2.php
Value of key1 is:Entry modified  directly on MySQL

[root@mysql memcache]#

如果我们想存放条目到不同的 MySQL 表,那么又会怎样?

我们仅仅需要创建一个新的表,添加一个新的容器,并且使用在 innodb_memcache 数据库的 config_options 表定义的分隔符。

mysql> SELECT * FROM innodb_memcache.config_options
    -> WHERE name = 'table_map_delimiter';
+---------------------+-------+
| name                | value |
+---------------------+-------+
| table_map_delimiter | .     |
+---------------------+-------+
1 row in set (0.00 sec)

mysql> CREATE TABLE memcached.dados2 LIKE memcached.dados;
Query OK, 0 rows affected (0.08 sec)

mysql> INSERT INTO innodb_memcache.containers(name, db_schema, db_table,
    -> key_columns, value_columns, flags, cas_column, expire_time_column,
    -> unique_idx_name_on_key)
    -> VALUES('bbb', 'memcached', 'dados2', 'c1', 'c2', 'c3','c4','c5','PRIMARY');
Query OK, 1 row affected (0.06 sec)

我们已经创建一个名为 dados2 的新表,并且添加了一个新的名为 bbb 的容器指向那个表,现在我们仅仅需要在 Memcached 中使用它作为前缀即可。

[root@mysql memcache]# cat test3.php
addServer('localhost', 11211);
    $m->set('@@bbb.key1', 'Should be stored on dados2 table');
    echo 'Value of bbb.key1 is:' . $m->get('@@bbb.key1') . "n";
?>
[root@mysql memcache]# php test3.php
Value of bbb.key1 is:Should be stored on dados2 table
mysql> SELECT * FROM memcached.dados2;
+------+----------------------------------+------+------+------+
| c1   | c2                               | c3   | c4   | c5   |
+------+----------------------------------+------+------+------+
| key1 | Should be stored on dados2 table |    0 |    2 |    0 |
+------+----------------------------------+------+------+------+
1 row in set (0.00 sec)

我们也可以映射这个表,将存储的值分为单独的域。

mysql> SELECT * FROM innodb_memcache.config_options
    -> WHERE name = 'separator';
+-----------+-------+
| name      | value |
+-----------+-------+
| separator | |     |
+-----------+-------+
1 row in set (0.00 sec)

我们将会使用这个字符来把值存储到不同的列中。让我们创建一个表,添加到一个新的容器中(我们将会指定新的分隔符——逗号’,’,来存放我们的数据):

mysql> CREATE TABLE products
    -> (id varchar(128),
    -> name varchar(255),
    -> value varchar(15),
    -> c3 int,
    -> c4 bigint,
    -> c5 int,
    -> PRIMARY KEY(id));
Query OK, 0 rows affected (0.05 sec)

mysql> INSERT INTO innodb_memcache.containers
    -> (name, db_schema, db_table, key_columns, value_columns,
    -> flags, cas_column, expire_time_column, unique_idx_name_on_key)
    -> VALUES ('products', 'memcached', 'products', 'id', 'name,value',
    -> 'c3','c4','c5','PRIMARY');
Query OK, 1 row affected (0.06 sec)

现在,让我们创建一个产品数组,然后添加这些数据到 Memcached 中。

[root@mysql memcache]# cat test4.php
addServer('localhost', 11211);
    $products = array(
    array('1', 'TV', '1999,00'),
    array('2', 'Hack', '399,00'),
    array('3', 'Table', '599,00'),
    array('4', 'Chair', '99,00')
    );

    foreach($products as $product)
    {
        $key = '@@products.' . $product[0];
        $value = $product[1] . '|' . $product[2];
        $m->set($key, $value);
    }
?>
[root@mysql memcache]# php test4.php
mysql> SELECT * FROM memcached.products;
+----+-------+---------+------+------+------+
| id | name  | value   | c3   | c4   | c5   |
+----+-------+---------+------+------+------+
| 1  | TV    | 1999,00 |    0 |    3 |    0 |
| 2  | Hack  | 399,00  |    0 |    4 |    0 |
| 3  | Table | 599,00  |    0 |    5 |    0 |
| 4  | Chair | 99,00   |    0 |    6 |    0 |
+----+-------+---------+------+------+------+
4 rows in set (0.00 sec)

服务器/服务 重启

让我们看看如果我们重启 MySQL 服务(重启服务器通用适用)会发生什么?

这些存储在 Memcached 中的数据在 MySQL 服务重启之后仍然会存在吗?

[root@mysql memcache]# service mysql restart
Shutting down MySQL...... SUCCESS!
Starting MySQL. SUCCESS!

[root@mysql memcache]# cat test5.php
addServer('localhost', 11211);
    echo 'Value of key1 is:' . $m->get('key1') . "n";
?>
[root@mysql memcache]# php test5.php
Value of key1 is:Entry modified  directly on MySQL

换句话说!即使服务重启或者服务器重启,这些数据仍然会存在。

SELinux

在 SELinux 启用的环境中,会阻止 Memcached 和 MySQL 集成,因为不允许监听 Memcached 端口,下面是一个怎样允许监听 Memcached 端口的例子(我使用 CentOS Linux 分发版):

在 /var/log/audit/audit.log 中查找包含 mysqld 和 denied 关键字的条目,如果你能找到,输入如下的命令来创建一个新的 SELinux 模块来允许:

type=AVC msg=audit(1423266535.066:5): avc:  denied  { name_bind } for
pid=1123 comm="mysqld" src=11211 scontext=system_u:system_r:mysqld_t:s0
tcontext=system_u:object_r:memcache_port_t:s0 tclass=tcp_socket
type=SYSCALL msg=audit(1423266535.066:5): arch=c000003e syscall=49
success=no exit=-13 a0=2f a1=7f3aec043230 a2=10 a3=7f3af61fa75c
items=0 ppid=999 pid=1123 auid=4294967295 uid=27 gid=27 euid=27 suid=27
fsuid=27 egid=27 sgid=27 fsgid=27 tty=(none) ses=4294967295 comm="mysqld"
exe="/usr/sbin/mysqld" subj=system_u:system_r:mysqld_t:s0 key=(null)

[root@mysql ~]# audit2why < /var/log/audit/audit.log
type=AVC msg=audit(1423266535.066:5): avc:  denied  { name_bind } for
pid=1123 comm="mysqld" src=11211 scontext=system_u:system_r:mysqld_t:s0
tcontext=system_u:object_r:memcache_port_t:s0 tclass=tcp_socket

    Was caused by:
        Missing type enforcement (TE) allow rule.

        You can use audit2allow to generate a loadable module to allow this access.

[root@mysql ~]# cd /root/
[root@mysql ~]# mkdir selinux-custom
[root@mysql ~]# cd selinux-custom
[root@mysql selinux-custom]# audit2allow -a -M mysql-memcache
******************** IMPORTANT ***********************
To make this policy package active, execute:

semodule -i mysql-memcache.pp
[root@mysql selinux-custom]# semodule -i mysql-memcache.pp

译者注:以下为译者添加,用于测试在 SELinux 环境下,MySQL 和 Memcached 的集成。

[root@mysql selinux-custom]# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /selinux
Current mode:                   enforcing
Mode from config file:          enforcing
Policy version:                 24
Policy from config file:        targeted
[root@mysql selinux-custom]# getenforce
Enforcing
[root@mysql selinux-custom]# /etc/init.d/mysql restart
Shutting down MySQL....                                    [  OK  ]
Starting MySQL.                                            [  OK  ]
[root@mysql memcache]# cd ~/memcache
[root@mysql memcache]# cat test6.php
addServer('localhost', 11211);
    echo 'Value of key1 is:' . $m->get('key1') . "n";
?>
[root@mysql selinux-custom]# netstat -tunap | grep LIST | grep mysql
tcp        0      0 0.0.0.0:11211               0.0.0.0:*                   LISTEN      7820/mysqld
tcp        0      0 :::11211                    :::*                        LISTEN      7820/mysqld
tcp        0      0 :::3306                     :::*                        LISTEN      7820/mysqld
[root@mysql memcache]# php test6.php
Value of key1 is:Entry modified  directly on MySQL

Memcached 选项

如果你想更改任何 Memcached 特殊的选项,你可以在 MySQL 的配置文件中添加daemon_memcached_option 参数,比如更改 Memcached 端口:

# In the configuration file, my.cnf generally
daemon_memcached_option=”-p11222”

就是这样,我希望你已经学会了如何安装和配置 MySQL 和 Memcached 插件的集成工作。

pyinfo():一个像 phpinfo 一样的 Python 脚本

pyinfo():一个像 phpinfo 一样的 Python 脚本
pyinfo():一个像 phpinfo 一样的 Python 脚本

作为一个热衷于 php 的家伙,我已经习惯了使用 phpinfo() 函数来让我轻松访问 php.ini 里的配置和加载的模块等信息。当然我也想要使用一个不存在的 pyinfo() 函数,但没有成功。按下 CTRL-E,google 一下是否有人实现了它?

是的,有人已经实现了。但是,对我来说它非常难看。荒谬!因为我无法忍受丑陋的布局,咳咳,我不得不亲自动手来改改。我用找到的代码,并重新进行布局使之更好看点。Python 官方网站的布局看起来不错,那么何不借用他们的颜色和背景图片呢?是的,这听起来像一个计划。

提醒你下,我仅仅在 Python 2.6.4 上运行过它,所以在别的版本上可能有风险(将它移植到任何其他版本它应该是没有问题的)。要使用它,只需要导入该文件, 并调用pyinfo()函数得到它的返回值打印到屏幕上。好嘞!

如果你在使用 mod_wsgi 时没有得到正确的返回结果,你可以如下运行它(当然得替换路径):

def application(environ, start_response):
    import sys
    path = 'YOUR_WWW_ROOT_DIRECTORY'
    if path not in sys.path:
        sys.path.append(path)
    from pyinfo import pyinfo
    output = pyinfo()
    start_response('200 OK', [('Content-type', 'text/html')])
    return [output]

via:http://bran.name/articles/pyinfo-a-good-looking-phpinfo-like-python-script/

作者:Bran van der Meer 译者:strugglingyouth 校对:wxy

本文由 LCTT 原创编译, Linux中国 荣誉推出

ReactPHP:PHP版的Node.js

从名字说起 

ReactPHP:PHP版的Node.js
ReactPHP:PHP版的Node.js

虽然ReactPHP项目已经发展了有4年之久,但是对于其称呼显得有点混乱。在开源中国为其建立的项目主页上,其被命名为React,或者node.PHP。国外的一些的博客谈及这个项目时,多数使用的是ReactPHP。到底哪种说法比较标准呢?我们不妨来看看官方的态度。此项目的官方主页是 http://www.reactphp.org 。打开官网,你会发现网站的title是React,其logo上的文字为reactphp。可以看出,官方更倾向于被命名为React或者ReactPHP。我建议使用ReactPHP作为其名称。原因大概有两个。

  1. React单词的意思太泛,并且已经有一些项目的名称与React相关,容易引起误解。
  2. 目前国内使用ReactPHP的人比较少,相关资料文档也比较少。在国外它一般被称为ReactPHP,使用ReactPHP在国外检索资料更容易。

ReactPHP与Node.js有着相同的特点

许多人认为ReactPHP是Node.js的php版本,这是有一定道理的。他们的确有很多相似的特点。

事件驱动,异步执行,非阻塞IO

什么是事件驱动?所谓事件驱动,简单的说就是,你告诉我你关注什么事情,等事情发生的时候我会主动通知你,然后你再作相应的处理。这样可以就可以把你解放出来,你只关注于处理好相应事件即可。采用事件驱动有什么优势呢?相对于常见的多进程编程,能更好的利用CPU资源。多进程编程会使进程数量变多,进程上下文切换频繁会增加系统压力,浪费宝贵的CPU资源。相对于多线程编程而言,可以降低编程复杂度。开发者不必再考虑线程间资源共享导致资源竞争等问题。

ReactPHP和Node.js都采用了事件驱动和非阻塞IO。从官方主页的宣传语上就可以得到印证。在Node.js的官网上有一段话:

Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient。

上面的意思是,Node.js使用事件驱动和非阻塞IO模型,以保证轻量级和高效。

在ReactPHP官网也有一段话:

Event-driven, non-blocking I/O with PHP.

上面的意思是,ReactPHP使用PHP语言实现了事件驱动和非阻塞。

ReactPHP和Node.js在实现事件驱动机制时也有相似之处。在事件的监听上,ReactPHP和Node.js都使用了libev库,但是也都是不只使用libev库。由于libev对windows支持不够好。因此,Node.js中封装了一层libuv。libuv是基于windows的IOCP和*nix的libev进行封装。而ReactPHP除了使用libev库外,还是用了其他的库。如,libevent。

ReactPHP和Node.js都各自有自己的生态圈。在各自生态圈中的一些模块一般都采用了事件驱动,异步编程的风格。如,ReactPHP的Stream模块,提供了以下几个事件:drain、error、close、pipe、end、data。相应的,在Node.js中也有一些类似的事件。Node.js的Net模,其中的net.Socket对象就有以下事件:connect、data、end、timeout、drain、error、close等。这样,开发人员只需要知道自己关注那些事件,并在这些事件上注册回调函数。等事件发生的时候,会主动执行这些注册的回调函数。这些回调函数都是异步执行的,这些函数虽然在注册的时候有先后顺序,但是在执行的时候是无序的,随机的,执行顺序和事件发生顺序相关。事件驱动再加上非阻塞IO,就可以极大的利用系统资源,代码无需阻塞等待资源可用。

单进程单线程

ReactPHP和Node.js一样都是采用了单进程和单线程的运行方式。单进程,单线程方式,没有多线程的资源抢占和上下文切换,高效率的运行,维护着一个事件队列。这种运行方式,通常情况下瓶颈一般在CPU而不是内存。由于单进程,单线程只能在一个CPU上运行,本身不能充分利用多个CPU资源。为了解决这个问题,我们可以启动多个进程,监听不同的端口,前端使用nginx等做代理,把请求分发到不同的进程上。对于多进程的管理上,现在已经有不少开源项目可以实现。如,php-pm

ReactPHP性能压测

相对于传统的nginx+php-fpm方式,ReactPHP的性能表现如何呢?现在我们来做下性能压测。服务器环境如下:

  • 8核CPU
  • PHP版本为5.5.15,使用opcache扩展
  • 操作系统为Centos5
  • Nginx版本为nginx/1.2.9
  • ReactPHP版本为0.4

为了公平起见,我们php-fpm和ReactPHP都只启动一个进程。压测工具我们使用ab,Apache开源的http服务压测工具。我们压测分两种情况来进行:第一种情况是只输出简单的Hello World。第二种情况只进行一次简单的sql语句查询,select 1 as num。

第一种情况:Hello World的压测结果如下,QPS:

ReactPHP:PHP版的Node.js
ReactPHP:PHP版的Node.js

第二种情况:SQL查询的压测结果如下,QPS:

ReactPHP:PHP版的Node.js
ReactPHP:PHP版的Node.js

可见,对于cpu密集型的应用,nginx+php-fpm的方式要比ReactPHP有更好的表现。但是对于数据库查询这样涉及网络IO的场景,ReactPHP的性能要远远好于nginx+php-fpm的方式。 

ReactPHP的应用场景

根据上面的测试,ReactPHP更适合IO密集型的应用。以下是ReactpHP比较适合的应用场景。

  1. 从RESTful API获取数据,并进行拼装输出只是请求api获取数据,然后进行简单的拼装,最后输出到客户端。本身业务逻辑不复杂。在请求的时候,可以同时对多个api进行请求,相对于顺序调用api的方式,会节省很多的时间,大大提高了响应的效率。
  2. 实时推送,在线聊天实时推送和在线聊天都需要维护大量的链接。这个正是ReactpHP擅长的。他可以很轻松的维护上万的链接。
  3. 分布式IO系统如一个数据库中间件层,它需要解析SQL为多条子SQL,然后把子SQL分发到不同的服务器查询数据,然后合并数据返回给客户端。这种情况下可以使用ReactPHP同时对多个数据库服务器进行查询。

如何使用ReactPHP 

ReactPHP可以使用composer安装,这个也是官方推荐的安装方式。首先安装composer。

curl -s https://getcomposer.org/installer| php

安装完成后,会在当前目录下生成一个composer.phar文件。然后我们使用composer.phar安装react。

php ./composer.phar require react/react

安装成功后,会在当前目录下生成一个vendor目录。下载的程序就在这个目录下。现在你就可以使用ReactPHP写程序了。例如,我们想提供一个http服务,我们将把客户端通过data参数提交的数据加上www.后进行返回。代码如下: 

writeHead(200, array('Content-Type' => 'text/plain'));
        $query = $request->getQuery();
        $data = isset($query["data"]) ? $query["data"] : "";
        $response->end("www.{$data}n");
};

$loop = ReactEventLoopFactory::create();
$socket = new ReactSocketServer($loop);
$http = new ReactHttpServer($socket, $loop);

$http->on('request', $app);

$socket->listen($port, '0.0.0.0');
$loop->run();
?> 

把上面的代码保持为文件reactphp.php。然后启动服务:

php ./reactphp.php 5501

最后,我们验证下效果,可以通过下面的方式访问。 

$curl  http://10.101.80.141:5501/?data=bo56.com

www.bo56.com 

ReactPHP也有自己的生态圈。如进行异步mysql查询的react-php。

小结

ReactPHP作为Node.js的PHP版本。在实现思路,使用方法,应用场景上的确有很多相似之处。但是ReactPHP毕竟比Node.js年轻,目前生态圈还是不如Node.js完善。目前文档也不是很完善,在国内应用也比较少。但是相信,它会越来越完善,应用越来越广。

史上最复杂的验证邮件地址的正则表达式

用正则表达式验证邮件地址似乎是一件简单的事情,但是如果要完美的验证一个合规的邮件地址,其实也许很复杂。

史上最复杂的验证邮件地址的正则表达式
史上最复杂的验证邮件地址的正则表达式

邮件地址的规范来自于 RFC 5322 。有一个网站 emailregex.com 专门列出各种编程语言下的验证邮件地址的正则表达式,其中很多正则表达式都是我听说过而从未见过的复杂——我想说,做这个网站的程序员是被邮件验证这件事伤害了多深啊!

其实,在产品环境中,一般来说并不需要这么复杂的正则表达式来做到99.99%正确。一般来说,从执行效率和测试覆盖率来说,只需要一个简单的版本即可:

/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/i

那么下面我们来看看这些更严谨、更复杂的正则表达式吧:

验证邮件地址的通用正则表达式(符合 RFC 5322 标准)

(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[x01-x08x0bx0cx0e-x1fx21x23-x5bx5d-x7f]|\[x01-x09x0bx0cx0e-x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[x01-x08x0bx0cx0e-x1fx21-x5ax53-x7f]|\[x01-x09x0bx0cx0e-x7f])+)])

由于各种语言对正则表达式的支持不同、语法差异和覆盖率不同,所以,不同语言里面的正则表达式也不同:

Python

这个是个简单的版本:

r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$)"

Javascript

这个有点复杂了:

/^[-a-z0-9~!$%^&*_=+}{'?]+(.[-a-z0-9~!$%^&*_=+}{'?]+)*@([a-z0-9_][-a-z0-9_]*(.[-a-z0-9_]+)*.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}))(:[0-9]{1,5})?$/i

Swift

[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}

PHP

PHP 的这个版本就更复杂了,覆盖率就更大一些:

/^(?!(?:(?:x22?x5C[x00-x7E]x22?)|(?:x22?[^x5Cx22]x22?)){255,})(?!(?:(?:x22?x5C[x00-x7E]x22?)|(?:x22?[^x5Cx22]x22?)){65,}@)(?:(?:[x21x23-x27x2Ax2Bx2Dx2F-x39x3Dx3Fx5E-x7E]+)|(?:x22(?:[x01-x08x0Bx0Cx0E-x1Fx21x23-x5Bx5D-x7F]|(?:x5C[x00-x7F]))*x22))(?:.(?:(?:[x21x23-x27x2Ax2Bx2Dx2F-x39x3Dx3Fx5E-x7E]+)|(?:x22(?:[x01-x08x0Bx0Cx0E-x1Fx21x23-x5Bx5D-x7F]|(?:x5C[x00-x7F]))*x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))]))$/iD

Perl / Ruby

对与 PHP 的版本,Perl 和 Ruby 表示不服,可以更严谨:

(?:(?:rn)?[ t])*(?:(?:(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*|(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)*<(?:(?:rn)?[ t])*(?:@(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*(?:,@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*)*:(?:(?:rn)?[ t])*)?(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*>(?:(?:rn)?[ t])*)|(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)*:(?:(?:rn)?[ t])*(?:(?:(?:[^()<>@,;:\".[]00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*|(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)*<(?:(?:rn)?[ t])*(?:@(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*(?:,@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[]00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*)*:(?:(?:rn)?[ t])*)?(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*>(?:(?:rn)?[ t])*)(?:,s*(?:(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*|(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)*<(?:(?:rn)?[ t])*(?:@(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*(?:,@(?:(?:rn)?[t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*)*:(?:(?:rn)?[ t])*)?(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|"(?:[^"r\]|\.|(?:(?:rn)?[ t]))*"(?:(?:rn)?[ t])*))*@(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*)(?:.(?:(?:rn)?[ t])*(?:[^()<>@,;:\".[] 00-31]+(?:(?:(?:rn)?[ t])+|Z|(?=[["()<>@,;:\".[]]))|[([^[]r\]|\.)*](?:(?:rn)?[ t])*))*>(?:(?:rn)?[ t])*))*)?;s

Perl 5.10 及以后版本

上面的版本,嗯,我可以说是天书吗?反正我是没有解读的想法了。当然,新版本的 Perl 语言还有一个更易读的版本(你是说真的么?) 

/(?(DEFINE)
(?
(?&mailbox) | (?&group)) (? (?&name_addr) | (?&addr_spec)) (? (?&display_name)? (?&angle_addr)) (? (?&CFWS)? < (?&addr_spec) > (?&CFWS)?) (? (?&display_name) : (?:(?&mailbox_list) | (?&CFWS))? ; (?&CFWS)?) (? (?&phrase)) (? (?&mailbox) (?: , (?&mailbox))*) (? (?&local_part) @ (?&domain)) (? (?&dot_atom) | (?"ed_string)) (? (?&dot_atom) | (?&domain_literal)) (? (?&CFWS)? [ (?: (?&FWS)? (?&dcontent))* (?&FWS)? ] (?&CFWS)?) (? (?&dtext) | (?"ed_pair)) (? (?&NO_WS_CTL) | [x21-x5ax5e-x7e]) (? (?&ALPHA) | (?&DIGIT) | [!#$%&'*+-/=?^_`{|}~]) (? (?&CFWS)? (?&atext)+ (?&CFWS)?) (? (?&CFWS)? (?&dot_atom_text) (?&CFWS)?) (? (?&atext)+ (?: . (?&atext)+)*) (? [x01-x09x0bx0cx0e-x7f]) (? \ (?&text)) (? (?&NO_WS_CTL) | [x21x23-x5bx5d-x7e]) (? (?&qtext) | (?"ed_pair)) (? (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))* (?&FWS)? (?&DQUOTE) (?&CFWS)?) (? (?&atom) | (?"ed_string)) (? (?&word)+) # Folding white space (? (?: (?&WSP)* (?&CRLF))? (?&WSP)+) (? (?&NO_WS_CTL) | [x21-x27x2a-x5bx5d-x7e]) (? (?&ctext) | (?"ed_pair) | (?&comment)) (? ( (?: (?&FWS)? (?&ccontent))* (?&FWS)? ) ) (? (?: (?&FWS)? (?&comment))* (?: (?:(?&FWS)? (?&comment)) | (?&FWS))) # No whitespace control (? [x01-x08x0bx0cx0e-x1fx7f]) (? [A-Za-z]) (? [0-9]) (? x0d x0a) (? ") (? [x20x09]) ) (?&address)/x

Ruby (简单版)

Ruby 表示,其实人家还有个简单版本:

/A([w+-].?)+@[a-zd-]+(.[a-z]+)*.[a-z]+z/i

.NET

这样的版本谁没有啊——.NET 说:

^w+([-+.']w+)*@w+([-.]w+)*.w+([-.]w+)*$

grep 命令

用 grep 命令在文件中查找邮件地址,我想你不会写个若干行的正则表达式吧,意思一下就行了:

$ grep -E -o "b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}b" filename.txt

SQL Server 

在 SQL Server 中也是可以用正则表达式的,不过这个代码片段应该是来自某个产品环境中的,所以,还体贴的照顾了那些把邮件地址写错的人:

 select email
 from table_name where
 patindex ('%[ &'',":;!+=/()<>]%', email) > 0 -- Invalid characters
 or patindex ('[@.-_]%', email) > 0 -- Valid but cannot be starting character
 or patindex ('%[@.-_]', email) > 0 -- Valid but cannot be ending character
 or email not like '%@%.%' -- Must contain at least one @ and one .
 or email like '%..%' -- Cannot have two periods in a row
 or email like '%@%@%' -- Cannot have two @ anywhere
 or email like '%.@%' or email like '%@.%' -- Cannot have @ and . next to each other
 or email like '%.cm' or email like '%.co' -- Camaroon or Colombia? Typos.
 or email like '%.or' or email like '%.ne' -- Missing last letter

Oracle PL/SQL

这个是不是有点偷懒?尤其是在那些“复杂”的正则表达式之后:

SELECT email
FROM table_name
WHERE REGEXP_LIKE (email, '[A-Z0-9._%-]+@[A-Z0-9._%-]+.[A-Z]{2,4}');

MySQL

好吧,看来最后也一样懒:

SELECT * FROM `users` WHERE `email` NOT REGEXP '^[A-Z0-9._%-]+@[A-Z0-9.-]+.[A-Z]{2,4}$';

那么,你有没有关于验证邮件地址的正则表达式分享给大家?

 

来源:https://linux.cn/article-5963-1.html

PHP 7.0 升级备注

PHP 7.0.0 beta1 发布了,在带来了引人注目的性能提升的同时,也带来了不少语言特性方面的改变。以下由 LCTT 翻译自对官方的升级备注,虽然目前还不是正式发布版,不过想必距离正式发布的特性已经差别不大了。(本文会持续追踪更新)

PHP 7.0 升级备注
PHP 7.0 升级备注

1. 向后不兼容的变化

语言变化

变量处理的变化

  • 间接变量、属性和方法引用现在以从左到右的语义进行解释。一些例子:

    $$foo['bar']['baz'] // 解释做 ($$foo)['bar']['baz']
    $foo->$bar['baz']   // 解释做 ($foo->$bar)['baz']
    $foo->$bar['baz']() // 解释做 ($foo->$bar)['baz']()
    Foo::$bar['baz']()  // 解释做 (Foo::$bar)['baz']()
    

    要恢复以前的行为,需要显式地加大括号:

    ${$foo['bar']['baz']}
    $foo->{$bar['baz']}
    $foo->{$bar['baz']}()
    Foo::{$bar['baz']}()
    
  • 全局关键字现在只接受简单变量。像以前的

    global $$foo->bar;
    

    现在要求如下写法:

    global ${$foo->bar};
    
  • 变量或函数调用的前后加上括号不再有任何影响。例如下列代码,函数调用结果以引用的方式传给一个函数

    function getArray() { return [1, 2, 3]; }
    
    $last = array_pop(getArray());
    // Strict Standards: 只有变量可以用引用方式传递
    $last = array_pop((getArray()));
    // Strict Standards: 只有变量可以用引用方式传递
    

    现在无论是否使用括号,都会抛出一个严格标准错误。以前在第二种调用方式下不会有提示。

  • 数组元素或对象属性自动安装引用顺序创建,现在的结果顺序将不同。例如:

    $array = [];
    $array["a"] =& $array["b"];
    $array["b"] = 1;
    var_dump($array);
    

    现在结果是 [“a” => 1, “b” => 1],而以前的结果是 [“b” => 1, “a” => 1]。

相关的 RFC:

list() 的变化

  • list() 不再以反序赋值,例如:

    list($array[], $array[], $array[]) = [1, 2, 3];
    var_dump($array);
    

    现在结果是 $array == [1, 2, 3] ,而不是 [3, 2, 1]。注意仅赋值顺序变化了,而赋值仍然一致(LCTT 译注:即以前的 list()行为是从后面的变量开始逐一赋值,这样对与上述用法就会产生 [3,2,1] 这样的结果了。)。例如,类似如下的常规用法

    list($a, $b, $c) = [1, 2, 3];
    // $a = 1; $b = 2; $c = 3;
    

    仍然保持当前的行为。

  • 不再允许对空的 list() 赋值。如下全是无效的:

    list() = $a;
    list(,,) = $a;
    list($x, list(), $y) = $a;
    
  • list() 不再支持对字符串的拆分(以前也只在某些情况下支持)。如下代码:

    $string = "xy";
    list($x, $y) = $string;
    

    现在的结果是: $x == null 和 $y == null (没有提示),而以前的结果是: $x == “x” 和 $y == “y” 。此外, list() 现在总是可以处理实现了 ArrayAccess 的对象,例如:

    list($a, $b) = (object) new ArrayObject([0, 1]);
    

    现在的结果是: $a == 0 和 $b == 1。 以前 $a 和 $b 都是 null。

相关 RFC:

foreach 的变化

  • foreach() 迭代不再影响数组内部指针,数组指针可通过 current()/next() 等系列的函数访问。例如:

    $array = [0, 1, 2];
    foreach ($array as &$val) {
        var_dump(current($array));
    }
    

    现在将指向值 int(0) 三次。以前的输出是 int(1)、int(2) 和 bool(false)。

  • 在对数组按值迭代时,foreach 总是在对数组副本进行操作,在迭代中任何对数组的操作都不会影响到迭代行为。例如:

    $array = [0, 1, 2];
    $ref =& $array; // Necessary to trigger the old behavior
    foreach ($array as $val) {
        var_dump($val);
        unset($array[1]);
    }
    

    现在将打印出全部三个元素 (0 1 2),而以前第二个元素 1 会跳过 (0 2)。

  • 在对数组按引用迭代时,对数组的修改将继续会影响到迭代。不过,现在 PHP 在使用数字作为键时可以更好的维护数组内的位置。例如,在按引用迭代过程中添加数组元素:

    $array = [0];
    foreach ($array as &$val) {
        var_dump($val);
        $array[1] = 1;
    }
    

    现在迭代会正确的添加了元素。如上代码输出是 “int(0) int(1)”,而以前只是 “int(0)”。

  • 对普通(不可遍历的)对象按值或按引用迭代的行为类似于对数组进行按引用迭代。这符合以前的行为,除了如上一点所述的更精确的位置管理的改进。

  • 对可遍历对象的迭代行为保持不变。

相关 RFC: https://wiki.php.net/rfc/php7_foreach

参数处理的变化

  • 不能定义两个同名的函数参数。例如,下面的方法将会触发编译时错误:

    public function foo($a, $b, $unused, $unused) {
        // ...
    }
    

    如上的代码应该修改使用不同的参数名,如:

    public function foo($a, $b, $unused1, $unused2) {
        // ...
    }
    
  • func_get_arg() 和 func_get_args() 函数不再返回传递给参数的原始值,而是返回其当前值(也许会被修改)。例如:

    function foo($x) {
        $x++;
        var_dump(func_get_arg(0));
    }
    foo(1);
    

    将会打印 “2” 而不是 “1”。代码应该改成仅在调用 func_get_arg(s) 后进行修改操作。

    function foo($x) {
        var_dump(func_get_arg(0));
        $x++;
    }
    

    或者应该避免修改参数:

    function foo($x) {
        $newX = $x + 1;
        var_dump(func_get_arg(0));
    }
    
  • 类似的,异常回溯也不再显示传递给函数的原始值,而是修改后的值。例如:

    function foo($x) {
        $x = 42;
        throw new Exception;
    }
    foo("string");
    

    现在堆栈跟踪的结果是:

    Stack trace:
    #0 file.php(4): foo(42)
    #1 {main}
    

    而以前是:

    Stack trace:
    #0 file.php(4): foo('string')
    #1 {main}
    

    这并不会影响到你的代码的运行时行为,值得注意的是在调试时会有所不同。

    同样的限制也会影响到 debug_backtrace() 及其它检查函数参数的函数。

相关 RFC: https://wiki.php.net/phpng

整数处理的变化

  • 无效的八进制表示(包含大于7的数字)现在会产生编译错误。例如,下列代码不再有效:

    $i = 0781; // 8 不是一个有效的八进制数字!
    

    以前,无效的数字(以及无效数字后的任何数字)会简单的忽略。以前如上 $i 的值是 7,因为后两位数字会被悄悄丢弃。

  • 二进制以负数镜像位移现在会抛出一个算术错误:

    var_dump(1 >> -1);
    // ArithmeticError: 以负数进行位移
    
  • 向左位移的位数超出了整型宽度时,结果总是 0。

    var_dump(1 << 64); // int(0)
    

    以前上述代码的结果依赖于所用的 CPU 架构。例如,在 x86(包括 x86-64) 上结果是 int(1),因为其位移操作数在范围内。

  • 类似的,向右位移的位数超出了整型宽度时,其结果总是 0 或 -1 (依赖于符号):

    var_dump(1 >> 64);  // int(0)
    var_dump(-1 >> 64); // int(-1)
    

相关 RFC: https://wiki.php.net/rfc/integer_semantics

字符串处理的变化

  • 包含十六进制数字的字符串不会再被当做数字,也不会被特殊处理。参见例子中的新行为:

    var_dump("0x123" == "291");     // bool(false)     (以前是 true)
    var_dump(is_numeric("0x123"));  // bool(false)     (以前是 true)
    var_dump("0xe" + "0x1");        // int(0)          (以前是 16)
    
    var_dump(substr("foo", "0x1")); // string(3) "foo" (以前是 "oo")
    // 注意:遇到了一个非正常格式的数字
    

    filter_var() 可以用来检查一个字符串是否包含了十六进制数字,或这个字符串是否能转换为整数:

    $str = "0xffff";
    $int = filter_var($str, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX);
    if (false === $int) {
        throw new Exception("Invalid integer!");
    }
    var_dump($int); // int(65535)
    
  • 由于给双引号字符串和 HERE 文档增加了 Unicode 码点转义格式(Unicode Codepoint Escape Syntax), 所以带有无效序列的 "u{" 现在会造成错误:

    $str = "u{xyz}"; // 致命错误:无效的 UTF-8 码点转义序列
    

    要避免这种情况,需要转义开头的反斜杠:

    $str = "\u{xyz}"; // 正确
    

    不过,不跟随 { 的 "u" 不受影响。如下代码不会生成错误,和前面的一样工作:

    $str = "u202e"; // 正确
    

相关 RFC:

错误处理的变化

  • 现在有两个异常类: Exception 和 Error 。这两个类都实现了一个新接口: Throwable 。在异常处理代码中的类型指示也许需要修改来处理这种情况。

  • 一些致命错误和可恢复的致命错误现在改为抛出一个 Error 。由于 Error 是一个独立于 Exception 的类,这些异常不会被已有的 try/catch 块捕获。

    可恢复的致命错误被转换为一个异常,所以它们不能在错误处理里面悄悄的忽略。部分情况下,类型指示失败不再能忽略。

  • 解析错误现在会生成一个 Error 扩展的 ParseError 。除了以前的基于返回值 / errorgetlast() 的处理,对某些可能无效的代码的 eval() 的错误处理应该改为捕获 ParseError 。

  • 内部类的构造函数在失败时总是会抛出一个异常。以前一些构造函数会返回 NULL 或一个不可用的对象。

  • 一些 E_STRICT 提示的错误级别改变了。

相关 RFC:

其它的语言变化

  • 静态调用一个不兼容的 $this 上下文的非静态调用的做法不再支持。这种情况下,$this 是没有定义的,但是对它的调用是允许的,并带有一个废弃提示。例子:

    class A {
        public function test() { var_dump($this); }
    }
    
    // 注意:没有从类 A 进行扩展
    class B {
        public function callNonStaticMethodOfA() { A::test(); }
    }
    
    (new B)->callNonStaticMethodOfA();
    
    // 废弃:非静态方法 A::test() 不应该被静态调用
    // 提示:未定义的变量 $this
    NULL
    

    注意,这仅出现在来自不兼容上下文的调用上。如果类 B 扩展自类 A ,调用会被允许,没有任何提示。

  • 不能使用下列类名、接口名和特殊名(大小写敏感):

    bool
    int
    float
    string
    null
    false
    true
    

    这用于 class/interface/trait 声明、 class_alias() 和 use 语句中。

    此外,下列类名、接口名和特殊名保留做将来使用,但是使用时尚不会抛出错误:

    resource
    object
    mixed
    numeric
    
  • yield 语句结构当用在一个表达式上下文时,不再要求括号。它现在是一个优先级在 “print” 和 “=>” 之间的右结合操作符。在某些情况下这会导致不同的行为,例如:

    echo yield -1;
    // 以前被解释如下
    echo (yield) - 1;
    // 现在被解释如下
    echo yield (-1);
    
    yield $foo or die;
    // 以前被解释如下
    yield ($foo or die);
    // 现在被解释如下
    (yield $foo) or die;
    

    这种情况可以通过增加括号来解决。

  • 移除了 ASP (<%) 和 script (

在 Linux 命令行中使用和执行 PHP 代码(一)

PHP是一个开源服务器端脚本语言,最初这三个字母代表的是“Personal Home Page”,而现在则代表的是“PHP:Hypertext Preprocessor”,它是个递归首字母缩写。它是一个跨平台脚本语言,深受C、C++和Java的影响。

在 Linux 命令行中使用和执行 PHP 代码(一)
在 Linux 命令行中使用和执行 PHP 代码(一)

在 Linux 命令行中运行 PHP 代码

PHP的语法和C、Java以及带有一些PHP特性的Perl变成语言中的语法十分相似,它当下大约正被2.6亿个网站所使用,当前最新的稳定版本是PHP版本5.6.10。

PHP是HTML的嵌入脚本,它便于开发人员快速写出动态生成的页面。PHP主要用于服务器端(而Javascript则用于客户端)以通过HTTP生成动态网页,然而,当你知道可以在Linux终端中不需要网页浏览器来执行PHP时,你或许会大为惊讶。

本文将阐述PHP脚本语言的命令行方面。

1. 在安装完PHP和Apache2后,我们需要安装PHP命令行解释器。

# apt-get install php5-cli          [Debian 及类似系统]
# yum install php-cli               [CentOS 及类似系统]

接下来我们通常要做的是,在/var/www/html(这是 Apache2 在大多数发行版中的工作目录)这个位置创建一个内容为 ,名为 infophp.php 的文件来测试(PHP是否安装正确),执行以下命令即可。

# echo '' > /var/www/html/infophp.php

然后,将浏览器访问 http://127.0.0.1/infophp.php ,这将会在网络浏览器中打开该文件。

在 Linux 命令行中使用和执行 PHP 代码(一)
在 Linux 命令行中使用和执行 PHP 代码(一)

检查PHP信息

不需要任何浏览器,在Linux终端中也可以获得相同的结果。在Linux命令行中执行/var/www/html/infophp.php,如:

# php -f /var/www/html/infophp.php
在 Linux 命令行中使用和执行 PHP 代码(一)
在 Linux 命令行中使用和执行 PHP 代码(一)

从命令行检查PHP信息

由于输出结果太大,我们可以通过管道将上述输出结果输送给 less 命令,这样就可以一次输出一屏了,命令如下:

# php -f /var/www/html/infophp.php | less
在 Linux 命令行中使用和执行 PHP 代码(一)
在 Linux 命令行中使用和执行 PHP 代码(一)

检查所有PHP信息

这里,‘-f‘选项解析并执行命令后跟随的文件。

2. 我们可以直接在Linux命令行使用phpinfo()这个十分有价值的调试工具而不需要从文件来调用,只需执行以下命令:

# php -r 'phpinfo();'
在 Linux 命令行中使用和执行 PHP 代码(一)
在 Linux 命令行中使用和执行 PHP 代码(一)

PHP调试工具

这里,‘-r‘ 选项会让PHP代码在Linux终端中不带<>标记直接执行。

3. 以交互模式运行PHP并做一些数学运算。这里,‘-a‘ 选项用于以交互模式运行PHP。

# php -a

Interactive shell

php > echo 2+3;
5
php > echo 9-6;
3
php > echo 5*4;
20
php > echo 12/3;
4
php > echo 12/5;
2.4
php > echo 2+3-1;
4
php > echo 2+3-1*3;
2
php > exit

输入 ‘exit‘ 或者按下 ‘ctrl+c‘ 来关闭PHP交互模式。

在 Linux 命令行中使用和执行 PHP 代码(一)
在 Linux 命令行中使用和执行 PHP 代码(一)

启用PHP交互模式

4. 你可以仅仅将PHP脚本作为shell脚本来运行。首先,创建在你当前工作目录中创建一个PHP样例脚本。

# echo -e '#!/usr/bin/phpn' > phpscript.php

注意,我们在该PHP脚本的第一行使用#!/usr/bin/php,就像在shell脚本中那样(/bin/bash)。第一行的#!/usr/bin/php告诉Linux命令行用 PHP 解释器来解析该脚本文件。

其次,让该脚本可执行:

# chmod 755 phpscript.php

接着来运行它,

# ./phpscript.php

5. 你可以完全靠自己通过交互shell来创建简单函数,这你一定会被惊到了。下面是循序渐进的指南。

开启PHP交互模式。

# php -a

创建一个函数,将它命名为 addition。同时,声明两个变量 $a$b

php > function addition ($a, $b)

使用花括号来在其间为该函数定义规则。

php > {

定义规则。这里,该规则讲的是添加这两个变量。

php { echo $a + $b;

所有规则定义完毕,通过闭合花括号来封装规则。

php {}

测试函数,添加数字4和3,命令如下:

php > var_dump (addition(4,3));

样例输出

7NULL

你可以运行以下代码来执行该函数,你可以测试不同的值,你想来多少次都行。将里头的 a 和 b 替换成你自己的值。

php > var_dump (addition(a,b));

php > var_dump (addition(9,3.3));

样例输出

12.3NULL
在 Linux 命令行中使用和执行 PHP 代码(一)
在 Linux 命令行中使用和执行 PHP 代码(一)

创建PHP函数

你可以一直运行该函数,直至退出交互模式(ctrl+z)。同时,你也应该注意到了,上面输出结果中返回的数据类型为 NULL。这个问题可以通过要求 php 交互 shell用 return 代替 echo 返回结果来修复。

只需要在上面的函数的中 ‘echo‘ 声明用 ‘return‘ 来替换

替换

php { echo $a + $b;

php { return $a + $b;

剩下的东西和原理仍然一样。

这里是一个样例,在该样例的输出结果中返回了正确的数据类型。

在 Linux 命令行中使用和执行 PHP 代码(一)
在 Linux 命令行中使用和执行 PHP 代码(一)

PHP函数

永远都记住,用户定义的函数不会从一个shell会话保留到下一个shell会话,因此,一旦你退出交互shell,它就会丢失了。

希望你喜欢此次教程。保持连线,你会获得更多此类文章。保持关注,保持健康。请在下面的评论中为我们提供有价值的反馈。点赞并分享,帮助我们扩散。

还请阅读: 12个Linux终端中有用的的PHP命令行用法——第二部分


via: http://www.tecmint.com/run-php-codes-from-linux-commandline/

作者:Avishek Kumar 译者:GOLinux 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出

来源:https://linux.cn/article-5906-1.html

在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法

在上一篇文章“在 Linux 命令行中使用和执行 PHP 代码(一)”中,我同时着重讨论了直接在Linux命令行中运行PHP代码以及在Linux终端中执行PHP脚本文件。

在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法

本文旨在让你了解一些相当不错的Linux终端中的PHP交互性 shell 的用法特性。

让我们先在PHP 的交互shell中来对php.ini设置进行一些配置吧。

6. 设置PHP命令行提示符

要设置PHP命令行提示,你需要在Linux终端中使用下面的php -a(启用PHP交互模式)命令开启一个PHP交互shell。

$ php -a

然后,设置任何东西(比如说Hi Tecmint ::)作为PHP交互shell的命令提示符,操作如下:

php > #cli.prompt=Hi Tecmint ::

Enable PHP Interactive Shell

启用PHP交互Shell

同时,你也可以设置当前时间作为你的命令行提示符,操作如下:

php > #cli.prompt=`echo date('H:m:s');` >

22:15:43 >

7. 每次输出一屏

在我们上一篇文章中,我们已经在原始命令中通过管道在很多地方使用了less命令。通过该操作,我们可以在那些不能一屏全部输出的地方获得分屏显示。但是,我们可以通过配置php.ini文件,设置pager的值为less以每次输出一屏,操作如下:

$ php -a
php > #cli.pager=less

Fix PHP Screen Output

限制PHP屏幕输出

这样,下次当你运行一个命令(比如说条调试器phpinfo();)的时候,而该命令的输出内容又太过庞大而不能固定在一屏,它就会自动产生适合你当前屏幕的输出结果。

php > phpinfo();
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法

PHP信息输出

8. 建议和TAB补全

PHP shell足够智能,它可以显示给你建议和进行TAB补全,你可以通过TAB键来使用该功能。如果对于你想要用TAB补全的字符串而言有多个选项,那么你需要使用两次TAB键来完成,其它情况则使用一次即可。

如果有超过一个的可能性,请使用两次TAB键。

php > ZIP [TAB] [TAB]

如果只有一个可能性,只要使用一次TAB键。

php > #cli.pager [TAB]

你可以一直按TAB键来获得建议的补全,直到该值满足要求。所有的行为都将记录到~/.php-history文件。

要检查你的PHP交互shell活动日志,你可以执行:

$ nano ~/.php_history | less
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法

检查PHP交互Shell日志

9. 你可以在PHP交互shell中使用颜色,你所需要知道的仅仅是颜色代码。

使用echo来打印各种颜色的输出结果,类似这样:

php > echo "color_code1 TEXT second_color_code";

具体来说是:

php > echo "33[0;31m Hi Tecmint x1B[0m";
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法

在PHP Shell中启用彩色

到目前为止,我们已经看到,按回车键意味着执行命令,然而PHP Shell中各个命令结尾的分号是必须的。

10. 在PHP shell中用basename()输出路径中最后一部分

PHP shell中的basename函数可以从给出的包含有到文件或目录路径的最后部分。

basename()样例#1和#2。

php > echo basename("/var/www/html/wp/wp-content/plugins");
php > echo basename("www.tecmint.com/contact-us.html");

上述两个样例将输出:

plugins
contact-us.html
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法

在PHP中打印基本名称

11. 你可以使用PHP交互shell在你的桌面创建文件(比如说test1.txt),就像下面这么简单

php> touch("/home/avi/Desktop/test1.txt");

我们已经见识了PHP交互shell在数学运算中有多优秀,这里还有更多一些例子会令你吃惊。

12. 使用PHP交互shell打印比如像tecmint.com这样的字符串的长度

strlen函数用于获取指定字符串的长度。

php > echo strlen("tecmint.com");
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法

在PHP中打印字符串长度

13. PHP交互shell可以对数组排序,是的,你没听错

声明变量a,并将其值设置为array(7,9,2,5,10)。

php > $a=array(7,9,2,5,10);

对数组中的数字进行排序。

php > sort($a);

以排序后的顺序打印数组中的数字,同时打印序号,第一个为[0]。

php > print_r($a);
Array
(
    [0] => 2
    [1] => 5
    [2] => 7
    [3] => 9
    [4] => 10
)
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法

在PHP中对数组排序

14. 在PHP交互Shell中获取π的值

php > echo pi();

3.1415926535898

15. 打印某个数比如32的平方根

php > echo sqrt(150);

12.247448713916

16. 从0-10的范围内挑选一个随机数

php > echo rand(0, 10);
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法

在PHP中获取随机数

17. 获取某个指定字符串的md5校验和sha1校验,例如,让我们在PHP Shell中检查某个字符串(比如说avi)的md5校验和sha1校验,并交叉校验bash shell生成的md5校验和sha1校验的结果。

php > echo md5(avi);
3fca379b3f0e322b7b7967bfcfb948ad

php > echo sha1(avi);
8f920f22884d6fea9df883843c4a8095a2e5ac6f

$ echo -n avi | md5sum
3fca379b3f0e322b7b7967bfcfb948ad  -

$ echo -n avi | sha1sum
8f920f22884d6fea9df883843c4a8095a2e5ac6f  -
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法
在 Linux 命令行中使用和执行 PHP 代码(二):12 个 PHP 交互性 shell 的用法

在PHP中检查md5校验和sha1校验

这里只是PHP Shell中所能获取的功能和PHP Shell的交互特性的惊鸿一瞥,这些就是到现在为止我所讨论的一切。保持连线,在评论中为我们提供你有价值的反馈吧。为我们点赞并分享,帮助我们扩散哦。


via: http://www.tecmint.com/execute-php-codes-functions-in-linux-commandline/

作者:Avishek Kumar 译者:GOLinux 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出

来源:https://linux.cn/article-5916-1.html

Linux:使用 varnish + nginx + lua 搭建网站的降级系统

前言

通常一个网站数据库挂掉后,后果将是非常严重的。基本上整个网站基本不可用了。对于一些网站来说,当数据库挂掉后,如果能提供基本的浏览服务,也是不错的。本文将尝试使用 varnish + nginx + lua 搭建网站降级系统来实现整个目标。

Linux:使用 varnish + nginx + lua 搭建网站的降级系统
Linux:使用 varnish + nginx + lua 搭建网站的降级系统

降级目标

降级方案的目标是,当网站出现致命故障时(如出现500错误,不能提供服务),可以把缓存的页面数据展现给用户。从而提供基本的浏览服务。

  1. 只提供基本的浏览服务
  2. 浏览的数据都是非登录状态下的数据
  3. 支持手动和自动降级。自动降级是当后端返回500错误次数在一段时间内达到一定阈值(不包含503)。手动降级是从控制界面操作。

降级方案

存储

使用varnish作为存储。有效的节约了物理内存,并保持了较好的性能。

更新

使用crond脚本从nginx的access日志中分析出请求url,然后向varnish发请求,从而更新varnish的缓存。缓存的异步更新,减少对nginx的压力。

降级

支持手动降级和自动降级。降级后,nginx自动从varnish中提取数据,并返回给用户。

流程图

Linux:使用 varnish + nginx + lua 搭建网站的降级系统
Linux:使用 varnish + nginx + lua 搭建网站的降级系统

流程描述

  1. 用户请求到nginx时,nginx会判断当前是否是降级状态。如果属于降级状态,直接从varnish中获取数据。非降级状态,把请求转到php-fpm。
  2. 当crond脚本请求varnish进行缓存数据更新时,如果当前varnish处于降级状态,则不进行缓存更新。如果没有处在降级状态,则把请求转到nginx,获取数据。然后把获取的数据缓存到varnish中。
  3. varnish会自动监控后端nginx的状态。如果检测到nginx已经处于降级状态,则varnish也会自动进入降级状态。

安装部署

vanish安装到/home/varnish 目录下。安装步骤如下:

首先,安装libpcre。

sudo yum install pcre pcre-devel

其次,安装varnish。

./configure --prefix=/home/varnish
make
sudo -u admin make install
sudo -u admin mkdir -p /home/varnish/vcache/
sudo chown admin:admin -R /home/varnish
sudo -u admin touch /home/varnish/vcache/varnish_cache.data
sudo chmod 777 /home/varnish/vcache/varnish_cache.data

再次,修改varnish配置文件和部署相关脚本。点击下载文件压缩包。配置文件名为default.vcl。

最后,启动varnish。启动脚本也在压缩包中,名称为 varnishctl

sudo /home/admin/varnish/sbin/varnishctl start

注意:启动后可以通过varnishlog命令查看是否运行正常。如果出现以下字样,说明运行正常。http的返回状态为200

$ /home/varnish/bin/varnishlog
0 Backend_health - default Still healthy 4--X-RH 4 2 4 0.002698 0.001722 HTTP/1.1 200 OK

部署lua脚本

lua脚本在/home/admin/nginx/data/lua目录下。 确保目录下有如下几个个脚本。

pc_get_downgrade_data.lua
init.lua
pc_status_stat.lua
pc_get_status.lua
pc_set_satus.lua

这几个脚本在下载的压缩包中有。

修改nginx配置文件

首先,在http域增加

init_by_lua_file 'lua/init.lua';
lua_shared_dict pc_status 1m;
lua_shared_dict pc_auto_status 1m;
#varnish config
upstream varnish{
    server 127.0.0.1:8080 weight=1 max_fails=2 fail_timeout=5s;
}

最后,在server域宏增加如下配置。

location @php {
  include fastcgi_params;
}
location @var {
 proxy_pass http://varnish$str_params;
}
location ~* ^(.+.php)(.*)$ {
  #check downgrade status, then get data from varnish
  set $str_params $uri;
  content_by_lua_file lua/pc_get_downgrade_data.lua;
}
location /hl_get_auto_status {
           if ($white_ip = 0) {
               return 403;
           }
           content_by_lua_file lua/pc_get_auto_status.lua;
 }
location /hl_get_status {
     if ($white_ip = 0) {
         return 403;
     }
     content_by_lua_file lua/pc_get_status.lua;
}
location /hl_set_status {
           if ($white_ip = 0) {
               return 403;
           }
           content_by_lua_file lua/pc_set_status.lua;
}

log_by_lua_file  lua/pc_status_stat.lua;

部署crond脚本

脚本varnish_crond.php。在crond中增加执行命令。每分钟执行一次。

来自crond的请求,user-agent数据为varnish_crond。把user-agent为varnish_crond请求特殊处理。保证能正常请求,并返回相关数据。

降级管理

varnish降级

只要让varnish配置中指定的监控脚本check.php返回500错误即可。varnish监控到指定脚本不可用,自动会进入降级状态。当脚本返回200状态后,varnish自动又会恢复正常。

nginx降级

设置降级:

curl -H "Host:demo.bo56.com" -i http://127.0.0.1/hl_set_status?status=1

恢复正常:

curl -H "Host:demo.bo56.com" -i http://127.0.0.1/hl_set_status?status=0

查看降级状态:

curl -H "Host:demo.bo56.com" -i http://127.0.0.1/hl_get_status

如果返回的值为1表示降级。

来源:http://www.bo56.com/%E4%BD%BF%E7%94%A8varnish-nginx-lua%E6%90%AD%E5%BB%BA%E7%BD%91%E7%AB%99%E7%9A%84%E9%99%8D%E7%BA%A7%E7%B3%BB%E7%BB%9F/

Linux:25 个有用 Apache ‘.htaccess’ 技巧

网站是我们生活中重要的一部分。它们是实现扩大业务、分享知识以及其它更多功能的方式。早期受制于只能提供静态内容,随着动态客户端和服务器端脚本语言的引入和现有静态语言的持续改进,例如从 html 到 html5,动态网站成为可能,剩下的也许在不久的将来也会实现。

对于网站,随之而来的是需要一个能向全球大规模用户显示站点的某个东西。这个需求可以通过托管网站的服务器实现。这包括一系列的服务器,例如:Apache HTTP Server、Joomla 以及 允许个人拥有自己网站的 WordPress。

Linux:25 个有用 Apache ‘.htaccess’ 技巧
Linux:25 个有用 Apache ‘.htaccess’ 技巧

25 个 htaccess 小技巧

想要拥有一个网站,可以创建一个自己的本地服务器,或者联系任何上面提到的或其它服务器管理员来托管他的网站。但实际问题也从这点开始。网站的性能主要取决于以下因素:

  • 网站消耗的带宽。
  • 面对黑客,网站有多安全。
  • 对数据库进行数据检索时的优化。
  • 显示导航菜单和提供更多 UI 功能时的用户友好性。

除此之外,保证托管网站服务器成功的多种因素还包括:

  • 对于一个流行站点的数据压缩量。
  • 同时为多个对请求同一或不同站点的用户服务的能力。
  • 保证网站上输入的机密数据安全,例如:Email、信用卡信息等等。
  • 允许更多的选项用于增强站点的动态性。

这篇文章讨论一个服务器提供的用于增强网站性能和提高针对坏机器人、热链等的安全性的功能:‘.htaccess’ 文件。

.htaccess 是什么?

htaccess (hypertext access,超文本访问) 是为网站所有者提供用于控制服务器环境变量以及其它参数的选项,从而增强他们网站的功能的文件。这些文件可以在网站目录树的任何一个目录中,并向该目录以及目录中的文件和子目录提供功能。

这些功能是什么呢?其实这些是服务器的指令,例如命令服务器执行特定任务的行,这些命令只对该文件所在目录中的文件和子目录有效。这些文件默认是隐藏的,因为所有操作系统和网站服务器默认配置为忽略它们,但如果查看隐藏文件的话,你就可以看到这些特殊文件。后续章节的话题将讨论能控制什么类型的参数。

注意:如果 .htaccess 文件保存在 /apache/home/www/Gunjit/ 目录,那么它会向该目录中的所有文件和子目录提供命令,但如果该目录包含一个名为 /Gunjit/images/ 子目录,且该子目录中也有一个 .htaccess 文件,那么这个子目录中的命令会覆盖父目录中 .htaccess 文件(或者目录层次结构中更上层的文件)提供的命令。

Apache Server 和 .htaccess 文件

Apache HTTP Server 俗称为 Apache,是为了表示对一个有卓越战争策略技能的美洲土著部落的尊敬而命名。它基于 NCSA HTTPd 服务器 ,是用 C/C++ 和 XML 建立的跨平台 Web 服务器,它在万维网的成长和发展中起到了关键作用。

它最常用于 UNIX,但 Apache 也能用于多种平台,包括 FreeBSD、Linux、Windows、Mac OS、Novel Netware 等。在 2009 年,Apache 成为第一个为超过一亿站点提供服务的服务器。

Apache 服务器可以让 www/ 目录中的每个用户有一个单独的 .htaccess 文件。尽管这些文件是隐藏的,但如果需要的话可以使它们可见。在 www/ 目录中可以有很多子目录,每个子目录通过用户名或所有者名称命名,包含了一个站点。除此之外你可以在每个子目录中有一个 .htaccess 文件,像之前所述用于配置子目录中的文件。

下面介绍如果配置 Apache 服务器上的 htaccess 文件。

Apache 服务器上的配置

这里有两种情况:

在自己的服务器上托管网站

在这种情况下,如果没有启用 .htaccess 文件,你可以通过在 http.conf(Apache HTTP 守护进程的默认配置文件) 中找到 部分启用。


定位如下行

AllowOverride None

更改为

AllowOverride All

现在,重启 Apache 后就启用了 .htaccess。

在不同的托管服务提供商的服务器上托管网站

在这种情况下最好咨询托管管理员,如果他们允许访问 .htaccess 文件的话。

用于网站的 25 个 Apache Web 服务器 ‘.htaccess’ 小技巧

1. 如何在 .htaccess 文件中启用 mod_rewrite

mod_rewrite 选项允许你使用重定向并通过重定向到其它 URL 来隐藏你真实的 URL。这个选项非常有用,允许你用短的容易记忆的 URL 替换长 URL。

要允许 mod_rewrite,只需要在你的 .htaccess 文件的第一行添加如下一行。

Options +FollowSymLinks

该选项允许你跟踪符号链接从而在站点中启用 modrewrite。后面会介绍用短 URL 替换。(LCTT 译注:+FollowSymLinks 只是启用 modrewrite 的前提之一,还需要在全局和虚拟机中设置 RewriteEngine on才能启用重写模块。)

2. 如何允许或禁止对站点的访问

通过使用 order、allow 和 deny 关键字,htaccess 文件可以允许或者禁止对站点或目录中子目录或文件的访问。

只允许 IP 192.168.3.1 的访问

Order Allow, Deny
Deny from All
Allow from 192.168.3.1

或

Order Allow, Deny
Allow from 192.168.3.1

这里的 Order 关键字指定处理 allow 和 deny 访问的顺序。对于上面的 ‘Order’ 语句,首先会处理 Allow 语句,然后是 deny 语句。

只禁止某个 IP 的访问

下面一行的意思是除了 IP 地址 192.168.3.1,允许所有用户访问网站。

Order Allow, Deny
Deny from 192.168.3.1
Allow from All

或

Order Deny, Allow
Deny from 192.168.3.1

3. 为不同错误码生成 Apache 错误文档

用简单几行,我们可以解决当用户/客户端请求一个站点上不可用的网页时服务器产生的错误码的错误文档,例如我们大部分人见过的浏览器中显示的 ‘404 Page not found’。‘.htaccess’ 文件指定了发生这些错误情况时采取何种操作。

要做到这点,需要添加下面的行到 ‘.htaccess’ 文件:

ErrorDocument  

‘ErrorDocument’ 是一个关键字,error-code 可以是 401、403、404、500 或任何有效的表示错误的代码,最后 ‘path-of-document’ 表示本地机器上的路径(如果你使用的是你自己的本地服务器) 或服务器上的路径(如果你使用任何其它服务器来托管网站)。

例子:

ErrorDocument 404 /error-docs/error-404.html

上面一行设置客户请求任何无效页面,服务器报告 404 错误时显示 error-docs 目录下的 ‘error-404.html’ 文档。

404 Page not found

The page you request is not present. Check the URL you have typed

 

上面的表示也正确,其中字符串相当于一个普通的 html 文件。

4. 设置/取消 Apache 服务器环境变量

在 .htaccess 文件中你可以设置或者取消站点所有者可以更改的全局环境变量。要设置或取消环境变量,你需要在你的 .htaccess 文件中添加下面的行。

设置环境变量

SetEnv OWNER “Gunjit Khera”

取消环境变量

UnsetEnv OWNER

5. 为文件定义不同 MIME 类型

MIME(多用途 Internet 多媒体扩展)是浏览器运行任何页面所默认识别的类型。你可以在 .htaccess 文件中为你的站点定义 MIME 类型,然后服务器就可以识别你定义类型的文件并运行。


    AddType application/javascript      js
    AddType application/x-font-ttf      ttf ttc

这里,mod_mime.c 是用于控制定义不同 MIME 类型的模块,如果在你的系统中已经安装了这个模块,那么你就可以用该模块去为你站点中不同的扩展名定义不同的 MIME 类型,从而让服务器可以理解这些文件。

6. 如何在 Apache 中限制上传和下载的大小

.htaccess 文件允许你能够控制某个用户从你的站点(通过 PHP)单次上传数据量的大小(LCTT 译注:原文有误,修改)。要做到这点你只需要添加下面的行到你的 .htaccess 文件:

php_value upload_max_filesize 20M
php_value post_max_size 20M
php_value max_execution_time 200
php_value max_input_time 200

上面的行设置最大上传大小、最大POST 提交数据大小、最长执行时间(例如,允许用户在他的本地机器上单次执行一个请求的最大时间)、限制的最大输入时间。

7. 让用户不能在你的站点上在线播放 .mp3 和其它文件

大部分情况下,人们在下载检查音乐质量之前会在网站上播放等等。作为一个聪明的销售者,你可以添加一个简单的功能,不允许任何用户在线播放音乐或视频,而是必须下载完成后才能播放。这非常有用,因为(无缓冲的)在线播放音乐和视频会消耗很多带宽。

要添加下面的行到你的 .htaccess 文件:

AddType application/octet-stream .mp3 .zip

8. 为站点设置目录索引

大部分网站开发者都知道第一个显示的页面是哪个,例如一个站点的首页,被命名为 ‘index.html’。我们大部分也见过这个。但是如何设置呢?

.htaccess 文件提供了一种方式用于列出一个客户端请求访问网站的主页面时会顺序扫描的一些网页集合,相应地如果找到了列出的页面中的任何一个就会作为站点的主页面并显示给用户。

需要添加下面的行产生所需的效果。

DirectoryIndex index.html index.php yourpage.php

上面一行指定如果有任何访问首页的请求到来,首先会在目录中顺序搜索上面列出的网页:如果发现了 index.html 则显示为主页面,否则会找下一个页面,例如 index.php,如此直到你在列表中输入的最后一个页面。

9. 如何为文件启用 GZip 压缩以节省网站带宽

繁忙的站点通常比只占少量空间的轻量级站点运行更慢,这是常见的现象。因为对于繁忙的站点需要时间加载巨大的脚本文件和图片以在客户端的 Web 浏览器上显示。

通常的机制是这样的,当浏览器请求一个 web 页面时,服务器提供给浏览器该页面,并在浏览器端显示该 web 页面,浏览器需要下载该页面并运行页面内的脚本。

这里 GZip 压缩所做的就是节省单个用户的服务时间而不用增加带宽。服务器上站点的源文件以压缩形式保存,当用户请求到来的时候,这些文件以压缩形式传送,然后在客户端上解压(LCTT 译注:原文此处有误)。这改善了带宽限制。

下面的行允许你压缩站点的源文件,但要求在你的服务器上安装 mod_deflate.c 模块。


    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE application/html
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/x-javascript

10. 处理文件类型

服务器默认的有一些特定情况。例如:在服务器上运行 .php 文件,显示 .txt 文件。像这些我们可以以源代码形式只显示一些可执行 cgi 脚本或文件而不是执行它们(LCTT 译注:这是为了避免攻击者通过上传恶意脚本,进而在服务器上执行恶意脚本进行破坏和窃取)。

要做到这点在 .htaccess 文件中有如下行。

RemoveHandler cgi-script .php .pl .py
AddType text/plain .php .pl .py

这些行告诉服务器只显示而不执行 .pl (perl 脚本)、.php (PHP 文件) 和 .py (Python 文件) 。

11. 为 Apache 服务器设置时区

从 .htaccess 文件可用于为服务器设置时区可以看出它的能力和重要性。这可以通过设置一个服务器为每个托管站点提供的一系列全局环境变量中的 ‘TZ’ 完成。

由于这个原因,我们可以在网站上看到根据我们的时区显示的时间。也许服务器上其他拥有网站的人会根据他居住地点的位置设置时区。

下面的一行为服务器设置时区。

SetEnv TZ India/Kolkata

12. 如果在站点上启用缓存控制

浏览器很有趣的一个功能是,很多时间你可以看到,当多次同时打开一个网站和第一次打开相比前者会更快。但为什么会这样呢?事实上,浏览器在它的缓存中保存了一些通常访问的页面用于加快后面的访问。

但保存多长时间呢?这取决于你自己。例如,你的 .htaccess 文件中设置的缓存控制时间。.htaccess 文件指定了站点的网页可以在浏览器缓存中保存的时间,时间到期后需要重新验证缓存,页面可能会从缓存中删除然后在下次用户访问站点的时候重建。

下面的行为你的站点实现缓存控制。


    Header Set Cache-Control "max-age=3600, public"


    Header Set Cache-Control "public"
    Header Set Expires "Sat, 24 Jan 2015 16:00:00 GMT"

上面的行允许缓存 .htaccess 文件所在目录中的页面一小时。

13. 配置单个文件

通常 .htaccess 文件中的内容会对该文件所在目录中的所有文件和子目录起作用,但是你也可以对特殊文件设置一些特殊权限,例如只禁止对某个文件的访问等等。

要做到这点,你需要在文件中以类似方式添加 标记:


Order allow, deny
Deny from 188.100.100.0

这是一个禁止 IP 188.100.100.0 访问 ‘conf.html’ 的简单例子,但是你也可以添加介绍过的 .htaccess 文件的任何功能,包括将要介绍的功能,例如:缓存控制、GZip 压缩。

大部分服务器会用这个功能增强 .htaccess 文件的安全,这也是我们在浏览器上看不到 .htaccess 文件的原因。在后面的章节中会介绍如何给文件授权。

14. 启用在 cgi-bin 目录以外运行 CGI 脚本

通常服务器运行的 CGI 脚本都保存在 cgi-bin 目录中,但是你可以在你需要的目录运行 CGI 脚本,只需要在所需的目录中的 .htaccess 文件添加下面的行,如果没有该文件就创建一个,并添加下面的行:

AddHandler cgi-script .cgi
Options +ExecCGI

15.如何用 .htaccess 在站点上启用 SSI

服务器端包括(SSI)顾名思义是和服务器部分相关的东西。这是什么呢?通常当我们在站点上有很多页面的时候,我们在主页上会有一个显示到其它页面链接的导航菜单,我们可以启用 SSI 选项允许导航菜单中显示的所有页面完全包含在主页面中。

SSI 允许多个页面包含同样的内容,因此只需要编辑一个文件就行,从而可以节省很多磁盘空间。对于 .shtml 文件,服务器默认启用了该选项。

如果你想要对 .html 启用该选项,你需要添加下面的行:

AddHandler server-parsed .html

这样 html 文件中如下部分会被替换为 SSI。


16. 如何防止网站列出目录列表

为防止任何客户端在本地机器罗列服务器上的网站目录列表,添加下面的行到你不想列出的目录的文件中。

Options -Indexes

17. 更改默认字符集和语言头

.htaccess 文件允许你更改网站使用的字符集,例如 ASCII 或 UNICODE,UTF-8 等,以及用于显示内容的默认语言。

在服务器的全局环境变量之后添加下面语句可以实现上述功能。

AddDefaultCharset UTF-8
DefaultLanguage en-US

18. 重定向一个非 www URL 到 www URL

在开始解释之前,首先看看如何启用该功能,添加下列行到 .htaccess 文件。

RewriteEngine ON
RewriteCond %{HTTP_HOST} ^abc.net$
RewriteRule (.*) http://www.abc.net/$1 [R=301,L]

上面的行启用重写引擎,然后在第二行检查所有涉及到主机 abc.net 或 环境变量 HTTP_HOST 为 “abc.net” 的 URL。

对于所有这样的 URL,代码永久重定向它们(如果启用了 R=301 规则)到新 URL http://www.abc.net/$1,其中 $1 是主机为 abc.net 的非 www URL。非 www URL 是大括号内的内容,并通过 $1 引用。

重写 URL 的重定向规则

重写功能简单的说,就是用短而易记的 URL 替换长而难以记忆的 URL。但是,在开始这个话题之前,这里有一些本文后面会使用的特殊字符的规则和约定。

特殊符号:

符号              含义
^         -     字符串开头
$         -     字符串结尾
|         -     或 [a|b] : a 或 b
[a-z]     -     a 到 z 的任意字母
+         -     之前字母的一次或多次出现
*         -     之前字母的零次或多次出现
?         -     之前字母的零次或一次出现

常量和它们的含义:

常量          含义
NC          -   区分大小写
L           -   最后的规则 – 停止处理后面规则
R           -   临时重定向到新 URL
R=301       -   永久重定向到新 URL
F           -   禁止发送 403 头给用户
P           -   代理 - 获取远程内容代替部分并返回
G           -   Gone, 不再存在
S=x         -   跳过后面的 x 条规则
T=mime-type -   强制指定 MIME 类型
E=var:value -   设置环境变量 var 的值为 value
H=handler   -   设置处理器
PT          -   Pass through - 用于 URL 还有额外的头
QSA         -   将查询字符串追加到替换 URL

19. 重定向整个站点到 https

下面的行会帮助你转换整个网站到 https:

RewriteEngine ON
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

上面的行启用重写引擎,然后检查环境变量 HTTPS 的值。如果设置了那么重写所有网站页面到 https。

20.一个自定义重写例子

例如,重定向 url ‘http://www.abc.net?p=100&q=20’ 到 ‘http://www.abc.net/10020pq’。

RewriteEngine ON
RewriteRule ^http://www.abc.net/([0-9]+)([0-9]+)pq$ ^http://www.abc.net?p=$1&q=$2

在上面的行中,$1 表示第一个括号,$2 表示第二个括号。

21. 重命名 htaccess 文件

为了防止入侵者和其他人查看 .htaccess 文件,你可以重命名该文件,这样就不能通过客户端浏览器访问。实现该目标的语句是:

AccessFileName  htac.cess

22. 如何为你的网站禁用图片盗链

网站带宽消耗比较大的另外一个重要问题是盗链问题,这是其它站点用于显示你网站的图片而链接到你的网站的链接,这会消耗你的带宽。这问题也被成为 ‘带宽盗窃’。

一个常见现象是当一个网站要显示其它网站所包含的图片时,由于该链接需要从你的网站加载内容,消耗你站点的带宽而为其它站点显示图片。为了防止出现这种情况,比如对于 .gif、.jpeg 图片等,下面的代码行会有所帮助:

RewriteEngine ON
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERERER} !^http://(www.)?mydomain.com/.*$ [NC]
RewriteRule .(gif|jpeg|png)$ - [F].

上面的行检查 HTTP_REFERER 是否没有设为空或没有设为你站点上的任何链接。如果是这样的话,你网页上的所有图片会用 403 禁止访问代替。

23. 如何将用户重定向到维护页面

如果你的网站需要进行维护并且你想向所有需要访问该网站的你的所有客户通知这个消息,对于这种情况,你可以添加下面的行到你的 .htaccess 文件,从而只允许管理员访问并替换所有访问 .jpg、.css、.gif、.js 等的页面内容。

RewriteCond %{REQUEST_URI} !^/admin/ [NC]
RewriteCond %{REQUEST_URI} !^((.*).css|(.*).js|(.*).png|(.*).jpg)    [NC]
RewriteRule ^(.*)$ /ErrorDocs/Maintainence_Page.html [NC,L,U,QSA]

这些行检查请求 URL 是否包含任何例如以 ‘/admin/’ 开头的管理页面的请求,或任何到 ‘.png, .jpg, .js, .css’ 页面的请求,对于任何这样的请求,用 ‘ErrorDocs/Maintainence_Page.html’ 替换那个页面。

24. 映射 IP 地址到域名

名称服务器是将特定 IP 地址转换为域名的服务器。这种映射也可以在 .htaccess 文件中用以下形式指定。

# 为了将IP地址 L.M.N.O 映射到域名 www.hellovisit.com
RewriteCond %{HTTP_HOST} ^L.M.N.O$ [NC]
RewriteRule ^(.*)$ http://www.hellovisit.com/$1 [L,R=301]

上面的行检查任何页面的主机是否包含类似 L.M.N.O 的 IP 地址,如果是的话第三行会通过永久重定向将页面映射到域名 http://www.hellovisit.com。

25. FilesMatch 标签

类似用于应用条件到单个文件的 标签, 能用于匹配一组文件并对该组文件应用一些条件,如下:


Order Allow, Deny
Deny from All

结论

.htaccess 文件能实现的小技巧还有很多。这告诉了我们这个文件有多么强大,通过该文件能给你的站点添加多少安全性、动态性以及其它功能。

我们已经在这篇文章中尽最大努力覆盖尽可能多的 htaccess 小技巧,但如果我们缺少了任何重要的技巧,或者你愿意告诉我们你的 htaccess 想法和技巧,你可以在下面的评论框中提交,我们也会在文章中进行介绍。


via: http://www.tecmint.com/apache-htaccess-tricks/

作者:Gunjit Khera 译者:ictlyh 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出

来源:https://linux.cn/article-5731-1.html

Linux:Docker 在 PHP 项目开发环境中的应用

环境部署是所有团队都必须面对的问题,随着系统越来越大,依赖的服务也越来越多,比如我们目前的一个项目就会用到:

  • Web服务器:Nginx
  • Web程序:PHP + Node
  • 数据库:MySQL
  • 搜索引擎:ElasticSearch
  • 队列服务:Gearman
  • 缓存服务:Redis + Memcache
  • 前端构建工具:npm + bower + gulp
  • PHP CLI工具:Composer + PHPUnit

因此团队的开发环境部署随之暴露出若干问题:

  1. 依赖服务很多,本地搭建一套环境成本越来越高,初级人员很难解决环境部署中的一些问题
  2. 服务的版本差异及OS的差异都可能导致线上环境BUG
  3. 项目引入新的服务时所有人的环境需要重新配置

对于问题1,可以用Vagrant这样的基于虚拟机的项目来解决,团队成员共享一套开发环境镜像。对于问题2,可以引入类似PHPBrew这样的多版本PHP管理工具来解决。但两者都不能很好地解决问题3,因为虚拟机镜像没有版本管理的概念,当多人维护一个镜像时,很容易出现配置遗漏或者冲突,一个很大的镜像传输起来也不方便。

Docker的出现让上面的问题有了更好的解决方案,虽然个人对于Docker大规模应用到生产环境还持谨慎态度,但如果仅仅考虑测试及开发,私以为Docker的容器化理念已经是能真正解决环境部署问题的银弹了。

下面介绍Docker构建PHP项目开发环境过程中的演进,本文中假设你的操作系统为Linux,已经安装了Docker,并且已经了解Docker是什么,以及Docker命令行的基础使用,如果没有这些背景知识建议先自行了解。

Linux:Docker 在 PHP 项目开发环境中的应用
Linux:Docker 在 PHP 项目开发环境中的应用

Hello World

首先还是从一个PHP在Docker容器下的Hello World实例开始。我们准备这样一个PHP文件

1
index.php


然后在同目录下创建文本文件并命名为

1
Dockerfile

,内容为:

# 从官方PHP镜像构建
FROM       php

# 将index.php复制到容器内的/var/www目录下
ADD        index.php /var/www

# 对外暴露8080端口
EXPOSE     8080

# 设置容器默认工作目录为/var/www
WORKDIR    /var/www

# 容器运行后默认执行的指令
ENTRYPOINT ["php", "-S", "0.0.0.0:8080"]

构建这个容器:

docker build -t allovince/php-helloworld .

运行这个容器

docker run -d -p 8080:8080 allovince/php-helloworld

查看结果:

curl localhost:8080
PHP in Docker

这样我们就创建了一个用于演示PHP程序的Docker容器,任何安装过Docker的机器都可以运行这个容器获得同样的结果。而任何有上面的php文件和Dockerfile的人都可以构建出相同的容器,从而完全消除了不同环境,不同版本可能引起的各种问题。

想象一下程序进一步复杂,我们应该如何扩展呢,很直接的想法是继续在容器内安装其他用到的服务,并将所有服务运行起来,那么我们的Dockerfile很可能发展成这个样子:

FROM       php
ADD        index.php /var/www

# 安装更多服务
RUN        apt-get install -y
           mysql-server
           nginx
           php5-fpm
           php5-mysql

# 编写一个启动脚本启动所有服务
ENTRYPOINT ["/opt/bin/php-nginx-mysql-start.sh"]

虽然我们通过Docker构建了一个开发环境,但觉不觉得有些似曾相识呢。没错,其实这种做法和制作一个虚拟机镜像是差不多的,这种方式存在几个问题:

  • 如果需要验证某个服务的不同版本,比如测试PHP5.3/5.4/5.5/5.6,就必须准备4个镜像,但其实每个镜像只有很小的差异。
  • 如果开始新的项目,那么容器内安装的服务会不断膨胀,最终无法弄清楚哪个服务是属于哪个项目的。

使用单一进程容器

上面这种将所有服务放在一个容器内的模式有个形象的非官方称呼:Fat Container。与之相对的是将服务分拆到容器的模式。从Docker的设计可以看到,构建镜像的过程中可以指定唯一一个容器启动的指令,因此Docker天然适合一个容器只运行一种服务,而这也是官方更推崇的。

分拆服务遇到的第一个问题就是,我们每一个服务的基础镜像从哪里来?这里有两个选项:

选项一、 统一从标准的OS镜像扩展,比如下面分别是Nginx和MySQL镜像

FROM ubuntu:14.04
RUN  apt-get update -y && apt-get install -y nginx
FROM ubuntu:14.04
RUN  apt-get update -y && apt-get install -y mysql

这种方式的优点在于所有服务可以有一个统一的基础镜像,对镜像进行扩展和修改时可以使用同样的方式,比如选择了ubuntu,就可以使用

1
apt-get

指令安装服务。

问题在于大量的服务需要自己维护,特别是有时候需要某个服务的不同版本时,往往需要直接编译源码,调试维护成本都很高。

选项二、 直接从Docker Hub继承官方镜像,下面同样是Nginx和MySQL镜像

FROM nginx:1.9.0
FROM mysql:5.6

Docker Hub可以看做是Docker的Github,Docker官方已经准备好了大量常用服务的镜像,同时也有非常多第三方提交的镜像。甚至可以基于Docker-Registry项目在短时间内自己搭建一个私有的Docker Hub。

基于某个服务的官方镜像去构建镜像,有非常丰富的选择,并且可以以很小的代价切换服务的版本。这种方式的问题在于官方镜像的构建方式多种多样,进行扩展时需要先了解原镜像的

1
Dockerfile

出于让服务搭建更灵活的考虑,我们选择后者构建镜像。

为了分拆服务,现在我们的目录变为如下所示结构:

~/Dockerfiles
├── mysql
│   └── Dockerfile
├── nginx
│   ├── Dockerfile
│   ├── nginx.conf
│   └── sites-enabled
│       ├── default.conf
│       └── evaengine.conf
├── php
│   ├── Dockerfile
│   ├── composer.phar
│   ├── php-fpm.conf
│   ├── php.ini
│   ├── redis.tgz
└── redis
    └── Dockerfile

即为每个服务创建单独文件夹,并在每个服务文件夹下放一个Dockerfile。

MySQL容器

MySQL继承自官方的MySQL5.6镜像,Dockerfile仅有一行,无需做任何额外处理,因为普通需求官方都已经在镜像中实现了,因此Dockerfile的内容为:

FROM mysql:5.6

在项目根目录下运行

docker build -t eva/mysql ./mysql

会自动下载并构建镜像,这里我们将其命名为

1
eva/mysql

由于容器运行结束时会丢弃所有数据库数据,为了不用每次都要导入数据,我们将采用挂载的方式持久化MySQL数据库,官方镜像默认将数据库存放在

1
/var/lib/mysql

,同时要求容器运行时必须通过环境变量设置一个管理员密码,因此可以使用以下指令运行容器:

docker run -p 3306:3306 -v ~/opt/data/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -it eva/mysql

通过上面的指令,我们将本地的3306端口绑定到容器的3306端口,将容器内的数据库持久化到本地的

1
~/opt/data/mysql

,并且为MySQL设置了一个root密码

1
123456

Nginx容器

Nginx目录下提前准备了Nginx配置文件

1
nginx.conf

以及项目的配置文件

1
default.conf

等。Dockerfile内容为:

FROM nginx:1.9

ADD  nginx.conf      /etc/nginx/nginx.conf
ADD  sites-enabled/*    /etc/nginx/conf.d/
RUN  mkdir /opt/htdocs && mkdir /opt/log && mkdir /opt/log/nginx
RUN  chown -R www-data.www-data /opt/htdocs /opt/log

VOLUME ["/opt"]

由于官方的Nginx1.9是基于Debian Jessie的,因此首先将准备好的配置文件复制到指定位置,替换镜像内的配置,这里按照个人习惯,约定

1
/opt/htdocs

目录为Web服务器根目录,

1
/opt/log/nginx

目录为Nginx的Log目录。

同样构建一下镜像

docker build -t eva/nginx ./nginx

并运行容器

docker run -p 80:80 -v ~/opt:/opt -it eva/nginx

注意我们将本地的80端口绑定到容器的80端口,并将本地的

1
~/opt

目录挂载到容器的

1
/opt

目录,这样就可以将项目源代码放在

1
~/opt

目录下并通过容器访问了。

PHP容器

PHP容器是最复杂的一个,因为在实际项目中,我们很可能需要单独安装一些PHP扩展,并用到一些命令行工具,这里我们以Redis扩展以及Composer来举例。首先将项目需要的扩展等文件提前下载到php目录下,这样构建时就可以从本地复制而无需每次通过网络下载,大大加快镜像构建的速度:

wget https://getcomposer.org/composer.phar -O php/composer.phar
wget https://pecl.php.net/get/redis-2.2.7.tgz -O php/redis.tgz

php目录下还准备好了php配置文件

1
php.ini

以及

1
php-fpm.conf

,基础镜像我们选择的是PHP 5.6-FPM,这同样是一个Debian Jessie镜像。官方比较亲切的在镜像内部准备了一个

1
docker-php-ext-install

指令,可以快速安装如GD、PDO等常用扩展。所有支持的扩展名称可以通过在容器内运行

1
docker-php-ext-install

获得。

来看一下Dockerfile

FROM php:5.6-fpm

ADD php.ini    /usr/local/etc/php/php.ini
ADD php-fpm.conf    /usr/local/etc/php-fpm.conf

COPY redis.tgz /home/redis.tgz
RUN docker-php-ext-install gd
    && docker-php-ext-install pdo_mysql
    && pecl install /home/redis.tgz && echo "extension=redis.so" > /usr/local/etc/php/conf.d/redis.ini
ADD composer.phar /usr/local/bin/composer
RUN chmod 755 /usr/local/bin/composer

WORKDIR /opt
RUN usermod -u 1000 www-data

VOLUME ["/opt"]

在构建过程中做了这样一些事情:

  1. 复制php和php-fpm配置文件到相应目录
  2. 复制redis扩展源代码到
    1
    /home
  3. 通过
    1
    docker-php-ext-install

    安装GD和PDO扩展

  4. 通过
    1
    pecl

    安装Redis扩展

  5. 复制composer到镜像作为全局指令

按照个人习惯,仍然设置

1
/opt

目录作为工作目录。

这里有一个细节,在复制tar包文件时,使用的Docker指令是

1
COPY

而不是

1
ADD

,这是由于

1
ADD

指令会自动解压

1
tar

文件

现在终于可以构建+运行了:

docker build -t eva/php ./php
docker run -p 9000:9000 -v ~/opt:/opt -it eva/php

在大多数情况下,Nginx和PHP所读取的项目源代码都是同一份,因此这里同样挂载本地的

1
~/opt

目录,并且绑定9000端口。

PHP-CLI的实现

php容器除了运行php-fpm外,还应该作为项目的php cli使用,这样才能保证php版本、扩展以及配置文件保持一致。

例如在容器内运行Composer,可以通过下面的指令实现:

docker run -v $(pwd -P):/opt -it eva/php composer install --dev -vvv

这样在任意目录下运行这行指令,等于动态将当前目录挂载到容器的默认工作目录并运行,这也是PHP容器指定工作目录为

1
/opt

的原因。

同理还可以实现phpunit、npm、gulp等命令行工具在容器内运行。

Redis容器

为了方便演示,Redis仅仅作为缓存使用,没有持久化需求,因此Dockerfile仅有一行

FROM redis:3.0

容器的连接

上面已经将原本在一个容器中运行的服务分拆到多个容器,每个容器只运行单一服务。这样一来容器之间需要能互相通信。Docker容器间通讯的方法有两种,一种是像上文这样将容器端口绑定到一个本地端口,通过端口通讯。另一种则是通过Docker提供的Linking功能,在开发环境下,通过Linking通信更加灵活,也能避免端口占用引起的一些问题,比如可以通过下面的方式将Nginx和PHP链接起来:

docker run -p 9000:9000 -v ~/opt:/opt --name php -it eva/php
docker run -p 80:80 -v ~/opt:/opt -it --link php:php eva/nginx

在一般的PHP项目中,Nginx需要链接PHP,而PHP又需要链接MySQL,Redis等。为了让容器间互相链接更加容易管理,Docker官方推荐使用Docker-Compose完成这些操作。

用一行指令完成安装

pip install -U docker-compose

然后在Docker项目的根目录下准备一个docker-compose.yml文件,内容为:

nginx:
    build: ./nginx
    ports:
      - "80:80"
    links:
      - "php"
    volumes:
      - ~/opt:/opt

php:
    build: ./php
    ports:
      - "9000:9000"
    links:
      - "mysql"
      - "redis"
    volumes:
      - ~/opt:/opt

mysql:
    build: ./mysql
    ports:
      - "3306:3306"
    volumes:
      - ~/opt/data/mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: 123456

redis:
    build: ./redis
    ports:
      - "6379:6379"

然后运行

1
docker-compose up

,就完成了所有的端口绑定、挂载、链接操作。

更复杂的实例

上面是一个标准PHP项目在Docker环境下的演进过程,实际项目中一般会集成更多更复杂的服务,但上述基本步骤仍然可以通用。比如EvaEngine/Dockerfiles是为了运行我的开源项目EvaEngine准备的基于Docker的开发环境,EvaEngine依赖了队列服务Gearman,缓存服务Memcache、Redis,前端构建工具Gulp、Bower,后端Cli工具Composer、PHPUnit等。具体实现方式可以自行阅读代码。

经过团队实践,原本大概需要1天时间的环境安装,切换到Docker后只需要运行10余条指令,时间也大幅缩短到3小时以内(大部分时间是在等待下载),最重要的是Docker所构建的环境都是100%一致的,不会有人为失误引起的问题。未来我们会进一步将Docker应用到CI以及生产环境中。

来源:http://www.wolonge.com/zhuanlan/detail/117441