我是一个 Python 程序员,也对 Lua,Go,Rust 感兴趣。目前在蚂蚁金服工作。

我的日常工作离不开 Vim 和 Tmux,当然还有 Git。平时喜欢在看 Github 上有趣的项目,学习新的东西,看书,写博客等。博客的内容一般是我的人生感悟,观后感读后感,以及技术方面的内容。

我的梦想是让网络变得更加开放、自由和快速。

声明:本博客内容仅代表本人观点,和我的雇主无关。本站所有内容未加说明均为原创,一经发布自动进入公共领域,本人放弃所有权利。

记一个 Python logging 多进程 rotate 问题

线下环境的日志轮转一直工作的很奇怪,今天必须要解决一下了。

我们 Django 的 settings 里面 LOGGING 配置了日志轮转,满了 100M 就开新的文件。奇怪的是我每次去看日志,发现都有很多 log 才几 M,根本没到 100M 就“轮转”了。实际的配置简化如下。

这个配置基本上是没有问题的。我把 maxBytes 改成 30M,然后观察文件的大小。发现刚开始是正常的,/var/log/django.log 一直在打,逐渐到了 30M。这个时候神奇的事情发生了,到了轮转的时刻,旧日志被 rename 成了 django.log.1 ,然后又被 rename 成了 django.log.2 ,突然,十几个文件都开始写入了,这些文件 last_modify 时间一直在更新,也一直在变大。

思考片刻,我觉得问题出在 Python 的 logging 模块并没有去处理多进程的问题。我的 Django 应用是用 uwsgi 跑的,开了 16 个进程。当日志快要超过 maxBytes 的时候,第一个进程执行了 rename、新开文件 A 的操作(这个具体的流程,看一下文档里对 rotate 的流程描述就明白了),开始朝新开的文件 A 写入;这时候第二个进程将第一个进程打开的新文件给 rename 了,然后自己又新开一个文件 B 写入。但是第一个进程依然持有文件 A 的句柄的,所以依然可以往 A 里面写入。以此类推,就导致 16 个进程大家都持有了一个文件句柄,大家同时向不同的文件写入。(这段流程是我根据现象推测的,如果不正确请指正)。

Python 自身的 logging 模块不关心多进程写文件的问题,我觉得是可以接受的。一个 Python 程序就是一个进程实例,无须关心所在的环境是什么样子。我考虑开始通过其他的方案解决这个问题。

尝试了 uwsgi 的 daemonize,遇到了两个问题:1)uwsgi 的日志和 django 的日志混在一起了 2)原本 uwsgi 的 log 是可以 rotate 的,结果我加上 daemonize 之后,日志里面出现一下错误提示,竟然直接不转了。

考虑到引入 syslog 这种第三方 log 进程或者 rotate,会增加运维的复杂度。而且得重新考虑 rotate、删除、写入不同文件等的方案,所以也没有去考虑用第三方进程的方式。

这个问题实际上是 rotate 操作执行的时候,导致了竞争条件,被执行了多次的问题。所以自然而然就想到,如果在执行这个操作的时候,能加一个锁就好了。只有一次 rotate 会被执行。找到了一个实现了这个功能 mrfh 的库。代码只有几百行,里面继承了 BaseRotatingHandler 实现了自己的一个 Handler,做的事情很简单,就是在 rotate 的时候增加一个基于文件的锁(基于 fcntl),保证 rotate 能只做一次就好了。

引入也很简单,只是安装好依赖,然后将 logging 配置的 handler 改成此库里面的 handler 就好了。

现在打出来的日志都是整整齐齐的 100M 了,另外他会在日志目录下生成一个 .filename.log.lock 作为锁,无伤大雅。

 

时区问题“最佳实践”

时区这个问题太头疼了,如果你编程,几乎会一定遇到这个问题。对一些时区没有学好的同学,每每碰到心里都会害怕的……其实这个问题很简单,你只知道自己在做什么就好了。

简单的介绍一下时区。中央时间是 UTC 时间,其他时区都是根据这个时间偏移的。中国的时间叫做“北京时间”,时区在“东八区”,记做是 UTC+8. 意思就是我们比中央时间快 8 个小时。我们过着早上9点,中央时间地区的人们过着早上1点(凌晨1点)。为了在程序中方便表达和理解,每个地区会用“区域/位置”格式来表达时区。比如中国的时区可以使用 Asia/Shanghai,纽约用 America/New_York 表示。

如果你在美国租一台 VPS,那么你将会得到 3 个时间:你在中国写程序,你的电脑上显示的“北京时间”,服务器跑着的是美国时间,然后很多程序会用 UTC 时间来表示。

如果不事先想清楚,你之后肯定会经历很多痛苦来琢磨一个时间到底是 UTC 时间还是美国时间。时间变量传来传去还经常弄丢时区信息,或者转换两次时区,都是很头疼的。我们之前做爬虫,保存数据的抓取时间、更新时间是很重要的,但是数据经过多次存储和处理,忘记了到底在哪里转换过时区,这个时间到底是什么时区的时间。后来开发了一个专门的组件用来处理,这个组件的逻辑又很不清晰,最后此组件的作者离职了,交给我来维护,所以……想象一下我的痛苦吧。

关于时区的最佳实践,和 Python 的 “Unicode” 最佳实践是一样的:三明治模型

拿到的时间处理成正确的时区,比如抓取了一个中国的网页,时区是北京时间,那么就生成一个北京时间。当数据开始保存的时候,只保存 UTC,这样你就知道,只要是内部系统,保存的时间全都是 UTC 时间。然后时间要进行展示的时候,再进行本地化。这个可以有些框架可以自动完成,比如 django。对于前后端分离的项目,可以考虑用 moment.js 这种框架实现。

另一个关键的点是:知道你的框架是怎么处理时间的

比如 Django,文档就说的很明白:当开启 USE_TIMEZONE 的时候,Django 会在数据库中存储 UTC 时间,对于 template 和 form Django 会自己在内部进行本地化。

When support for time zones is enabled, Django stores datetime information in UTC in the database, uses time-zone-aware datetime objects internally, and translates them to the end user’s time zone in templates and forms.

有关系统的时间,其实内部是用一个时间戳来计时的。Unix Timestamp 是不带时区信息的,因为这个数字代表的是从1970年(UTC)开始过去了多少秒。所以理论上讲,世界上所有的电脑在内部的计时是一样的,只是显示出来的时间是不同的。如果担心时区混乱,在系统中存储时间戳也是一个不错的方法。

但是需要注意的是,并不是所有的 Timestamp 都是不带时区信息的。我写这篇文章主要是因为今天因为 Celery 的一个时间问题调试了半天。最后发现 Celery 用的时间戳竟然不是 UTC 的,还带时区的信息!具体的代码可以看这里。我实在搞不懂他们在搞毛,为啥不用一个标准的时间戳。

另外 Python 有一个处理时间的库叫 arrow ,这个库因为一个 get() 函数就能处理各种各样的时间格式而受欢迎,之前我们要从网站的文本中提取时间,用了很多这个库。但是我的建议是:千万不要用!这个库的 API 太混乱了,比如 get() 当不写第二个可选参数的时候回忽略第三个参数 tz_info 而导致时区无法识别,写上第二个可选参数第三个参数才可以被识别(后来貌似已经修复了)。

推荐使用 Pendulum 。

另外时间有一个标准的格式,可能你已经见过了,形如这样:2019-01-13T16:27:26+00:00。这是 ISO 8601 规定的格式。大多数库都能处理这种格式。

 

React 事件绑定的正确姿势

最近在写 React,对于将一个事件处理函数绑定到按钮上,总是有一种感觉:这么写也行,那么写也行。所以就参考了文档,整理了一下各种写法。主要是参考 React 的文档来写的,非常基础,非 React 的入门同学,这篇文章可以不看了。

绑定事件最基本的做法如下:

第 18 行将 this.handleClick 绑定到了按钮上。注意这里第 7 行在类的初始化函数中有一个绑定 this 的操作,这个是必须的。如果不写,那么 handleClick() 函数中的 this 将会是 undefined (这个是 JS 的特性,不是 React 的)。在 console 中会看到错误如下:

如果你写过 jQuery,从这个例子中可以看出,和 jQuery 绑定事件的区别如下:

  1. 不是传入一个 string ,而是函数
  2. 组织默认的形式不是 return false 而是 e.preventDefault()
  3. jquery 是小写命名,React 是驼峰命名
  4. 不要往 DOM 上面去添加 Listener,而是在 render 的时候提供 listener

如果不想每次在初始化函数中写一个 bind,可以有两种方案:

第一种,将函数定义为 class 的一个 field:

第二种,绑定的时候定义一个匿名函数(箭头函数),将匿名函数与元素绑定,而不是直接绑定事件处理函数,这样 this 在匿名函数中就不是 undefined 了:

第二种方法有一个问题,每次 LoggingButton 渲染的时候都会创建一个新的 callback 匿名函数。在这个例子中没有问题,但是如果将这个 onClick 的值作为 props 传给子组件的时候,将会导致子组件重新 render,所以不推荐。

Bind 函数的时候传递参数,下面两种写法都可以(推荐第二种):

在第二种中,e 代表 React Event,将会自动作为第二个参数。

 

2018年年鉴

每年年末的时候我都会写写东西,总结一下今年过的如何。今年元旦的假期在南京旅行,每天都玩的很累。就现在补上吧。

工作

今年换了工作,算是比较大的一件事。从以前写爬虫和后端变成了一个 SRE。新的工作有很多挑战,有技术上的,也有沟通方面的。工作方式发生了很大的变化,以前的工作非常纯粹,我们有专业的 PM 和设计,我只管完成需求,写代码就好了。工作也非常轻松,每天都六点下班,工作之余还能看看自己感兴趣的东西。同事们也很专业,没有什么可挑剔的,我们有 Code Review,有基于 Python 的技术栈,代码有测试。每天工作都很开心。技术进步也很快,在 Code Review 中大家会互相指出错误,这是非常宝贵的财富。大家经常玩新鲜的东西,看起来不错的可以用到我们自己的产品中。我很感激在前公司的这一年。

来蚂蚁金服之后,从以前工作是 100% 的编程,变成了有 50% 的时间在沟通,50% 的时间在编程。开会的时间很多,但不是所有的会都是有意义的。以前公司人少,无论找谁喊一声就行了,现在找人很麻烦,不是每一个人都回复很及时,更多的情况是他会让你去找另一个人,这样找来找去,最后找过五六个人还没解决一个很简单的问题是很正常的。而文档、帮助维护的又不是很好,找人问又成了一个常态,所以这对我来说成了一个很大的困扰。最近正好看到一个理论,讲的是一个组织做出来的东西,实际上是这个组织沟通方式的体现。

“organizations which design systems … are constrained to produce designs which are copies of the communication structures of these organizations.”

— M. Conway

我在公司内部的宣传中看到一个小姑娘说,自己进公司之前是比较内向的一个人,进公司三年后变成了一个怼天怼地的女汉子。这个公司比较欣赏这样的人,可我觉得很反感。你工作中怼的都是你的同事啊,大家低头不见的,怼来怼去这样好吗?大家在一起和和气气的工作,每天都开开心心的不好吗?为什么喜欢吵来吵去呢。

当然了,蚂蚁金服是很大一家公司,我觉得这个公司的任何一个人,都不能描述出这个公司是什么样子的,顶多是盲人摸象。每个部门,每个团队都不一样。我也不认为这些问题是蚂蚁金服存在的,不同的公司会有不同的问题,也不见得其他公司没有我所说的这些问题。说这么多负面的地方,原因是我对这家公司的期望是挺高的,结果来了之后发现并不是我想象的那样。不过好的是,我可以有机会去改变那些不好的地方。

话说回来,这一年做 SRE,一个难得的经历是有许许多多各种各样的故障可以看,可以参加 Review。每一个故障都是新的,有些很蠢,有些很有意思,我都感到惊讶,这样隐蔽的故障都能这么快找到原因。遗憾的是,我还没有亲手第一个找到过一个故障的根本原因。

关于加班,下半年基本上都 10 点之后下班。工作时间变得很长,也是让我困惑的一个地方。没有时间学习了,换工作之后,自己的技术成长明显没有以前快了,开发的效率也变低了。以前九点上班,6点下班,我能写很多代码,完成很多任务。现在一天工作10个小时以上,做的事情却没有以前多。幸运的是,现在住的离公司近,上下班很方便,晚上每天都加班,打车公司报销。花在路上的时间没有那么多了。以前我竟然每天花 4 个小时上下班!

明年想办法多留给自己一些时间吧,空闲的时间是真正长知识的时间。我见过一些同事,基本的计算机常识都没有,还停留在毕业生的水平,感觉就是毕业之后一直没有时间学习导致的。学习基本的原理(虽然工作中用不到),了解你使用的工具,这其实是会让你事半功倍的东西,可惜很多人看不到这一点。

开源和学习

今年开始接触了 openresty 和 lua,很有意思的一个领域。在读 PIL 但是还没有读完,PIL4 已经比第一版厚很多了。openresty 社区的人非常友好,从文档就看出来了,无论谁写的库都是非常标准的风格,Toc,简介,函数文档。明年继续深入学习一下这方面,多看看库的源代码。

openresty 的一个模块的控制台我是用 starlette 写的,一个基于 asyncio 的很小的框架。顺便看了它的源代码,提交了两个关于 staticfile 的 patch。后来没时间搞了。

自己翻译的这本书,已经很长时间了还没结束,今年进度已经到了大约 2/3 了,争取最近就完结它。

另外看了很多 Kubernetes 的资料和相关的软件,但是一直没机会用。我有一个不错的 idea,已经放了一年没有下手干了,不过短期看来,我基本没有时间做自己喜欢的事情。

社区

来了杭州之后,在杭州组织了 4 次 Python 社区的 Meetup。听了很多有意思的分享,学到很多东西。这个事情是很有意义的,希望明年把它继续下去,时间是一个大问题。

10 月份去主持了北京 PyCon 的语言特性专场,今年的分享特别精彩,认识了很多大神,打开了很多新世界的大门。去上海 PyCon 做了一个很入门的分享,反响还算不错。今年还发了很多 Tweets,平时看到有意思的东西都会转发一下。明年争取继续在社区中学习吧,也多分享自己的东西。

生活

今年的生活没有太大的变化,除了在公司的时间变多了。我尝试过健身来着,不过全年加起来估计才有 10 次左右吧,明年坚持一下。越来越觉得,健康才是最重要的,也许我开始变老了。

希望明年少浪费一些时间,多做一些有意义的事情。多学一些知识,多看书,少说话。

往年:

  1. 2013年
  2. 2014年
  3. 2015年
  4. 2016年
  5. 2017年 (看了一下2017年定下的4个目标,很好,一个都就没完成)
 

SRE&Devops 每周分享 Issue #6 Closing

Hi,这是这一系列的最后一篇内容,之后不会再每周提供定时发布。遇到有意思的文章我会分享在 Twitter 上,这种方式更加实时,也比较有互动性。这是我的 Twitter: laixintao 。以下是本期内容。

 

Ubuntu 发布的 《上云白皮书》

现在可选的云服务多种多样,公有云、私有云、混合云,还有不同的厂商提供了不同的服务,从 Bare mental Server 到 VPS 到容器服务。提供 Infrastructure 的经常还伴随着提供上层的服务。Ubuntu 发布的这个 PDF 介绍了各种云的概念,推荐一下,了解之后可以针对自己的需求和规模选择最合适的场景。

Envoy Proxy at Reddit

Reddit 的用户越来越多,服务规模也越来越大。Reddit 使用 Envoy 作为四层/七层负载均衡服务器,本文介绍了他们的方案。

Build a serverless Twitter reader using AWS Fargate

AWS 的一篇 serverless 手把手教程。

The Definitive PHP 5.6, 7.0, 7.1, 7.2 & 7.3 Benchmarks (2019)

最新版本的 PHP 的一些性能测试。

Using Golang to Build Microservices at The Economist: A Retrospective

经济学人使用 Go 语言来构建微服务的历史。

Why on earth did we choose Jenkins for 2019?

介绍了 Jenkins 的一些优点。

新闻:Red Hat 将 etcd 捐赠给了 CNAB

HOW DASHBOARDS ARE CHANGING HUMAN BEHAVIOR IN DEVOPS

Dashboard 怎么用,怎么设计,这是一个哲学。

Our learnings from adopting GraphQL

Netflix 介绍的使用 GraphQL 的经验。

rendora/rendora

给爬虫渲染出页面的一个项目。(使用无界面 Chrome 浏览器)

bloomberg/goldpinger

Debug k8s 的一个工具。