Comments

生产环境上用了 MongoDB,三个节点组成的 ReplicaSet(复制集)。部署好后,应用一直没出过问题,所以平时也没管过。今天早上突然想上服务器看看,于是登录了 primary 节点查看日志,发现这条日志不断重复:

2016-04-15T03:02:39.470+0000 W NETWORK  [ReplExecNetThread-28676] Failed to connect to 172.31.168.48:11102, reason: errno:111 Connection refused

其实就是有个 secondary 节点一直连接不上。不太可能是网络问题,所以很可能是那个节点的 mongod 进程挂掉了。登录上 secondary 节点,mongod 进程果然不在运行;查看日志发现最后一条是在 2016-03-21. 一时间有两个疑问涌上心头:

  1. 为什么会挂掉?
  2. 如何修复?
Read on →
Comments

最近生产环境上出现了一个奇怪的问题。某日下午,APP 向某个域名发出的所有请求没有响应,服务端也没收到请求;而向另一个域名的请求却没有问题。先记录一下背景:

  • 两个域名:api.example.com, web.example.com
  • 环境:AWS + ELB + Nginx
  • 后端:Python + Django + Gunicorn

出问题的是 api.example.com (下文简称 API)这个域名,所以 web.example.com 就不细说。由于一些历史原因,API 的请求链路大概是这样:

                      proxy_pass         backends                      proxy_pass
APP -----> API Nginx -------------> ELB -----------> Backend Nginx(s) ------------> Gunicorn(s)

其中 API 的 Nginx 配置大概是这样:

location /test {
    proxy_pass http://name.of.elb.aws.com;
}
Read on →
Comments

上一篇博客 简单介绍了 zhihu-go 项目的缘起,本篇简单介绍一下关于处理 HTML 的细节。

因为知乎没有开发 API,所以只能通过模拟浏览器操作的方式获取数据,这些数据有两种格式:普通的 HTML 文档和某些 Ajax 接口返回的 JSON(返回的数据实际上也是 HTML)。其实也就是爬虫了,抓取网页,然后提取数据。一般来说从 HTML 文档提取数据有这些做法:正则、XPath、CSS 选择器等。对我来说,正则写起来比较复杂,代码可读性差而且维护起来麻烦;XPath 没有详细了解,不过用起来应该不难,而且 Chrome 浏览器可以直接提取 XPath. zhihu-go 里用的是选择器的方式,使用了 goquery.

goquery 是 “a little like that j-thing, only in Go”,也就是用 jQuery 的方式去操作 DOM. jQuery 大家都很熟,API 也很简单明了。本文不详细介绍 goquery,下面选几个场景(API)讲讲在 zhihu-go 里的应用。

Read on →
Comments

我是知乎重度用户,每天都会花点时间在知乎上面看点东西。有段时间时间线里经常出现爬虫相关的话题,也看到不少直接爬知乎信息的项目;其中一个就是 zhihu-python. 实际上 zhihu-python 不是一个完整的爬虫,正如其文档说明的那样,是一个 API 库,可以基于这些 API 实现一个爬虫应用。zhihu-python 实现了用户、问题、答案、收藏夹相关的信息获取类 API,对于大多数信息获取的目的已经足够。这个项目很受欢迎,然而说实话,代码质量一般,不过思路值得借鉴。

Read on →
Comments

极光推送 是国内最早的第三方消息推送服务,官方提供了多种语言的 SDK 和 REST API,详情见 官方文档。遗憾的是缺少一个 Go 语言版本的 SDK,于是我就动手造轮子,封装了一个 Go 的版本。

实际上这个项目在今年 3 月份就完成了主要的推送相关的接口,在 GitHub 上也收获了几个 star 和 fork. 最近几天突然兴起,又翻出来把 device, tag, alias, report 的一些相关接口也封装完成了。

啰嗦了一大堆,差点忘了最重要的东西,下面给出链接:

欢迎使用,并 反馈 issues创建 pull request.

Comments

前一篇 Blog 简单介绍了 Celery 及其用法,现在我们看看在 Flask 项目中如何使用 Celery.

注意,这篇 Blog 严重参考了这两篇文章:

  1. Using Celery With Flask: 写了一个完整而且有意义的例子来展示如何在 Flask 中使用 Celery.
  2. Celery and the Flask Application Factory Pattern: 是上文的姊妹篇,描述的是更为真实的场景下,Celery 与 Flask Application Factory 的结合使用。
Read on →
Comments

Introduction

分布式任务队列

Celery 是一个分布式任务队列,下面是 官网 的一段描述:

Celery is an asynchronous task queue/job queue based on distributed message passing. It is focused on real-time operation, but supports scheduling as well.

Celery 简单、灵活、可靠,是一个专注于实时处理的任务队列,同时也支持任务调度。

何为任务队列?

摘自 Celery 官方文档的 中文翻译

任务队列是一种在线程或机器间分发任务的机制。

消息队列的输入是工作的一个单元,称为任务,独立的职程(Worker)进程持续监视队列中是否有需要处理的新任务。

Celery 用消息通信,通常使用中间人(Broker)在客户端和职程间斡旋。这个过程从客户端向队列添加消息开始,之后中间人把消息派送给职程。

Celery 系统可包含多个职程和中间人,以此获得高可用性和横向扩展能力。

Read on →

写 API 的时候,总是会想着如何能提升性能。在一般的 Web 应用里,基本上没什么 CPU 密集型的计算,大部分时间还是消耗在 IO 上面:查询数据库、读写文件、调用第三方 API 等。有些可以异步的操作,比如发送注册邮件、手机验证码等,可以用任务队列来处理。在 Python 的生态里,Celery 就是一个很成熟的解决方案。但是对于很多查询请求,还是需要同步返回的。

如果真的遇到性能问题,正确的做法是先找出性能瓶颈,然后对症下药。比如优化数据库索引、优化数据库查询语句、优化算法和数据结构,加速查询和计算。但是最快的计算就是不算——或只计算一次,也就是把计算(查询)的结果缓存起来,以后相同条件的计算(查询)直接从缓存里获取,而不需要重新计算(查询)。

对于耗时的计算,缓存是一种非常有效的优化手段。但缓存也不是万能的,引入缓存的同时,一些其他问题或需要注意的事情也随之而来,比如数据同步、缓存失效、命中率、分布式等。这里不深入探讨这些问题,仅针对下面这种场景,使用缓存来优化 API 性能:

  • GET 查询
  • 查询很耗时
  • 相同条件、不同时间(或某段时间内)的查询结果是一致的

比如获取静态页面(也可以通过 Nginx 直接返回),查询某些元数据列表(如国家列表、产品分类等)。

Read on →
Comments

在一个 Web 应用里,不管是为了业务逻辑的正确性,还是系统安全性,做好参数(querystring, form, json)验证都是非常必要的。

WTForms 是一个非常好用而且强大的表单校验和渲染的库,提供 Form 基类用于定义表单结构(类似 ORM),内置了丰富的字段类型和校验方法,可以很方便的用来做校验。如果应用需要输出 HTML,集成到模板里也很容易。对于 JSON API 应用,用不到渲染的功能,但是结构化的表单和校验功能依然非常有用。

Read on →
Comments

遵循良好的编码风格,可以有效的提高代码的可读性,降低出错几率和维护难度。在团队开发中,使用(尽量)统一的编码风格,还可以降低沟通成本。

网上有很多版本的编码规范,基本上都是遵循 PEP8 的规范:

除了在编码时主动遵循规范,还有很多有用的工具:

  • IntelliJ IDEA 和 PyCharm 的格式化代码功能
  • Google 开源的 Python 文件格式化工具:github.com/google/yapf
  • pyflakes, pylint 等工具及各种编辑器的插件

本文的内容主要摘自互联网上各种版本的规范,因为公司有些小伙伴代码风格不太好,所以整理了一份算是团队的编码规范。

Read on →
getElementsByTagName('BODY')[0]).appendChild(s); }()); getElementsByTagName('BODY')[0]).appendChild(s); }()); getElementsByTagName('BODY')[0]).appendChild(s); }());