Month: 3 月 2014

换位思考

有个很有意思的现象, 就是说不管是怎样的人, 温文儒雅的男生或是温柔知性的女性, 一坐上驾驶位, 立马就会变成悍匪和泼妇, 另一个说法是司机们眼里其他司机只有两种, 比我慢的傻逼和比我快的二逼

我自己开车几个月了, 感觉大部分情况确是如此. 当我们是行人或骑行者时, 经常会抱怨车不让我, 车乱开, 雨天溅人一身水等等, 而在驾驶位上的时候, 经常痛恨的又是那些不按标线和信号灯过马路的非机动车, 随时可能乱冒出来的行人. 其实多去换位思考下, 可能就没那么多问题和纠结. 自己还是尽量做到以下内容, 希望诸位司机和行人共勉

司机角色

多看行车标线, 不随便变道, 特别是去不熟的地方, 提前在导航上做下功课, 提前看好各种路牌, 要变道事先准备, 避免急匆匆变道被人滴甚至被人撞上

不野蛮开快车, 也不开太慢挡住其他人, 高速或快速路上开慢了挡住后面人一是让人烦心, 二是其实反倒不安全, 自己如果慢, 自觉去右侧道, 把左边留给要超车的人

绿灯了赶快起步给后面人拉开空间, 杭州大部分交通灯都有倒计时, 提前挂档准备松离合走人, 节省宝贵的绿灯时间, 提升通行效率

停车时多考虑下经过的车和前后左右别的停着的车, 不要让其他车以非常难受的姿势从你身边擦过去, 另外也考虑下停车位置旁边车经过的速度, 速度快的地方多留点空, 后视镜能叠起来就叠起来给其他人省空间

尽量让行人, 经过时减速, 在路口和公交站附近多留意随意乱穿马路的人, 特别要注意小朋友和女性, 尤其是还在打电话或玩手机的, 切记不要用常规思路去理解还在吵架的情侣 (最后这一条是开车以来唯一一次差点撞上人, 一对吵架的情侣从公交站前非斑马线横穿, 走到一大半又突然折返, 防不胜防)

行人和骑行角色

行人走路边的人行道, 骑车时走非机动车道, 别跟汽车去抢. 骑车如果被逼的走机动车道了, 尽量靠边让车能从你旁边安全经过, 不要非得让车需要擦着你过, 有能转进非机动车道的机会立马转进去, 命比速度重要多了

过马路走斑马线, 看自己的灯, 并留意机动车的绿灯情况, 红绿灯还分左右转等方向性的, 不要随意看到个绿灯就过

多左右看, 保持自己是匀速直线运动, 不要突然冒出来或停下来, 汽车其实没法很快刹住, 而且突然加减速会让车很难受, 司机一般不会喜欢在你面前玩个急刹然后烧胎起步的

不要站路上让车很难受的位置, 比如支路路口的正中间, 让车从你左边或右边过都很不爽, 没事往旁边站一点, 方便他人, 也让自己处于更安全的位置

路上就尽量别低头玩手机了, 打电话也长话短说, 多注意看四周环境, 雨雪天时司机的视野下降很厉害, 自己打着伞也多回头看看, 新交规更保护行人, 但最好也别被用上相关条款, 用上一般都是出事了

饭团性能优化记

缘起

前年冬天在人人时, 为了方便组里一起吃饭的同学们互相算账, 参考以前度厂的饭团设置, 在团队里拉起一个饭团, 然后写了个小系统来记账

设计

一开始的想法是这样的

1. 饭团设置一个团长, 团长管饭团的钱, 出去吃饭时由团长付钱
2. 每顿饭按人均消费额, 扣除参团人的余额
3. 每个人把钱交给团长, 余额不足时由团长催促交钱

所以设计的数据模型是

    人 {
        id,
        姓名
    }
    饭 {
        id,
        付款人,    # 外键, 多对一
        参与人,    # 外键, 多对多
        消费额
    }

吃饭就在饭那个表里加一条记录, 充值也算一顿特殊的饭. 每顿饭后的账面和最终余额按时间遍历所有记录实时算, 这样一是省了记每顿饭后余额的存储开销, 二是避免有历史修改而需要更新余额表一堆数据的麻烦事. 考虑到饭团也就十来个人, 在可预见的未来数据量人最多到百级, 饭撑死也就是千级, 每次遍历的代价应该也不大 (事实上在我写本篇文章的时候, 饭团历史总人数不到 20, 算上充值转账等总共也不到 500 顿饭)

这个余额实时计算的思路和 BitCoin 的余额判断方法也挺像的, 正是因为我写饭团踩了不少坑, 所以我觉得 BitCoin 某些方面还是有很大问题的, 这个回头另外讨论

另外为了统计方便和可追查, 希望记录每顿饭是哪天在哪吃的, 新增和修改数据

    饭 {
        ...
        日期时间,
        店        # 外键, 多对多
    }
    店 {
        id,
        饭店名
    }

后来考虑未来可能有人因为转岗或离职离开饭团而饭团里还有余额需要退款, 新增两个特殊的店来记录充值退款操作, 自此数据模型设计完毕. 最终的 sqlite schema 如下

    CREATE TABLE "ft_people" (
        "id" integer NOT NULL PRIMARY KEY,
        "name" varchar(200) NOT NULL
    );

    CREATE TABLE "ft_deal" (
        "id" integer NOT NULL PRIMARY KEY,
        "restaurant_id" integer NOT NULL REFERENCES "ft_restaurant" ("id"),
        "pay_people_id" integer NOT NULL REFERENCES "ft_people" ("id"),
        "deal_date" datetime NOT NULL,
        "charge" real NOT NULL
    );
    CREATE INDEX "ft_deal_75ae3b0c" ON "ft_deal" ("pay_people_id");
    CREATE INDEX "ft_deal_be4c8f84" ON "ft_deal" ("restaurant_id");

    CREATE TABLE "ft_deal_peoples" (
        "id" integer NOT NULL PRIMARY KEY,
        "deal_id" integer NOT NULL,
        "people_id" integer NOT NULL REFERENCES "ft_people" ("id"),
        UNIQUE ("deal_id", "people_id")
    );
    CREATE INDEX "ft_deal_peoples_1a9336ea" ON "ft_deal_peoples" ("deal_id");
    CREATE INDEX "ft_deal_peoples_3cff102f" ON "ft_deal_peoples" ("people_id");

    CREATE TABLE "ft_restaurant" (
        "id" integer NOT NULL PRIMARY KEY,
        "name" varchar(200) NOT NULL
    );

因为懒得自己去管理数据的写和更新操作, 刚好那段时间看了下 django, 感觉自带 ORM 和 admin 组件的 django 会是开发的好选择, 于是对着 tutorial 学过去后就开工了. 很快写完, 框架用的 django1.5, 数据库用 sqlite, 页面是裸写的 html, 没有任何 javascript, 仅有的一点 css 也硬编码在 html 文件里了

功能和美化

用了一段时间后发现离一开始的设定有一些变化, 比如团长不一定每顿饭都出席, 那需要有另外的人付账, 然后团长又要给付账的人团费, 还不如直接让付账人的钱直接进饭团余额. 这个功能用最初的功能也可以做到, 只是让团费的作用没那么清晰了. 用到后来, 发现其实是不需要有饭团团长这个设定的, 每顿饭谁付钱就算谁的, 反正饭团记录的是每个人的帐户余额, 团费其实就是团长的帐户余额. 需要交团费或互相转账时直接添加一顿转出人付款, 参与人只有收款人的特殊虚拟饭就可以了, 于是又加了个叫转账的虚拟店来记录转账操作, 自此充值和退款两个虚拟店就变得毫无用处了

一开始所有饭团记录都只有一页, 后来应大家的统计需求, 按参与人/付款人/店分别做了个过滤器, 这个实现的很简单, 就是对不符合过滤器的记录, 只计算不输出就行了

当饭团运作了半年多后, 单页的饭团太长, 又将默认页面改成只看最近一个月的, 另外提供了个翻页的按钮和查看全部的选项. 另一个问题是饭团成立时的团员有人转去了其他团队不再一起吃饭, 这些人最近的记录都是空的, 放着一是不好看, 二是人多了页面宽度超过很多人显示器的大小, 于是给人加了一个 “是否活跃” 的属性, 默认不显示那些不活跃的人

前不久回头去看饭团的前端, 觉得虽然算不上丑死人, 但是也没好看到哪去, 刚好就用 bootstrap 套了下, 并把各种过滤器提供表单输入的功能弄成一个查询表单. 本打算把表单直接塞导航栏, 结果发现 bootstrap 原生的 select 什么的真心太丑, 放导航栏严重破坏美感, 后来找了个 bootstrap-select 的插件来支持, 这个就很赞了

用上 bootstrap 时一嫌自己管理 css/js 麻烦, 二怕又扯上被人盗用跑流量的狗血, 直接用了国内大公司的 cdn 内容. 后来用 bootstrap-select 时, 发现国外的 cdn 太慢, 国内又没找到靠谱的, 就只能在自己项目里拷贝了一份, 结果测试环境都 OK, 在线上的 fastcgi 环境里总显示有问题, 提示找不到文件, 怒了在 nginx 里对自己的 static 文件夹又加了一条 alias 才行. 后来想这么弱智的事情不会是 django 的问题, 就去找官方文档, 在 https://docs.djangoproject.com/en/dev/howto/static-files/ 里来回看了几次才发现最后有一段关于怎么 Deployment 的, 原来还要收集一次, 也还是要加 static alias 的嘛, 只是解决了为什么之前要给 static/admin 单加一条的问题. 从这个角度来说, django 还是略复杂蛋疼, flask 就简单的多, 完全交给你自己去弄, 而且 templates 和 static 都汇集放好管理, 或许 django 是为了给每个 app 单独的分发权?

性能优化

饭团弄好后先是架在了公司我跑 Ubuntu Server 的台式机上, 直接就用 runserver 的模式跑的. 后来因为台式机偶尔会掉电, 饭团没设开机自动启动, 偶尔也会忘了开, 加上内网 IP 不一定固定, 用起来还是有点小烦, 于是迁移到我的 VPS 上

我贪便宜 15$/yr 买的 buyvm VPS, 内存只有 128M, 之前曾经写过一篇各种压榨内存的优化记录, 饭团丢上去就发现这货居然还是内存大户, 搜了下改成用 flup 以 fastcgi 的模式跑, 并把实例压到只有一个, 反正访问也不频繁, 不用处理啥并发. 用了小半年后觉得偶尔有点卡, 不过一直认为是 buyvm 的机器烂加上服务器在美国多半是网络延迟, 就没再管他

等到去年冬天的时候, 发现这慢的已经完全不成样子了, 而且有报页面超过返回大小, 将默认页面改成只看最近一个月这也是个主要原因, 当时还以为是网络的问题导致卡 (我那个 vps 走联通线路只有不到 50KB/s 的速度)

今年过完年, 在想在新团队是不是也能搭个这货玩, 把之前的数据拷贝到本地去测试了下各项功能, 发现打开首页需要接近 10 秒, 这都是本地了, 不能再赖网络, 于是加各种 Debug 信息去看到底慢到哪里. 实时的余额计算流程大概是这样

    遍历所有饭:
        获取饭的信息, 包括关联的餐厅和付款人等
        遍历所有人
            判断是否参加了本顿饭, 如果没有
                直接沿用上条记录
            如果有
                判断是否是付款人, 如果是
                    增加本顿饭总额扣掉自己那份的进余额
                如果不是
                    从余额里扣掉本顿饭钱
        添加输出信息

这里面的参团判断是用 O(n^2) 遍历实现的, 一开始就十来个人, 就算是平方复杂度也能慢到哪里去, 结果一堆 debug 信息放下去那个地方还真是特别慢. 仔细想了下估计是那个遍历所有人做判断的地方, 每次都新做了一次 SQL 查询, 好吧, 把判断用的表先遍历一次提出来做个 dict, 果然快了一些. 经过这步后耗时从 10s 降到 1s, 感觉再快也快不过跨太平洋的网络耗时, 就没再继续压榨性能

上一个改动做完没几天笨狗折腾了个 digitalocean 的 VPS 玩, 这个延迟又低速度又快, 于是有想着对那个 1s 的性能做优化, 按说这么点数据要 0.1 秒都不正常. 继续琢磨, 猜是每次取一顿饭, 都做了若干次 SQL 查询去取外键数据, 于是把人和餐厅的数据都预先提取出来构建 dict, 然后查询的时候使用就好, 这样又能快一点. 再回头去看那个多出来的辅助表, 猜是那个表每遍历一顿饭又去做了一次查询, 干脆自己把 view 里的查询都裸写, 每次页面请求都把四个表数据都 select * 出来, 然后自己去拼, 反正数据也不复杂, 这样一次页面请求只用四次 SQL 查询, 果然速度就降到了 0.01s 内. 因为数据量不大, 对内存压力也几乎没有, 而且 digitalocean 的内存有 512M, 也不用那么抠内存

问题感想

我中间曾经想要不要换 flask 重写一次, 自己管数据库, 后来找到性能瓶颈后还是留在了 django 那, 能用就懒得去动, 而且自己写个 admin 还是略麻烦

跟熊吐槽 django 的 ORM 怎么这么烂, 深度插件控的熊表示你这个一定有合适插件来帮你干这事而不是靠自己裸写 SQL 的, 不过笨狗表示有找插件和配置的时间, 我裸写的东西早搞完了. 果然笨狗还是又笨又懒, 还好目前看也还没太多篓子

对了, 饭团的 github 开源地址在: https://github.com/whusnoopy/fantuan, 欢迎 fork 帮忙优化

最后挂个 DigitalOcean 的邀请链接: https://www.digitalocean.com/?refcode=8a3c1464993e 如果你通过这个注册并付款, 我会有返点支持我继续用 DO

Life is Cool

前几天赫然发现二月整个月都没写东西… 好吧, 先随便唠叨点给补上. 偶尔推荐下音乐也挺好的: Sweetbox – Life is Cool

0x00 感觉过年后回杭州就没几天晴天, 一直在下雪下雨, 不过最近还好只是春雨细斜
0x01 有时候回去晚点或者不赶巧还是会把车停外面, 反正都要早起挪车, 不如顺便把喵送去上班
0x02 上下班的点和周末跑上塘中河高架完全就是找虐, 还不如走地面
0x03 按说从市区出来走高架是挺快的, 但是德胜高架文一路的下口基本上没个十分钟是下不来的
0x04 加上德胜转盘早上东往南大排队, 南往西要穿过前面那条队, 算起来高架上光堵就要一刻钟到半个小时
0x05 还有一次在堵在下匝道的时候听广播说有司机开着奥迪在匝道上睡着了, 说前晚跟朋友聊太晚困的不行
0x06 上面那个事据说最后旁边司机过去敲门都没敲醒, 过了十分钟交警来了才给弄醒
0x07 喵这个月转去麻醉科要早到, 贼心不改六点半上高架奔市区, 发现还是堵的一塌糊涂
0x08 受堵车加每天回家找车位要兜几圈影响, 另外每天上班距离不长车都没热开就到了, 这箱油开的太费了
0x09 昨天加完油回来一算, 前面这箱油开到了百公里 9 升油耗, 每公里油钱都快七毛了
0x0a 登云路上那个中石化加油站靠东南的自助加油机 93 号油的卡槽有问题, 每次都不认我的卡
0x0b 早上上班顺路过去时加油站在卸油, 我遇上这事的概率是不是太大了点
0x0c 新办公室装修基本搞完了, 家具大部分进去了, 昨天带公司人都去看了下, 大家表示味道能接受
0x0d 整个过程都是被各种拖延, 看来预计工期过于乐观这事也不是程序猿特有的问题嘛
0x0e 桌子和柜子被拖了一天, 到了后发现桌子下的小柜子比我要求的小太多了, 跟老板说好重做另送一次吧
0x0f 椅子倒是比预计的还早了一天到, 只是第一批椅子 EMS 小哥被隔壁楼物业坑的不行从上午拖到下午
0x10 被坑的小哥只是在地下车库拖着包裹走把地板油漆划了点不仔细看都看不到的印子而已
0x11 然后被要求赔一千块, 我和一起愉快的过去收货的熊到现场看了下表示尼玛隔壁楼物业就是神经病
0x12 但是这事我们也不好掺和, 只能默默的去吃饭然后看事情没有解决的迹象就又回去了
0x13 后面其实就没啥好说的了, 这个椅子装的时候还是要费点小劲的, 坐过的人都说好
0x14 当然好了, 一千多块呢, 有些小细节不够完美跟厂商反馈时人家表示再贵点的椅子就没这个问题了
0x15 好吧果然还是一分钱一分货, 要真忽悠群主买他家三千多的椅子这种事我还是开不了口, 而且没必要
0x16 新办公室的楼里只有联通的光缆, 一众表示南方只有电信靠谱的人同觉得大厦物业真黑
0x17 联通奇葩放光纤的模式我完全看不懂, 反正我是不能理解为什么我们办公室就有三层光交换设备
0x18 联通更奇葩的是办事效率, 上周五过来调了一次设备, 然后拖到今天才有人来办开通
0x19 联通最奇葩的是为什么开通宽带和电话这种小事要派三个人来, 为了表示对我们的重视么?
0x1a 但是你们三个人只有一个在干活, 另一个在围观卖萌, 还有个在跑腿拎包这算什么事
0x1b 最最重要的, 你们都来了三个人了, 最后跟我说上面弄错设备号了暂时还是没法用这又是闹哪样
0x1c 默默自己去吃吉祥馄饨, 吃到一半的时候背后有人问店员要吸管, 心想这边不卖喝的你要吸管做咩
0x1d 那哥们说我要吸管喝汤, 我擦这是怎样的矫情喝汤有勺子你不用, 而且热饮烫口不能用吸管吧
0x1e 等我走的时候特意看了下要吸管的居然还是个中年大叔, 估计店员当时一脸的黑线比我密多了
0x1f 最近交警叔叔们似乎各种勤快, 不管是早高峰站路口指挥的, 还是早高峰站路口抓限行的
0x20 连抓违停都勤快了好多, 昨天给新办公室去买锁路上就碰上抄牌的, 吓得赶紧先开走再找锁店
0x21 另外我去年十月被城管贴的违停条终于在交警系统也能查到了, 到底什么时候去交呢
0x22 周末小区门口买水果时遇到城管拍照门口垃圾乱放, 我以为要开条了结果城管大叔进去拿扫把给帮扫了
0x23 罚款不是目的, 大家把问题解决这样才好嘛, 这么想我那次应该去找下应该还在附近的城管的
0x24 最近折腾装修的时间更多, 正事都没怎么干, 貌似我把后台接口写完就放那了
0x25 迁 VPS 的时候还发现之前的饭团性能差的不行, 一次查询居然要快十秒才出结果
0x26 看了下对自己一年多前写的 django 已经不能理解了, 抽空改 Flask 实现吧
0x27 数据模型自己管 sqlite 好了, 反正也不是多复杂的数据结构
0x28 管理后台也自己写好了, 同样反正功能很简单, 也没啥安全需求, 就算被黑找每日备份恢复下就行