存档

作者存档

C语言中p[j]和j[p]

2018年8月30日 没有评论

首页我们来看一段有关C语言指针相关的代码,代码不是很规范,但是可以表明问题。

#include <stdio.h>

int main() {
    char *p;
    int i=1;
    p = &i;
    *(p+1) = 2;
    p[2] = 3;
    for (int j = 0; j < 3; ++j) {
        printf("p[%d]: %d \n", j, p[j]);
        printf("%d[p]: %d \n", j, j[p]);
    }
    return 0;
}

那么这里”p[j]”和”j[p]”数据结果会一样吗?

是的,他们的结果完全一样,下面是上面代码的运行结果。

p[0]: 1 
0[p]: 1 
p[1]: 2 
1[p]: 2 
p[2]: 3 
2[p]: 3 

大多数的C语言书籍里面,都会把p[j]说成数组p中的第j个元素,虽然这种说法也没有错,其实p[j]等价于*(p+j)的。这也是我上面示例中赋值时使用2中方法的原因。“加法运算可以交换顺序,所以将*(p + i)写成*(i + p) 也是可以的。同理,将p[i]写成i[p]也是可以的(可能你会不相信,但这样写既不会出错,也能正常运行)。摘录来自: 川合秀实. “30天自制操作系统”。

分类: C/C++ 标签:

elasticsearch自定义ik词库

2018年8月7日 没有评论

最近一直在研究使用elasticsearch,es默认的分析器对中文只能提供一个一个汉字的分词,这对搜索有些要求的应用或项目来说有些差强人意的。所以很多人选择国人开发的分词插件ik来选择中文默认的分词器,插件在github上面的地址是medcl/elasticsearch-analysis-ik

安装ik

官方提供了2中方式来安装ik,下载稳定的发布包,或者通过”elasticsearch-plugin”来安装。elasticsearch-plugin安装是很方便,不过可能会有一些问题要解决“java.security.AccessControlException: access denied”问题,在最新版的6.3版本中,加入了”plugin-security.policy”来解决这个文。如果有AccessControlException问题的,查看《elasticsearch ik socket access denied》来解决你的问题。

plugin安装命令

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip

 发布包方式

cd {your-es-root}/plugins/ && mkdir ik
cp -r elasticsearch-analysis-ik-* {your-es-root}/plugins/ik

这里的”{your-es-root}”是你es的根目录,”elasticsearch-analysis-ik-*”表示你解压以后的ik插件目录。

可以通过访问”http://localhost:9200/_cat/plugins?pretty”来查看你的ik是否安装成功。

自定义分词

ik的配置文件IKAnalyzer.cfg.xml可以在 ” {conf}/analysis-ik/config/IKAnalyzer.cfg.xml" 或者 “{plugins}/elasticsearch-analysis-ik-*/config/IKAnalyzer.cfg.xml"。如果你没有找到试试用find命令来查找。

<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<!-- <entry key="ext_dict">custom/custom.dic</entry> -->
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<entry key="remote_ext_dict">http://xxxxx/ik/custom.dic</entry>
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">http://xxxxxx/ik/stopwords.dic</entry> -->
</properties>

这是IKAnalyzer.cfg.xml的文件格式,我们要自定义分词,把”ext_dict”设置成我们分词文件所在的目录就可以。分词文件的格式是一行一个分词,也就是用”\n”分隔,和下面要介绍的热加载分词格式一样。然后重启es,如果没有错误发生,那么自定义分词就生效了。但是这种方式如果我们想要修改添加分词每次必须要重启es。

热加载自定义分词

通过”remote_ext_dict”我们可以配置热加载的分词词库,这是一个url地址,它大概会每隔60秒来检查一下地址,如果有变化就加载新的词库。

1、该 http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。

2、该 http 请求返回的内容格式是一行一个分词,换行符用 \n 即可。

把词库生成到nginx服务器指定路径下面即可。注意修改完配置文件以后要生效也需要重启。

elasticsearch ik socket access denied

2018年8月7日 没有评论

最近在使用elasticsearch,然后中分词选择使用ik,ik支持热加载分词,但是配置的时候总是失败。没有撸过java代码,所有对java在socket连接时居然还需要权限验证还是蛮吃惊的。我当初是用elasticsearch-plugin安装的,如果使用ik github上面提供的稳定版的发布包来手动安装就不会有这个问题,里面的”plugin-security.policy”已经包含了解决方案。

在启动es时,日志文件里面有如下错误信息。

[2018-08-07T14:52:26,004][WARN ][o.e.g.Gateway            ] [75Cwi4-] recovering index [index/Tmk_kfl-SqqeSSPx7vjXQw] failed - recovering as closed
java.security.AccessControlException: access denied ("java.net.SocketPermission" "120.77.217.214:80" "connect,resolve")
	at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472) ~[?:1.8.0_65]
	at java.security.AccessController.checkPermission(AccessController.java:884) ~[?:1.8.0_65]
	at java.lang.SecurityManager.checkPermission(SecurityManager.java:549) ~[?:1.8.0_65]
	at java.lang.SecurityManager.checkConnect(SecurityManager.java:1051) ~[?:1.8.0_65]
	at java.net.Socket.connect(Socket.java:584) ~[?:1.8.0_65]
	at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:74) ~[?:?]
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:141) ~[?:?]
	at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353) ~[?:?]
	at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380) ~[?:?]
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) ~[?:?]
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184) ~[?:?]
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88) ~[?:?]
	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) ~[?:?]
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184) ~[?:?]
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82) ~[?:?]
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107) ~[?:?]
	at org.wltea.analyzer.dic.Dictionary.getRemoteWords(Dictionary.java:470) ~[?:?]
	at org.wltea.analyzer.dic.Dictionary.loadRemoteExtDict(Dictionary.java:439) ~[?:?]
	at org.wltea.analyzer.dic.Dictionary.loadMainDict(Dictionary.java:380) ~[?:?]
	

这个错误是由java安全管理器SecurityManager引起的,相关的信息可以查看一下这篇《java安全管理器SecurityManager入门》。由于我对这方面的知识了解不多,就不再展开了。解决这个问题的思路是我们在SecurityManager配置文件里面允许这些socket连接。找到”java.policy”文件,我在mac上面的位置在”/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/lib/security/java.policy”,当然如果你是windows或者linux系统,可以搜索java.policy文件,注意不要修改成其他软件自带的jre中的配置文件。

grant {
    ...
    //默认配置省略
    permission java.net.SocketPermission "*", "connect,resolve";
}

在配置文件最后增加上面的权限相关的最后2行内容,然后重启es,一切就ok了!

nginx跨域设置

2018年6月27日 没有评论

跨域问题在现在前后端分离的项目中是一个很常见的问题,以前我写过一篇《nginx反向代理解决跨域问题》的文章。前段时间在做nginx配置允许跨域时一直失败,参考的是segmentflaut上面的一篇文章,尝试了许久都没有成功,搞的我都有点开始怀疑人生了。

后来我去查看了一下nginx官方add_header的文档,发现它教程中放在location块中是没有问题的,add_header支持放在”httpserverlocationif in location“,也就是不仅可以放在location中,也可以放在location中的if块里面。那么到底哪里出了问题哪?

在一篇叫《Using CORS》文章中,作者提到”Access-Control-Allow-Origin (required) – This header must be included in all valid CORS responses。”,也就是说我们必须要保证所有cors请求都要返回我们设置的header,而”location /”是一个默认的匹配块,如果匹配到其他块,那么 我们设置在location中的跨域header设置就会无效了,于是就做了下面的改动。

在nginx配置文件下面增加cors.conf配置文件,内容如下:

add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

if ($request_method = 'OPTIONS') {
    return 204;
}

然后在server块中include进来跨域配置文件:

location / {
       index  index.html index.htm index.php;
       autoindex on;
       try_files $uri $uri/ /index.php?$query_string;
}

include cors.conf;

这样在其他的网站配置文件中只需要 “include cors.conf”即可。当然如果你也可以直接用cors.conf配置文件里面的内容替换”include cors.conf”。重启nginx,就成功可以让js跨域请求了啊!

mac安装openresty

2018年6月12日 没有评论

OpenResty( 也称为 ngx_openresty) 是一个全功能的 Web 应用服务器。它打包了标准的Nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项。通过揉和众多设计良好的 Nginx 模块,OpenResty 有效地把 Nginx 服务器转变为一个强大的Web 应用服务器,基于它开发人员可以使用 Lua 编程语言对 Nginx 核心以及现有的各种Nginx C 模块进行脚本编程,构建出可以处理一万以上并发请求的极端高性能的 Web 应用。OpenResty 致力于将你的服务器端应用完全运行于 Nginx 服务器中,充分利用 Nginx 的事件模型来进行非阻塞 I/O 通信。不仅仅是和 HTTP 客户端间的网络通信是非阻塞的,与MySQL、PostgreSQL、Memcached 以及 Redis 等众多远方后端之间的网络通信也是非阻塞的。

Brew安装

brew tap homebrew/nginx
brew install homebrew/nginx/openresty

这是官方给的通过brew方式安装的方法,可惜我没有安装成功。

编译安装

利用brew安装openresty依赖。

brew update
brew install pcre openssl curl

下载openresty源码,然后解压编译安装。

./configure --prefix=/usr/local/opt/openresty \
--with-cc-opt="-I/usr/local/opt/openssl/include/ -I/usr/local/opt/pcre/include/" \
--with-luajit \
--without-http_redis2_module \
--with-ld-opt="-L/usr/local/opt/openssl/lib/ -L/usr/local/opt/pcre/lib/"
make -j2
sudo make install

我在安装的时候最开始没有用 “–with-cc-opt”来新增openssl和pcre头文件的位置,导致在make阶段总是报错,提示找不到”ssl.h”头文件。

openresty的nginx文件在/usr/local/opt/openresty/nginx/sbin/nginx,如果你没有安装其他版本的nginx,可以将/usr/local/opt/openresty/nginx/sbin/加入path目录,就可以使用nginx命令了。如果之前已有安装其他版本的nginx,就不建议在将openresty中nginx的sbin目录加入到PATH中,可以给openresty内置的nginx起个别名,方便以后使用。

export PATH=$PATH:/usr/local/opt/openresty/nginx/sbin
alias nginx_lua="/usr/local/opt/openresty/nginx/sbin/nginx"

 

 

分类: Mac, nginx 标签: ,

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 标签: