为何不可使用Redis的KEYS命令

在Redis KEYS命令的文档中,有如下一句话:

consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases.

简而言之,KEYS命令性能很差,尽量不要在生产环境中使用。

不过官方文档也提到了,可以使用SCAN命令

那么,KEYS命令为什么性能差呢?

server.c中可以看到,KEYS命令被keysCommand函数处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void keysCommand(client *c) {
dictIterator *di;
dictEntry *de;
sds pattern = c->argv[1]->ptr;
int plen = sdslen(pattern), allkeys;
unsigned long numkeys = 0;
void *replylen = addDeferredMultiBulkLength(c);

di = dictGetSafeIterator(c->db->dict);
allkeys = (pattern[0] == '*' && pattern[1] == '0');
while((de = dictNext(di)) != NULL) {
sds key = dictGetKey(de);
robj *keyobj;

if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
keyobj = createStringObject(key,sdslen(key));
if (expireIfNeeded(c->db,keyobj) == 0) {
addReplyBulk(c,keyobj);
numkeys++;
}
decrRefCount(keyobj);
}
}
dictReleaseIterator(di);
setDeferredMultiBulkLength(c,replylen,numkeys);
}

db本来就是一个dict,所以直接拿到一个dictIterator di,然后依次遍历,整个KEYS操作的复杂度是O(n),n为redis中key的数量。

这个操作,会阻塞到redis进程的。

可以看到,redis对于KEYS *这种情况做了优化。如果pattern是*,那么直接遍历,而不用判断是否match

阅读更多

Laravel Facade实现细节考

前两天有人讲Laravel中的Facade的时候,看到了__callStatic的实现,探究了下为何如此。

现有实现

switch实现

我们在调用Facede的方法的时候,绝大多数都会被__callStatic来处理,Larvel 5.1的__callStatic实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch (count($args)) {
case 0:
return $instance->$method();
case 1:
return $instance->$method($args\[0\]);
case 2:
return $instance->$method($args\[0\], $args\[1\]);
case 3:
return $instance->$method($args\[0\], $args\[1\], $args\[2\]);
case 4:
return $instance->$method($args\[0\], $args\[1\], $args\[2\], $args\[3\]);
default:
return call\_user\_func\_array(\[$instance, $method\], $args);
}

这个实现是 Taylor Otwell 最初实现的版本

cufa实现

是上述switch实现的简化版:

1
return call_user_func_array([$instance, $method], $args);

Argument Unpack实现

阅读更多

composer中指定依赖分支名的坑

之前只是看着别人写的composer.json,知道如果依赖一个项目的master分之,则在依赖的version中可以写dev-master。我就以为所有对分支的依赖,都是写成dev-<branch_name>

后来发现,v1.x这种分支名,不能直接使用dev-v1.x来声明依赖。 查了下官方文档

For every branch, a package development version will be created. If the branch name looks like a version, the version will be {branchname}-dev. For example, the branch 2.0 will get the 2.0.x-dev version (the .x is added for technical reasons, to make sure it is recognized as a branch). The 2.0.x branch would also be valid and be turned into 2.0.x-dev as well. If the branch does not look like a version, it will be dev-{branchname}. master results in a dev-master version.

翻译如下:

对于每个分支,对应的包都会创建一个开发版本。如果分支名看起来像一个版本号,则开发版本的名字是{branchname}-dev。 比如,分支2.0则会创建2.0.x-dev版本(后面的 .x 是为了技术原因添加,为了确保这个版本能够被识别为分支)。 2.0.x 分支也会被转换为 2.0.x-dev。如果分支看起来不像一个版本,版本号将会是dev-{branchname}。master分支的版本号是dev-master。


另外,当同时存在1.0 tag 和1.0.x分支时,1.0.x-dev指向的是1.0分支

阅读更多

Laravel Migration 类名重复分析

开发者在开发中一般都会为Migration起类名,最常见的就是AlterUserTable这种名字,但是如果后续的开发者第二次修改表,就有了两个类名相同的Migration了。 这样有什么问题吗? 首先,这个Migration如果和之前类名相同的Migration不在同一批次的话,是可以执行成功的。 接下来看看为什么。

Migration的执行

从Migration的执行来看,\Illuminate\Database\Migrations\Migrator::getMigrationFiles拿到所有的Migration列表,通过和执行过的Migration array_diff之后,获取没有执行的Migration列表,在执行这些Migration的时候会在\Illuminate\Database\Migrations\Migrator::requireFiles方法中require对应的Migration文件,如果恰好两个类名相同的Migration需要执行,那么就会出现错误 Cannot declare class AlterTestTable, because the name is already in use in ./database/migrations/2016_07_20_081952_alter_test_table.php on line 31 当然,如果这些Migration都在不同批次中,那么永远也不会有这个错误发生了嘛。

Migration的回退

\Illuminate\Database\Migrations\MigrationRepositoryInterface::getLast获取最后一次执行的Migration文件名列表,然后在\Illuminate\Database\Migrations\Migrator::runDown方法中将Migration名字resolve到类名(比如 2016_07_20_081952_alter_test_tableAlterTestTable 类,这时候就通过new这个类,运行down方法来回退。 同样的,在这种情况下,如果有两个一样名字的类,则autoload机制只会选择一个。 注意,在这种情况下,即使两个Migration不在同一个批次当中回退,那也会有一个Migration永远不能回退。


  • 这也解释了为什么在本地创建了migration后,运行后,然后rollback的时候提示找不到这个类,而必须要dumpautoload才可以。
  • 由于Migration up的时候是通过Laravel自己解析到代码的,而down的时候是通过composer解析的,这导致了Migration的up和down不对称,确实不怎么好看。
  • 作为一个合格的开发,显然不应该让Migration类名重复!
阅读更多

《硅谷钢铁侠:埃隆·马斯克的冒险人生》

“我们想要会飞的汽车,而不是140个字符。”

  • 马斯克有自己的想法

他需要做的是,制定一个目标,然后尽力实现。 在SpaceX,他知道自己要造便宜的火箭,所以员工必须努力优化每个部件;在特斯拉,员工必须有最好的执行力。

“他(马斯克)总是说,让最基本的物理原理说话。” “我们首先要决定世界上最好的遮阳板是什么样子的,然后做得比那更好。”

  • 勇敢面对未知

虽然之前马斯克不知道如何发射火箭,但是在SpaceX开始之后,马斯克自学相关知识,甚至问员工问题。 但是这些未知都没有阻止马斯克去造火箭、生产电动汽车,相反,智能门把手和触屏操控系统是一次非常完美的反击。

  • 面对压力越挫越勇

三家公司多次遇到危机,财务危机之下,马斯克亲自审核支出,公关危机之中,马斯克自己写新闻稿来辩驳。 当然,马斯克出色的学习能力、完美的记忆力也是马斯克成功的条件,但这一点,我们没法学习到。


坚持自己的想法非常困难,但实现自己的想法对于个人来说非常重要,但是马斯克对于自己想法的固执、对于员工的苛责实在不是一个好的表达。自己的想法可能伴随着来自众人、来自传统的压力,但是新东西要是一开始就得到了旧世界的承认,那还是新东西吗?

“我告诉他,‘不只是那个家伙错了,我再重申一遍,你也错了。’” 即使在社交场合中,马斯克也可能会突然从餐桌前站起来,不做任何解释,径直走到外面去看星星,仅仅因为他不愿意跟傻瓜待在一起闲聊。

阅读更多

如何命名二进制PHP扩展?

Python界有自己的文件名约定https://www.python.org/dev/peps/pep-0427/#file-name-convention,比如distribution-1.0-1-py27-none-any.whl一看就知道自己能不能用到这个whl包。 最近需要发布thrift_protocol扩展,故自己定下一个命名规范,在此记录之。 首先看看php如何判断扩展兼容与否,dl.c:172dl.c:182分别根据zend_api(比如20151012)和build_id(比如API20151012,NTS)来判断是否兼容。 所以我们用build_id+platform就可以表示ABI版本了,加上name和version就得出命名规范: {distribution}-{version}-{build_id}-{platform}.{so,dll} 比如: thrift_protocol-1.0-API20151012,NTS-linux_x86_64.so 遗憾的是,github会吞掉英文逗号,所以只能用英文句号替代了。 不过,phpize的时候能看到zend_api,无法看到build_id,算是一个不方便的地方,只能通过php -i | grep 'PHP Extension Build'来变通获取了。

阅读更多

将ngx_brotli编译成动态nginx模块

Brotli是google新出的一个压缩算法,据说比Zopfli的压缩率要高20–26%(Zopfli是google之前发布的一个和Deflate兼容的压缩算法)。 Chrome 49+、Firefox 44+以及Opera 36+都支持Brotli了,所以,是时候尝试一把了。 但是为了支持这个特性,自己编译部署nginx太麻烦了,nginx 1.9.11开始支持动态模块,所以也来尝试一把。

系统需求

sudo apt-get install autoconf libtool nginx-extras

本人测试环境ubuntu 14.04, ppa:nginx/stable, nginx/1.10.1

安装libbrotli

按照 libbrotli 的说明,依次执行如下命令安装

git clone https://github.com/bagder/libbrotli
./autogen.sh
./configure
make
sudo make install

为了让系统中使用到libbrotli的程序能够加载so文件,故创建软链:

sudo ln -s /usr/local/lib/libbrotlienc.so.1 /lib/libbrotlienc.so.1

准备源代码

下载nginx和ngx_brotli的源代码:

wget http://nginx.org/download/nginx-1.10.1.tar.gz
git clone https://github.com/google/ngx\_brotli

编辑ngx_brotli/config文件,在顶部添加一行

have=NGX_HTTP_HEADERS . auto/have

configure

在nginx源码目录执行

./configure \
–add-dynamic-module=../ngx_brotli/ \
–with-http_dav_module \
–with-http_realip_module \
–with-http_v2_module \
–with-threads \
–with-http_ssl_module \
–with-ipv6

并make,得到两个so文件:

$ find -name ngx_http_brotli*.so
./objs/ngx_http_brotli_static_module.so
./objs/ngx_http_brotli_filter_module.so

加载模块

/etc/nginx/nginx.conf文件中添加如下两行,使nginx加载模块:

load_module ‘/path/to/ngx_http_brotli_static_module.so’;
load_module ‘/path/to/ngx_http_brotli_filter_module.so’;

接下来在http段中添加如下两个配置:

brotli on;
brotli_static on;

再执行sudo nginx -t 看看有没有错误,如果没有错误的话那就成功了。 不过,brotli压缩算法只有在https连接中才能生效。

module “/path/to/ngx_http_brotli_static_module.so” is not binary compatible in nginx.conf

这个错误是由于module的signature和nginx的signature不匹配导致的,我的解决办法是,打印出这两个signature,逐位比对,并据此调整configure的参数。 参考 ngx_module.h 比如,第25位表示NGX_HTTP_V2定义与否,此时可以添加--with-http_v2_module来修正signature。 第22位表示NGX_THREADS定义与否,可以添加–with-threads来修正。 第8位和第9位与IPv6有关,只需添加–with-ipv6即可。 至于第30位,只能通过编辑ngx_brotli/config文件,添加have=NGX_HTTP_HEADERS . auto/have来搞定了。

阅读更多

How to install/upgrade elasticsearch cluster via ansible?

First, we have a playbook:

es.yml
1
2
3
4
5
- hosts: es
become: yes
roles:
- elasticsearch
serial: "50%"

serial: "50%" means ansbile will run tasks in 50% of hosts (or less).

And, we should add a rule elasticsearch:

roles/elasticsearch/tasks/main.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
---
- name: disable shard allocation for the cluster
uri: url=http://localhost:9200/_cluster/settings method=PUT body='{{ es_allocation.disable }}' body_format=json
  ignore_errors: yes

- name: download ealsticsearch deb pacakge
local_action:
module: get_url
url: "{{ elasticsearch_url }}"
dest: "cache/elasticsearch-{{ elasticsearch_version }}.deb"
checksum: "sha256:{{ elasticsearch_sha256 }}"
become: no

- name: check if elasticsearch is installed
command: dpkg-query --showformat='${Version}' -W elasticsearch
register: elasticsearch_version_check
failed_when: elasticsearch_version_check.rc > 1
changed_when: elasticsearch_version_check.rc == 1

- block:
- name: copy elasticsearch package
copy: src="cache/elasticsearch-{{ elasticsearch_version }}.deb" dest=/tmp/elasticsearch.deb
- name: install elasticsearch
apt: deb=/tmp/elasticsearch.deb
always:
- name: delete elasticsearch package
file: path=/tmp/elasticsearch.deb state=absent
when: elasticsearch_version_check.stdout != "{{ elasticsearch_version }}"

# configure elasticsearch
# install plugins

- name: restart elasticsearch
service: name=elasticsearch state=restarted

- name: wait for elasticsearch node to come back up
wait_for: port=9200 delay=15

- name: enable shard allocation for the cluster
uri: url=http://localhost:9200/_cluster/settings method=PUT body='{{ es_allocation.enable }}' body_format=json

- name: wait for cluster health to return to green
uri: url=http://localhost:9200/_cluster/health method=GET
register: response
until: "response.json.status == 'green'"
retries: 50
delay: 5
  1. we try to disable allocation for cluster upgrade
  2. download elasticsearch’s deb package to local cache
  3. install and configure, restart elasticsearch
  4. wait 9200 port is available, enable allocation, wait for cluster become green.

some vars:

roles/elasticsearch/vars/main.yml
1
2
3
4
5
6
7
8
---
elasticsearch_version: 2.3.2
elasticsearch_url: "https://download.elasticsearch.org/elasticsearch/release/org/elasticsearch/distribution/deb/elasticsearch/{{ elasticsearch_version }}/elasticsearch-{{ elasticsearch_version }}.deb"
elasticsearch_sha256: 3d474b0123ec8ad4ebfa089f8cde607033e6cbef28a6a0df318bdc3d2a546cd8

es_allocation:
disable: '{"transient":{"cluster.routing.allocation.enable":"none"}}'
enable: '{"transient":{"cluster.routing.allocation.enable": "all"}}'
阅读更多

How to use Theano? (with OpenCL)

I have a MacBook Pro with AMD display card, so I want Theano use OpenCL. First, create virtualenv and activate it:

1
2
python3 -m venv venv
source venv/bin/activate

Install some dependencies for Theano and OpenCL:

1
pip install cython nose

those dependencies isn’t in setup.py of Theano (or pygpu).

Install Theano (latest development version, so far, 0.8.2 doesn’t support OpenCL):

1
pip install git+https://github.com/Theano/Theano.git

If github is temporary unavailable, try my personal csdn mirror(CSDN CODE can sync github repo conveniently):

1
pip install git+https://code.csdn.net/u010096836/theano.git

Next step is installing gpuarray for supporting OpenCL:

阅读更多

APT Hash sum mismatch

工作当中遇到此问题,然后偶尔看见此文。 此文是 APT Hash sum mismatch 的翻译。

TL;DR

APT 仓库能够提供未压缩和压缩过的文件格式。常用的几个的压缩格式是 gzip, bzip 和lzma。 apt 的 Bug 在处理lzma文件(.xz)读写的时候会偶尔报告“Hash sum mismatch”错误。 本文提供一种变通方式,另外,在特定版本的 apt 中,该bug已经被修复。 使用如下系统的用户应该升级到系统提供的最新的 apt 版本:

  • Ubuntu Trusty (14.04) 及更新的版本
  • Debian Jessie (8) 及更新的版本

更早系统的用户应该升级系统或者使用如下的变通方法。 目前,我们决定关闭 packagecloud 的所有仓库中对 lzma 格式的支持。元数据仍然能够以 gzip, bzip 以及未压缩的格式来获取。packagecloud 仓库的所有者们并不需要做任何事情。

Hash sum mismatch 变通方法

用户可以通过两种方法强制 apt 不使用 lzma 格式的元数据。 第一种办法就是运行 apt-get 的时候添加额外的命令行参数:

apt-get update -o Acquire::CompressionTypes::Order::=gz

(注意:如果你偏向于使用 bzipped 格式,你也可以指定参数为“bz2”) 或者,你可以在全局的 apt 设置中指定这个选项,这样你就不必每次运行 apt 的时候输入这些。 步骤如下:

  1. 创建 /etc/apt/apt.conf.d/99compression-workaround 文件
  2. 添加这些文本到文件中: Acquire::CompressionTypes::Order:: "gz";
阅读更多