通过DNS认证来部署Let's Encrypt

原来是通过http认证的方式来完成ACME 的 Identifier Validation Challenges,但是内网的机器就无法完成这个认证,今天看了下,LE支持dns认证了,所以实践了一下。

安装

首先安装Certbot,按照官网操作即可: https://certbot.eff.org/instructions

生成证书

然后执行

1
2
3
certbot -d ssl-test.robberphex.com \
--manual --preferred-challenges \
dns certonly

对于MacOS用户来说,可以执行

1
2
3
4
5
certbot --config-dir /usr/local/etc/letsencrypt \
--logs-dir /usr/local/var/log/letsencrypt \
--work-dir /usr/local/var/lib/letsencrypt \
-d ssl-test.robberphex.com \
--manual --preferred-challenges dns certonly
  • 需要输入邮箱
  • 同意用户协议
  • 同意记录IP
  • 设置域名的TXT记录 比如图中,设置_acme-challenge.ssl-test.robberphex.com的TXT记录为x-P6A_dQ4_ggZtPvX_bOUeaY7hSM_IS6o-Gzj3h7LBw,然后回车。
  • 提示证书生成成功

配置

阅读更多

使用repox搭建sbt/maven镜像

最近sbt的速度实在是不能忍受了,所以使用repox搭建了sbt镜像。 搭建过程没有什么好说的,直接sbt assembly,然后

java -Xmx512m -jar target/scala-2.11/repox-assembly-0.1-SNAPSHOT.jar

就好了。


但是发现了两个repox的问题:

  1. 下载文件时,服务器全部下载完后,才能传输给sbt(不支持nginx那种“流式代理”),这在下载大文件时尤其明显。
  2. 有的时候,pom文件总是404 比如curl https://repo1.maven.org/maven2/org/w3c/css/sac/1.3/sac-1.3.pom -I的结果是200,curl http://106.75.27.110:8078/org/w3c/css/sac/1.3/sac-1.3.pom -I的结果就是404,重试好几次都不行;重启repox就好了。
阅读更多

Flume在hdfs产生大量日志文件的问题

线上两台flume向hdfs写日志,但是后来发现每5分钟产生的日志文件数量很多(远远超过两个的数量)。 后来找了一个时间观察下日志,发现如下日志:

16/12/13 11:38:11 INFO hdfs.BucketWriter: Closing idle bucketWriter hdfs://xxx/xx/xxx_log_20161213/.xxx_log.1481600173693.tmp at 1481600291480

根据日志找代码,发现是BucketWriter在配置了hdfs.idleTimeout的情况下,会在超时后关闭不活跃的日志文件flume官方文档对此参数的描述如下:

Timeout after which inactive files get closed (0 = disable automatic closing of idle files)

找到原因后,当然就很简单了,由于此参数默认是0,直接删除这个参数的相关配置就好了。每5分钟产生的日志数量就是两个了。

阅读更多

为何不可使用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来搞定了。

阅读更多