存档

2018年5月 的存档

swoole_process源码读解-构造析构函数

2018年5月16日 没有评论

php源码对大部分php程序员来说都是神秘的,因为php本身是一门神奇伟大的语音。它可以让没有什么编程基础的人可以编程,而且偏向于应用。而swoole是php中一个非常不错的扩展,它让php对异步编程更容易,对高并发做了很多的支持。而我想抽时间写一个针对swoole源码解读的文章,来让我们更深入的了解理解swoole,更好的在项目中使用它。

适合人群

那么这个系列文章比较适合哪些程序员哪?对php扩展开发有一定了解,有c/c++语言功底的。如果你对php扩展开发不是很了解,可以查看一下我以前写过php扩展开发的文章,或者看一下《PHP扩展开发及内核应用》这本书。

源码版本

php版本:7.1.11

swoole版本:2.1.2

下面将进入正题,我们将一起来查看swoole_process是如何实现的。

swoole_process构造函数

构造函数源码实现在swoole_process.c:239

static PHP_METHOD(swoole_process, __construct)
{
    zend_bool redirect_stdin_and_stdout = 0;
    long pipe_type = 2;
    zval *callback;
    
    //判断当前是不是命令行模式
    if (!SWOOLE_G(cli))
    {
        swoole_php_fatal_error(E_ERROR, "swoole_process only can be used in PHP CLI mode.");
        RETURN_FALSE;
    }

    //当前是否server中的master进程中
    if (SwooleG.serv && SwooleGS->start == 1 && swIsMaster())
    {
        swoole_php_fatal_error(E_ERROR, "swoole_process can't be used in master process.");
        RETURN_FALSE;
    }

    //解析参数,具体的请查看swoole官方手册中各参数含义
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|bl", &callback, &redirect_stdin_and_stdout, &pipe_type) == FAILURE)
    {
        RETURN_FALSE;
    }

    char *func_name = NULL;
    //检查传入的回调函数是否可以执行
    if (!sw_zend_is_callable(callback, 0, &func_name TSRMLS_CC))
    {
        swoole_php_fatal_error(E_ERROR, "function '%s' is not callable", func_name);
        efree(func_name);
        RETURN_FALSE;
    }
    efree(func_name);

    //分配process所需的内存
    swWorker *process = emalloc(sizeof(swWorker));
    bzero(process, sizeof(swWorker));

    int base = 1;
    //计算当前已启动process数量
    if (SwooleG.serv && SwooleGS->start)
    {
        base = SwooleG.serv->worker_num + SwooleG.task_worker_num + SwooleG.serv->user_worker_num;
    }
    if (php_swoole_worker_round_id == 0)
    {
        php_swoole_worker_round_id = base;
    }
    //设置当前process id,注意这里不是进程pid
    process->id = php_swoole_worker_round_id++;

    //输出重定向
    if (redirect_stdin_and_stdout)
    {
        process->redirect_stdin = 1;
        process->redirect_stdout = 1;
        process->redirect_stderr = 1;
        /**
         * Forced to use stream pipe
         */
        pipe_type = 1;
    }
    
    //使用管道,创建管道
    if (pipe_type > 0)
    {
        swPipe *_pipe = emalloc(sizeof(swWorker));
        int socket_type = pipe_type == 1 ? SOCK_STREAM : SOCK_DGRAM;
        if (swPipeUnsock_create(_pipe, 1, socket_type) < 0)
        {
            RETURN_FALSE;
        }

        process->pipe_object = _pipe;
        process->pipe_master = _pipe->getFd(_pipe, SW_PIPE_MASTER);
        process->pipe_worker = _pipe->getFd(_pipe, SW_PIPE_WORKER);
        process->pipe = process->pipe_master;
        //设置process对象的pipe属性
        zend_update_property_long(swoole_process_class_entry_ptr, getThis(), ZEND_STRL("pipe"), process->pipe_master TSRMLS_CC);
    }
    
    //将当前对象加入到swoole全局的对象管理当中
    swoole_set_object(getThis(), process);
    //设置process对象的callback属性
    zend_update_property(swoole_process_class_entry_ptr, getThis(), ZEND_STRL("callback"), callback TSRMLS_CC);
}

 

这里面有调用swoole_set_object函数,getThis函数获得当前对象指针,可以理解为php中常用的$this关键字,下面我们看一下swoole_set_object是如何实现的。

swoole_set_object在swoole.c:699

void swoole_set_object(zval *object, void *ptr)
{
    SWOOLE_GET_TSRMLS;
    int handle = sw_get_object_handle(object);
    assert(handle < SWOOLE_OBJECT_MAX);
    
    //是否需要为swoole_objects重新分配内存
    if (handle >= swoole_objects.size)
    {
        uint32_t old_size = swoole_objects.size;
        //新swoole_objects大小
        uint32_t new_size = swoole_get_new_size(old_size, handle TSRMLS_CC);

        void *old_ptr = swoole_objects.array;
        void *new_ptr = NULL;
        
        //重新分配内存
        new_ptr = realloc(old_ptr, sizeof(void*) * new_size);
        if (!new_ptr)
        {
            swoole_php_fatal_error(E_ERROR, "malloc(%d) failed.", (int )(new_size * sizeof(void *)));
            return;
        }
        bzero(new_ptr + (old_size * sizeof(void*)), (new_size - old_size) * sizeof(void*));
        swoole_objects.array = new_ptr;
        swoole_objects.size = new_size;
    }
    swoole_objects.array[handle] = ptr;
}

sw_get_object_handle是1个宏函数,最终展开结果是((*(object)).value.obj)->handle。其作用是获得当前对象的handle,也就是它的索引。

typedef struct _zval_struct     zval; //zend_types.h:84
typedef struct _zend_object     zend_object; //zend_types.h:89

struct _zend_object { //zend_types.h:277
	zend_refcounted_h gc;
	uint32_t          handle; // TODO: may be removed ???
	zend_class_entry *ce;
	const zend_object_handlers *handlers;
	HashTable        *properties;
	zval              properties_table[1];
};

typedef union _zend_value { //zend_types.h:101
	zend_long         lval;				/* long value */
	double            dval;				/* double value */
	zend_refcounted  *counted;
	zend_string      *str;
	zend_array       *arr;
	zend_object      *obj;
	zend_resource    *res;
	zend_reference   *ref;
	zend_ast_ref     *ast;
	zval             *zv;
	void             *ptr;
	zend_class_entry *ce;
	zend_function    *func;
	struct {
		uint32_t w1;
		uint32_t w2;
	} ww;
} zend_value;

struct _zval_struct {   //zend_types.h:121
	zend_value        value;			/* value */
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    type,			/* active type */
				zend_uchar    type_flags,
				zend_uchar    const_flags,
				zend_uchar    reserved)	    /* call info for EX(This) */
		} v;
		uint32_t type_info;
	} u1;
	union {
		uint32_t     next;                 /* hash collision chain */
		uint32_t     cache_slot;           /* literal cache slot */
		uint32_t     lineno;               /* line number (for ast nodes) */
		uint32_t     num_args;             /* arguments number for EX(This) */
		uint32_t     fe_pos;               /* foreach position */
		uint32_t     fe_iter_idx;          /* foreach iterator index */
		uint32_t     access_flags;         /* class constant access flags */
		uint32_t     property_guard;       /* single property guard */
		uint32_t     extra;                /* not further specified */
	} u2;
};

#define sw_get_object_handle(object)        Z_OBJ_HANDLE_P(object)
#define Z_OBJ_HANDLE_P(zval_p)      Z_OBJ_HANDLE(*(zval_p))
#define Z_OBJ_HANDLE(zval)          (Z_OBJ((zval)))->handle
#define Z_OBJ(zval)					(zval).value.obj

 析构函数

析构函数实现在swoole_process.c:318

static PHP_METHOD(swoole_process, __destruct)
{
    swWorker *process = swoole_get_object(getThis());
    swPipe *_pipe = process->pipe_object;
    if (_pipe)
    {
        _pipe->close(_pipe);
        efree(_pipe);
    }
    if (process->queue)
    {
        efree(process->queue);
    }
    efree(process);
}

析构函数主要判断是否使用管道和队列,然后关闭管道和释放管道队列占用的内存,然后释放进程占用内存。

swoole process父子进程使用队列通信

2018年5月11日 没有评论

最近在研究一点swoole一方面的东西,打算用swoole process做一个工具,而且想用里面的queue作为父子进程通信的方式。看了官方的文档,发现使用useQueue的时候,这个并没有给样例,在push里面看到了一个例子,但是发现它实现的方式和文档对useQueue的介绍有些不一致。文档里面说useQueue如果将mode设置为2时(默认就是2),所有创建的子进程都会从队列中争抢消息的。但是push中给的例子却是遍历所有worker,向队列里面插入消息。感觉这方面怪怪的,在swoole群里面问了一下,几个人在聊开车,对我的问题不知道太简单还是不屑一顾,没有一个人回答。求人不如求己,于是就去看了一下useQueue、push、pop相关的源码,发现了官方文档上面说的还是对的,push里面的例子却是有些问题,感觉官方文档的例子放这么久一个不合适的例子有点不应该,不过开源的东西,靠大家自发的贡献,我们也不能要求太多了。

下面是我自己写的一个使用消息队列并设置为非阻塞模式的例子,同时已经更新到swoole process useQueue文档的示例中去了。

<?php
$worker_num = 2;
$process_pool = [];

$process= null;
$pid = posix_getpid();

function sub_process(swoole_process $worker)
{
    sleep(1); //防止父进程还未往消息队列中加入内容直接退出
    echo "worker ".$worker->pid." started".PHP_EOL;
    while($msg = $worker->pop()){
        if ($msg === false) {
            break;
        }
        $sub_pid = $worker->pid;
        echo "[$sub_pid] msg : $msg".PHP_EOL;
        sleep(1);//这里的sleep模拟必须,否则可能1个worker就把所有信息全接受了
    }
    echo "worker ".$worker->pid." exit".PHP_EOL;
    $worker->exit(0);
}

$customMsgKey = 1;//默认为空,这个地方可以随便填的
$mod = 2 | swoole_process::IPC_NOWAIT;//这里设置队列为非阻塞模式

//创建worker进程
for($i=0;$i<$worker_num; $i++) {
    $process=new swoole_process('sub_process');
    $process->useQueue($customMsgKey, $mod);
    $process->start();
    $pid = $process->pid;
    $process_pool[$pid] = $process;
}

$messages = [
    "Hello World!",
    "Hello Cat!",
    "Hello King",
    "Hello Leon",
    "Hello Rose"
];
//由于所有进程是共享使用1个消息队列,所以只需向一个字进程发送消息即可
$process = current($process_pool);
foreach ($messages as $msg) {
    $process->push($msg);
}

swoole_process::wait();
swoole_process::wait();

echo "master exit".PHP_EOL;

如有问题请大家指正,useQueue文档地址

分类: PHP 标签:

lumen数据库时区设置

2018年5月10日 没有评论

前段时间写过一篇关于日期选择使用的问题博客,其实说起来也惭愧,用mysql的timestamp做日期我已经有2年多的使用经验了,以前一直不了解timestamp里面居然保存的居然是带时区的。今天就是记录一下laravel/lumen里面怎么设置连接时的时区问题。

那么是如何发现这个问题lumen/laravel里面需要设置数据库时区的哪?

最近公司使用lumen在做一下项目,下单的时间在app里面看着是正常的,但是我通过一些mysql工具连接时,发现时间却是+8小时以后的时间.

查看ORM源码,它的注释里面说日期类型的字符创会转化成“DateTime/Carbon”实例,然后再进一步处理是输出或者存入数据库。

我们可以通过在.env中设置”DB_TIMEZONE”来解决时区不一致的问题。

DB_TIMEZONE=+8:00

一般来说要保证我们设置DB_TIMEZONE和APP_TIMEZONE一致的,所以一般配置文件都是这样的。

APP_TIMEZONE=Asia/Shanghai
DB_TIMEZONE=+8:00

 

docker exec进入容器

2018年5月9日 没有评论

docker技术原来越普及,那么我们怎么进入一个正在运行中的docker容器哪?

目前常用的有4种方式:

1、docker attach
2、SSH
3、nsenter
4、docker exec

目前比较推荐使用第4中docker exec的方式,当然这篇博客主要目的是学习记忆,我自己也是这方面的新人。

启动php-fpm容器

docker run -d --name="king_php72" php:7.2.4-fpm

进入容器

上面的命令我们在启动时已经给docker命名了,所以可以直接通过名字的方式通过exec进入。

docker exec -it king_php72 /bin/bash

如果我们不清楚docker容器的名字,我们可以通过docker ps来获取容器的ID,通过容器ID进入容器。

# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
76c6cbb1a025        php:7.2.4-fpm       "docker-php-entrypoi…"   5 minutes ago       Up 5 minutes        9000/tcp                 king_php72
# docker exec -it 76c6cbb1a025 /bin/bash

参考文章:《如何进入Docker容器》

 

mysql中的数据存储选择

2018年5月3日 没有评论

MySQl中有多种表示日期和时间的数据类型。其中YEAR表示年份,DATE表示日期,TIME表示时间,DATETIME和TIMESTAMP表示日期和实践。它们的对比如下

YEAR ,字节数为1,取值范围为“1901——2155”
DATE,字节数为4,取值范围为“1000-01-01——9999-12-31”
TIME,字节数为3,取值范围为“-838:59:59——838:59:59”
DATETIME,字节数为8,取值范围为“1000-01-01 00:00:00——9999-12-31 23:59:59”
TIMESTAMP,字节数为4,取值范围为“19700101080001——20380119111407”.

以前经常看到有人说时间存储为int类型的时候查询会比timestamp类型快很多,到底有多大的差别哪?我动手测试了一下,给大家分享一下测试结果。

一、表结构

test1表为时间存储为int类型,test2表为存储为timestamp类型。

CREATE TABLE `test1` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL DEFAULT '',
  `address` varchar(100) NOT NULL DEFAULT '',
  `email` varchar(50) NOT NULL DEFAULT '',
  `created_at` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_createdat` (`created_at`),
  KEY `idx_name_time` (`name`,`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `test2` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL DEFAULT '',
  `address` varchar(100) NOT NULL DEFAULT '',
  `email` varchar(50) NOT NULL DEFAULT '',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_createdat` (`created_at`),
  KEY `idx_name_time` (`name`,`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

 二、生成测试数据

我是自己写的python脚本,利用了python的faker库。

import faker
import time
import pymysql

def timestamp_datetime(value):
    format = '%Y-%m-%d %H:%M:%S'
    # value为传入的值为时间戳(整形),如:1332888820
    value = time.localtime(value)
    ## 经过localtime转换后变成
    ## time.struct_time(tm_year=2012, tm_mon=3, tm_mday=28, tm_hour=6, tm_min=53, tm_sec=40, tm_wday=2, tm_yday=88, tm_isdst=0)
    # 最后再经过strftime函数转换为正常日期格式。
    dt = time.strftime(format, value)
    return dt

def gen_sql(max=1000):
    sqlData = []
    i=0
    while i < max :
        name = generator.name()
        email = generator.email()
        unix_time=generator.unix_time()
        address = generator.address()
        time=timestamp_datetime(unix_time)
        sql = "INSERT INTO test1 (`name`,`address`,`email`,`created_at`) VALUE ('%s','%s','%s',%d);" % (name,address,email,unix_time)
        sql2 = "INSERT INTO test2 (`name`,`address`,`email`,`created_at`) VALUE ('%s','%s','%s','%s');" % (name,address,email,time)
        i=i+1
        sqlData.append(sql)
        sqlData.append(sql2)
    return sqlData

start_time = time.time()

generator = faker.Faker(locale="zh-cn")
total = 1000000*2
one_time=1000
times=total/one_time
print("start generating data")

conn = pymysql.connect(
    host = "127.0.0.1",
    user = "root",
    password = "123456",
    database = "sql_test",
    charset = 'utf8',
    cursorclass = pymysql.cursors.DictCursor)


cursor = conn.cursor()

i=0
while i < times:
    sql_data = gen_sql(one_time)
    for sql in sql_data:
        cursor.execute(sql)
    #commit以后更新才会提交到数据库
    conn.commit()
    i=i+1
    print("add records : %d" % (i*one_time))

cursor.close()
conn.close()
use_time=time.time()-start_time
print("done,use time:%f" % (use_time))

 三、占用空间

由于innodb的show table status from db中的值不准确,所以这里我们可以当做相等。

四、查询性能

#43ms
 select SQL_NO_CACHE * from test1 where created_at > 1025340225 limit 10; 
# 82ms
 select SQL_NO_CACHE * from test2 where created_at > '2002-06-29 16:43:45' limit 10;

单一索引查询,从结果来看差距是很大的,几乎有一倍了,但是就看这点损耗你可以接受吗?

 

分类: Mysql 标签: