
大白话聊聊分布式事务 什么是分布式事务 简单的来说就是,一个大的操作由两个或者更多的小的操作共同完成。而这些小的操作又分布在不同的网络主机上。这些操作,要么全部成功执行,要么全部不执行。 拿转账的例子来说下什么是分布式事务。张三和李四在不同的城市,存储他们账户信息的服务器也在不同的网络主机上。张三有30元钱,李四有30元钱。张三给李四转账5元就是一个事务。完成这个事务,需要两个操作。首先得从张三账户上扣5元,然后再给李四账户上加5元。事务执行完毕后,必须是两个操作都执行成功,要么都失败。 事务的特性 分布式事务本身就是事务,所以也有事务的特性。事务有四个特征ACID:A:原子性(Atomicity) 事务中的各个操作单元要么全部做,要么就全部不做。不能事务执行后,处于只做一半的状态。 C:一致性(Consistency) 事务执行后,必须由一个一致状态变为另外一个一致状态。 I:隔离性(Isolation) 事务之间不能相互干扰。 D:持久性(Durability) 一旦事务完成,对于数据的变更是永久的。 分布式事务实现方式 下面我们就以上面转账的例子来说下实现分布式事务的几种方式。 两阶段提交 两阶段提交的大概流程: 第一阶段:正常情况下的操作过程如下:在第一阶段,主要是事务管理者(经理)发起事务,让各个事务资源方(职员甲乙)确认资源是否满足,并做预处理(冻结)。资源方的操作有可能失败也可能成功。如,张三账户不足5元钱,没办法冻结,就是失败。资源方把操作结果反馈给事务管理者。 异常情况下的操作过程如下:如果预提交过程中出现任何问题,导致事务不能执行。将会通知资源方进行撤销预提交操作。 第二阶段:如果是职员甲和职员乙都告诉经理,操作成功了。正常情况下的操作过程如下:这个时候,张三账户上的金额是25元。李四账户上的金额是35元。 异常情况下的操作过程如下:这个时候,张三账户上的金额是30元。李四账户上的金额也是30元。 总而言之,事务的执行会分为预提交和提交两步进行。任何一个小操作出问题,导致事务不能完成,将会进行回滚操作。 两阶段提交需要注意的 两阶段提交,要有一个事务管理者协调各方的操作。各方对资源的占用要到整个事务结束后才能释放。这样会影响事务的效率。当并发量大的时候,系统的性能会严重下滑。 事务管理者和资源方存在通信。有可能存在通信不通畅。如,职员甲接受到扣钱的命令后,职员甲扣完了钱。由于电话故障等原因,导致没办法通知经理已经操作成功。因此,各方还要处理通信超时问题。 整个流程需要事务管理者协调各个资源方进行操作。但是,事务管理者可能出现问题。导致没办法进行协调。如,经理生病了。 注意幂等性问题。可能存在对资源方重复调用的情况。这种情况下,资源方被调用多次和调用一次的效果要一样。如上例中,经理第二次通知职员甲扣5元的时候,职员甲要像上次一样,告诉经理扣款已经完成。但是,职员甲不能再进行扣款的动作。 基于消息实现 基于消息的实现大概过程如下:基于消息的分布式事务实现中,引入了消息中间件(助理),负责消息的传递和事务执行状态的询问。这样就降低了系统间的耦合度。 为什么职员甲在进行扣款前,要告诉助理?主要是怕自己扣款成功了,又忘记告诉助理。这个时候,助理也就没办法通知职员乙操作了。告诉助理后,如果职员甲忘记了,助理可以询问职员甲,是否执行成功。进而决定是否该通知职员乙进行加钱操作。 如果职员甲扣款失败,则应该通知助理。助理在这种情况下,就不英再通知职员乙加钱。 基于消息实现的问题 基于消息的分布式事务实现,讲究的是最终一致性。也即所有的本地事务执行完毕后,整个状态的一致。 整个事务一般不会有回滚操作。如,当职员乙操作失败的时候,职员应该是再次通知职员乙重试。
聊聊Swoole2.0协程 Swoole 2.0正式版发布了。2.0版本最大的更新是增加了对协程(Coroutine)的支持。正式版已同时支持PHP5和PHP7。基于Swoole2.0协程PHP开发者可以已同步的方式编写代码,底层自动进行协程调度,转变为异步IO。解决了传统异步编程嵌套回调的问题。 目前Swoole底层内置的协程客户端组件包括:udpclient、tcpclient、httpclient、redisclient、mysqlclient,基本涵盖了开发者常用的几种通信协议。协程组件只能在服务器的onConnect、onRequest、onReceive、onMessage 回调函数中使用。 注意,Swoole 2.0.5以前的版本还是灰度测试版本,可能会存在问题。 beta是因为协程是全新的版本。 协程的使用示例 /** 只有在Server中才能使用协程。包括 http server,websocket server 和 server。 */ $server = new Swoole\Http\Server('127.0.0.1', 9501); /** 触发on request事件时,SWOOLE会开辟一个协程栈,对协程栈进行初始化 */ $server->on('Request', function ($request, $response) { $tcp_cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); /** client在调用connect函数后,SWOOLE会将PHP上下文信息保存到当前栈内 然后将协程挂起,待确认连接成功后,触发epoll事件,然后协程切换 恢复PHP上下文信息,返回结果,继续执行PHP代码 */ if ($tcp_cli->connect('127.0.0.1', 9906) === false) { $response->end("connect server failed."); return; } $tcp_cli->send('test for the coro'); /** client在调用recv函数后,SWOOLE会将PHP上下文信息保存到当前栈内 然后将协程挂起待后端svr回包,触发epoll事件,然后协程切换 恢复PHP上下文信息,返回结果,继续执行PHP代码 如果后端在设定的超时时间内,未能回包,返回false client的errCode定为110 */ $ret = $tcp_cli->recv(100); $tcp_cli->close(); if ($ret) { $response->end(" swoole response is ok"); } else { $response->end(" recv failed error : {$tcp_cli->errCode}"); } }); $server->start(); 协程的执行流程 主进程->回调函数: 嘿,有数据到了,麻烦你处理下 Note right of 回调函数: 遇到异步IO 回调函数-->主进程: 已保存状态,你执行其他任务吧 Note left of 主进程: 去执行其他任务 主进程->回调函数: 发送的数据,有消息返回啦,你处理下 咱们就以上面的示例代码为例,说一说协程的执行流程。Http Server监听9051端口。当有相关事件发生时,如有数据到达,就会执行绑定到Request上的回调函数。在执行回调函数之前,会创建一个协程。这时,会保存CPU寄存器的状态和ZendVM Stack信息。在回调函数执行过程中,如果遇到IO操作,如$tcp_cli->connect(,就会保存当前的状态,并让出CPU使用权。当前请求执行被挂起。让CPU出使用权后,CPU就可以用于处理其他事件。如处理其他客户端的Request请求。当被挂起的请求,又有新的事件发生,如上面$tcp_cli->connect()的数据已经返回。这时,会使用挂起前保存的状态信息恢复,然后继续执行回调函数。如果在执行过程中,再次遇到IO操作,会继续执行保存状态和让出CPU使用权。 协程的意义 这些IO操作都是非阻塞的,即发送请求和获取数据分为两步。当请求发送完毕后,就会进行状态保存和让出CPU使用权。在等待请求数据返的这段时间,CPU可以执行一些其他程序。这样就可以充分利用CPU。 协程的实现 Swoole的协程是基于 setjmp 、 longjmp 实现的。Swoole为每个协程都分配了空间,用于保存协程切换时的状态信息。进行协程切换时会自动保存Zend VM的内存状态(主要是EG全局内存和vm stack)。当回调函数执行完毕后,会自动销毁分配的空间。 创建协程 什么时候会创建协程?在Server的onConnect、onRequest、onReceive、onMessage 回调函数被执行前会创建一个协程。协程创建的方法是coro_create。相关源码可以查看swoole_coroutine.c文件。coro_create方法中主要进行了如下操作: int sw_coro_create(zend_fcall_info_cache *fci_cache, zval **argv, int argc, zval **retval, void *post_callback, void* params) { // 为回调函数的执行做一些准备工作 ....... COROG.require = 1; // 使用setjmp开启一个协程 if (!setjmp(*swReactorCheckPoint)) { // setjmp第一次调用会进入此代码分支,执行回调函数 zend_execute_ex(execute_data TSRMLS_CC); ...... // 执行完毕后,关闭协程 coro_close(TSRMLS_C); ...... coro_status = CORO_END; } else { /** 如果执行longjump,会调到上面的setjmp(*swReactorCheckPoint)行。 但是,setjmp的返回值为非0。因此,longjump后,会进入此代码分支。 让出CPU执行权。 */ coro_status = CORO_YIELD; } COROG.require = 0; return coro_status; } 协程让出CPU执行权yield 什么时候会让出CPU执行权?当回调函数中遇到异步IO的时候,会让出CPU执行权。如,代码中的connect操作。下面,我们就以connect操作为例,看看让出CPU执行权时都做了那些操作。connect的相关代码在swoole_coroutine.c文件中。代码如下: static PHP_METHOD(swoole_client_coro, connect) { long port = 0, sock_flag = 0; ...... //nonblock async // 发送连接数据,无需等待对方返回数据,就执行下面代码 if (cli->connect(cli, host, port, timeout, sock_flag) < 0) { ...... } ...... // 获取一个内存空间,用于保存当前执行的上下文信息。 php_context *sw_current_context = swoole_get_property(getThis(), 0); ...... // 保存协程信息 coro_save(sw_current_context); // 让出CPU使用权 coro_yield(); } 保存协程信息 所谓的协程信息主要就是当前的上下文执行信息。coro_save方法在swoole_coroutine.c文件中。代码如下: sw_inline php_context *sw_coro_save(zval *return_value, php_context *sw_current_context) { // 下面的代码主要是把当前的执行状态保存到之前获取的内存空间中 zend_execute_data *current = EG(current_execute_data); if (ZEND_CALL_INFO(current) & ZEND_CALL_RELEASE_THIS) { zval_ptr_dtor(&(current->This)); } zend_vm_stack_free_args(EG(current_execute_data)); zend_vm_stack_free_call_frame(EG(current_execute_data)); strncpy(SWCC(uid), COROG.uid, 20); SWCC(current_coro_return_value_ptr) = return_value; SWCC(current_execute_data) = EG(current_execute_data)->prev_execute_data; SWCC(current_vm_stack) = EG(vm_stack); SWCC(current_vm_stack_top) = EG(vm_stack_top); SWCC(current_vm_stack_end) = EG(vm_stack_end); SWCC(current_task) = COROG.current_coro; SWCC(allocated_return_value_ptr) = COROG.allocated_return_value_ptr; return sw_current_context; } 让出CPU执行权 coro_yield方法的作用是让出CPU执行权。代码在swoole_coroutine.c文件中。 sw_inline void coro_yield() { SWOOLE_GET_TSRMLS; // 还原栈信息 #if PHP_MAJOR_VERSION >= 7 EG(vm_stack) = COROG.origin_vm_stack; EG(vm_stack_top) = COROG.origin_vm_stack_top; EG(vm_stack_end) = COROG.origin_vm_stack_end; #else EG(argument_stack) = COROG.origin_vm_stack; EG(current_execute_data) = COROG.origin_ex; #endif // 跳转到coro_create方法中setjmp代码行。 longjmp(*swReactorCheckPoint, 1); } 在这个方法中主要进行了还原栈信息和longjump操作。COROG.origin_vm_stack 这些栈信息的初始化在coro_init方法中。记录了协程执行前的状态。 恢复协程 当异步IO有数据返回后,会进行协程恢复。协程恢复的方法是coro_resume。在swoole_coroutine.c文件中。代码如下: int sw_coro_resume(php_context *sw_current_context, zval *retval, zval *coro_retval) { // 使用之前保存的协程信息恢复执行上下文环境。 EG(vm_stack) = SWCC(current_vm_stack); .... int coro_status; // 设置跳转点,方便在执行过程中再遇到异步IO操作,进行跳转。 if (!setjmp(*swReactorCheckPoint)) { //coro exit // 继续执行回调函数 zend_execute_ex(sw_current_context->current_execute_data TSRMLS_CC); coro_close(TSRMLS_C); coro_status = CORO_END; } else { //coro yield coro_status = CORO_YIELD; } if (unlikely(coro_status == CORO_END && EG(exception))) { sw_zval_ptr_dtor(&retval); zend_exception_error(EG(exception), E_ERROR TSRMLS_CC); } return coro_status; } 可见,创建协程和恢复协程的整体代码结构差不多。 结束协程 当回到函数执行完毕后,会结束协程。coro_close方法用于结束协程。源码在swoole_coroutine.c文件中。 sw_inline void coro_close(TSRMLS_D) { // 释放为协程而申请的相关资源 efree(EG(vm_stack)); efree(COROG.allocated_return_value_ptr); // 恢复执行栈 EG(vm_stack) = COROG.origin_vm_stack; EG(vm_stack_top) = COROG.origin_vm_stack_top; EG(vm_stack_end) = COROG.origin_vm_stack_end; --COROG.coro_num; swTrace("closing coro and %d remained. usage size: %zu. malloc size: %zu", COROG.coro_num, zend_memory_usage(0), zend_memory_usage(1)); }
本文是以PHP7作为基础,讲解如何从零开始创建一个PHP扩展。本文主要讲解创建一个扩展的基本步骤都有哪些。示例中,我们将实现如下功能: <?php echo say(); ?> 输出内容: $ php ./test.php $ hello word 在扩展中实现一个say方法,调用say方法后,输出 hello word。 扩展开发步骤 第一步:生成代码 PHP为我们提供了生成基本代码的工具 ext_skel。这个工具在PHP源代码的./ext目录下。 $ cd php_src/ext/ $ ./ext_skel --extname=say extname参数的值就是扩展名称。执行ext_skel命令后,这样在当前目录下会生成一个与扩展名一样的目录。 第二步,修改config.m4配置文件 config.m4的作用就是配合phpize工具生成configure文件。configure文件是用于环境检测的。检测扩展编译运行所需的环境是否满足。现在我们开始修改config.m4文件。 $ cd ./say $ vim ./config.m4 打开,config.m4文件后,你会发现这样一段文字。 dnl If your extension references something external, use with: dnl PHP_ARG_WITH(say, for say support, dnl Make sure that the comment is aligned: dnl [ --with-say Include say support]) dnl Otherwise use enable: dnl PHP_ARG_ENABLE(say, whether to enable say support, dnl Make sure that the comment is aligned: dnl [ --enable-say Enable say support]) 其中,dnl 是注释符号。上面的代码说,如果你所编写的扩展如果依赖其它的扩展或者lib库,需要去掉PHP_ARG_WITH相关代码的注释。否则,去掉 PHP_ARG_ENABLE 相关代码段的注释。我们编写的扩展不需要依赖其他的扩展和lib库。因此,我们去掉PHP_ARG_ENABLE前面的注释。去掉注释后的代码如下: dnl If your extension references something external, use with: dnl PHP_ARG_WITH(say, for say support, dnl Make sure that the comment is aligned: dnl [ --with-say Include say support]) dnl Otherwise use enable: PHP_ARG_ENABLE(say, whether to enable say support, Make sure that the comment is aligned: [ --enable-say Enable say support]) 第三步,代码实现 修改say.c文件。实现say方法。找到PHP_FUNCTION(confirm_say_compiled),在其上面增加如下代码: PHP_FUNCTION(say) { zend_string *strg; strg = strpprintf(0, "hello word"); RETURN_STR(strg); } 找到 PHP_FE(confirm_say_compiled, 在上面增加如下代码: PHP_FE(say, NULL) 修改后的代码如下: const zend_function_entry say_functions[] = { PHP_FE(say, NULL) /* For testing, remove later. */ PHP_FE(confirm_say_compiled, NULL) /* For testing, remove later. */ PHP_FE_END /* Must be the last line in say_functions[] */ }; /* }}} */ 第四步,编译安装 编译扩展的步骤如下: $ phpize $ ./configure $ make && make install 修改php.ini文件,增加如下代码: [say] extension = say.so 然后执行,php -m 命令。在输出的内容中,你会看到say字样。 第五步,调用测试 自己写一个脚本,调用say方法。看输出的内容是否符合预期。
前言 这次,我们将演示如何在PHP扩展中接受传入的参数和输出返回值。 <?php function default_value ($type, $value = null) { if ($type == "int") { return $value ?? 0; } else if ($type == "bool") { return $value ?? false; } else if ($type == "str") { return is_null($value) ? "" : $value; } return null; } var_dump(default_value("int")); var_dump(default_value("int", 1)); var_dump(default_value("bool")); var_dump(default_value("bool", true)); var_dump(default_value("str")); var_dump(default_value("str", "a")); var_dump(default_value("array")); ?> 我们将在扩展中实现default_value方法。 代码 基础代码 这个扩展,我们将在say扩展上增加 default_value 方法。say扩展相关代码大家请看这篇博文。PHP7扩展开发之hello word 文中已经详细介绍了如何创建一个扩展和提供了源码下载。 实现default_value方法 default_value方法的PHP扩展源码: PHP_FUNCTION(default_value) { zend_string *type; zval *value = NULL; #ifndef FAST_ZPP /* Get function parameters and do error-checking. */ if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|z", &type, &value) == FAILURE) { return; } #else ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(type) Z_PARAM_OPTIONAL Z_PARAM_ZVAL_EX(value, 0, 1) ZEND_PARSE_PARAMETERS_END(); #endif if (ZSTR_LEN(type) == 3 && strncmp(ZSTR_VAL(type), "int", 3) == 0 && value == NULL) { RETURN_LONG(0); } else if (ZSTR_LEN(type) == 3 && strncmp(ZSTR_VAL(type), "int", 3) == 0 && value != NULL) { RETURN_ZVAL(value, 0, 1); } else if (ZSTR_LEN(type) == 4 && strncmp(ZSTR_VAL(type), "bool", 4) == 0 && value == NULL) { RETURN_FALSE; } else if (ZSTR_LEN(type) == 4 && strncmp(ZSTR_VAL(type), "bool", 4) == 0 && value != NULL) { RETURN_ZVAL(value, 0, 1); } else if (ZSTR_LEN(type) == 3 && strncmp(ZSTR_VAL(type), "str", 3) == 0 && value == NULL) { RETURN_EMPTY_STRING(); } else if (ZSTR_LEN(type) == 3 && strncmp(ZSTR_VAL(type), "str", 3) == 0 && value != NULL) { RETURN_ZVAL(value, 0, 1); } RETURN_NULL(); } 代码说明 获取参数 在PHP7中提供了两种获取参数的方法。zend_parse_parameters和FAST ZPP方式。 zend_parse_parameters 在PHP7之前一直使用zend_parse_parameters函数获取参数。这个函数的作用,就是把传入的参数转换为PHP内核中相应的类型,方便在PHP扩展中使用。参数说明:第一个参数,参数个数。一般就使用ZEND_NUM_ARGS(),不需要改变。第二个参数,格式化字符串。这个格式化字符串的作用就是,指定传入参数与PHP内核类型的转换关系。 代码中 S|z 的含义就是: S 表示参数是一个字符串。要把传入的参数转换为zend_string类型。 | 表示之后的参数是可选。可以传,也可以不传。 z 表示参数是多种类型。要把传入的参数转换为zval类型。 除此之外,还有一些specifier,需要注意: !如果接收了一个PHP语言里的null变量,则直接把其转成C语言里的NULL,而不是封装成IS_NULL类型的zval。 / 如果传递过来的变量与别的变量共用一个zval,而且不是引用,则进行强制分离,新的zval的is_ref__gc==0, and refcount__gc==1. 更多格式化字符串的含义可以查看官方网站。https://wiki.php.net/rfc/fast_zpp FAST ZPP 在PHP7中新提供的方式。是为了提高参数解析的性能。对应经常使用的方法,建议使用FAST ZPP方式。使用方式:以ZEND_PARSE_PARAMETERS_START(1, 2)开头。第一个参数表示必传的参数个数,第二个参数表示最多传入的参数个数。以ZEND_PARSE_PARAMETERS_END();结束。中间是传入参数的解析。值得注意的是,一般FAST ZPP的宏方法与zend_parse_parameters的specifier是一一对应的。如:Z_PARAM_OPTIONAL 对应 |Z_PARAM_STR 对应 S但是,Z_PARAM_ZVAL_EX方法比较特殊。它对应两个specifier,分别是 ! 和 / 。! 对应宏方法的第二个参数。/ 对应宏方法的第三个参数。如果想开启,只要设置为1即可。 FAST ZPP相应的宏方法可以查看官方网站 https://wiki.php.net/rfc/fast_zpp#proposal 返回值 方法的返回值是使用RETURN_开头的宏方法进行返回的。常用的宏方法有:RETURN_NULL() 返回nullRETURN_LONG(l) 返回整型RETURN_DOUBLE(d) 返回浮点型RETURN_STR(s) 返回一个字符串。参数是一个zend_string * 指针RETURN_STRING(s) 返回一个字符串。参数是一个char * 指针RETURN_STRINGL(s, l) 返回一个字符串。第二个参数是字符串长度。RETURN_EMPTY_STRING() 返回一个空字符串。RETURN_ARR(r) 返回一个数组。参数是zend_array *指针。RETURN_OBJ(r) 返回一个对象。参数是zend_object *指针。RETURN_ZVAL(zv, copy, dtor) 返回任意类型。参数是 zval *指针。RETURN_FALSE 返回falseRETURN_TRUE 返回true 更多宏方法请查看 Zend/zend_API.h中的相关代码。 更多函数说明请查看
前言 这次,我们将演示如何在PHP扩展中如何对类型进行一些操作。如,判断变量类型。要实现的PHP代码如下: <?php function get_size ($value) { if (is_string($value)) { return "string size is ". strlen($value); } else if (is_array($value)) { return "array size is ". sizeof($value); } else { return "can not support"; } } var_dump(get_size("abc")); var_dump(get_size(array(1,2))); ?> 分别获取string 和 array的长度。 代码 基础代码 这个扩展,我们将在say扩展上增加 get_size 方法。say扩展相关代码大家请看这篇博文。PHP7扩展开发之hello word 文中已经详细介绍了如何创建一个扩展和提供了源码下载。 实现get_size方法 get_size方法的PHP扩展源码: PHP_FUNCTION(get_size) { zval *val; size_t size; zend_string *result; HashTable *myht; if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &val) == FAILURE) { return; } if (Z_TYPE_P(val) == IS_STRING) { result = strpprintf(0, "string size is %d", Z_STRLEN_P(val)); } else if (Z_TYPE_P(val) == IS_ARRAY) { myht = Z_ARRVAL_P(val); result = strpprintf(0, "array size is %d", zend_array_count(myht)); } else { result = strpprintf(0, "can not support"); } RETURN_STR(result); } 代码说明 zval变量相关的宏方法大部分定义在Zend/zend_types.h文件中。 类型相关宏方法 Z_TYPE_P(zval *) 获取zval变量的类型。常见的类型都有: #define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 Z_STRLEN_P(zval *) 获取字符串的长度。 数组 在 Zend/zend_hash.c文件中包含一些array处理的方法。zend_array_count(HashTable *) 获取数组的元素个数。zend_array 和 HashTable其实是相同的数据结构。在Zend/zend_types.h文件中有定义。 typedef struct _zend_array HashTable; 字符串拼接 strpprintf是PHP为我们提供的字符串拼接的方法。第一个参数是最大字符数。 PHP7变量相关资料 在PHP7中对于zval变量的结构有了不小的改动。大家可以查看下面三篇文章。介绍的比较详细。https://github.com/laruence/php7-internal/blob/master/zval.mdhttp://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-1.htmlhttp://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-2.html 更多函数说明请查看
对socket_read的返回值做下判断。如果返回-1,则对errno多下判断。socket_last_error() 方法获取errno。
给你一段用C语言写的示例。
执行命令 sestatus
如果输出内容不是 disabled
执行命令 setenforce disabled
一般编程语言都会有作用域的概念。如果,变量已经不再作用域了,变量所占用的资源还不释放,那就太浪费内存空间了。除非必要,否则会也不会干费力不讨好的事情。
贴一段代码。
输出结果
0
不错啊
其实主要是为了容灾,把数据把多地保存多份。如果一个地方数据出现问题,尽量能做到无缝切换。
你看下你的代码。连接数据库的时候主机名是不是写的 localhost? 如果是 你改成 127.0.0.1 试试。
有过简单的了解,他和nodejsyou很多相同的特点。如
1、事件驱动,异步执行,非阻塞IO
2、单进程单线程
更多信息可以查看 ReactPHP,PHP版的Node.js
开始,把php_tclip.h中的全局变量声明部分修改为如下:
ZEND_BEGIN_MODULE_GLOBALS(tclip)
CascadeClassifier face_cascade;
char *face_config_path;
ZEND_END_MODULE_GLOBALS(tclip)
结果报如下错误:
error: ‘CascadeClassifier’ does not name a type
看来这个类没有在头文件中声明。那就另辟蹊径,改为其它方式。把代码改为
ZEND_BEGIN_MODULE_GLOBALS(tclip)
void *face_cascade;
char *face_config_path;
ZEND_END_MODULE_GLOBALS(tclip)
然后在tclip.c中进行了如下修改:
1.首先定义了一个全局变量。
static CascadeClassifier face_cascade;
2.修改PHP_MINIT_FUNCTION(tclip)。代码如下:
PHP_MINIT_FUNCTION(tclip)
{
/* If you have INI entries, uncomment these lines */
REGISTER_INI_ENTRIES();
string face_config_path = (TCLIP_G(face_config_path) == "" || TCLIP_G(face_config_path) == NULL)? "/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_alt.xml" :TCLIP_G(face_config_path);
if( !face_cascade.load( face_config_path ) ){
php_error_docref(NULL TSRMLS_CC, E_WARNING, "can not load classifier file!%s", face_config_path.c_str());
return FAILURE;
}
TCLIP_G(face_cascade) = &face_cascade;
return SUCCESS;
}
调用全局对象的方式如下:
((CascadeClassifier *)TCLIP_G(face_cascade))->detectMultiScale( img_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) );
注意,php扩展想线程安全,那就用相关宏来访问全局变量。如上面的TCLIP_G(face_cascade)。
参考资料 php扩展中如何定义线程安全的全局对象
闲话少说,直接上代码:
下面是我在config.m4中写的自动加载opencv相关so库和头文件的代码。
dnl # --with-tclip -> check with-path
SEARCH_PATH="/usr/lib/pkgconfig" # 定义pkgconfig文件,即扩展名pc文件存放路径
SEARCH_FOR="opencv.pc" # 要寻找的文件
if test -r $PHP_TCLIP/$SEARCH_FOR; then #
TCLIP_DIR=$PHP_TCLIP
else # search default path list
AC_MSG_CHECKING([for tclip files in default path])
for i in $SEARCH_PATH ; do
if test -r $i/$SEARCH_FOR; then
TCLIP_DIR=$i
AC_MSG_RESULT(found in $i)
fi
done
fi
dnl
if test -z "$TCLIP_DIR"; then
AC_MSG_RESULT([not found])
AC_MSG_ERROR([Please reinstall the tclip distribution])
fi
OPENCV_FLAGS="pkg-config opencv --libs --cflags opencv
"
for i in $OPENCV_FLAGS;do
if test ${i:0:2} = "-I" ;then
PHP_ADD_INCLUDE(${i:2})
elif test ${i:${#i}-3} = ".so" ;then
dir_name=`dirname $i`
file_name=${i/$dir_name/}
file_name=${file_name/\/lib/}
file_name=${file_name/.so/}
PHP_ADD_LIBRARY_WITH_PATH($file_name,$dir_name,TCLIP_SHARED_LIBADD)
fi
done
示例代码:
/*
function curl($urls = array(), $callback = '')
{
$response = array();
if (empty($urls)) {
return $response;
}
$chs = curl_multi_init();
$map = array();
foreach($urls as $url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_NOSIGNAL, true);
curl_multi_add_handle($chs, $ch);
$map[strval($ch)] = $url;
}
do{
if (($status = curl_multi_exec($chs, $active)) != CURLM_CALL_MULTI_PERFORM) {
if ($status != CURLM_OK) { break; } //如果没有准备就绪,就再次调用curl_multi_exec
while ($done = curl_multi_info_read($chs)) {
$info = curl_getinfo($done["handle"]);
$error = curl_error($done["handle"]);
$result = curl_multi_getcontent($done["handle"]);
$url = $map[strval($done["handle"])];
$rtn = compact('info', 'error', 'result', 'url');
if (trim($callback)) {
$callback($rtn);
}
$response[$url] = $rtn;
curl_multi_remove_handle($chs, $done['handle']);
curl_close($done['handle']);
//如果仍然有未处理完毕的句柄,那么就select
if ($active > 0) {
curl_multi_select($chs, 0.5); //此处会导致阻塞大概0.5秒。
}
}
}
}
while($active > 0); //还有句柄处理还在进行中
curl_multi_close($chs);
return $response;
}
//使用方法
function deal($data){
if ($data["error"] == '') {
echo $data["url"]." -- ".$data["info"]["http_code"]."\n";
} else {
echo $data["url"]." -- ".$data["error"]."\n";
}
}
$urls = array();
for ($i = 0; $i < 10; $i++) {
$urls[] = 'http://www.baidu.com/s?wd=etao_'.$i;
$urls[] = 'http://www.so.com/s?q=etao_'.$i;
$urls[] = 'http://www.soso.com/q?w=etao_'.$i;
}
curl($urls, "deal");
参考资料 php实现并发处理之curl篇
有很多中方式,这里说一种cas的方式。
其实这里并不是严格的CAS,而是使用了比较交换原子操作的思想。
生成思路如下:
每次生成全局id时,先从sequence表中获取当前的全局最大id。然后在获取的全局id上做加1操作。把加1后的值更新到数据库。更新时是关键。
如加1后的值为203,表名是users,数据表结构如下:
CREATE TABLE SEQUENCE
(
`name` varchar(30) NOT NULL COMMENT '分表的表名',
`gid` bigint(20) NOT NULL COMMENT '最大全局id',
PRIMARY KEY (`name`)
) ENGINE=InnoDB
那么更新语句是。
update sequence set gid = 203 where name = 'users' and gid < 203;
sql语句的 and gid < 203 是为了保证并发环境下gid的值只增不减。
如果update语句的影响记录条数为0说明,已经有其他进程提前生成了203这个值,并写入了数据库。需要重复以上步骤从新生成。
代码实现如下:
//$name 表名
function next_id_db($name){
//获取数据库全局sequence对象
$seq_dao = Wk_Sequence_Dao_Sequence::getInstance();
$threshold = 100; //最大尝试次数
for($i = 0; $i < $threshold; $i++){
$last_id = $seq_dao->get_seq_id($name);//从数据库获取全局id
$id = $last_id +1;
$ret = $seq_dao->set_seq_id($name, $id);
if($ret){
return $id;
break;
}
}
return false;
}
更多方式,可以查看 数据库分表后,并发环境下,生成全局id生成的几种方式
nginx的ngx_http_limit_req_module模块限制了百度蜘蛛的抓取频率。每分钟允许百度蜘蛛抓取200次,多余的抓取请求返回503。
nginx的配置:
limit_req_zone $anti_spider zone=anti_spider:60m rate=200r/m;
limit_req zone=anti_spider burst=5 nodelay;
if ($http_user_agent ~* "baiduspider") {
set $anti_spider $http_user_agent;
}
参数说明:
指令limit_req_zone 中的rate=200r/m 表示每分钟只能处理200个请求。
指令limit_req 中的burst=5 表示最大并发为5。即同一时间只能同时处理5个请求。
指令limit_req 中的 nodelay 表示当已经达到burst值时,再来新请求时,直接返回503
IF部分用于判断是否是百度蜘蛛的user agent。如果是,就对变量$anti_spider赋值。这样就做到了只对百度蜘蛛进行限制了。