分类 IT技术 下的文章

服务器硬盘损坏宕机

  • 宕机原因:
    服务器已经使用3年多了,前几天一块儿磁盘分区就出现读写数据不完整问题。 因为那块分区是作为备份用的,而且服务器一直也没出现过问题,所以也没有很重视没有很急迫的把那块儿磁盘修复了。本来想着这两天修复的,屋漏偏逢连夜雨,今天下午服务器磁盘终于歇菜了。

  • 解决难点:
    服务器宕机以后就开始联系托管机房的工作人员,机房人员说是系统崩溃需要重新安装系统。但是重新安装系统可能会丢失磁盘文件,关键是这两天刚好因为备份磁盘分区出现问题没有备份,数据是万万不能丢失的。其次有些网银接口等用的是IP,如果更好IP的话,需要更改的东西很多;而且域名解析也需要一段时间的;如果直接加一台新的服务器这样迁移时间太长不划算。

  • 解决办法:
    这台服务器是作为单独的web,还有两台数据库服务器作为主从。主库服务器的配置比较高,然后考虑其作为临时web服务器;然后把宕机服务器的IP也绑定到这太主库服务器上。开始联系机房人员更改IP,同时也开始在配置必须的web环境。不能丢失数据:把原来的硬盘挂到主库服务器上把数据迁移过来。经过一晚上的紧张忙碌,迁移很顺利,工作量相对也较小,网站也恢复正常。

Pure-FTP安装配置

Pure-FTPd 是一款免费(BSD)的,安全的,高质量和符合标准的FTP服务器。 侧重于运行效率和易用性。 这篇文章介绍如何在CentOS下安装Pure-FTP服务, 并且使用MySQL存储虚拟FTP用户。

安装Pure-FTP

  • yum方式安装
yum -y install pure-ftpd
  • 编译安装方式,下载地址,为了方便起见,我在这里使用了几个基本的编译命令来配置编译一个全功能版本的程序
# ./configure --prefix=/usr/local/pure-ftpd/ --with-language=simplified-chinese --with-everything
# make && make check && make install
  • 设置Pure-FTP开机启动
chkconfig --add pure-ftpd
chkconfig --level 2345 pure-ftpd on
  • 配置Pure-FTP
ChrootEveryone              yes         # 启用chroot, 将每个用户限制在自己的home目录下
BrokenClientsCompatibility yes # 兼容不同客户端, 默认:no
Daemonize yes # 后台运行
MaxClientsNumber 20 # 最大用户连接数
MaxClientsPerIP 4 # 每个ip最大连接数
VerboseLog yes # 记录日志
DisplayDotFiles no # 显示隐藏文件
AnonymousOnly no # 只允许匿名用户访问
NoAnonymous yes # 不允许匿名用户连接
SyslogFacility none # 不将日志在syslog日志中显示
DontResolve yes # 不进行客户端DNS解析
MaxIdleTime 5 # 最大空闲时间
LimitRecursion 2048 16 # 浏览限制,文件2000,目录8层
AnonymousCanCreateDirs no # 匿名用户可以创建目录
MaxLoad 4 # 如果系统负载超过下面所给的数字,那么匿名用户将无法下载
PassivePortRange 45000 60000 # 被动模式端口范围
#AnonymousRatio 1 10 # 匿名用户上传/下载比率
#UserRatio 1 10 # 所有用户上传/下载比率
AntiWarez yes # 禁止下载匿名用户上传但未经验证的文件
#AnonymousBandwidth 200 # 匿名用户带宽限制(KB)
UserBandwidth 128 # 所有用户最大带宽(KB)
Umask 133:022 # 创建文件/目录默认掩码
MinUID 100 # 验证登录用户的最小UID
AllowUserFXP no # 仅运行用户进行FXP传输
AllowAnonymousFXP no # 对匿名用户和非匿名用户允许进行匿名 FXP 传输
ProhibitDotFilesWrite no # 不能删除/写入隐藏文件
ProhibitDotFilesRead no # 禁止读取隐藏文件
AutoRename no # 有同名文件时自动重新命名
AnonymousCantUpload yes # 不允许匿名用户上传文件
AltLog clf:/var/log/pureftpd.log # clf格式日志文件位
MySQLConfigFile /etc/pure-ftpd/pureftpd-mysql.conf # 用户数据库文
MaxDiskUsage 99 # 当磁盘使用量打到99%时禁止上传
CreateHomeDir yes # 如果虚拟用户的目录不存在则自动创建#需要ftp根目录权限为755 chmod 775 /data/ftpdata/
CustomerProof yes # 防止命令误操作

安装免费开源的Pure-ftp Web管理工具vftp

  • 下载地址
  • 上传到Web目录以后安装, 执行安装, 根据提示安装相应的PHP模块, 配置/etc/pure-ftpd/pureftpd-mysql.conf 文件即可。配置好以后就可以通过web界面新建FTP虚拟用户了
#php安装php-posix, 安装以后需要重启web服务器
yum -y install php-process

PHP正则表达式之模式修正符

  • i (PCRE_CASELESS) :如果设置了这个修饰符,模式中的字母会进行大小写不敏感匹配。
/**
*result:
Array()
*/
$str = 'AbbbEEs';
$matches = array();
preg_match('/eE/', $str, $matches);
print_r($matches);

/**
*result:
Array([0] => EE)
*/
$str = 'AbbbEEs';
$matches = array();
preg_match('/ee/i', $str, $matches);
print_r($matches);
  • m (PCRE_MULTILINE):默认情况下,PCRE认为目标字符串是由单行字符组成的(然而实际上他可能由多行),“行首”元字符(^)进匹配字符串的开始位置,而“行末”元字符($)仅匹配字符串末尾,或者最后的换行符(除非设置D修饰符)。这个行为和perl相同。当这个修饰符设置以后,“行首”和“行末”就会匹配目标字符串中任意换行符之后或者之前,还分别匹配目标字符串的最开始和最末尾位置。这等同于perl的/m修饰符。如果目标字符串中没有\n字符,或者模式中没有出现^或$,设置这个修饰符不产生任何影响。
/**
*结果输出:
Array
(
[0] => Array
(
[0] => <p>paragraph1</p>
[1] => <p>paragraph2</p>
[2] => <p>paragraph3</p>
)
)
$str = '<p>paragraph1</p>
<p>paragraph2</p>
<p>paragraph3</p>
';
$matches = array();
preg_match_all('/^<p>.*?<\/p>\r$/m', $str, $matches);//在windows系统中要加\r,因为敲回车输入的是\r\n, 在linux下测试去掉\r
print_r($matches);
*/
  • D (PCRE_DOLLAR_ENDONLY):如果设置这个修饰符,模式中的元字符美元符号($)仅仅匹配字符串的末尾。如果没有设置这个修饰符,当字符串以一个换行符结尾时,美元符号($)还会匹配该换行符(但不会匹配之前的任何换行符). 如果设置了修饰符m, D修饰符不起作用。在perl中没有与此修饰符相同效果的修饰符.
/**
*********在windows系统中要加\r,因为敲回车输入的是\r\n, 在linux下测试请去掉\r**********
*/

/**
**未设置修饰符D时, $会匹配字符串末尾的换行符
*retult:
Array
(
[0] => <p>paragraph3</p>
)
*/
$str = '<p>paragraph3</p>
';
$match = array();
preg_match('/<p>.*?<\/p>\r$/', $str, $matches);
print_r($matches);

/**
**设置修饰符D时, $不会匹配字符串末尾的换行符
*retult:
Array()
*/
$str = '<p>paragraph3</p>
';
$match = array();
preg_match('/<p>.*?<\/p>\r$/D', $str, $matches);
print_r($matches);

/**
**设置修饰符D时, $不会匹配字符串末尾的换行符, 需要指明换行符
*retult:
Array([0] => <p>paragraph3</p>)
*/
$str = '<p>paragraph3</p>
';
$match = array();
preg_match('/<p>.*?<\/p>\r\n$/D', $str, $matches);
print_r($matches);
  • s (PCRE_DOTALL):默认情况下元字符(.)是匹配除换行符(\n)的所有字符,当设置了这个修饰符后,元字符(.)匹配包括换行符的所有字符.
/**
*不设置s时,不能匹配换行符
*result:
Array( [0] => sfs)
*/
$str = 'sfsdf
sfdsa';
$matches = array();
preg_match('/sf.*s/', $str, $matches);
print_r($matches);

/**
*设置s后,能匹配换行符
*result:
Array
(
[0] => sfsdf
sfds
)
*/
$str = 'sfsdf
sfdsa';
$matches = array();
preg_match('/sf.*s/s', $str, $matches);
print_r($matches);
  • U (PCRE_UNGREEDY):这个修饰符扭转了量词的贪婪模式,使量词默认为非贪婪的。通过在量词后紧跟?可以使其成为非贪婪的.
/**
*量词*默认为贪婪模式
*result:
Array
(
[0] => <p>paragraph1</p><p>paragraph2</p><p>paragraph3</p>
)
*/
$str = '<p>paragraph1</p><p>paragraph2</p><p>paragraph3</p>';
$matches = array();
preg_match('/<p>.*<\/p>/', $str, $matches);
print_r($matches);

/**
*修饰符U扭转量词的贪婪模式
*result:
Array
(
[0] => <p>paragraph1</p>
)
*/
$str = '<p>paragraph1</p><p>paragraph2</p><p>paragraph3</p>';
$matches = array();
preg_match('/<p>.*<\/p>/U', $str, $matches);
print_r($matches);

/**
*量词后紧跟?变为非贪婪模式
*result:
Array
(
[0] => <p>paragraph1</p>
)
*/
$str = '<p>paragraph1</p><p>paragraph2</p><p>paragraph3</p>';
$matches = array();
preg_match('/<p>.*?<\/p>/', $str, $matches);
print_r($matches);
  • e (PCRE_REPLACE_EVAL):如果设置了这个修饰符,preg_replace在进行了对替换字符串的 后向引用替换之后, 将替换后的字符串作为php代码评估之行(eval函数方式), 并使用之行结果 作为实际参与替换的字符串. 单引号, 双引号, 反斜线()和NULL字符在 后向引用替换时会被用反斜线转义.
/**
*result:
ggggggggggoooo
*/
$str = 'goooo';
echo preg_replace('/(g)/e', 'str_repeat(\\1, 10)', $str);
  • S: 当一个模式需要多次使用的时候, 为了得到匹配速度的提升, 值得花费一些时间 对其进行一些额外的分析. 如果设置了这个修饰符, 这个额外的分析就会执行. 当前, 这种对一个模式的分析仅仅适用于非锚定模式的匹配(即没有单独的固定开始字符).
  • A (PCRE_ANCHORED):如果设置了这个修饰符, 模式被强制为"锚定"模式, 也就是说约束匹配使其仅从 目标字符串的开始位置搜索. 这个效果同样可以使用适当的模式构造出来, 并且 这也是perl种实现这种模式的唯一途径.
/**
*以abc开头的字符串可以被匹配到
Array ( [0] => abc )
*/
$str = "abcdeft";
$matches = array();
preg_match('/abc/A', $str, $matches);
print_r($matches);

/**
*不是以abc开头的字符串不能匹配到
Array ( [0] => )
*/
$str = "dabcdeft";
$matches = array();
preg_match('/abc/A', $str, $matches);
print_r($matches);
  • x (PCRE_EXTENDED):如果设置了这个修饰符, 模式中的没有经过转义的或不在字符类中的空白数据字符总会被忽略, 并且位于一个未转义的字符类外部的#字符和下一个换行符之间的字符也被忽略. 这个修饰符 等同于perl中的/x修饰符, 使被编译模式中可以包含注释. 注意: 这仅用于数据字符. 空白字符 还是不能在模式的特殊字符序列中出现, 比如序列(?(引入了一个条件子组(译注: 这种语法定义的 特殊字符序列中如果出现空白字符会导致编译错误. 比如( ?(就会导致错误.).
/**
*因为模式中的空格被忽略, 匹配不到结果:
Array ( [0] => )
*/
$str = "ab cdeft";
$matches = array();
preg_match('/ab c/x', $str, $matches);
print_r($matches);

/**
*匹配到结果:
Array ( [0] => ab c )
*/
$str = "ab cdeft";
$matches = array();
preg_match('/ab c/', $str, $matches);
print_r($matches);
  • X (PCRE_EXTRA):这个修饰符打开了PCRE与perl不兼容的附件功能. 模式中的任意反斜线后就ingen一个 没有特殊含义的字符都会导致一个错误, 以此保留这些字符以保证向后兼容性. 默认 情况下, 在perl中, 反斜线紧跟一个没有特殊含义的字符被认为是该字符的原文. 当前没有其他特性由这个修饰符控制.

解决linux下umount显示device is busy的问题

  • 最近服务器磁盘的一个作为数据备份的分区经常出现问题, 写入的数据经常不完整。在该分区执行rm删除操作的时候提示rm:无法删除 文件 只读文件系统; 分析原因估计是磁盘算坏的缘故, 现在正在备份数据, 备份完数据以后准备修复磁盘试试。不过首先需要把磁盘分区umount了

但是在卸载挂载分区的时候出现另一个问题: 执行umount的时候提示: device is busy 错误, 无法umount卸载磁盘分区; 分析原因应该是被其它程序占用了. 经过Google以后发现了一个非常有用的命令:
fuser : 可以显示出当前哪个程序在使用磁盘上的某个文件、挂载点、甚至网络端口,并给出程序进程的详细信息.

  • 使用 fuser -m -v /dev/sda1 命令可以查看当前分区被那个用户那个进程占用了

-m 参数显示所有使用指定文件系统的进程,后面可以跟挂载点,或是dev设备,-v 参数给出详细的输出,可以看出,原来是 proftp 这个程序还在霸占着磁盘分区,fuser 还给出了程序的进程号,知道了进程号,你就可以随便怎么处置这个程序

  • 使用 fuser -m -k /dev/sda1 命令可以把占用着当前磁盘分区的程序杀死

难道没有文明点儿的办法卸载磁盘分区么, 答案是有的。如果不是很着急卸载umount磁盘分区的时候可以使用以下命令:
umount -l /dev/sda1

选项 –l 并不是马上umount,而是在该目录空闲后再umount

PHP 之 CURL学习(二)

  • curl学习(一)中所做的工作都是针对一个url的单次请求,当要处理1个URL队列时, 为了提高性能, 可以采用CURL提供的curl_multi_*族函数实现简单的并发.
$urls = array(
'http://www.cnblogs.com',
'http://www.google.com.hk',
'http://www.baidu.com',
'http://www.weibo.com',
'http://www.comsenz.com',
'http://www.csdn.net',
);
$opt_arr = array(
CURLOPT_HEADER => FALSE,
CURLOPT_TIMEOUT => 3,
CURLOPT_RETURNTRANSFER => TRUE,
);
$multi = curl_multi_init();//句柄初始化
//参数设置
foreach($urls as $key => $url) {
$ch_arr[$key] = curl_init();//初始化一个curl资源
$opt_arr[CURLOPT_URL] = $url;//设置url
curl_setopt_array($ch_arr[$key], $opt_arr);//参数设置
curl_multi_add_handle($multi, $ch_arr[$key]);//加入句柄队列
}

$isrunning = NULL;//预定义一个状态变量
do {//执行批处理句柄
$mrc = curl_multi_exec($multi, $isrunning);//$isrunning 一个用来判断操作是否仍在执行的标识的引用。
} while($mrc == CURLM_CALL_MULTI_PERFORM); //常量 CURLM_CALL_MULTI_PERFORM 代表还有一些刻不容缓的工作要做

while($isrunning && $mrc == CURLM_OK) {
if(curl_multi_select($multi) != -1) {//curl_multi_select阻塞直到cURL批处理连接中有活动连接,失败时返回-1
do {
$mrc = curl_multi_exec($multi, $isrunning);
} while($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
//所有请求接收完之后进行数据的解析等后续处理
foreach($ch_arr as $key => $ch) {
//获取内容进行后续处理
$contents = curl_multi_getcontent($ch);
//do something to deal data
curl_multi_remove_handle($multi, $ch_arr[$key]);//关闭句柄
curl_close($ch);
}
curl_multi_close($multi);

注意:CURL在PHP中的多线程处理其实并不是真正的多线程,而是用单线程批处理模拟的多线程效果。

上述代码显然有缺陷:数据处理都是在所有url请求接收完成以后才进行的,如果某些url处理比较慢显然就耽误了整个队列的处理时间,造成了CUP的闲置和浪费,这显然不是我们想要的结果。但是我们可以这样处理:每当一个url请求完成就开始处理,同时等待其它url请求返回。代码如下:

$urls = array(
'http://www.cnblogs.com',
'http://www.google.com.hk',
'http://www.baidu.com',
'http://www.weibo.com',
'http://www.comsenz.com',
'http://www.csdn.net',
'http://www.php.net',
);
$opt_arr = array(
CURLOPT_HEADER => FALSE,
CURLOPT_TIMEOUT => 5,
CURLOPT_RETURNTRANSFER => TRUE,
);
//句柄初始化
$multi = curl_multi_init();
//参数设置
foreach($urls as $key => $url) {
$ch_arr[$key] = curl_init();//初始化一个curl资源
$opt_arr[CURLOPT_URL] = $url;//设置url
curl_setopt_array($ch_arr[$key], $opt_arr);//参数设置
curl_multi_add_handle($multi, $ch_arr[$key]);//加入句柄队列
}
//预定义一个状态变量
$isrunning = NULL;
//执行批处理句柄
do {
while(($mrc = curl_multi_exec($multi, $isrunning)) == CURLM_CALL_MULTI_PERFORM);//$isrunning 一个用来判断操作是否仍在执行的标识的引用。
if($mrc != CURLM_OK)
break;
while($done = curl_multi_info_read($multi)) {//成功时返回相关信息的数组,失败时返回FALSE
$key = array_search($done['handle'], $ch_arr);
if(curl_getinfo($done['handle'], CURLINFO_HTTP_CODE) == '200') {
$content = curl_multi_getcontent($done['handle']);
//deal $content
echo ++$j,": $urls[$key] : ",strlen($content),"\n";
curl_multi_remove_handle($multi, $done['handle']);
curl_close($done['handle']);
} else {
echo ++$j,": ",$urls[$key],": ",curl_error($done['handle']),"\n";
}
}
} while($isrunning);
curl_multi_close($multi);

上述代码仍然是有缺陷的,不知道聪明的读者您发现没有?当URL队列很大时(比如1000),这就是一个大并发显然不合理的,参考这里

function rolling_curl($urls, $callback, $custom_options = null) {

$rolling_window = 5;
$rolling_window = (sizeof($urls) < $rolling_window) ? sizeof($urls) : $rolling_window;

$master = curl_multi_init();
$curl_arr = array();

//设置curl参数
$std_options = array(CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5);
$options = ($custom_options) ? ($std_options + $custom_options) : $std_options;

//初始化curl资源队列
$arr_chs = array();
for ($i = 0; $i < $rolling_window; $i++) {
$arr_chs[$urls[$i]] = curl_init();
$options[CURLOPT_URL] = $urls[$i];
curl_setopt_array($arr_chs[$urls[$i]],$options);
curl_multi_add_handle($master, $arr_chs[$urls[$i]]);
}

do {
while(($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM);
if($execrun != CURLM_OK) {
break;
}
while($done = curl_multi_info_read($master)) {
$info = curl_getinfo($done['handle']);
if ($info['http_code'] == 200) {

$content = curl_multi_getcontent($done['handle']);
$callback($content);

//新建一个curl资源并加入并发队列
if($i < sizeof($urls)) {
$arr_chs[$urls[$i]] = curl_init();
$options[CURLOPT_URL] = $urls[$i]; // increment i
curl_setopt_array($arr_chs[$urls[$i]], $options);
curl_multi_add_handle($master, $arr_chs[$urls[$i]]);
}

curl_multi_remove_handle($master, $done['handle']);
curl_close($done['handle']);
} else {

echo curl_errno($done['handle']),":",curl_error($done['handle']),"\n";
}
}
} while($running);

curl_multi_close($master);
return true;
}