Author Archives: Solrex Yang
很多语言或协议选择使用 ASCII 字符 “\”(backslash,0x5c) 作为字符串的转义符,包括 JSON 中的字符串。一般来说,使用 Python 中的 JSON 模块编码英文,不会存在转义符的问题。但如果使用 JSON 模块编解码中文,就可能面临着中文字符包含转义符带来的 bug。本篇文章给出了一个 badcase。
中文解码错误
测试用例文件里面包含繁体的“運動”二字,使用 GB18030 编码。使用 json 解码的错误如下:
$ cat decode.dat
{"a":"運動"}
$ python
>>> import json
>>> fp=open('decode.dat', 'r')
>>> json.load(fp, encoding='gb18030')
Traceback (most recent call last):
File "", line 1, in
File "/home/yangwb/local/lib/python2.7/json/__init__.py", line 278, in load
**kw)
File "/home/yangwb/local/lib/python2.7/json/__init__.py", line 339, in loads
return cls(encoding=encoding, **kw).decode(s)
File "/home/yangwb/local/lib/python2.7/json/decoder.py", line 360, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/home/yangwb/local/lib/python2.7/json/decoder.py", line 376, in raw_decode
obj, end = self.scan_once(s, idx)
UnicodeDecodeError: 'gb18030' codec can't decode byte 0xdf in position 0: incomplete
multibyte sequence
发生这个问题的原因,就存在于“運”字的编码之中。“運”的 GB18030 编码是 0xdf5c,由于第二个字符与转义符 “\” 编码相同,所以剩下的这个 0xdf 就被认为是一个 incomplete multibyte sequence。
我本来认为,既然已经提供了编码,json 模块就能够区分汉字与转义符(所以我觉得这应该是 json 的一个 bug)。但从实验来看,并非如此。对于一些不需提供字符编码的 JSON 解码器来说,我们倒可以用一种比较 tricky 的方法绕过上面这个问题,即在“運”字后面加一个额外的转义符:
{"a":"運\動"}
遗憾的是,这种方法对 Python 的 json 模块不适用。我仍不知道该如何解决这个解码问题。
中文编码——没错误!
对于相同的 case,Python 倒是能够编码成功:
$ cat in.dat
運動
$ python
>>> import json
>>> in_str = open('in.dat', 'r').read()
>>> out_f = open('out.dat', 'w', 0)
>>> dump_str = json.dumps({'a': in_str}, ensure_ascii=False, encoding='gb18030')
>>> out_f.write(dump_str.encode('gb18030'))
$ cat out.dat
{"a": "運動"}
所以这件事情就把我给搞糊涂了,Python 的 json 模块不能解码自己编码的 json 串。所以我觉得这可能是一个 bug,或者至少是 2.7.1 版本的 bug。
PS: 要仔细看文档
20120516:经网友 TreapDB 提醒,加载字符串时自己做 Unicode 转换,貌似能够解决这个问题。
$ cat decode.dat
{"a":"運動"}
$ python
>>> import json
>>> in_str = open('decode.dat', 'r').read().decode('gb18030')
>>> json.loads(in_str)
回头仔细看了一下 json 的文档,其中有这么一段:
Encodings that are not ASCII based (such as UCS-2) are not allowed, and should be wrapped with codecs.getreader(encoding)(fp), or simply decoded to a unicode object and passed to loads().
已经注明了 encoding 不支持非 ASCII-based 编码的参数,所以应该使用 getreader 进行转码,而不是让 json 模块去转码。看来是我没读懂文档,大惊小怪了,回家面壁去!
>>> json.load(codecs.getreader('gb18030')(fp))
今天博客服务器(Hostmonster 主机)全站从中午开始出现 500 错误,然后我登陆进 CPanel 各种查看日志、进程、数据库、PHP 状态,均未发现异常。后来又清理 php.ini、.htaccess,重启 PHP,也没有任何改善。只好给客服投了个 Ticket,准备等待客服解决。
后来灵机一动,发现同一主机 host 的其它 WordPress,有的活得很好,有的也是挂掉了。于是用排除法清理 wp-config.php,最终确定是 wp-config.php 中的 WP_CACHE 配置项有问题,删掉之后访问就恢复正常。
define('WP_CACHE', true); //Added by WP-Cache Manager
但由于 WP_CACHE 配置项是 WP Super Cache 自动增加的,一旦登陆进后台,WP Super Cache 就会自动把它再加上,后台页面又会出现 500 错误。于是乎我只好将整个 WP Super Cache 插件干掉(包括 wp-content 下的 php 脚本),终于一切恢复了正常。印象里删掉的 WP Super Cache 的版本是 0.9.9.*。
rm advanced-cache.php backup-* cache/ wp-cache-config.php plugins/wp-super-cache/ -rf
考虑到 WP Super Cache 还是对性能有一定改善,又看了一下最新版的 WP Super Cache 是 1.0 版,我怀疑是 WP Super Cache 版本较旧造成的问题。虽然该版本已经使用了很长时间,不明白为什么今天才会爆出来 500 错误(也许 Hostmonster 主机程序进行了升级?),我还是装上了最新版本 WP Super Cache 插件。期望它不要再出现类似问题,否则只能弃用了。
既然我的博客不是同一主机上的个例,我想可能在 Hostmonster 上的其它主机也可能会遇到此类问题,特记录下来供参考。
做后台系统比做客户端软件的辛苦的地方,就是不能让程序轻易地挂掉。因为在生产环境中无法容易地复现或调试 bug,很多时候需要程序日志提供足够的信息,所以一个后台系统的程序员必须要明白该如何打日志(logging)。
很多语言都有自己现成的 logging 库,比如 Python 标准库中的 logging 模块,Apache 的 log4cxx(C++), log4j(Java)。如果你愿意找,很容易能找到基本满足自己需求的日志程序库。当然,自己实现一个也不是很困难。难点不在于写这些库,而是如何去使用它们。
大部分情况下,我们关注的都是日志的级别和内容。即哪些情况下,该打哪个级别的日志,日志语句中,该怎么写。
在程序开发的过程中,我们需要很多的日志协助分析程序问题;但在生产环境中,我们没有那么多的空间存储丰富的日志,而且日志量太大对于问题排查反而是累赘。有些人使用预处理解决这个问题,在 debug 版本和 release 版本中编译进不同的日志语句。这样能够解决一些问题,但却使得在生产环境中无法轻易地打印更多的日志。大部分人更接受的做法是,使用配置(参数)控制日志的打印级别,在需要更多日志的时候,可以随时打开它们。为了实现日志“少但是足够”的目标,开发人员必须明白日志信息的价值,即哪些日志应该属于哪个级别。
日志的作用是提供信息,但不同的日志语句,提供的信息量却是不一样的。有的日志里会写“Failed to get sth..”,但却忘记加上失败调用的返回值。同程序一样,日志语句中有的是变量(某个变量内容),有的是常量(提示信息)。常量你总能从程序源代码中获得,但变量不行。所以在一条日志中,信息量最大的是变量,是函数返回值/字符串内容/错误码,因而变量应该尽量放在靠前的位置。常量也不是一点价值没有,写得好的提示语句,会使问题一目了然,可以免去你到代码中 grep,然后重读代码的麻烦。
上面这两点,几乎所有知道 logging 重要性的同学都会了解。但关于 logging 对性能的影响,很多人没有足够的警惕心。例如有人会在一个按行解析文件的函数中写下这样的日志:
int parseline(...)
{
log_trace("Enter parseline with ...");
DO_SOMETHING;
log_trace("Exit parseline with ...");
return 0;
}
乍一看,由于 log_trace 级别不高,在生产环境中肯定会关闭,那么这样做看起来对性能没太大影响。但实际上 log_trace 可能是这样实现的:
#define log_trace(fmt, arg...) \
xx_log(LVL_TRACE, "[%s:%d][time:%uus]" fmt, __FILE__, __LINE__,\
log_getussecond(), ## arg)
#endif
可以看到 log_trace 宏中自动添加了很多信息,值得注意的是时间参数 log_getussecond()。大家都知道统计时间需要系统调用,那么无论 log_getussecond() 函数是如何实现的,它的代价肯定是高于一般的简单函数。
我们本以为 log_trace 在 LVL_TRACE 级别被关闭的情况下,消耗的代价仅仅是一个函数调用和分支判断,却没有发现宏参数中还隐藏着一个需要调用系统调用的函数。当文件不大是还算能够忍受,但当这个文件是一个数据库,扫描每一行都要执行两次 log_trace() 时,它对系统性能的影响就绝不可忽视了。
所以,最佳的做法还是,在性能攸关的代码中,使用可被预处理掉的 logging 语句,仅仅在 debug 发布中才能见到这些日志,release 版本中不把它们编译进来。
此外,上面这个 log_trace,是一个糟糕的设计。logging 模块只应该干 logging 的事情,开发人员需要时间统计时会自己完成。
Network Connect 是 Juniper 公司出品的配合其安全硬件 VPN 解决方案的软件包,很多公司使用这个 VPN … Continue reading
exVim 是一个非常优秀的 Vim 环境,通过它能够省去很多 Vim 插件的配置工作。自从使用上 exVim 后,我基本没有再自定义 … Continue reading
上周末正好有事到望京附近,顺便带老婆和朋友一起逛了逛 798 艺术中心,照了几张照片。我在里面逛得倒是依然自得,可惜同行的两位女同学兴致寥寥,于是没待太久就散了。
预先声明:这不是一篇软文,我真的是出于对 MIUI 系统的喜爱才写的。 我的手机型号是华为 U8800,联通定制机,自带的 ROM 里太多乱七八糟的软件,后来就刷了华为官版的海外 ROM。这个 … Continue reading
我上次去上海是 8 年前,记忆都已经模糊了,只剩下几个场景比较清晰。与吴诗涛坐在沪宁双层城际列车的楼梯间地板上享受列车空调,路过华东理工收费的小足球场和封闭的草坪足球场,以及在臭水沟边秦嘉诚宿舍里塞紧蚊帐捉蚊子。在豫园吃过什么已经忘了,只记得自动售货机的可乐很贵,地铁也很贵。逛南京路步行街的时候在下雨,走到外滩雨更大。在雨中仰望了一下金茂大厦和东方明珠电视塔,不记得那时有没有环球金融中心。 上周因公差有幸免费重游上海。之所以说有幸是因为互联网公司不比其它行业,即使是大公司,出差机会也很少。对于华为、宝洁这样的公司来说,恐怕不出差反而是有幸了。我也是第一次坐飞机——不要笑我。通过飞机相连,两个城市间的距离变得好小! 这次到上海的第一印象是厚厚的云层,以及落地前虹桥机场附近的繁华,飞机就好像贴着闹市区的头顶飞过,宾馆饭店的霓虹灯广告牌都清楚可辨。出了航站楼,就闻到一股久违了的江南的潮湿空气,更重要的是,空气中没有煤烟味。经过北京漫长的冬天,这种感觉给人的冲击感太强烈了。 第一天是在张江工作,所以住在了锦江之星的张江店。我厂的上海研发中心跟安捷伦在同一幢楼里,很不起眼。室内装修跟大厦、奎科没有什么太大不同,哦,跟奎科没有什么太大不同。跟奎科一样,会议室也比较容易预定。开了一天会,不提。 由于次日是周末,所以当天晚上就坐地铁 2 … Continue reading
一直在特定领域的分布式系统一线摸爬滚打,曾取得一些微不足道的成绩,也犯过一些相当低级的错误。回头一看,每一个成绩和错误都是醉人的一课,让我在兴奋和懊恼的沉迷中成长。自己是个幸运儿,作为一个 freshman 就能够有机会承担许多 old guy 才能够有的职责。战战兢兢、如履薄冰的同时,在一线的实作和思考也让我获得了一些珍贵的经验,却直至今日才够胆量写出来一晒。这篇文章标题前面是“妄谈”两字,所持观点未必被所有人认可,我姑妄言之,有心之人姑听之。若有些友好的讨论,亦我所愿也。 我做的虽然也是分布式系统,却不够胆去讨论通用分布式系统的设计原则。因而这篇文章的主题限定到一个特定领域的分布式系统设计,这样即使别人有疑惑,我也可以把 TA … Continue reading
我以前是一个略具 geek 精神的人。现在不算了,写出来的好玩的计算机技术文章也没那么多了。虽然变无趣了,但我还有生活,所以我决定发掘一下其它的领域。生活中的琐事虽小,仔细琢磨下却有一些有趣的知识在里面。某些人从极小的时候就掌握的常识,对其他人来说可能到老都不明白。典型的例子有识别地图、指南针、手表或者分辨麦苗、韭菜等。 今天说的灯泡接口,也是类似。我从小到大,做过不少次爬上跳下换灯泡的活儿,本来觉得是一件很稀松平常的事情。但自食其力后才发现,原来也没那么简单,至少面临着一个复杂的问题:怎样选购正确的灯泡? 在我老家那个落后的小城镇里,很多东西都是二元的。免费电视信号只有两个:县台和县教育台;(铁路)地下道只有两个:东地下道和西地下道;灯泡也只有两种:(螺)丝口或者挂口。这样购买的风险很低。白炽灯泡一元一个,实在不行的话,买两个不同接口的灯泡就完了。反正在我小的时候有过买错灯泡的经历,算不得难堪。 长大后忽然发现,这世界不再是二元的了。典型的例子就是普通灯泡接口不再是两种,灯泡商品也不再是两种,当然价钱也不再是一元。去年年初,我老婆从公司带回来一个小台灯。它有一个圆盘形的底座,底座中央是一个笔直的灯杆,看起来像是一个倒立的图钉。在图钉的钉尖儿上是灯泡的接口,有一个可爱的圆柱状塑料灯罩可以把灯泡罩起来。灯泡的接口很奇怪,看起来是丝口,但又比丝口细。包装盒上的文字介绍极少,少到几乎无法阅读。在这些几乎无法阅读的文字中,我找到一个神奇的代码:E14。凭着直觉,我认出这应该是灯泡接口的型号。 我用来认知灯泡世界的模型改变了,只好重新建立模型。然后我才知道,原来我平常说的丝口,学名应该叫做“爱迪生螺旋(Edison Screw)接口” … Continue reading
