Tagged: MySQL

MySQL位运算的应用

我们知道,PHP当中的错误级别常量,是根据二进制位特性而确定的一个个整数,可以简单的通过位运算定制PHP的错误报告。我们也可以将其应用到 MySQL 当中!

你是否遇到过下面这样的情况?

一、用户可能有若干不同属性或不同状态,然后你可能会在数据表中通过一个个字段去标记和实现,比如:

id int(11) 用户ID(自增)
username char(50) 用户名
password char(50) 密码
... ... ...
confirm_info tinyint(1) 是否确认资料 [ 0/1 ]
confirm_contract tinyint(1) 是否确认协议 [ 0/1 ]
freeze tinyint(1) 冻结状态 [ 0/1 ]
payer tinyint(1) 笔记是否付费用户 [ 0/1 ]
high_quality tinyint(1) 标记是否优质用户 [ 0/1 ]

二、用户拥有多个用户组(角色),常见的实现方法有:

1、增加一个字段,专门存它的用户组,用逗号隔开

id int(11) 用户ID(自增)
username char(50) 用户名
password char(50) 密码
... ... ...
confirm_info tinyint(1) 是否确认资料 [ 0/1 ]
confirm_contract tinyint(1) 是否确认协议 [ 0/1 ]
freeze tinyint(1) 冻结状态 [ 0/1 ]
payer tinyint(1) 笔记是否付费用户 [ 0/1 ]
high_quality tinyint(1) 标记是否优质用户 [ 0/1 ]
role_id varchar(255) 如:1,3,11

使用 FIND_IN_SET() 或 LIKE 进行数据查询。这种存储方式,虽然比较直观,结构也比较接单,但是,但是查询和维护数据都不方便

2、增加一个关联表

user_id int(11) 用户ID
role_id int(11) 角色ID
... ... ...

通过JOIN,连表查询。这种存储方式,似乎很好扩展,无论是增加了用户组,还是重新给用户划分用户组,直接操作这张关联表就行。但是,连表查询终究是需要操作多张表的,效率肯定不如单表查询,而且如果用户属性多了(不仅限于分组),那就得关联更多的表或者查多次,使查询变得更加复杂!


我们知道,PHP当中的错误级别常量,是根据二进制位特性而确定的一个个整数,可以简单的通过位运算定制PHP的错误报告。我们也可以将其应用到 MySQL 当中,还是以上面的用户表为例,我们只用一字段 status,专门记录用户状态,其结构如下:

id int(11) 用户ID(自增)
username char(50) 用户名
password char(50) 密码
... ... ...
status int(11) 用户状态位

然后我们给每一种状态设定一个对应的值,这个值需要使用2的N次方来表示,比如:

1 已确认资料
2 已确认协议
4 已冻结
8 付费用户
16 优质用户

查询技巧示例:

1、查询所有已冻结的用户:

SELECT * FROM `user` WHERE `status` & 4 = 4

2、查询所有未确认协议的用户

SELECT * FROM `user` WHERE `status` & 2 = 0

3、设置ID等于186的用户为优质用户

UPDATE `user` SET `status` = `status` | 16 WHERE `id` = 186

4、取消ID等于186的优质用户身份

UPDATE `user` SET `status` = `status` ^ 16 WHERE `id` = 186

5、查询已冻结的优质付费用户(4 + 8 + 16 = 28)

SELECT * FROM `user` WHERE `status` & 28 = 28

同理,上述应用场景二中的分组问题,也可以使用同样的方法,优化表结构!然后使用位运算,实现各种条件的查询!

PHP实现MySQL并发查询

一般的,一个看似很简单的页面,一次http请求后,到达服务端,穿过Cache层,落到后台后,实际可能会有很多很多的数据查询逻辑!而这些查询实际是不相互依赖的,也即可以同时查询。比如各种用户信息,用户的APP列表,每个APP对应的流量数据、消耗记录、服务状态,平台运行状态,消息通知,新闻资讯等等。
这篇文章主要介绍了数据查询层,如何把串行变并行,提高查询效率、提升应用性能。实现方式包括:mysqlnd异步查询,cURL并发请求,Swoole异步非阻塞!

PHP脚本是按文档流的形式来执行的,所以我们在编写PHP程序时,代码基本都是串行的,尤其是SQL,比如:
倒流’s Bolg
这种方式,是每次查询都需要等待结果返回之后再开始下一次的查询,
运行时间 = (第1次发送请求时间 + 0.01 + 第1次返回时间)+(第2次发送请求时间 + 0.05 + 第2次返回时间)+(第3次发送请求时间 + 0.03 + 第3次返回时间)

如果是并发查询,那么流程就成了:
倒流’s Bolg
运行时间 = 发送请求时间 + 0.05 + 返回时间

显然,并发查询要比串行查询快!

那么PHP可以实现并发查询吗?答案是肯定的!

一、利用MySQL的异步查询功能

目前 MySQL 的异步查询,只在 MySQLi 扩展提供,查询方法分别是:

  1. 使用 MYSQLI_ASYNC 模式执行 mysqli::query
  2. 获取异步查询结果:mysqli::reap_async_query

需要注意的是,使用异步查询,需要使用 mysqlnd 作为PHP的MySQL数据库驱动,
mysqlnd(MySQL Native Driver) 是 Zend 公司开发的 MySQL 数据库驱动,采用PHP开源协议,用于代替旧版的由 MySQL AB公司(现在的Oracle)开发的 libmysql,PHP 5.3 及以上版本开始提供,PHP5.4 之后的版本 mysqlnd 为默认配置选项,
如果 PHP 小于 5.4,编译时需要指定编译参数:

--with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd

MySQL异步查询示例脚本:

<?php
/**
 * MySQL异步查询示例脚本:
 * @filename p_async.php
 * @url http://test.979137.com/ParallelSQL/p_async.php
 */

//期望结果集:获取以下用户的每个月每个APP的消费统计
$top = array('979137', '555555', '666666', '888888', '999999');
$ret = array_fill_keys($top, array());

//组织结构查询
$cmd = $resources = array();
$sql = "SELECT access_key,SUM(amount) sum_amount FROM consume_2016%s WHERE uid=%d AND product='SAE' GROUP BY access_key";
foreach($top as $uid) {
    for($i = 1; $i <= 12; $i++) {
        $ret[$uid][$i] = $tmp = array();
        $tmp['uid'] = $uid;
        $tmp['month'] = $i;
        $tmp['resource'] = $resources[] = new \mysqli('localhost', 'root', '123456', 'sae', '3306');
        $tmp['resource']->set_charset('utf8');
        $tmp['resource']->query(sprintf($sql, sprintf('%02d', $i), $uid), MYSQLI_ASYNC);
        $tag = spl_object_hash($tmp['resource']);
        $cmd[$tag] = $tmp;
    }
}

$total = $query_times = count($resources);

//获取结果
do {
    $read = $error = $reject = $resources;
    //等待查询结束
    if (!\mysqli::poll($read, $error, $reject, 1)) {
        continue;
    }
    //批量获取结果
    foreach($read as $resource) {
        $result = $resource->reap_async_query();
        if ($result) {
            $tag = spl_object_hash($resource);
            $uid = $cmd[$tag]['uid'];
            $month = $cmd[$tag]['month'];
            while(($row = $result->fetch_assoc()) != false) {
                $ret[$uid][$month][$row['access_key']] = $row['sum_amount'];
            }
            $result->free();
            $total--;

        } else die('MySQLi error: '.$resource->error);
    }
} while ($total > 0);

var_dump($ret);

二、cURL实现并发请求

先解释下 cURL 和 SQL 怎么就扯上关系了呢!

我们知道在很多系统架构里,PHP是不会直接操作DB的,而是 RESTful 架构,这时候所有操作都接口化了,
这时上述所讲的 SQL 就演变成 接口调用 了,
因为 API,所以 cURL!

以下是PHP中cURL多线程相关函数:

curl_multi_add_handle — 向curl批处理会话中添加单独的curl句柄
curl_multi_close — 关闭一组cURL句柄
curl_multi_exec — 运行当前 cURL 句柄的子连接
curl_multi_getcontent — 如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流
curl_multi_info_read — 获取当前解析的cURL的相关传输信息
curl_multi_init — 返回一个新cURL批处理句柄
curl_multi_remove_handle — 移除curl批处理句柄资源中的某个句柄资源
curl_multi_select — 等待所有cURL批处理中的活动连接
curl_multi_setopt — 为 cURL 并行处理设置一个选项
curl_multi_strerror — 返回描述错误码的字符串文本

我们可以利用这些多线程函数,实现 cURL 并发请求,从而实现并发 SQL!

cURL并发请求,服务端接口示例脚本:

<?php
/**
 * cURL并发请求,服务端接口示例脚本
 * @filename consume.php
 * @url http://test.979137.com/ParallelSQL/consume.php
 */
$resource = new \mysqli('localhost', 'root', '123456', 'sae', '3306');
$resource->set_charset('utf8');

$sql = "SELECT access_key,SUM(amount) sum_amount FROM consume_%d WHERE uid=%d AND product='%s' GROUP BY access_key";
$sql = sprintf($sql, $_GET['ym'], $_GET['uid'], $_GET['product']);
$res = $resource->query($sql);

$out['code'] = $resource->errno;
$out['message'] = $resource->error;
$data = array();
if (is_object($res)) {
    while(($row = $res->fetch_assoc()) != false) {
        $data[$row['access_key']] = $row['sum_amount'];
    }
}
$out['data'] = $data;

header('Content-Type: application/json; charset=utf-8');
echo json_encode($out);

cURL并发请求,客户端调用示例脚本:

<?php
/**
 * cURL并发请求,客户端调用示例脚本
 * @filename p_curl.php
 * @url http://test.979137.com/ParallelSQL/p_curl.php
 */

//期望结果集:获取以下用户的每个月每个APP的消费统计
$top = array('979137', '555555', '666666', '888888', '999999');
$ret = array_fill_keys($top, array());

$mch = curl_multi_init();
$opt[CURLOPT_HEADER] = 0;
$opt[CURLOPT_CONNECTTIMEOUT] = 60;
$opt[CURLOPT_RETURNTRANSFER] = true;
$opt[CURLOPT_HTTPHEADER] = array('Host: api.979137.com');

//生成句柄并加入到批处理
$cmd = array();
foreach($top as $uid) {
    for($i = 1; $i <= 12; $i++) {
        $ret[$uid][$i] = $tmp = array();
        $tmp['url'] = sprintf('http://127.0.0.1/ParallelSQL/consume.php?ym=2016%02d&uid=%d&product=SAE', $i, $uid);
        $tmp['uid'] = $uid;
        $tmp['month'] = $i;
        $tmp['resource'] = curl_init($tmp['url']);
        curl_setopt_array($tmp['resource'], $opt);
        curl_multi_add_handle($mch, $tmp['resource']);
        $cmd[] = $tmp;
    }
}

//并发执行,直到全部结束
do {
    curl_multi_exec($mch, $active); 
} while ($active > 0);

//获取全部结果
foreach($cmd as $c) {
    $res = curl_multi_getcontent($c['resource']);
    $http_code = curl_getinfo($c['resource'], CURLINFO_HTTP_CODE);
    if ($res === false || $http_code != 200) {
        die(curl_error($c['resource']));
    }
    $res = json_decode($res, true);
    $res['code'] && die($res['message']);
    $ret[$c['uid']][$c['month']] = $res['data'];
}
curl_multi_close($mch);

var_dump($ret);

查询效率对比

1、数据表机构:

CREATE TABLE `consume_201612` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `uid` int(10) unsigned NOT NULL,
  `product` enum('UID','SAE','SC2','SCE','SEM','SCS','SLS') NOT NULL DEFAULT 'SAE',
  `access_key` varchar(255) NOT NULL,
  `service_code` varchar(255) NOT NULL,
  `amount` int(10) unsigned NOT NULL,
  `remark` varchar(255) NOT NULL,
  `data` text NOT NULL,
  `times` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `uid_pa` (`uid`,`product`,`access_key`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

2、数据量:总共分12张表,每张表的数量在 500万~1100万 之间,全量数据在1亿以上

3、MySQL异步查询结果:

$ mysql.server restart
$ php p_async.php
Query times: 60
Run time: 2.4453s
(CPU)User time: 0.087966s
(CPU)System time: 0.117625s

4、cURL并发请求查询结果

$ mysql.server restart
$ php p_curl.php
Query times: 60
Run time: 2.173035s
(CPU)User time: 0.40652s
(CPU)System time: 0.869902s

5、普通串行查询结果

$ mysql.server restart
$ php p_sync.php
Query times: 60
Run time: 20.485623s
(CPU)User time: 0.083185s
(CPU)System time: 0.036566s
Memory usage: 304.72kb

并发执行时,我们可以在 MySQL 服务器看到所有的正在执行的SQL:

总结

1、在并发查询下,查询效率提高了近10倍
2、使用 MySQL 异步查询,因为需要给所有查询都创建一个新的连接,而 MySQL 服务端会为每个连接创建一个单独的线程进行处理,如果创建的线程数过多,会给系统造成负担,请谨慎使用
3、使用 cURL 并发请求后端接口时,CPU负载明显上升,所以并发请求后端接口,一定程度上会增加后端压力,这和前端大流量下的高并发原理是一样的
4、使用 cURL 并发请求,还需要考虑一个网络延时的问题,网络延时越小,查询效率提升越明显。如果你是想代替类方法或函数调用,在条件允许的情况,建议直接连接服务器本机即127.0.0.1
5、在并发请求下,因为需要一次性接收全部返回结果,所以会占用更多的内存资源

需要说明的是,在实际应用中 cURL 的并发请求,一般不只单用于数据查询,而是为了完成更多的后台业务逻辑,
所以,在服务器负载能力允许的情况下,推荐使用 cURL 并行转发的形式,提升前端响应速度!

最后说一下 Swoole,

Swoole 是 PHP 的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询!
其中的异步MySQL,其原理是通过 MYSQLI_ASYNC 模式查询,然后获取 mysql 连接的 socket,加入到 epoll 时间循环中,当数据库返回结果时会回调指定函数。
这个过程是完全异步非阻塞的,不浪费CPU,具体实现方式这里不再详细介绍!

Linux实现IP和端口转发方法

应用场景举例:
实现下面两个Shell

mysql -h10.13.34.119 -P3306 -uroot -p123456
mysql -h10.13.34.120 -P3307 -uroot -p123456

都是同样访问 10.13.34.119:3306 这个数据库服务器
这时候我们就需要将 10.13.34.120:3307 映射到 10.13.34.119:3306 了

第一种:利用iptable

iptables,大家都知道,网络防火墙,就是用于实现Linux下访问控制的东东
先打开IP转发:

echo 1 > /proc/sys/net/ipv4/ip_forward

添加NAT规则,并重启iptables:

service iptables stop
iptables -t nat -A PREROUTING --dst 10.13.34.119 -p tcp --dport 3306 -j DNAT --to-destination 10.13.34.120:3307
iptables -t nat -A POSTROUTING --dst 10.13.34.120 -p tcp --dport 3307 -j SNAT --to-source 10.13.34.119
service iptables save
service iptables start

第二种:使用SSH协议转发

ssh的三个强大的端口转发命令:
- 转发到远端:ssh -C -f -N -g -L 本地端口:目标IP:目标端口 用户名@目标IP
- 转发到本地:ssh -C -f -N -g –R 本地端口:目标IP:目标端口 用户名@目标IP
- ssh -C -f -N -g -D listen_port user@Tunnel_Host

这里我们使用第一种,只需在 10.13.34.120 这个机器执行以下命令:

ssh -C -f -N -g root@10.121.34.119 -L 3307:10.121.34.119:3306

-C :压缩数据传输。
-f :后台认证用户/密码,通常和 -N 连用,不用登录到远程主机。
-N :不执行脚本或命令,通常与 -f 连用。
-g :在-L/-R/-D参数中,允许远程主机连接到建立的转发的端口,如果不加这个参数,只允许本地主机建立连接。
-L :转发规则,本地端口:目标IP:目标端口

如此,当你使用

mysql -h10.13.34.120 -P3307 -uroot -p123456

时,就会自动转发到 10.121.34.119:3306

php –with-mysqli=mysqlnd –with-pdo-mysql=mysqlnd

1、什么是mysqlnd驱动?

PHP手册上的描述:

MySQL Native Driver is a replacement for the MySQL Client Library (libmysql).
MySQL Native Driver is part of the official PHP sources as of PHP 5.3.0

mysqldnd即MySQL Native Driver简写,即是由PHP源码提供的mysql驱动连接代码,它的目的是代替旧的libmysql驱动。

传统的安装php的方式中,我们在编译PHP时,一般需要指定以下几项:

--with-mysql=/usr/local/mysql
--with-mysqli=/usr/local/mysql
--with-pdo-mysql=/usr/local/mysql

这实际上就是使用了MySQL官方自带的libmysql驱动,这是比较老的驱动,PHP5.3开始已经不建议使用它了,而建议使用mysqlnd

2、PDO与mysqlnd, libmysql又是何种关系?

PDO是一个应用层抽象类,底层和mysql server连接交互需要mysql驱动的支持。也就是说无论你使用了何种驱动,都可以使用PDO.
PDO提供了PHP应用程序层API接口,而mysqlnd,libmysql则负责与mysql server进行网络协议交互(它并不提供php应用程序层API功能)

3、为何要使用mysqlnd驱动?

PHP官方手册描述:

A.libmysql驱动是由mysql AB公司(现在是oracle公司)编写, 并按mysql license许可协议发布,所以在PHP中默认是被禁用的.
而mysqlnd是由php官方开发的驱动,以php license许可协议发布,故就规避了许可协议和版权的问题

B.因为mysqlnd内置于PHP源代码,故你在编译安装php时就不需要预先安装mysql server也可以提供mysql client API (mysql_connect, pdo , mysqli), 这将减化一些工作量.

C. mysqlnd是专门为php优化编写的驱动,它使用了PHP本身的特性,在内存管理,性能上比libmysql更有优势. php官方的测试是:libmysql将每条记录在内存中保存了两份,而mysqlnd只保存了一份

D. 一些新的或增强的功能
增强的持久连接
引入特有的函数mysqli_fetch_all()
引入一些性能统计函数mysqli_get_cache_stats(), mysqli_get_client_stats(),
mysqli_get_connection_stats(),
使用上述函数,可很容易分析mysql查询的性能瓶颈!
SSL支持(从php 5.3.3开始有效)
压缩协议支持
命名管道支持(php 5.4.0开始有效)

4、看到这里,你可能跃跃欲试,很想使用mysqlnd驱动

提示: 如果使用mysqlnd,并不需要预先安装mysql
编译php时,修改以下几个项参数即可
[code lang="shell"]--with-mysql=mysqlnd
--with-mysqli=mysqlnd
--with-pdo-mysql=mysqlnd [/code]

Starting MySQL… ERROR! The server quit without updating PID file

刚编译完 MySQL 遇到这个问题?? 别着急,下面是本人总结,据说看完的99%都解决了!

1、你初始化数据库了吗??[code]/usr/local/mysql/bin/mysql_install_db [/code]

2016-05-25 19:56:18 [WARNING] mysql_install_db is deprecated. Please consider switching to mysqld --initialize
2016-05-25 19:56:18 [ERROR] The data directory needs to be specified.

我的是 MySQL 5.7,新版已经改成用 mysqld --initialize 来初始化数据库了[code]/usr/local/mysql/bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql[/code]注意:
1)后面的参数一定要加上,--user代表运行用户,--basedir代表MySql安装目录,--datadir代表数据库数据所在目录,每个人的配置不一样,根据自己的编译参数修改一下。
2)执行前,请先清空编译前指定的datadir,默认是在安装目录的data/(我编译时指定在了/data/mysql),否则会报如下错误:

--initialize specified but the data directory has files in it. Aborting.
2016-05-25T12:07:27.006980Z 0 [ERROR] Aborting

2、可能是 datadir 目录没有写的权限
[code]chown -R mysql:mysql /data/mysql && chmod -R 755 /data/mysql[/code]
3、错误日志文件没有权限(根据你的编译参数和my.cnf查看你的错误文件路径)
[code][chown -R mysql:mysql /var/log/mysqld.log && chmod -R 755 /var/log/mysqld.log[/code]
4、可能进程里已经存在mysql进程[code]ps -ef | grep mysql[/code]如果有使用 “kill -9 进程号” 杀死,然后重新启动mysqld!

5、可能是第二次在机器上安装MySQL,有残余数据影响了服务的启动。
清空之前的 datadir 目录,和MySQL安装目录,如果存在mysql-bin.index,就赶快把它删除掉吧。

6、MySQL在启动时没有指定配置文件时会使用 /etc/my.cnf 配置文件,请打开这个文件查看在[mysqld]节下有没有指定数据目录(datadir)。
请在[mysqld]下设置这一行:[code]datadir = /data/mysql[/code]
7、skip-federated字段问题
检查一下/etc/my.cnf文件中有没有没被注释掉的 skip-federated 字段,如果有就立即注释掉吧。

8、selinux惹的祸,如果是centos系统,默认会开启selinux
关闭它,打开/etc/selinux/config,把SELINUX=enforcing改为SELINUX=disabled后存盘退出重启机器试试。

LAMP三层独立架构部署

云厉的博客

考虑到复杂的应用环境,我们将Apache/PHP/MySQL三种服务独立配置不同主机

我们知道Apache与PHP有三种工作模式:

  • CGI
  • Modules
  • FastCGI

第一种现在用得很少了,性能差;对于动态页面全部由Apache进程启用PHP解释器,然后再释放
第二种需要到本地磁盘加载Modules模块,性能比第一种好,但还是会有很多消耗资源
第三种PHP是一个独立应用,通过网络套接字接口,接收Apache进程传过来的请求,所有PHP进程都由PHP自身管理
在PHP5.4以前,如果PHP需要连接mysql需要MySQL头文件等,
在php5.4以后就不需要了,直接使用

--with-mysql=mysqlnd --with-pdo-mysql=mysqlnd --with-mysqli=mysqlnd

由于Apache并不处理动态页面,所以前端用户请求的PHP页面需要转发到PHP服务器上,这里就需要对Apache做反向代理设置
当然了,我们在这里最好启用虚拟主机功能,这样方便管理。

编译安装Apache2.4.4

准备软件包:
apr-1.4.6.tar.gz
apr-util-1.4.1.tar.gz
httpd-2.4.4.tar.bz2
pcre-8.32.tar.gz

[root@station01 ~]# tar xf apr-1.4.6.tar.gz
[root@station01 ~]# cd apr-1.4.6
[root@station01 apr-1.4.6]# ./configure --prefix=/usr/local/apr
[root@station01 apr-1.4.6]# make &&make install
[root@station01 apr-1.4.6]# cd ..
[root@station01 ~]# tar xf apr-util-1.4.1.tar.gz
[root@station01 ~]# cd apr-util-1.4.1
[root@station01 apr-util-1.4.1]# ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr
[root@station01 apr-util-1.4.1]# make &&make install
[root@station01 apr-util-1.4.1]# cd
[root@station01 ~]# tar xf httpd-2.4.4.tar.bz2
[root@station01 ~]# cd httpd-2.4.4
[root@station01 httpd-2.4.4]# ./configure --prefix=/usr/local/apache --sysconfdir=/etc/httpd --enable-so --enable-ssl --enable-cgi --enable-rewrite --with-zlib --with-pcre=/usr/local/pcre --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr-util --enable-modules=most --enable-mpms-shared=all --with-mpm=event
[root@station01 httpd-2.4.4]# make &&make install
[root@station01 httpd-2.4.4]# cp build/rpm/httpd.init /etc/init.d/httpd
[root@station01 httpd-2.4.4]# vim /etc/init.d/httpd
httpd=${HTTPD-/usr/local/apache/bin/httpd}
pidfile=${PIDFILE-/usr/local/apache/logs/${prog}.pid}
lockfile=${LOCKFILE-/var/lock/subsys/${prog}}
RETVAL=0
CONFFILE=/etc/httpd/httpd.conf
[root@station01 httpd-2.4.4]# chkconfig --add httpd
[root@station01 httpd-2.4.4]# chkconfig httpd on
[root@station01 httpd-2.4.4]# service httpd start

编译安装PHP5.4

准备软件包:
libmcrypt-2.5.7-5.el5.i386.rpm
libmcrypt-devel-2.5.7-5.el5.i386.rpm
php-5.4.13.tar.bz2

[root@station02 ~]# rpm -ivh *.rpm
[root@station02 ~]# tar xf php-5.4.13.tar.bz2
[root@station02 ~]# cd php-5.4.13
[root@station02 php-5.4.13]# ./configure --prefix=/usr/local/php  --with-openssl  --enable-mbstring --with-freetype-dir --with-jpeg-dir --with-png-dir --with-zlib --with-libxml-dir=/usr --enable-xml  --enable-sockets  --with-mcrypt  --with-config-file-path=/etc  --with-config-file-scan-dir=/etc/php.d --with-bz2  --enable-maintainer-zts --with-mysql=mysqlnd --with-pdo-mysql=mysqlnd --with-mysqli=mysqlnd --enable-fpm
[root@station02 php-5.4.13]# make &&make install
[root@station02 php-5.4.13]# cp php.ini-production /etc/php.ini
[root@station02 php-5.4.13]# cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
[root@station02 php-5.4.13]# chmod +x /etc/init.d/php-fpm
[root@station02 php-5.4.13]# chkconfig --add php-fpm
[root@station02 php-5.4.13]# chkconfig php-fpm on
[root@station02 php-5.4.13]# cd /usr/local/php/
[root@station02 php]# cp etc/php-fpm.conf.default etc/php-fpm.conf
[root@station02 php]# vim etc/php-fpm.conf[/code]
配置fpm的相关选项为你所需要的值,并启用pid文件:
[code]pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 2
pm.max_spare_servers = 8
pid = /usr/local/php/var/run/php-fpm.pid
[root@station02 php]# service php-fpm start[/code]
检查php-fpm是否正常
[code][root@station02 php]# netstat -tunlp |grep 9000
[root@station02 php]# ps -ef |grep php-fpm

二进制配置MySQL

[root@station03 ~]# tar xf mysql-5.5.28-linux2.6-i686.tar.gz -C /usr/local/
[root@station03 ~]# cd /usr/local/
[root@station03 local]# groupadd -g 3306 mysql
[root@station03 local]# useradd -g mysql -u 3306 mysql -s /sbin/nologin -M
[root@station03 local]# ln -s mysql-5.5.28-linux2.6-i686/ mysql
[root@station03 local]# cd mysql
[root@station03 mysql]# chown -R mysql.root .
[root@station03 mysql]# chown -R mysql.mysql data/
[root@station03 mysql]# scripts/mysql_install_db --datadir=/usr/local/mysql/data/ --user=mysql
[root@station03 mysql]# cp support-files/mysql.server  /etc/init.d/mysqld
[root@station03 mysql]# chmod +x /etc/init.d/mysqld
[root@station03 mysql]# chkconfig --add mysqld
[root@station03 mysql]# chkconfig mysqld on
[root@station03 mysql]# cp support-files/my-large.cnf /etc/my.cnf
[root@station03 mysql]# vim /etc/my.cnf
#如果data目录更改,则需要在mysqld段中注明
[root@station03 mysql]# service mysqld start
[root@station03 mysql]# vim /etc/profile.d/mysqld.sh
export PATH=$PATH:/usr/local/mysql/bin
[root@station03 mysql]# . /etc/profile.d/mysqld.sh
[root@station03 ~]# mysql
mysql> update user set password=password('asdasd') where user='root';
mysql> flush privileges;

Apache与PHP整合工作

[root@station01 ~]# vim /etc/httpd/httpd.conf[/code]
[code]#DocumentRoot "/usr/local/apache/htdocs"
DirectoryIndex index.html index.php
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
Include /etc/httpd/extra/httpd-vhosts.conf
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
LoadModule proxy_module modules/mod_proxy.so
[root@station01 ~]# vim /etc/httpd/extra/httpd-vhosts.conf
<VirtualHost *:80>
       DocumentRoot "/web/html"
       ServerName "www.neo.com"
       ErrorLog "logs/www.neo.com_error_log"
       CustomLog "logs/www.neo.com_access_log" combined
       <Directory "/web/html">
               Options None
               Require all granted
       </Directory>
       ProxyRequests Off
       ProxyPassMatch ^/(.*)\.php$ fcgi://192.168.100.12:9000/web/html/$1.php
</VirtualHost>[/code]
[code][root@station01 ~]# mkdir /web/html -p
[root@station02 ~]# mkdir /web/html -p
[root@station01 ~]# vim /web/html/index.php
<?php
phpinfo();

浏览器访问http://192.168.100.11/index.php
云厉的博客
验证PHP是否能连上MySQL

<?php
$link=mysql_connect('192.168.100.13','root','asdasd');
if(!$link)
    echo "<h1>connect failed !!!!</h1> ";
else
    echo "<h1>success ....</h1>";
mysql_close();

云厉的博客

转载自:http://junwang.blog.51cto.com/5050337/1422899

MySQL在大型网站的应用架构演变

架构的可扩展性往往和并发是息息相关,没有并发的增长,也就没有必要做高可扩展性的架构,这里对可扩展性进行简单介绍一下,常用的扩展手段有以下两种:
- Scale-up: 纵向扩展,通过替换为更好的机器和资源来实现伸缩,提升服务能力
- Scale-out: 横向扩展, 通过加节点(机器)来实现伸缩,提升服务能力

对于互联网的高并发应用来说,无疑Scale out才是出路,通过纵向的买更高端的机器一直是我们所避讳的问题,也不是长久之计,
在scale out的理论下,可扩展性的理想状态是什么?

可扩展性的理想状态

一个服务,当面临更高的并发的时候,能够通过简单增加机器来提升服务支撑的并发度,
且增加机器过程中对线上服务无影响(no down time),这就是可扩展性的理想状态!

V1.0 简单网站架构

一个简单的小型网站或者应用背后的架构可以非常简单, 数据存储只需要一个MySQL instance就能满足数据读取和写入需求(这里忽略掉了数据备份的实例),处于这个时间段的网站,一般会把所有的信息存到一个database instance里面。
云厉的博客

在这样的架构下,我们来看看数据存储的瓶颈是什么?
- 数据量的总大小 一个机器放不下时
- 数据的索引(B+ Tree)一个机器的内存放不下时
- 访问量(读写混合) 一个实例不能承受时

只有当以上3件事情任何一件或多件满足时,我们才需要考虑往下一级演变。 从此我们可以看出,事实上对于很多小公司小应用,这种架构已经足够满足他们的需求了,初期数据量的准确评估是杜绝过度设计很重要的一环,毕竟没有人愿意为不可能发生的事情而浪费自己的精力。

例如:对于用户信息这类表 (3个索引),16G内存大概能放下2000W行数据的索引,简单的读和写混合访问量3000/s左右没有问题,你的应用场景是否?

V2.0 垂直拆分

一般当V1.0 遇到瓶颈时,首先最简便的拆分方法就是垂直拆分。
何谓垂直?就是从业务角度来看,将关联性不强的数据拆分到不同的instance上,从而达到消除瓶颈的目标。
以图中的为例,将用户信息数据,和业务数据拆分到不同的三个实例上。对于重复读类型比较多的场景,我们还可以加一层cache,来减少对DB的压力。

云厉的博客

在这样的架构下,我们来看看数据存储的瓶颈是什么?
单实例单业务 依然存在V1.0所述瓶颈
遇到瓶颈时可以考虑往本文更高V版本升级, 若是读请求导致达到性能瓶颈可以考虑往V3.0升级, 其他瓶颈考虑往V4.0升级

V3.0 主从架构

此类架构主要解决V2.0架构下的读问题,通过给Instance挂数据实时备份的思路来迁移读取的压力,在MySQL的场景下就是通过主从结构,主库抗写压力,通过从库来分担读压力,对于写少读多的应用,V3.0主从架构完全能够胜任
云厉的博客

在这样的架构下,我们来看看数据存储的瓶颈是什么?
写入量主库不能承受

V4.0 水平拆分

对于V2.0 V3.0方案遇到瓶颈时,都可以通过水平拆分来解决,水平拆分和垂直拆分有较大区别,垂直拆分拆完的结果,在一个实例上是拥有全量数据的,而水平拆分之后,任何实例都只有全量的1/n的数据。
以下图Userinfo的拆分为例,将userinfo拆分为3个cluster,每个cluster持有总量的1/3数据,3个cluster数据的总和等于一份完整数据
注:这里不再叫单个实例 而是叫一个cluster 代表包含主从的一个小MySQL集群
云厉的博客
数据如何路由?

1)Range拆分
sharding key按连续区间段路由,一般用在有严格自增ID需求的场景上,
如Userid, Userid Range的小例子:以userid 3000W 为Range进行拆分:
1号cluster userid 1-3000W
2号cluster userid 3001W-6000W

2)List拆分
List拆分与Range拆分思路一样,都是通过给不同的sharding key来路由到不同的cluster,
但是具体方法有些不同,List主要用来做sharding key不是连续区间的序列落到一个cluster的情况,如以下场景:

假定有20个音像店,分布在4个有经销权的地区,如下表所示:

地区 商店ID
北区 3, 5, 6, 9, 17
东区 1, 2, 10, 11, 19, 20
西区 4, 12, 13, 14, 18
中心区 7, 8, 15, 16

业务希望能够把一个地区的所有数据组织到一起来搜索,这种场景List拆分可以轻松搞定

3)Hash拆分
通过对sharding key 进行哈希的方式来进行拆分,常用的哈希方法有除余,字符串哈希等等。
除余如按userid%n 的值来决定数据读写哪个cluster,其他哈希类算法这里就不细展开讲了。

数据拆分后引入的问题?

数据水平拆分引入的问题主要是只能通过sharding key来读写操作,
例如以userid为sharding key的切分例子,读userid的详细信息时,一定需要先知道userid,这样才能推算出再哪个cluster进而进行查询,
假设我需要按username进行检索用户信息,需要引入额外的反向索引机制(类似HBASE二级索引),
如在redis上存储username->userid的映射,以username查询的例子变成了先通过查询username->userid,再通过userid查询相应的信息。
实际上这个做法很简单,但是我们不要忽略了一个额外的隐患,那就是数据不一致的隐患。
存储在redis里的username->userid和存储在MySQL里的userid->username必须需要是一致的,这个保证起来很多时候是一件比较困难的事情,
举个例子来说,对于修改用户名这个场景,你需要同时修改redis和MySQL,这两个东西是很难做到事务保证的,
如MySQL操作成功 但是redis却操作失败了(分布式事务引入成本较高),
对于互联网应用来说,可用性是最重要的,一致性是其次,所以能够容忍小量的不一致出现。
毕竟从占比来说,这类的不一致的比例可以微乎其微到忽略不计(一般写更新也会采用mq来保证直到成功为止才停止重试操作)

在这样的架构下,我们来看看数据存储的瓶颈是什么?
在这个拆分理念上搭建起来的架构,理论上不存在瓶颈(sharding key能确保各cluster流量相对均衡的前提下),不过确有一件恶心的事情,那就是cluster扩容的时候重做数据的成本,如我原来有3个cluster,但是现在我的数据增长比较快,我需要6个cluster,那么我们需要将每个cluster 一拆为二,一般的做法是
- 摘下一个slave,停同步,
- 对写记录增量log(实现上可以业务方对写操作 多一次写持久化mq 或者MySQL主创建trigger记录写 等等方式)
- 开始对静态slave做数据, 一拆为二
- 回放增量写入,直到追上的所有增量,与原cluster基本保持同步
- 写入切换,由原3 cluster 切换为6cluster

有没有类似飞机空中加油的感觉,这是一个脏活,累活,容易出问题的活,
为了避免这个,我们一般在最开始的时候,设计足够多的sharding cluster来防止可能的cluster扩容这件事情

V5.0 云计算(云数据库)

云计算现在是各大IT公司内部作为节约成本的一个突破口,
对于数据存储的MySQL来说,如何让其成为一个SaaS(Software as a Service)是关键点。
在MS的官方文档中,把构建一个足够成熟的SaaS(MS简单列出了SaaS应用的4级成熟度)所面临的3个主要挑战:
可配置性,可扩展性,多用户存储结构设计称为"three headed monster"
可配置性和多用户存储结构设计在MySQL SaaS这个问题中并不是特别难办的一件事情,所以这里重点说一下可扩展性。

MySQL作为一个SaaS服务,在架构演变为V4.0之后,依赖良好的sharding key设计, 已经不再存在扩展性问题,只是他在面对扩容缩容时,有一些脏活需要干,而作为SaaS,并不能避免扩容缩容这个问题!
所以只要能把V4.0的脏活变成:
- 扩容缩容对前端APP透明(业务代码不需要任何改动)
- 扩容缩容全自动化且对在线服务无影响

那么他就拿到了作为SaaS的门票
云厉的博客

对于架构实现的关键点,需要满足对业务透明,扩容缩容对业务不需要任何改动,那么就必须 “eat our own dog food”,
在你MySQL SaaS内部解决这个问题,一般的做法是我们需要引入一个Proxy,Proxy来解析sql协议,
按sharding key 来寻找cluster, 判断是读操作还是写操作来请求主 或者 从,这一切内部的细节都由proxy来屏蔽。
这里借淘宝的图来列举一下proxy需要干哪些事情
云厉的博客

百度公开的技术方案中也有类似的解决方案:
数据库大会百度Dbproxy中间层架构概述

对于架构实现的关键点,扩容缩容全自动化且对在线服务无影响; 扩容缩容对应到的数据操作即为数据拆分和数据合并,要做到完全自动化有非常多不同的实现方式,总体思路和V4.0介绍的瓶颈部分有关,目前来看这个问题比较好的方案就是实现一个伪装slave的sync slave, 解析MySQL同步协议,然后实现数据拆分逻辑,把全量数据进行拆分。具体架构见下图:

云厉的博客

其中Sync slave对于Original Master来说,和一个普通的MySQL Slave没有任何区别,也不需要任何额外的区分对待。需要扩容/缩容时,挂上一个Sync slave,开始全量同步+增量同步,等待一段时间追数据。以扩容为例,若扩容后的服务和扩容前数据已经基本同步了,这时候如何做到切换对业务无影响? 其实关键点还是在引入的proxy,这个问题转换为了如何让proxy做热切换后端的问题。这已经变成一个非常好处理的问题了.

作者:大熊
原文:http://www.cnblogs.com/Creator

甲骨文推出MySQL Fabric,简化MySQL的高可用性与可扩展性

为了满足当下对Web及云应用需求,甲骨文宣布推出MySQL Fabric。MySQL Fabric是一款可简化管理MySQL数据库群的整合式系统。
该产品通过故障检测和故障转移功能提供了高可用性,同时通过自动数据分片功能实现可扩展性。

为持续推动MySQL的创新,甲骨文宣布MySQL Fabric全面上市。MySQL Fabric是一开源框架,能够管理MySQL服务器群。

下载地址:http://dev.mysql.com/downloads/fabric

该系统更加容易实现基于MySQL应用的横向扩展及可用性的提高。

MySQL Fabric 主要功能

与MySQL复制功能共同使用实现高可用性时,MySQL Fabric能够提供自动故障检测和故障转移功能。具体包括:

  • 监控主数据库,如果服务器出现故障, [url=thread-46-1-1.html]MySQL Fabric[/url]将选出一个从数据库,并将其升级为新的主数据库。
  • 提供事务处理的自动路径选择以连接到当前主数据库,及从数据库间的查询负载均衡。拓扑及服务器状态对应用透明。
  • 自动数据分片和再分片功能简化了开发运营团队的流程管理工作。这帮助用户能够:
  • 对表分片,实现读写的横向扩展;
  • 选择对哪些表分片,并指定分片键的字段,包括使用基于哈希映射还是基于区间映射;
  • 将现有数据片转移到新的服务器上,或者将这些数据片进一步分成更小的数据片。

对PHP、Python和Java连接器的扩展支持使得事务处理和查询能够直接发送到合适的MySQL服务器上。因为无需代理,不会产生额外延迟。

甲骨文高管引言

甲骨文公司MySQL开发副总裁Tomas Ulin表示:

全球很多大型网站和云应用都借助MySQL复制和分片功能来提升高可用性和可扩展性。MySQL Fabric通过自动故障检测和故障转移实现了高可用性以及自动数据分片,并对所有人开放。凭借MySQL Fabric这款整合的开源框架,无论MySQL应用是在内部部署还是云中,甲骨文都能使其轻松并安全地实现横向扩展。

Linux查看Nginx、Apache、MySQL、PHP的编译参数

软件安装好了,想查看当初编译时的参数,怎么搞?

1、Nginx,可以通过 -V 参数查看Nginx版本及编译参数

/usr/local/nginx/sbin/nginx -V

nginx version: nginx/1.10.2
built by gcc 4.4.6 20110731 (Red Hat 4.4.6-4) (GCC)
configure arguments: --prefix=/usr/local/nginx/ --with-http_image_filter_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_dav_module --with-http_auth_request_module --with-http_secure_link_module --with-http_slice_module --with-http_stub_status_module --with-stream --with-pcre=/usr/local/tools/pcre-8.38 --with-zlib=/usr/local/tools/zlib-1.2.8

2、Apache,编译参数存放在 apache 的 config.nice 文件,前提时,没有做过 make clean

locate config.nice | grep apache | xargs cat

#! /bin/sh
#
# Created by configure

"./configure" \
"--with-apr=/usr/local/apr/" \
"--with-apr-util=/usr/local/apr-util/" \
"--with-pcre=/usr/local/pcre/" \
"ap_cv_void_ptr_lt_long=no" \
"--enable-proxy" \
"--enable-proxy-connect" \
"--enable-proxy-ftp" \
"--enable-proxy-http" \
"--enable-proxy-scgi" \
"--enable-proxy-ajp" \
"--enable-proxy-balancer" \
"$@"

3、MySQL,利用 msyqlbug 工具,查看MySQL信息

VISUAL=vim; export VISUAL; mysqlbug

...
Some paths: /usr/bin/perl /usr/bin/make /usr/bin/gmake /usr/bin/gcc /usr/bin/cc
GCC: Using built-in specs.
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enab
le-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions
--enable-gnu-unique-object --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --with-java-home=/usr/lib/jvm/j
ava-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-mul
tilib --with-ppl --with-cloog --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.4.6 20110731 (Red Hat 4.4.6-4) (GCC)
Compilation info (call): CC='/usr/local/gcc-4.3.4/bin/gcc' CFLAGS=' -fPIC -Wall -O3 -g -static-libgcc -fno-omit-frame-pointer -fno-strict-aliasing -D
DBUG_OFF -DMY_PTHREAD_FASTMUTEX=1' CXX='/usr/local/gcc-4.3.4/bin/g++' CXXFLAGS=' -fPIC -Wall -Wno-unused-parameter -fno-implicit-templates -fno-exce
ptions -fno-rtti -O3 -g -static-libgcc -fno-omit-frame-pointer -fno-strict-aliasing -DDBUG_OFF -DMY_PTHREAD_FASTMUTEX=1' LDFLAGS='' ASFLAGS=''
Compilation info (used): CC='/usr/local/gcc-4.3.4/bin/gcc' CFLAGS=' -fPIC -Wall -O3 -g -static-libgcc -fno-omit-frame-pointer -fno-strict-aliasing -D
DBUG_OFF -DMY_PTHREAD_FASTMUTEX=1' CXX='/usr/local/gcc-4.3.4/bin/g++' CXXFLAGS=' -fPIC -Wall -Wno-unused-parameter -fno-implicit-templates -fno-exce
ptions -fno-rtti -O3 -g -static-libgcc -fno-omit-frame-pointer -fno-strict-aliasing -DDBUG_OFF -DMY_PTHREAD_FASTMUTEX=1' LDFLAGS='' ASFLAGS=''
LIBC:
...
...

4、PHP,可以通过 -i 参数可以打印PHP全局变量和安装信息

/usr/bin/php -i | grep configure

Configure Command => './configure' '--prefix=/usr/local/php/' '--enable-fpm' '--enable-opcache' '--enable-inline-optimization' '--with-libxml-dir=/usr/local/libxml2/' '--with-curl=/usr/local/curl/' '--enable-dba' '--with-gd' '--enable-gd-native-ttf' '--enable-gd-jis-conv' '--with-jpeg-dir=/usr/local/jpeg/' '--with-png-dir=/usr/local/libpng/' '--with-zlib-dir=/usr/local/zlib/' '--with-freetype-dir=/usr/local/freetype/' '--with-pcre-regex=/usr/local/pcre/' '--with-pcre-jit' '--with-pcre-dir=/usr/local/pcre/' '--enable-mbstring' '--enable-pcntl' '--enable-soap' '--enable-shmop' '--enable-sockets' '--enable-sysvsem' '--enable-zip' '--enable-mysqlnd' '--with-mysqli=mysqlnd' '--with-pdo-mysql=mysqlnd'

微信公众号:程序员到架构师

最新文章

Return Top