介绍一下 graphviz/dot 的一些学习资料

最近一直在用 dot 来画图,如果你不知道 dot 这个东西,那么 graphviz 应该听说过吧,graphviz 是 dot 语言的一个实现。dot 是一个绘图语言,可以表达有向图和无向图,用 dot 语言描述的图可以 render 成 png、jpeg、pdf、svg 等等各种格式,非常方便。

比如下面这个 dot 文件:

可以渲染成下面这个文件:

dot-example

 

Mac 的 graphviz 提供了命令行的工具可以渲染 dot 文件。可以通过 brew 来安装: brew install graphviz 。

但是每次编辑然后切换到命令行渲染,再用 Preview 打开浏览,非常繁琐,这种文件修修改改是比较频繁的,预览体验很不好,比较合适的方法是用 jupyter 这样的 notebook 来实时渲染。Graphviz 目前没有可以直接在 jupyter 里面跑的 kernel,不过 Python 的封装是支持在 jupyter 看到结果的。这个缺点是你要写 Python 语言,调用 Python 的函数和方法,show 函数等等。不能写原生的 dot 语言。

我在这上面包了一层,调用这个包可以直接在 jupyter 的 cell 里面写 dot 了,项目的地址如下:

https://github.com/laixintao/jupyter-dot-kernel

这个 kernel 其实挺好写的,去看了一下文档,基本复用一下 IPython 的 kernel 就可以了。但是发送二进制数据回前端的 Response 结构很妖娆,文档也看不怎么明白,网上的资料也比较少…… 算是一个小坑吧,在这上面花了不少时间。最后是一拍脑袋,去找了其他 kernel 的代码看了一下才弄清楚的。贴在下面让大家感受下……

 

然后我还在 jupyter 里面写了一个教程(WIP),目前写了基础的教程, 看完之后基本都知道怎么样了。后面打算继续完善一些一些更高级的用法。教程的地址如下:

https://github.com/laixintao/learn-dot

 

我参考的主要是一些手册和文档,其实目前找到的也不是很多。那个 dot 文档竟然没有一个目录,有必要整理一下。

  1. dot 文档:
    1. The DOT Language
    2. Command-line Usage
    3. Output Formats
    4. Graph Attributes 这个文档比较关键,列举了所有可以用的属性
    5. Node Shapes
    6. Colors
    7. Arrow Shapes
  2. graphviz manual: man dot
  3. dot guide 官方 PDF
 

婚姻的决定:Everything Forever or Nothing Ever Again

这篇文章是在 hackernews 上面看到的,最近好奇怪,V2EX 和 hackernews 都看到很多感情类的帖子。这篇文章还不错,特地部分翻译一下。以下为译文。原文地址


一段感情的开始,并没有标准的模式。有些人是开始一次约会,然后再一次约会,然后再约会,有一天双方都自然而然承认这是一段亲密的男女关系了;有些人虽然一直在约会,但是只是朋友关系,直到一次正式确立关系的谈话,才算开始关系;有些人是一直保持着柏拉图式的朋友关系,但是表面之下一直有某种感情的涌动,直到一个突发的吻将感情点燃。

但一般情况下,第一次都是这样的:

之后,你们便沉浸在甜蜜之中:

你们之间的新关系会24小时陪着你,即使彼此不在身边。

你们将生活中所有烦心的事抛在脑后,这样的感觉非常好。接着,这种事情便自然地发生了。

 

所有那些歌词你都感同身受了。

这样持续一段时间之后,你开始意识到一些变化。最初的独角兽变成了普通的马,又变成了自行车,最后什么都不剩了。你爱慕的那个完美的人,开始做不完美的事情。一开始你觉得对方一些小癖好很可爱,如今变得烦人。你开始意识到自己和一个很糟糕的人在一起。

事情变得越来越糟,蝴蝶和彩虹变成了破灭的幻想,曾经给你鼓舞的感情现在成了你的枷锁。

曾经的你被爱蒙蔽了双眼,看不见的阴暗面现在清楚的呈现在你眼前,重重地牵绊着你。

大部分的感情都到此为止了。

但是也有可能,看到你的伴侣的黑暗面,你可以后退一步,看一下好的地方和不好的地方。你可以摘下有色眼镜,看看和你在一起的这个人:这个三维的、独一无二的、漂亮的、笨拙的人。

他是最好的。

也是最差的。

是你的队友。

也是你的后盾。

于是你决定珍惜你现有的生活。

从此一起生活。

当生活终于要走上正轨的时候,又有别的事情出现了。

 

世界上大多数的社会,都不喜欢让一段感情持续很久。对社会来说,一段关系只是一个简单的测试场——一个决定的孵化器。如果一段关系持续了很长时间,依然没有重大的决定(是否结婚),从社会的角度讲,这就是不合理的。为了纠正这个“不合理”,社会将从各种角度对这段感情施加压力。

有些人比社会还要强大,但大多数人不是这样的。对于我们大多数人来说,社会的规则就是我们的规则,当你和你的伴侣在蓝色的平衡木走下去时,你会感觉到可以行走的空间越来越少了。你必须要做出决定了。

你要决定,让现在的生活成为你将来每一天的生活,或者放弃现在的所有。

大多数人并不具备做出决定的能力。我们的生命很短暂,做出60年的承诺并不是一件小事。我们在一个小环境中长大,并没有面对多少选择。大多数人在面临这个决定的时候,经验相对较少。对一个独立的、成年的自我的理解不完全,甚至很多人才刚刚有了自我的意识。

但是社会不管,你必须作出决定。

 

就翻译到这里吧,后文中讨论了一般人做出决定的方法,以及对结婚这个决定的建议。如果你读到这里依然感兴趣,就去阅读原文吧。感情是很复杂的,每一个人面临的决定都不同。

 

索尼 Digital Paper Dpt-rp1 阅读器使用体验

自从 Kindle 面世以来,我已经买过三个了,买来之后使用率很高,基本没有沦落到“盖泡面”的下场。但是一直以来都有一个痛点就是,阅读不能重排的 PDF 太痛苦了,只能通过缩放放大字体,而缩放之后移动又很慢,所以我基本上只在电脑上看这种 PDF。

其实 iPad 是个不错的选择(除了看 PDF,这玩意对我来说是在想不出有什么用处)。最近本来期待苹果的发布会能发售一款新的 iPad,如果好呢,我就买新的,如果不好,那么旧款肯定会降价,就买旧款。结果发布会结束也没有出现新的 iPad,大概是觉得 iPad 实在找不到什么新的亮点了吧。

和同事吃饭的时候,被提醒索尼还有一个电纸书阅读器呢,于是吃完饭回来看了看,呵,这么巧,正好是今年 6 月份登陆大陆国行了,在京东售价 5666 元。然后就下单了,虽然超出预算好多。(跟同事吃了一顿饭被安利这么贵的东西,不过第二天我反安利了他一台显示器,算是扯平了)

第二天送到了。拿到的感受:廉价感!包装就一盒子,打开就一摞纸的手感(背面貌似是牛皮纸?)但是好轻、好薄啊!

仅仅是普通的盒子而已,很轻很轻。

打开发现本体和盒子一样大

欢迎来到崭新的阅读世界

这也太薄了,怕不小心真和纸一样被撕烂

官方的保护套 600 元,而且是牛皮纸材质的。心里感觉不值。于是买了一个第三方的保护套,350 元。后来想想也挺贵的啊!不过对比 600 的竟然没感觉了,肉疼。这个保护套手感挺好的,不足之处是让这个电纸书的总重量足足多了一倍。安装方式是 3M 胶粘上的,官方的保护套也是这样的。也能接受吧,毕竟一般不会拆。

第三方皮革材质保护套

打开的样子

充电线和索尼 PS4 的充电线貌似是一样的。说明书说要使用官方的充电线传输和充电,防止磁干扰,为了保险,还是听他的吧。

Digital Paper App 全平台都有,手机也有 App,但是体验太差,貌似没什么用。拔下传输线无须弹出操作,只要不在传输状态直接拔掉就可以。

展示一下阅读效果。

一本 PDF

纪念 John McCarthy

放笔的地方,磁力+凹槽卡住来固定

和 A4 纸的对比

放大之后使用手写笔体验很不错

阅读论文的效果

看漫画的效果

使用一段时间来看,优点和缺点都和网上大家说的一样:

优点:

  1. 分辨率高 1650*2200像素
  2. 大。完美解决了我的痛点,虽然缩放之后移动还是很慢,但是 13.3 英寸,和我上一台 Mac Pro 一样大的屏幕,你还用的着缩放吗?看书的字一般都比同本实体书的大
  3. 轻。349g。
  4. 带书写,做笔记方便。

缺点:

  1. 贵。可以理解,买的人少,相当于研发的费用只好少数购买者均摊了。
  2. 没有背光。这其实是对我来说最痛的,经常要坐车,我现在用的 Kindle 就是不发光的,没有背光很不方便。不过这么大的屏幕,背光很难打均匀吧。
  3. 软件垃圾。非常垃圾,比如 PDF 竟然不支持目录,不支持跳转;Wi-Fi 蓝牙传输特别繁琐,我到现在也没搞明白怎么用;不支持其他格式;竟然有 NFC 解锁屏幕的功能,大哥这个有个毛用啊,正规的功能你不好好做好。我还是就简简单单的用电脑来传吧。
  4. 手写笔笔尖磨损严重,这是大家都提到的。购买的时候把这部分也要考虑到预算里面去。笔尖10支 268 元。

我买书是从不含糊的,我觉得书无论多贵,买来只要看完就是赚到了,无论多便宜,买来不看一样是浪费。这个阅读器确实是有点贵了,不过能让我读更多书的话,也是值得的。很多 pdf 只找的到不能重排的版本。

不过一个替代的方案可以是 1,淘宝待打印邮寄过来;2,买一台打印机,6000 块钱够十几年的阅读量了吧。

另一个有意思的点是,整个包装、说明书和机器都在宣传一种“无纸化办公”的概念。在日本打字比较繁琐,有时候手写比打字还快,所以在日本的办公文化中,这款产品可能会解决浪费纸张的问题。电纸书内也提供了手写笔记、日程的功能,甚至可以用来写手帐?

 

最后推荐一些资源:

  1. 电纸书官方说明书 PDF
  2. 一个每天发一本盗版书的网站(羞耻)
  3. 不通过 APP 向电纸书 WiFi 传文件的脚本(in Python)
  4. Automated download of Sony Digital Paper notes on Linux

 

相关阅读:

  1. DPT-RP1使用体验
  2. 开箱
 

打开Wolfram之门

第一次接触到 Wolfram 是在 Matrix67 的博客,看到他博客上的精致的演示图片惊呆了,并且很多是动态的。后来从利器这一篇博客中知道博主用的是 Wolfram Language。软件叫做 Mathematica,是一款商业软件,类似 matlab。那时候 Wolfram 就给我留下了一个“很漂亮”的印象。

后来我一直想寻找一种作图的工具,可以用一种语言描述我想表达的数据结构,让我展示在博客上或者 slides 中。Dot 语言貌似是一个不错的选择,它非常简单,表达能力也还可以,基本上花1个小时弄懂它的 AST,然后试一下,就可以谙熟于心了。并且它的实现 Graphviz 还有 Python 版本。这里还有一个 Python 的库 GraphvizAnim 可以基于 Graphviz 制作动态的图片。

然而这个库唯一的缺点就是,它的每一个点的位置是自动布局算法生成的。因为渲染出的图片位置不完美,我曾经一直在网上搜索控制节点位置的方法,后来看到维基百科明确的说明,才放弃。维基百科的一个例子渲染出的结果如下图。

dot 渲染的缺陷

首先无法渲染出正方形,其次 (gof)’ 的位置也有问题。解决这个问题,要么借助其他 svg 编辑工具调整位置,要么就忍受这种自动布局算法的缺陷。Graphviz 提供了不同的工具,它们的不同仅仅是渲染节点的布局算法不同。

  • dot – filter for drawing directed graphs
  • neato – filter for drawing undirected graphs
  • twopi – filter for radial layouts of graphs
  • circo – filter for circular layout of graphs
  • fdp – filter for drawing undirected graphs
  • sfdp – filter for drawing large undirected graphs
  • patchwork – filter for squarified tree maps
  • osage – filter for array-based layouts

最近又想起来 Wolfram 这个好东西,Matrix67 对它的评价如此之高,不去试试可能会失去很多乐趣 :)

Wolfram 是 Stephen Wolfram 耗费了 30 年的心血开发的,目的是建立一种 Knowledge Based Language,比如说每个国家的首都,每个国家的国旗,这种通用的知识。也内置了很多丰富的库函数,一行代码几乎可以做到任何事情。

Naming everything after yourself, huh?

——Youtube网友评论

了解 Wolfram 的强大,可以去 Youtube 看 Stephen 的这个视频。视频中快速展示了几种酷炫的用法,第一次看这个视频的时候让我目瞪口呆,也是这个视频勾起了我学习 Wolfram 的好奇心。有意思的是,Wolfram 是一个基于符号的编程语言,这个最初有点难以理解,比如说你引用了一个不存在的符号,解释器不会报错,而是当做一个定义的符号来对待。图片、公式、地图、地理位置、未知变量 x 等都可以叫做符号。Stephen 的视频中为了解释“面向符号的”语言,说了太多次 Symbolic,所以视频下面有很多欢乐的吐槽。

New drinking game: drink every time Stephen says the word “Symbolic”

 

没有纠结太多,我在官方注册了 15 天 TRIAL。然后按照邮件的指引下载了一个安装工具,但是安装工具一直卡,使用代理下载也不行。尝试了 10 多次都这样,于是就去用户论坛发帖求助。

Wolfram 安装提示 “Feiled to update the catalog”

有人回复说给客服提交工单,客户会邮件直接发给你全部程序下载链接,可以跳过安装器。于是我 9 月 3 日提交了一个工单,9 月 8 日收到了回复,邮件有 Mac 和 Windows 的下载链接,大约 4G 左右。Mac 上面下载完之后按照普通 dmg 安装就可以。

安装好之后,终于可以弹钢琴尝试一下了。

Plan a city tour 和钢琴 Demo

配置目前看起来有些复杂,很多稀奇古怪的问题。比如 Proxy 一直导致网络有问题,直接连接就没问题了。以及 Kernel 不知道为啥最多开启 4 个,只要多余 4 个就会有问题。

快速编程入门》很不错,还有面向 Python 程序员的解释。一个晚上就可以上手。后面打算读一下《Wolfram 全书》。

 

回来更新下,在 Wolfram 的价格页发现除了 Industry 的 license,其他的购买都只有 4 个 Kernel 可以用。我不知道这种纯粹限制用户机能的限制有什么意义。

Number of Mathematica Computation kernels available for parallel computing across an equivalent number of cores. Mathematica Core Extensions can be used to extend parallel support for machines with additional cores.

 

使用uWSGI的spooler做异步任务

最近项目上线,遇到了比较烦的问题,我们无法在线上环境使用 redis。原因貌似是 redis 对集群不太友好,高可用比较难做。所以公司没有现有的可以申请用的 redis 集群(但是有类似的替代品)。

在之前解决分布式定时任务的时候,我引入了 celery,但是很可惜,celery 目前支持的几个 Broker 在我们这里都没有。想了很多方案之后,还是决定不再用 celery + redis 的组合了。寻找一个不依赖外部 Broker 的异步队列。

其实需求主要是两部分:1 需要支持定时任务功能,并且多个节点不能重复执行,这就需要一个全局的 Lock 之类的东西。 2 能够执行一些异步的任务,比如用户发请求,直接返回 Response,表示请求成功,然后再慢慢处理任务。

找了一圈之后,发现 uWSGI 自带的 spooler 功能基本可以满足异步任务的需求。定时任务可以使用 django-cron 。这篇文章分享下 spooler,下一次再分享下 django-cron 这个项目吧。

spooler 解决的主要是这样一种场景:收到用户请求的时候要执行一个耗时比较长的任务,比如发送邮件,通过网络请求更新数据库的一些数据(我们就是这种),而用户可以不必关心任务执行的结果,只要知道任务成功开始执行了就行了。

spooler 的原理

异步任务队列的生产者可以是任何能产生 spool file 的程序,是可以跨语言的。任务用用一个文件夹下面的文件来表示的。指定一个文件夹,一个文件就是一个任务。

后端应用 app 可以往 spool 中放任务(调用 spooler 的 API 生成一个文件),然后uWSGI 启动的时候会将 spawn 出来 spooler 进程,就是 worker,处理这些异步的任务,任务处理成功就将文件删掉。如下图。

 

尝试 spooler 第一步

首先我们新建一个 django 项目来演示 spooler,方便读者阅读。依赖只有 django 和 uwsgi ,通过 pip 安装即可。然后用 django-admin 开启一个新的项目。需要执行的命令如下:

然后我们可以使用 uwsgi 来启动项目了。

可以访问下 localhost:9090 端口看是否启动成功。

将任务放入队列

将任务放入队列我们只要调用 uWSGI 的 spool 函数就可以了。可以接受一个 dict 或者直接是 keyword args。我们在上一步生成的 demo django 项目中写一个向任务队列添加任务的函数如下,直接写在 urls.py 里面了。

代码比较好懂,访问 URL add_task 的时候就会调用 write_task 往队列里面 spool 一个任务。其中要注意的是 spool 的内容在 Python3 中必须是 bytes 的。

我们使用下面的命令执行,执行之前,需要先建立 task 文件夹,我们用这个文件夹来存储任务。

如果你仔细看的话,会发现最后的输出信息如下。

确实有了 spooler 的 worker ,而不加 --spooler 参数的话是没有的。

最后的提示是说没有找到 spooler function ,这是因为我们没有写消费者,所以目前任务会被成功放进去,但是不会被执行。可以试一下,访问我们事先定义好的 localhost:9090/add_task。可以看到每访问一次,task 文件夹就会多一个文件。

其中,spool 函数还有以下特殊的参数,可以满足更多对任务定制的需求。

  • ‘spooler’ => specify the ABSOLUTE path of the spooler that has to manage this task
  • ‘at’ => unix time at which the task must be executed (read: the task will not be run until the ‘at’ time is passed)
  • ‘priority’ => this will be the subdirectory in the spooler directory in which the task will be placed, you can use that trick to give a good-enough prioritization to tasks (for better approach use multiple spoolers)
  • ‘body’ => use this key for objects bigger than 64k, the blob will be appended to the serialzed uwsgi packet and passed back to the spooler function as the ‘body’ argument

编写 spooler 函数(消费者)

spooler 相当于是 celery 的 worker,是真正将任务取出来进行处理的部分,实际就是从 uWSGI 设置的 spooler 文件夹处理每一个文件。如果你 spooler 写的不对,或者文件夹配置不对的话,这个文件夹会越来越大,相当于任务积压没有被处理。

uWSGI 是跨语言的,perl,ruby,python 都可以写 spooler。下面是一个 Python 的 spooler 的例子。

这里要注意的是,返回的值必须是以下 uWSGI 内置的 int 值:

  • -2 (SPOOL_OK) – 任务成功,spool 文件将会被删除
  • -1 (SPOOL_RETRY) – 任务失败,将会被重试
  • 0 (SPOOL_IGNORE) – 忽略任务,在多语言环境可能导致竞争,使用此返回值可以让某些语言的实例跳过此任务

我们可以将这部分代码保存在项目下面的 worker.py 中。由于这段代码在 uWSGI 启动的时候不会被执行,所以启动命令加一个 --import 参数。

启动之后可以看到 tasks 文件夹中的文件逐渐消失了,每5s 少一个,同时 uWSGI 打印出了执行记录。

另外,通过 spooler-process 参数可以控制并发量。比如下面这个命令开启 4 个 spooler 进程。

一些高级的特性

如果单机有多个 uWSGI 的实例,但是只想启动一个干活的,其他的都只负责 spool 任务。那么可以使用 External spool

另外放任务的过程,其实就是 uWSGI 打包好一个任务写到一个文件里面,所以如果我们向网络中其他 uWSGI 实例,通过 socket 写入,也是可以的,这样就可以使用 Networked spoolers

任务权重。在上面的内容中,已经介绍过 spool 函数有一个 priority 参数,可以控制任务的权重。实际上 spooler 在运行的时候,会扫面文件夹,如果扫描到数字,就会优先深度执行数字文件夹里面的内容。但是 uWSGI 执行的时候要加 --spooler-ordered 参数。

比如下面这个 spooler 文件夹的内容:

实际执行的结果会是:

其他还支持一些 Options 参数,可以参考文档

非常重要的 Tips

我是照着 uWSGI 的文档学习的,可以说这个文档很不友好…… 不是按照初学者的路线组织的,纯粹是解释项目组织的,跟一个 wiki 一样,可能是不同的人一直加 feature 然后更新文档导致的吧…… 总之原来的文档最后一段是比较重要的,本文也是。如果你看到一半就关掉这篇文章,那么你惨了……

第一点,如果要在实例之间共享内存,可以使用 uWSGI 的 cache 或者 sharedarea

第二点,也是比较重要的一点:Python 有 uwsgidecorators.py ,Ruby 有 uwsgidsl.rb 。不要直接用本文介绍的低级 API

使用优美的装饰器

如果你使用本文介绍的这些函数的话,可能已经发现,只能写一个延时任务,因为你修改的是全局的 uwsgi.spooler 的值。如果要支持多个任务,就要自己写 dispatcher,像参考资料1中做的那样。

uwsgidecorators.py 里面提供了 3 个很有用的函数。

uwsgidecorators.spool 可以帮你自动分发多个任务,用起来非常像 Celery。还可以自动帮你设置返回值(默认是 uwsgi.SPOOL_OK)。

uwsgidecorators.spoolforever 功能同上,不同的是此装饰器永远返回 uwsgi.SPOOL_RETRY ,也就意味着这个任务会永远被重试,永远被执行。

uwsgidecorators.spoolraw 这个函数需要用户自己写返回值。

有兴趣的也推荐看一下这些装饰器的源代码。可以看到它会帮你处理很多事情,所以千万不要用原始的 API 啊,装饰器就够了。

 

参考资料:

  1. 使用uwsgi实现异步任务  手把手的教程,不错
  2. uWSGI文档
  3. uwsgi_tasks 这个项目对 spooler 不太友好的 API 进行了封装