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应用是在内部部署还是云中,甲骨文都能使其轻松并安全地实现横向扩展。

git config 配置文件

[size=5][b]一、客户化你的Git环境。[/b][/size]

你只需要做这些设置一次;即使你升级了,他们也会绑定到你的环境中。你也可以在任何时刻通过运行命令来重新更改这些设置。
Git有一个工具(命令)被称为git config,它允许你获得和设置配置变量;
这些变量可以控制Git的外观和操作的各个方面。这些变量可以被存储在三个不同的位置:
1./etc/gitconfig 文件:包含了适用于系统所有用户和所有库的值。如果你传递参数选项’--system’ 给 git config,它将明确的读和写这个文件。
2.~/.gitconfig 文件 :具体到你的用户。你可以通过传递--global 选项使Git 读或写这个特定的文件。
3.位于git目录的config文件 (也就是 .git/config) :无论你当前在用的库是什么,特定指向该单一的库。每个级别重写前一个级别的值。因此,在.git/config中的值覆盖了在/etc/gitconfig中的同一个值。

  在Windows系统中,Git在$HOME目录中查找.gitconfig文件(对大多数人来说,位于C:\Documents and Settings\$USER下)。它也会查找/etc/gitconfig,尽管它是相对于Msys 根目录的。这可能是你在Windows中运行安装程序时决定安装Git的任何地方。

[size=5][b]二.你的标识(Your Identity)[/b][/size]

2.1 当你安装Git后首先要做的事情是设置你的用户名称和e-mail地址。这是非常重要的,因为每次Git提交都会使用该信息。它被永远的嵌入到了你的提交中:
  $ git config --global user.name "daoliu"
  $ git config --global user.email [url=mailto:979137@qq.com]979137@qq.com[/url]
  重申一遍,你只需要做一次这个设置。如果你传递了 --global 选项,因为Git将总是会使用该信息来处理你在系统中所做的一切操作。如果你希望在一个特定的项目中使用不同的名称或e-mail地址,你可以在该项目中运行该命令而不要--global选项。

2.2 你的编辑器(Your Editor)
  现在,你的标识已经设置,你可以配置你的缺省文本编辑器,Git在需要你输入一些消息时会使用该文本编辑器。缺省情况下,Git使用你的系统的缺省编辑器,这通常可能是vi 或者 vim。如果你想使用一个不同的文本编辑器,例如Emacs,你可以做如下操作:
  $ git config --global core.editor emacs

2.3 你的比较工具(Your Diff Tool)
  另外一个你可能需要配置的有用的选项是缺省的比较工具它用来解决合并时的冲突。例如,你想使用vimdiff:
  $ git config --global merge.tool vimdiff
  Git可以接受kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, 和 opendiff作为有效的合并工具。你也可以设置一个客户化的工具;查看第7章获得更多关于此的信息。

2.4 检查你的设置(Checking Your Settings)
  如果你想检查你的设置,你可以使用 git config --list 命令来列出Git可以在该处找到的所有的设置:
  $ git config --list
  user.name=Scott Chacon
  user.email=schacon@gmail.com
  color.status=auto
  color.branch=auto
  color.interactive=auto
  color.diff=auto
  ...

  你可能会看到一个关键字出现多次,这是因为Git从不同的文件中(例如:/etc/gitconfig以及~/.gitconfig)读取相同的关键字。 在这种情况下,对每个唯一的关键字,Git使用最后的那个值。
  你也可以查看Git认为的一个特定的关键字目前的值,使用如下命令 git config {key}:
  $ git config user.name
  Scott Chacon

2.5 获取帮助(Getting help)
  如果当你在使用Git时需要帮助,有三种方法可以获得任何git命令的手册页(manpage)帮助信息:
  $ git help
  $ git --help
  $ man git-
  例如,你可以运行如下命令获取对config命令的手册页帮助:
  $ git help config
  这些命令非常友好,因为你可以在任何地方存取他们,即使非在线状态。如果手册页和本书仍然不足而你需要个人的帮助,你可以试着使用Freenode IRCServer上的#git或#github 频道(irc.freenode.net)。这些频道会定期由数百个对Git非常熟悉的专业人士所维护,他们会非常乐意帮助你。

2.6 总结(Summary)
  你应该对Git是什么以及Git与你可能使用的其它CVCS之间的不同有了一个基本的了解。你也应当在你的系统中有了一个具有你个人标识的可以工作的Git版本。是时候来学习一些Git的基本知识了。

9个PHP中很有用的功能,不知道你用过了吗?

1、函数的任意数目的参数

你可能知道PHP允许你定义一个默认参数的函数。但你可能并不知道PHP还允许你定义一个完全任意的参数的函数

// 两个默认参数的函数
function foo($arg1 = '', $arg2 = '') {
    echo "arg1: $arg1\n";
    echo "arg2: $arg2\n";
}
foo('hello','world');
/* 输出:
arg1: hello
arg2: world
*/

foo();
/* 输出:
arg1:
arg2:
*/

现在我们来看一看一个不定参数的函数,其使用到了func_get_args方法:

// 是的,形参列表为空
function foo() {
    // 取得所有的传入参数的数组
    $args = func_get_args();
    foreach ($args as $k => $v) {
        echo "arg".($k+1).": $v\n";
    }
}

foo();
/* 什么也不会输出 */

foo('hello');
/* 输出
arg1: hello
*/

foo('hello', 'world', 'again');
/* 输出
arg1: hello
arg2: world
arg3: again
*/

2、查找与匹配文件

很多PHP的函数都有一个比较长的自解释的函数名,
但是,当你看到glob的时候,你可能并不知道这个函数是用来干什么的,除非你对它已经很熟悉了。
你可以认为这个函数就好scandir一样,其可以用来查找文件。

// 取得所有后缀为.jpg 的图片文件
$files = glob('*.jpg');
print_r($files);
/* 输出:
Array
(
    [0] => phptest.jpg
    [1] => pi.jpg
    [2] => post_output.jpg
    [3] => test.jpg
)
*/[/code]
你还可以查找多种后缀名
[code]// 取PHP文件和TXT文件
$files = glob('*.{php,txt}', GLOB_BRACE);
print_r($files);
/* 输出:
Array
(
    [0] => phptest.php
    [1] => pi.php
    [2] => post_output.php
    [3] => test.php
    [4] => log.txt
    [5] => test.txt
)

你还可以加上路径:

$files = glob('../images/a*.jpg');

print_r($files);
/* 输出:
Array
(
    [0] => ../images/apple.jpg
    [1] => ../images/art.jpg
)
*/

3、看内存使用信息

观察你程序的内存使用能够让你更好的优化你的代码。
PHP 是有垃圾回收机制的,而且有一套很复杂的内存管理机制。你可以知道你的脚本所使用的内存情况。
要知道当前内存使用情况,你可以使用memory_get_usage函数;
想知道使用内存的峰值,你可以调用memory_get_peak_usage函数。

echo "Initial: ".memory_get_usage()." bytes \n";
/* 输出
Initial: 361400 bytes
*/

// 使用内存
for ($i = 0; $i < 100000; $i++) {
    $array []= md5($i);
}

// 删除一半的内存
for ($i = 0; $i < 100000; $i++) {
    unset($array[$i]);
}

echo "Final: ".memory_get_usage()." bytes \n";
/* prints
Final: 885912 bytes
*/

echo "Peak: ".memory_get_peak_usage()." bytes \n";
/* 输出峰值
Peak: 13687072 bytes
*/

4、CPU使用信息

使用getrusage函数可以让你知道CPU的使用情况。(注意,这个功能在Windows下不可用)

print_r(getrusage());
/* 输出
Array
(
    [ru_oublock] => 0
    [ru_inblock] => 0
    [ru_msgsnd] => 2
    [ru_msgrcv] => 3
    [ru_maxrss] => 12692
    [ru_ixrss] => 764
    [ru_idrss] => 3864
    [ru_minflt] => 94
    [ru_majflt] => 0
    [ru_nsignals] => 1
    [ru_nvcsw] => 67
    [ru_nivcsw] => 4
    [ru_nswap] => 0
    [ru_utime.tv_usec] => 0
    [ru_utime.tv_sec] => 0
    [ru_stime.tv_usec] => 6269
    [ru_stime.tv_sec] => 0
)
*/

这个结构看上出很晦涩,除非你对CPU很了解。下面一些解释:

  • ru_oublock: 块输出操作
  • ru_inblock: 块输入操作
  • ru_msgsnd: 发送的message
  • ru_msgrcv: 收到的message
  • ru_maxrss: 最大驻留集大小
  • ru_ixrss: 全部共享内存大小
  • ru_idrss:全部非共享内存大小
  • ru_minflt: 页回收
  • ru_majflt: 页失效
  • ru_nsignals: 收到的信号
  • ru_nvcsw: 主动上下文切换
  • ru_nivcsw: 被动上下文切换
  • ru_nswap: 交换区
  • ru_utime.tv_usec: 用户态时间 (microseconds)
  • ru_utime.tv_sec: 用户态时间(seconds)
  • ru_stime.tv_usec: 系统内核时间 (microseconds)
  • ru_stime.tv_sec: 系统内核时间?(seconds)

要看到你的脚本消耗了多少CPU,我们需要看看“用户态的时间”和“系统内核时间”的值。秒和微秒部分是分别提供的,您可以把微秒值除以100万,并把它添加到秒的值后,可以得到有小数部分的秒数。

// sleep for 3 seconds (non-busy)
sleep(3);
$data = getrusage();
echo "User time: ".($data['ru_utime.tv_sec'] + $data['ru_utime.tv_usec'] / 1000000);
echo "System time: ".($data['ru_stime.tv_sec'] + $data['ru_stime.tv_usec'] / 1000000);
/* 输出
User time: 0.011552
System time: 0
*/

sleep是不占用系统时间的,我们可以来看下面的一个例子:

// loop 10 million times (busy)
for($i=0;$i<10000000;$i++) {
}

$data = getrusage();
echo "User time: ".($data['ru_utime.tv_sec'] + $data['ru_utime.tv_usec'] / 1000000);
echo "System time: ".($data['ru_stime.tv_sec'] + $data['ru_stime.tv_usec'] / 1000000);

/* 输出
User time: 1.424592
System time: 0.004204
*/[/code]
这花了大约14秒的CPU时间,几乎所有的都是用户的时间,因为没有系统调用。

系统时间是CPU花费在系统调用上的上执行内核指令的时间。下面是一个例子:
[code]$start = microtime(true);
// keep calling microtime for about 3 seconds
while(microtime(true) - $start < 3) {
}

$data = getrusage();
echo "User time: ".($data['ru_utime.tv_sec'] + $data['ru_utime.tv_usec'] / 1000000);
echo "System time: ".($data['ru_stime.tv_sec'] + $data['ru_stime.tv_usec'] / 1000000);
/* prints
User time: 1.088171
System time: 1.675315
*/

我们可以看到上面这个例子更耗CPU

5、系统常量

PHP系统常量可以让你得到当前的行号 (LINE),文件 (FILE),目录 (DIR),函数名 (FUNCTION),类名(CLASS),方法名(METHOD) 和名字空间 (NAMESPACE),很像C语言。

我们可以以为这些东西主要是用于调试,当也不一定,比如我们可以在include其它文件的时候使用 FILE (当然,你也可以在 PHP 5.3以后使用 DIR ),下面是一个例子。

// this is relative to the loaded script's path
// it may cause problems when running scripts from different directories
require_once('config/database.php');

// this is always relative to this file's path
// no matter where it was included from
require_once(dirname(__FILE__) . '/config/database.php');[/code]
下面是使用 __LINE__ 来输出一些debug的信息,这样有助于你调试程序:
[code]// some code
// ...
my_debug("some debug message", __LINE__);
/* 输出
Line 4: some debug message
*/

// some more code
// ...
my_debug("another debug message", __LINE__);
/* 输出
Line 11: another debug message
*/

function my_debug($msg, $line) {
    echo "Line $line: $msg\n";
}

6、生成唯一的ID

有很多人使用md5来生成一个唯一的ID,如下所示:

echo md5(time() . mt_rand(1,1000000));

其实,PHP中有一个叫uniqid的函数是专门用来干这个的:

// generate unique string
echo uniqid();
/* 输出
4bd67c947233e
*/

// generate another unique string
echo uniqid();
/* 输出
4bd67c9472340
*/

可能你会注意到生成出来的ID前几位是一样的,这是因为生成器依赖于系统的时间,这其实是一个非常不错的功能,因为你是很容易为你的这些ID排序的。这点MD5是做不到的。

你还可以加上前缀避免重名:

// 前缀
echo uniqid('foo_');
/* 输出
foo_4bd67d6cd8b8f
*/

// 有更多的熵
echo uniqid('',true);
/* 输出
4bd67d6cd8b926.12135106
*/

// 都有
echo uniqid('bar_',true);
/* 输出
bar_4bd67da367b650.43684647
*/

而且,生成出来的ID会比MD5生成的要短,这会让你节省很多空间。

7. 数据序列化

你是否会把一个比较复杂的数据结构存到数据库或是文件中?你并不需要自己去写自己的算法。PHP早已为你做好了,其提供了两个函数: serialize和unserialize

// 一个复杂的数组
$myvar = array(
    'hello',
    42,
    array(1,'two'),
    'apple'
);

// 序列化
$string = serialize($myvar);

echo $string;
/* 输出
a:4:{i:0;s:5:"hello";i:1;i:42;i:2;a:2:{i:0;i:1;i:1;s:3:"two";}i:3;s:5:"apple";}
*/

// 反序例化
$newvar = unserialize($string);

print_r($newvar);
/* 输出
Array
(
    [0] => hello
    [1] => 42
    [2] => Array
        (
            [0] => 1
            [1] => two
        )
    [3] => apple
)
*/

这是PHP的原生函数,然而在今天JSON越来越流行,所以在PHP5.2以后,PHP开始支持JSON,你可以使用 json_encode() 和 json_decode() 函数

// a complex array
$myvar = array(
    'hello',
    42,
    array(1,'two'),
    'apple'
);

// convert to a string
$string = json_encode($myvar);

echo $string;
/* prints
["hello",42,[1,"two"],"apple"]
*/

// you can reproduce the original variable
$newvar = json_decode($string);

print_r($newvar);
/* prints
Array
(
    [0] => hello
    [1] => 42
    [2] => Array
        (
            [0] => 1
            [1] => two
        )
    [3] => apple
)
*/

这看起来更为紧凑一些了,而且还兼容于Javascript和其它语言。但是对于一些非常复杂的数据结构,可能会造成数据丢失。

8、字符串压缩

当我们说到压缩,我们可能会想到文件压缩,其实,字符串也是可以压缩的。PHP提供了 gzcompress和gzuncompress

$string = "Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Nunc ut elit id mi ultricies
adipiscing. Nulla facilisi. Praesent pulvinar,
sapien vel feugiat vestibulum, nulla dui pretium orci,
non ultricies elit lacus quis ante. Lorem ipsum dolor
sit amet, consectetur adipiscing elit. Aliquam
pretium ullamcorper urna quis iaculis. Etiam ac massa
sed turpis tempor luctus. Curabitur sed nibh eu elit
mollis congue. Praesent ipsum diam, consectetur vitae
ornare a, aliquam a nunc. In id magna pellentesque
tellus posuere adipiscing. Sed non mi metus, at lacinia
augue. Sed magna nisi, ornare in mollis in, mollis
sed nunc. Etiam at justo in leo congue mollis.
Nullam in neque eget metus hendrerit scelerisque
eu non enim. Ut malesuada lacus eu nulla bibendum
id euismod urna sodales. ";

$compressed = gzcompress($string);

echo "Original size: ". strlen($string)."\n";
/* 输出原始大小
Original size: 800
*/

echo "Compressed size: ". strlen($compressed)."\n";
/* 输出压缩后的大小
Compressed size: 418
*/

// 解压缩
$original = gzuncompress($compressed);

几乎有50% 压缩比率。同时,你还可以使用gzencode和gzdecode函数来压缩,只不过其用了不同的压缩算法。

9、注册停止函数

有一个函数叫做register_shutdown_function,可以让你在整个脚本停止前运行代码。脚本停止前运行是指在执行完所有PHP语句后再调用函数,不要理解成客户端关闭流浏览器页面时调用函数。

可以这样理解调用条件:

  • 当页面被用户强制停止时
  • 当程序代码运行超时时
  • 当PHP代码执行完成时

让我们看下面的两个示例加以理解:

function shutdown() {
    global $salary;
    $salary ? die('HeHe!') : die('No salary!');
}
register_shutdown_function('shutdown');

$salary = false;
exit; //手动停止代码往下执行。代码执行到这里时,将调用 shutdown 函数
$salary = true;[/code]
[code]function shutdown() {
    global $salary;
    $salary ? die('HeHe!') : die('No salary!');
}
register_shutdown_function('shutdown');

$salary = false;
$theobj = 1;
$theobj = new Salary(); // 类不存在,发生致命错误,代码停止执行。此时将调用 shutdown 函数;
$salary = true;

PHP提取百度网盘真实下载地址

文件哪里存?当然选网盘,永久免费、容量够大、速度快。
不过下载是个问题,网盘一盘不提供直链,只能进入分享页手动下载,虽然下载后能看到直链,但这个直链是有时间限制的,也就是说那是临时的。

需求来了,当我们把文件存储到网盘后,并不希望用户跑到网盘去下载,而是点击链接就直接下载

下面提供一个封装好的百度网盘真实下载地址提取函数,有空再去研究研究微云的

function baiduPan($url) {
        if (strpos($url, 'pan.baidu.com') === false) return false;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_URL, $url);
        $html = curl_exec($ch);
        if (preg_match('/bdstoken="(.*?)".*?fsId="(.*?)".*?share_uk="(.*?)".*?share_id="(.*?)"/i', $html, $matches)) {
                $proxyUrl  = sprintf('http://pan.baidu.com/share/download?bdstoken=%s&uk=%s&shareid=%s&fid_list=%%5B%s%%5D', $matches[1], $matches[3], $matches[4], $matches[2]);
                curl_setopt($ch, CURLOPT_URL, $proxyUrl);
                $proxyHtml = curl_exec($ch);
                $jsonObj   = json_decode($proxyHtml, true);
                $downloadButtonUrl = $jsonObj['dlink'];
                $downloadButtonUrl = str_replace("\\\\/", "/", $downloadButtonUrl);
                curl_setopt($ch, CURLOPT_URL, $downloadButtonUrl);
                curl_setopt($ch, CURLOPT_VERBOSE, 1);
                curl_setopt($ch, CURLOPT_HEADER, 1);
                $result = curl_exec($ch);
                list($header, $body) = explode("\r\n\r\n", $result, 2);
                if (preg_match('/Location: (.*?)(\r?\n)/is', $header, $matches)) {
                        return $matches[1];
                }
        }
        return false;
}
$testurl = 'http://pan.baidu.com/share/link?shareid=3142223074&uk=1864945200';
$downurl = baiduPan($testurl);
//这个就是文件的真实下载地址,可以用浏览器或者下载工具试试
echo $downurl; 


规则是活的,代码是死的,所以无法保证可以永久使用,如发现无法正常使用,那就是网盘规则变了,需要自行修改代码咯,就个人经验来看,百度网盘一般两到三个月会更新一次,不过变化不会太大。

目录遍历和计算文件个数的简单函数

通过本函数,可以计算出指定目录的所有文件个数,以及遍历所有的文件

/**
 * @path 路径,支持相对和绝对
 * @absolute 返回的文件数组,是否包含完整路径
 */
function get_files($path, $absolute=1) {
    $files = array();
    $_path = realpath($path);
    if (!file_exists($_path)) return false;
    if (is_dir($_path)) {
        $list = scandir($_path);
        foreach ($list as $v) {
            if ($v == '.' || $v == '..') continue;
                $_paths = $_path.'/'.$v;
            if (is_dir($_paths)) {
                //递归
                $files = array_merge($files, get_files($_paths,$absolute));
            } else {
                $files[] = $absolute>0 ? $_paths : $v;
            }
        }
    } else {
        if (!is_file($_path)) return false;
        $files[] = $_path;
    }
    return $files;
}

$a = get_files('./Caige/api');
$b = get_files('./Caige/api', 0);
echo '<pre>';
var_dump($a);
var_dump($b);
echo count($a).'<br />'.count($b);

一款不错的弹窗/消息提示组件(ymPrompt),多功能、多风格

倒流’s Bolg

应该有很多人用过,这里分享给没用过的码农,可以考虑试试!
觉得还是挺不错的!功能比较多~

不多废话,直接看演示:包含使用简介,多种风格可选(若出现乱码,请确保浏览器的编码格式为GBK):
http://demo.979137.com/tools/ymPrompt

PS:
1、很多人说该组件效率不高。个人使用感受:如果负载不是特别高的网站,它对网站性能的影响微乎其微;一个高性能的WEB程序,前端的优化只是很小一部分。
2、有人觉得不好看,组件有7个风格可以选择,可以看看后面几个风格,页面可以直接设置预览,满足各种需求。
3、功能比较多,需要慢慢尝试

主流文本编辑器学习曲线

倒流’s Bolg
[size=3]图中的纵横坐标没有标明。我所理解的是——X轴是熟练程度,Y轴是技能。
于是对于 [url=http://baike.baidu.com/view/568629.htm]Notepad[/url] 来说,技能和熟练程度呈正比。
对于[url=http://baike.baidu.com/view/261613.htm]VS[/url]来说,熟练程度越大,所需要技能先是越来越多,而随着熟练程度的增长,你需要的技能也越少。
而对于 [url=http://baike.baidu.com/subview/27682/5889285.htm]VI[/url] 来说,一开始就需要相当大的技能,但一旦掌握这些技能,则你将会越来越熟练。
而对于 [url=http://baike.baidu.com/view/113220.htm]emacs[/url] 来说,技能和熟练程度是呈旋涡状。[/size]
[size=3]
[/size]

秒懂系列|PHP判断是否手机访问的方法

很多时候,我们需要对请求来源进行区别处理,如主题模版、智能推广等等...

PHP作为作为后端语言,没办法像前端语言一样获取机器设备信息

下面这个函数,可以帮助你实现对手机用户的判断识别:

function ismobile() {
    // 如果有HTTP_X_WAP_PROFILE则一定是移动设备
    if (isset ($_SERVER['HTTP_X_WAP_PROFILE']))
        return true;
    //此条摘自TPM智能切换模板引擎,适合TPM开发
    if(isset ($_SERVER['HTTP_CLIENT']) &&'PhoneClient'==$_SERVER['HTTP_CLIENT'])
        return true;
    //如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息
    if (isset ($_SERVER['HTTP_VIA']))
        //找不到为flase,否则为true
        return stristr($_SERVER['HTTP_VIA'], 'wap') ? true : false;
    //判断手机发送的客户端标志,兼容性有待提高
    if (isset ($_SERVER['HTTP_USER_AGENT'])) {
        $clientkeywords = array(
            'nokia','sony','ericsson','mot','samsung','htc','sgh','lg','sharp','sie-','philips','panasonic','alcatel','lenovo','iphone','ipod','blackberry','meizu','android','netfront','symbian','ucweb','windowsce','palm','operamini','operamobi','openwave','nexusone','cldc','midp','wap','mobile'
        );
        //从HTTP_USER_AGENT中查找手机浏览器的关键字
        if (preg_match("/(" . implode('|', $clientkeywords) . ")/i", strtolower($_SERVER['HTTP_USER_AGENT']))) {
            return true;
        }
    }
    //协议法,因为有可能不准确,放到最后判断
    if (isset ($_SERVER['HTTP_ACCEPT'])) {
        // 如果只支持wml并且不支持html那一定是移动设备
        // 如果支持wml和html但是wml在html之前则是移动设备
        if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) {
            return true;
        }
    }
    return false;
}

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

最新文章

Return Top