豪翔天下

Change My World by Program


  • 首页

  • 关于

  • 归档

  • menu.favorites

  • menu.wiki
豪翔天下

《重新认识你自己》——我还不懂哲学

发表于 2015-04-26 | 分类于 韦编三绝 |

这本书是我最好的一个朋友给我推荐的,在我们眼里他一直是哲学家。

之前以为,像这种书名顶多就会像某些心灵鸡汤一样,叫我们该怎么做,不该怎么做。但刚看到第一章“没有任何向导”的时候,我就震惊了,当时就意识到,这绝对是一本不可
多得的好书。绝对只有到达某种境界的人才能看懂。当完全拜读完此书后我真的懵了,我不知道该怎么形容他了,好多好多,我根本看不懂,不是像看技术书籍那种看不懂,而是
你一想书中蕴涵的哲理的时候你就感觉头脑发热,完全是一种“懵”的状态。

此书原名叫“Freedom From the Known”,当时看到这种翻译也是想了很久,后来发现,结合书中的内容,确实,翻译为“重新认识你自己”或许能更让
人理解。它一步一步地让你从你的世界观中挣脱,获得自由,获得新生。对于我这样一个还在象牙塔里的少年郎来说,连世界都没有观过,哪儿来的世界观呢。我知道的还太少,
我的人生经历还太少,所以我认为,这本书我还不适合看。

人生有三种境界,看山是山,看山不是山,看山还是山。我肯定还属于第一种。

再看王国维的人生三境界:
昨夜西风凋碧树。独上高楼,望尽天涯路。
衣带渐宽终不悔,为伊消得人憔悴。
众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。
这样看来,我还是在第一境界里游走。

总之,等等吧,等我有足够阅历了再回头看这本书,到那时,肯定会有不一样的理解。那时的我肯定是一个不一样的我。当然,我首先得认识我自己,当然不是一点一点累积与自
己有关的知识,去学习那些能够让我更深刻认识自己的工具。

语录:

多少世纪以来,我们被我们的老师、尊长、书本和圣人用汤匙喂大。我们总是说:“请告诉我,那高原、深山及大地的背后是什么?”我们总是满足于他人的描绘,这表示我们
其实是活在别人的言论中,活得既肤浅又空虚,因此我们充其量只是“二手货”人类。我们活在别人口中的世界,不是受制于自己的个性和倾向,便是受制于外在的情况和环境,
因此我们只是环境的产物,我们不再新鲜,我们从没有为自己发现过什么东西,我们的心中没有什么东西是原创的、清新的和明澈的。

认识自己便是智慧的开端

如果你说你要慢慢地学习认识自己,一点一滴地累积,这表示你并不在认识目前的你,你只是在累积有关自己的知识罢了。

你必须由衷地想去了解一件事物,才会付出全部的心力去觉察它。

所谓“活在当下”,就是在刹那间领会其中的美及喜悦,而不是眷恋它所带来的快感。

凡是不断追逐快感的心,无可避免终将面对它的阴影及痛苦。

该为恐惧负责的就是念头,道理就是这么简单呐,你不妨亲自观察一下。当你正在专心应付某种危机时,你并没有恐惧,等到念头一起,恐惧才由心生。

你一旦认清自己就是恐惧,和恐惧无二无别,自然会停止所有的斗争,然后恐惧就会完全止息下来。

只有停止比较,才能使自性呈现,能够活在自性中,才能有真正的平安。

自由乃是一种心智状态,它不是从某种东西挣脱的自由,而是一种自由的意识,一种可以怀疑和追问一切的自由,它强烈、活跃而富有生气,因此能清除各式各样的依赖、奴役
、臣服及逆来顺受的阴影。

随心所欲而不逾矩

如果你只裴阳你的孩子适应社会,那就是在训练他们如何斗争,那么他们就会被社会摘个。如果你真的爱你的孩子,怎么还会鼓励他们加入这场人间的苦战?

真正好好看过日出、日落或湖面月色的人寥寥无几

观念永远属于过去,而行动却属于现在,生活也是属于现在的。

念头跑来跑去并不是问题,让自己觉察每个念头的动向,才是关键所在。

豪翔天下

《浪潮之巅》——英雄造时势

发表于 2015-04-19 | 分类于 韦编三绝 |

说说本书

这本书可以说是每一个互联网爱好者的必读书籍,尤其是那些想在互联网界大显身手的人。这不是单纯的心灵鸡汤,而是一本史书,一本不仅仅陈述基本事实,还道出几十年来腥
风血雨中的不变规律。它给了你一个直观的感受,让你仿佛身临其境,短短几天就体会到互联网诞生至今的那些潮起潮落,而是否能踏上下一波浪潮,还得看你自己的造化。

《浪潮之巅》点燃了很多人创业的灵感,也点燃了我奋斗的激情,每次看到这种关于互联网的鸿篇巨著,我就庆幸自己当初选择了这个行业,并热爱上这个行业。在互联网,要踏
上浪潮之巅,很容易,也很不容易,这本书同样也削减了一些人的锐气,互联网的路上,一不小心,便会永世不得超生,它每天都在千变万化,我们要更加脚踏实地。

或许,正如书中所说,不是时势造英雄,恰好是英雄创造了时势。

据作者吴军介绍,这本书很多都是当年发表在谷歌黑板报上的文章,我之前没看过,但我知道,也只有吴军这样的经历才能站在那样的高度审时度势,酿造这本史诗。(另外,有
不少读者说此书存在许多细节错误,这些东西我觉得没必要去计较,毕竟,我们不是学历史…)

说说互联网公司

这本书有上下两册,上册主要讲互联网大公司的兴衰,下册则主要讲互联网的本质。从AT&T、IBM、摩托罗拉、Google、Apple这些大牌公司中发现,一个企业
能有发展,基因是决定性作用。我也曾一直在观察着,比如Google,我也一直找不到一个准确的词语来形容我的发现,后来发现了“基因”这个词,我才恍然顿悟,这不就
是企业基因吗?它伴随着企业诞生,也是企业能否踏上浪潮之巅的关键所在。

我想,我以后所在的公司,不看它有多壮大,而更看它是否适合我,是否是我想要的公司。(这句话并不代表我有多大的选择的余地,只是,这种公司肯定会有很多的)

说说我最喜欢的Google

我接触互联网比较晚,没有见证谷歌飞速发展的年代,我刚开始使用谷歌的时候,就正好碰上它离开某国的时候,作为一个愤青,自然对它多了一份喜爱。

我看互联网公司,主要是看该公司的所作所为。Don’t be evil!这是谷歌的格言,我想,谷歌或许还是做过一些对不起用户的事的,但是它依然在做着很多好事,
这才是真正以用户为中心的公司,踏上浪潮之巅也是自然而然的事情了。国内的一些公司也一样,七牛、Segmentfault等新兴的互联网公司也是深受大众的喜爱。

自由、开放、分享,永远都是互联网的代名词

豪翔天下

[转]一蓑烟雨任平生:奈良鹿丸的人生观

发表于 2015-04-18 | 分类于 韦编三绝 |

原文地址:http://movie.douban.com/review/2877890/

奈良鹿丸那段很经典的独白,恐怕是每个鹿丸迷都能背下来的:

“我本来想过着随便当个忍者,随便赚点钱……然后和不美又不丑的女人结婚,生两个小孩,第一个是女孩,第二个是男孩……等长女儿结婚,儿子也能够独当一面的时候,就从
忍者的工作退休……之后,每天过着下将棋或围棋的悠闲隐居生活……然后比自己的老婆还要早老死……我就是想过这种生活……”

鹿丸在《火影忍者》中本应是个不起眼的人物。模样不算帅,也不算丑;气质不算酷,也不算俗;武力不算强,也不算弱;出场不算少,也不算多。总之,不管从哪边算起,都不
是一个出头椽子。在木叶忍者的合影中,是一个拿放大镜才能找到的人。就是这样一个人物,在很多人气调查中,都排进了前五名。

鹿丸在《火影忍者》里有一个很特别的地方,就是他的人生观,也就是我们在上边引用的那一段。这个人生观既不高尚,也不低俗,却是鹿丸向往的,也是很多鹿丸迷喜欢他的重
要原因。

《火影忍者》这部动画片,一个重要课题就是,人生意义的追寻。几乎每场较大的战斗,敌我双方都会回顾生命的历程,探讨人生的意义。就像古雅典城里,哲学家们的论战一样
,人生观论战中输了的人,最终战斗也会输。《火影忍者》表面上看,是一场场忍者之间的对决;实际上,是一场场人生哲学的较量。就像TVB的时装剧一样,经常通过警察、
律师、厨师等特定职业,来探讨人生观;在《火影忍者》中,一个个忍者,就是一个个人生观的载体。很多忍者都背负着这个沉重的课题,甚至包括,以“食为天”的秋道丁次,
也曾经为生活的意义烦恼过。

鹿丸则不然,几乎没有和别人探讨过人生观的问题。因为他已经有了一个,不需要太大努力,就能实现的人生目标。在这个问题上,鹿丸并不困惑。

不像漩涡鸣人那样整天风风火火的,也不像宇智波佐助那样整天装帅耍酷,鹿丸始终给人一种从容的感觉。这种从容,来自于他对自己人生观的深信不疑。像鸣人、佐助、大蛇丸
那样,整天把人生观挂在嘴边的人,实际上,对自己的人生观并无十分把握;否则,就不会逢人便宣讲、证明自己的人生观。当然,也正是因为这样,他们才能成为主角人物,观
众在他们的矛盾和成长中,得到共鸣。

鹿丸另一个受欢迎之处,就是他给人的安全感。我们设想一下,一个由鹿丸带队的小组,和一个由鸣人带队的小组,显然前者更能给人安全感。这种安全感来自于鹿丸强烈的责任
感。在追回佐助的任务中,临出发的时候,鹿丸给队员们说了一段话:

“佐助跟我没有很深的交情,我也不喜欢这个人。但佐助也是木叶的忍者,是我们的同伴。所以我们要拼命把他救出来,这是我们木叶的风格。并且,虽然我的性格这样,但也不
会在这件事上怕麻烦。因为我的行为关系到你们的性命。”

说到责任感,就要说到鹿丸的另一个经典口头禅:

“真麻烦……”

对于责任,鹿丸是能躲就躲,即使是中忍考试这样“催人奋发”的大事,也是被鸣人一把推下看台,强行参加考试。从这一集的标题《云彩真是好啊……干劲zero的男人》,
即可以看出鹿丸的避世心态。

不过,避世不等于逃避责任。就像在追回佐助的任务中,临出发时,鹿丸给队员们说的那段话一样。鹿丸的嫌麻烦心态,意味着不愿意在没把握的情况下,轻率地承担责任;而一
旦承担了责任,就要信守承诺。

我们更常见的是,因为一时的激情,盲目承担下了工作、婚姻、朋友的责任。当激情过后,遇到困难时,便开始推三阻四,这才是俗人逃避责任的心态。

《世说新语》里有一个华歆和王朗的故事:

“华歆、王朗俱乘船避难,有一人欲依附,歆辄难之。朗曰:“幸尚宽,何为不可?”后贼追至,王欲舍所携人。歆曰:“本所以疑,正为此耳。
既已纳其自托,宁可以急相弃邪?”遂携拯如初。世以此定华、王之优劣。”

《谱叙》中也记载了华歆另外一件类似的事:

“歆少以高行显名。避西京之乱,与同志郑泰等六七人,间步出武关。道遇一丈夫独行,原得俱,皆哀欲许之。歆独曰:“不可。今已在危险之中,祸福患害,义犹一也。无故受
人,不知其义。既以受之,若有进退,可中弃乎!”众不忍,卒与俱行。此丈夫中道堕井,皆欲弃之。歆曰:“已与俱矣,弃之不义。”相率共还出之,而后别去。众乃大义之。
”

华歆能够在评估自己的实际情况后,作出正确决定,即这个责任承担不起。之后,这两个责任被王朗和郑泰等人强加在头上。既然承担了责任,就要承担到底,这是君子所为;而
王朗、郑泰之流,在这两件事上,只不过是“拍脑袋决策,拍胸脯保证,拍屁股就走”的俗人。

当第一天坐在新办公室的时候,当爱人偎依在自己胸膛的时候,当在酒桌上拍着胸脯对朋友说:“这事儿交给我”的时候,用《火影忍者》中的人生观来说,许下承诺的一刻,总
有一种“被认可”的快感。但不要忘了,这也意味着背负上了新的责任,很多时候,我们没有能力承担这个责任,或不愿意为此去努力,我们只想享受那一刻“被认可”的快感。

想想看,如果鹿丸像大蛇丸那样,完全没有责任感,只靠那不高尚,也不低俗的人生理想,很难产生如此强烈的人格魅力。

因此,鹿丸淡泊的人生观不是理想主义的写照,而是一个基于现实主义的理想。

喜欢鹿丸,就要像他那样,找到自己深信不疑的人生理想,并为自己所承担的责任而努力。

豪翔天下

[转]RESTful API设计指南

发表于 2015-04-11 | 分类于 编程之路 |

原文地址:阮一峰的网络日志

网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备……)。

因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现”API First“的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。我以前写过一篇《理解RES
Tful架构》
,探讨如何理解这个概念。

今天,我将介绍RESTful
API的设计细节,探讨如何设计一套合理、好用的API。我的主要参考了两篇文章(1,2)。

一、协议

API与用户的通信协议,总是使用HTTPs协议
。

二、域名

应该尽量将API部署在专用域名之下。

1
https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

1
https://example.org/api/

三、版本(Versioning)

应该将API的版本号放入URL。

1
https://api.example.com/v1/

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

四、路径(Endpoint)

路径又称”终点”(endpoint),表示API的具体网址。

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库
中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。

举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

  • https://api.example.com/v1/zoos
  • https://api.example.com/v1/animals
    • https://api.example.com/v1/employees

五、HTTP动词

对于资源的具体操作类型,由HTTP动词表示。

常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。

    • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
    • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
    • DELETE(DELETE):从服务器删除资源。
      还有两个不常用的HTTP动词。

    • HEAD:获取资源的元数据。

    • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
      下面是一些例子。

    • GET /zoos:列出所有动物园

    • POST /zoos:新建一个动物园
    • GET /zoos/ID:获取某个指定动物园的信息
    • PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
    • PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
    • DELETE /zoos/ID:删除某个动物园
    • GET /zoos/ID/animals:列出某个指定动物园的所有动物
    • DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

六、过滤信息(Filltering)

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数。

  • ?limit=10:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置。
    • ?page=2&per_page=100:指定第几页,以及每页的记录数。
    • ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
    • ?animal_type_id=1:指定筛选条件
      参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET
      /animals?zoo_id=ID 的含义是相同的。

七、状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [_]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
  • 401 Unauthorized - [_]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [_] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [_]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
    状态码的完全列表参见这里。

八、错误处理(Error handling)

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

1
2
3
{
error: "Invalid API key"
}

九、返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

  • GET /collection:返回资源对象的列表(数组)
  • GET /collection/resource:返回单个资源对象
    • POST /collection:返回新生成的资源对象
    • PUT /collection/resource:返回完整的资源对象
    • PATCH /collection/resource:返回完整的资源对象
    • DELETE /collection/resource:返回一个空文档

十、Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

1
2
3
4
5
6
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给
出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

Hypermedia API的设计被称为HATEOAS。Github的API
就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

1
2
3
4
5
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然
后就得到了下面结果。

1
2
3
4
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

十一、其他

(1)API的身份认证应该使用OAuth
2.0
框架。

(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

(完)

个人总结

RESTful设计风格是仅仅针对API的设计,其他的,比如新建功能页面的url还是需要自己另外定义的,当然可以在后面直接加参数,比如GET /zoos?add=1

豪翔天下

Python编码格式各种问题的解决方案

发表于 2015-04-07 | 分类于 编程之路 |

其实Python3的字符串默认是unicode格式了,但utf-8并不能解决所有问题。首先,我们得在所有的文件前加上这样一句:

# -_- coding:utf-8 -_-
或者




# coding: utf-8

其次,就是注意一般的常见的编码格式,爬去某些奇葩的网页时可能会遇到的,这时候只需要使用对应的转换进行了,如果不知道网页的格式就只能一个一个猜了,常见的有

ASCII
GB18030
GB2312
GBK
UTF-8
豪翔天下

程序员之内功修炼

发表于 2015-04-05 | 分类于 边走边想 |

直到上个月,我都还一直以为能使用个第三方库,想要什么功能去Google一下肯定能搜索到,这样就算是高手了。可是,最近的实习生应聘却让我吃了一些苦头。刚开始时
,我还一直想,为什么即使是BAT这样的大公司也都问那些在实际开发中根本就用不到的网上一搜就一大片的东西,感觉他们也落入了俗套。但最近看的几篇文章,遇到的几件
事却让我意识到自己犯了一个多大的错。

在这里,我想把只会堆砌代码的程序员称呼为码农,当然不是贬低,而是确实这个词比较直观,比如当年给进城务工的人士命名为农民工一样,绝对没有歧视的意思。
在我看来,内功包括三个方面:

熟练程度

这绝对不是一朝一夕就能简单掌握的,必须日复一日年复一年的写代码,才能达到所谓“熟练”的程度。不然,每次就像我之前那样,除了最熟悉的一两门语言、一两个框架外,
甚至是很基础的语法问题都得去搜一下,然后,每次遇到问题都重复地去寻找答案,完全没有技术含量,可却从另一方面说明,你仅仅只是一个码农。

创造能力

我不鼓励重复造轮子,但你得保证,在没有轮子的时候能自己造一个出来。前阵子因为业务的需要,得破解一下某系统的验证码,以前一直以为这种事情网上应该有很多的库吧。
结果去Google一下才让我大吃一惊,只发现了一个很古老的东西,pytesseract,是对Google Tesseract的一个简单的封装,识别效果差的令
人发指。没办法,只能计划着自己造,我以前跟着一位导师研究过一点AI方面的知识,知道这种验证码是可以实现很高识别率的。可是,从哪儿开始呢?这种时候,我这种码农
就只能呵呵了。

系统架构

天地万物为我所用。程序员,垒砌代码,总得知道在哪儿垒吧。如果,能够自己设计系统架构,能够清楚明白每一种技术选型的利与弊,那样,一个完整的工程才真正属于你,否
则,你永远只属于你那一方没人愿意去碰的基础的代码,而且,我总感觉,像这种重复性的劳动,总有一天会被更智能的东西取代。

当年,小米刚出来的时候,所有硬件都是出自别人之手,当时我很仰慕,能够集中所有最好的东西做出来一个产品,这样,应该很牛了吧。可后来发现,小米牛的不是这点,它也
在造轮子,至少我认为小米系统,是国内最好的一个改装版android,很难想像,小米没有MIUI会是多么的脆弱。
内功这种东西,有没有最重要,用不用才是另一回事儿。不过,是有一些工作是只需要码农的,那就是外包,而且是低质量项目的外包。

豪翔天下

[转]从0到100——知乎架构变迁史

发表于 2015-04-04 | 分类于 编程之路 |

转自:http://www.admin10000.com/document/5744.html

许很多人还不知道,知乎在规模上是仅次于百度贴吧和豆瓣的中文互联网最大的UGC(用户生成内容)社区。知乎创业三年来,从0开始,到现在已经有了100多台服务器。
目前知乎的注册用户超过了1100万,每个月有超过8000万人使用;网站每个月的PV超过2.2亿,差不多每秒钟的动态请求超过2500。

在ArchSummit北京2014大会上,知乎联合创始人兼 CTO
李申申带来了知乎创业三年多来的首次全面技术分享(幻灯片下载)。本文系根据演讲内容整理而成。

初期架构选型

在2010年10月真正开始动手做知乎这个产品时,包含李申申在内,最初只有两位工程师;到2010年12月份上线时,工程师是四个。

知乎的主力开发语言是Python。因为Python简单且强大,能够快速上手,开发效率高,而且社区活跃,团队成员也比较喜欢。

知乎使用的是Tornado框架。因为它支持异步,很适合做实时Comet应用,而且简单轻量,学习成本低,再就是有FriendFeed
的成熟案例,Facebook 的社区支持。知乎的产品有个特性,就是希望跟浏览器端建立一个长连接,便于实时推送Feed和通知,所以Tornado比较合适。

最初整个团队的精力全部放在产品功能的开发上,而其他方面,基本上能节约时间、能省的都用最简单的方法来解决,当然这在后期也带来了一些问题。

最初的想法是用云主机,节省成本。知乎的第一台服务器是512MB内存的Linode主机。但是网站上线后,内测受欢迎程度超出预期,很多用户反馈网站很慢。跨国网络
延迟比想象的要大,特别是国内的网络不均衡,全国各地用户访问的情况都不太一样。这个问题,再加上当时要做域名备案,知乎又回到了自己买机器找机房的老路上。

买了机器、找了机房之后又遇到了新的问题,服务经常宕掉。当时服务商的机器内存总是出问题,动不动就重启。终于有一次机器宕掉起不来了,这时知乎就做了Web和数据库
的高可用。创业就是这样一个情况,永远不知道明早醒来的时候会面临什么样的问题。

这是当时那个阶段的架构图,Web和数据库都做了主从。当时的图片服务托管在又拍云上。除了主从,为了性能更好还做了读写分离。为解决同步问题,又添加了一个
服务器来跑离线脚本,避免对线上服务造成响应延迟。另外,为改进内网的吞吐量延迟,还更换了设备,使整个内网的吞吐量翻了20倍。

在2011年上半年时,知乎对Redis已经很依赖。除了最开始的队列、搜索在用,后来像Cache也开始使用,单机存储成为瓶颈,所以引入了分片,
同时做了一致性。

知乎团队是一个很相信工具的团队,相信工具可以提升效率。工具其实是一个过程,工具并没有所谓的最好的工具,只有最适合的工具。而且它是在整个过程中,随着整
个状态的变化、环境的变化在不断发生变化的。知乎自己开发或使用过的工具包括Profiling(函数级追踪请求,分析调优)、Werkzeug(方便调试的工具)、
Puppet(配置管理)和Shipit(一键上线或回滚)等。

日志系统

知乎最初是邀请制的,2011年下半年,知乎上线了申请注册,没有邀请码的用户也可以通过填写一些资料申请注册知乎。用户量又上了一个台阶,这时就有了一些发
广告的账户,需要扫除广告。日志系统的需求提上日程。

这个日志系统必须支持分布式收集、集中存储、实时、可订阅和简单等特性。当时调研了一些开源系统,比如Scribe总体不错,但是不支持订阅。Kafka是Scala
开发的,但是团队在Scala方面积累较少,Flume也是类似,而且比较重。所以开发团队选择了自己开发一个日志系统——Kids(Kids Is Data
Stream)。顾名思义,Kids是用来汇集各种数据流的。

Kids参考了Scribe的思路。Kdis在每台服务器上可以配置成Agent或Server。Agent直接接受来自应用的消息,把消息汇集之后,可以打给下一个
Agent或者直接打给中心Server。订阅日志时,可以从Server上获取,也可以从中心节点的一些Agent上获取。

具体细节如下图所示:

知乎还基于Kids做了一个Web小工具(Kids Explorer),支持实时看线上日志,现在已经成为调试线上问题最主要的工具。

Kids已经开源,放到了Github上。

事件驱动的架构

知乎这个产品有一个特点,最早在添加一个答案后,后续的操作其实只有更新通知、更新动态。但是随着整个功能的增加,又多出了一些更新索引、更新计数、内容审查等操作,
后续操作五花八门。如果按照传统方式,维护逻辑会越来越庞大,维护性也会非常差。这种场景很适合事件驱动方式,所以开发团队对整个架构做了调整,做了事件驱动的架构。

这时首先需要的是一个消息队列,它应该可以获取到各种各样的事件,而且对一致性有很高的要求。针对这个需求,知乎开发了一个叫Sink的小工具。它拿到消息后,先做本
地的保存、持久化,然后再把消息分发出去。如果那台机器挂掉了,重启时可以完整恢复,确保消息不会丢失。然后它通过Miller开发框架,把消息放到任务队列。Sin
k更像是串行消息订阅服务,但任务需要并行化处理, Beanstalkd就派上了用场,由其对任务进行全周期管理。架构如下图所示:

举例而言,如果现在有用户回答了问题,首先系统会把问题写到MySQL里面,把消息塞到Sink,然后把问题返回给用户。Sink通过Miller把任务发给
Beanstalkd,Worker自己可以找到任务并处理。

最开始上线时,每秒钟有10个消息,然后有70个任务产生。现在每秒钟有100个事件,有1500个任务产生,就是通过现在的事件驱动架构支撑的。

页面渲染优化

知乎在2013年时每天有上百万的PV,页面渲染其实是计算密集型的,另外因为要获取数据,所以也有IO密集型的特点。这时开发团队就对页面进行了组件化,还升级了数
据获取机制。知乎按照整个页面组件树的结构,自上而下分层地获取数据,当上层的数据已经获取了,下层的数据就不需要再下去了,有几层基本上就有几次数据获取。

结合这个思路,知乎自己做了一套模板渲染开发框架——ZhihuNode。

经历了一系列改进之后,页面的性能大幅度提升。问题页面从500ms 减少到150ms,Feed页面从1s减少到600ms。

面向服务的架构(SOA)

随着知乎的功能越来越庞杂,整个系统也越来越大。知乎是怎么做的服务化呢?

首先需要一个最基本的RPC框架,RPC框架也经历了好几版演进。

第一版是Wish,它是一个严格定义序列化的模型。传输层用到了STP,这是自己写的很简单的传输协议,跑在TCP上。一开始用的还不错,因为一开始只写了一两个服务
。但是随着服务增多,一些问题开始出现,首先是ProtocolBuffer会 生成一些描述代码,很冗长,放到整个库里显得很丑陋。另外严格的定义使其不便使用。这
时有位工程师开发了新的RPC框架——Snow。它使用简单的JSON做数据序列化。但是松散的数据定义面对的问题是,比如说服务要去升级,要改写数据结构,很难知道
有哪几个服务在使用,也很难通知它们,往往错误就发生了。于是又出了第三个RPC框架,写RPC框架的工程师,希望结合前面两个框架的特点,首先保持Snow简单,其
次需要相对严格的序列化协议。这一版本引入了 Apache
Avro。同时加入了特别的机制,在传输层和序列化协议这一层都做成了可插拔的方式,既可以用JSON,也可以用Avro,传输层可以用STP,也可以用二进制协议。

再就是搭了一个服务注册发现,只需要简单的定义服务的名字就可以找到服务在哪台机器上。同时,知乎也有相应的调优的工具,基于Zipkin开发了自己的
Tracing系统。

按照调用关系,知乎的服务分成了3层:聚合层、内容层和基础层。按属性又可以分成3类:数据服务、逻辑服务和通道服务。数据服务主要是一些要做特殊数据类型的存储,比
如图片服务。逻辑服务更多的是CPU密集、计算密集的操作,比如答案格式的定义、解析等。通道服务的特点是没有存储,更多是做一个转发,比如说Sink。

这是引入服务化之后整体的架构。

豪翔天下

Python编码及注释规范

发表于 2015-04-03 | 分类于 编程之路 |

Python,我最喜欢的语言。但是,在其强大的功能以及强制的编码格式背后,也会引来一大波的编码方面的困扰,所以依然得需要进行一些规范化。

注释

函数或者文档的注释使用三引号,结尾空一行

函数注释需要注明三个参数:Args(参数)、Return(返回值)和Raises(抛出的错误),例如:

def exampleFunc(one, two):
    """
    这里是函数的功能





Args:
    one: 参数一的注释
    two: 参数二的注释

Return:返回值的解释,如果返回值比较复杂,比如是一个json数据,那么还需要将返回的格式卸载这儿

Raises: 非必须
"""</pre>

类的注释,依然得有一行文档字符串,若有共有属性,需要在注释处表名,例如:

class exampleClass(基类):
    """
    类的注释





Attributes:
    公有属性1: 解释
"""</pre>

文件注释:一般包括了编码信息、版权、许可声明、模块头等信息,例如:

# coding = utf-8




# Copyright 2015 ........




"""
这里是模块头,用一行文字概括文件或模块或脚本的作用
"""

命名

module_name:模块
package_name:包
ClassName:类
method_name:方法
ExceptionName:错误
function_name:函数
GLOBAL_VAR_NAME:全局变量
function_parameter_name:函数参数
local_var_name:局部变量
has_或is_:定义布尔类型元素

空行

两个函数的定义之间空两行,而方法或者语句模块之间则只空一行

空格

二元操作符之间添加空格

其它

对于常字符串,Python可以使用小括号将行隐式地连接在一起而不用在每行末尾加上加号,例如:

a = (
    'wang'
    'hao'
)
print(a)




# 打印出来就是wanghao
豪翔天下

[转]一图胜千言:谈框架和架构的区别

发表于 2015-03-28 | 分类于 编程之路 |

该文章是我之前在麦库里收藏的,但不知为何现在已找不到原文链接了,网上都是些转载文章,还不完整。如果作者能看到,希望作者可以联系我。为使文章结构更清晰,下文稍
有改动。

架构和框架的区别

笔者发现,人们对软件架构存在非常多的误解,其中一个最为普遍的误解就是:将架构和框架(Framework)混为一谈。本文阐述了它们的区别。算是对思辨成果的一个
应用吧。一图胜千言,下图切中肯綮地点出了架构和框架的区别。一句话,框架是软件,架构不是软件。

框架是一种特殊的软件,它并不能提供完整无缺的解决方案,而是为你构建解决方案提供良好的基础。框架是半成品。典型地,框架是系统或子系统的半成品;框架中的服务可以被最终应用直接调用,而框架中的扩展点是供应用开发人员定制的“可变化点”。

软件架构不是软件,而是关于软件如何设计的重要决策。软件架构决策涉及到如何将软件系统分解成不同的部分、各部分之间的静态结构关系和动态交互关系等。经过完整的开发过程之后,这些架构决策将体现在最终开发出的软件系统中;当然,引入软件框架之后,整个开发过程变成了“分两步走”,而架构决策往往会体现在框架之中。或许,人们常把架构和框架混为一谈的原因就在于此吧。

理解了本图,我们就很容易理解Frank Buschmann等人在《面向模式的软件体系结构(第一卷)》中为框架所下的定义了,其中也提到了框架和架构的关系:
框架是一个可实例化的、部分完成的软件系统或子系统,它为一组系统或子系统定义了架构,并提供了构造系统的基本构造块,还为实现特定功能定义了可调整点。在面向对象环境中,框架由抽象类和具体类组成。(A framework is a partially complete software (sub-) system that is intended to be instantiated. It defines the architecture for a family of (sub-) systems and provides the basic building blocks to create them. It also defines the places where adaptations for specific functionality should be made. In an object-oriented environment a framework consists of abstract and concrete classes.)

在以前经常和同事、朋友,甚至是合作伙伴之间进行一些技术上的交流,很多时候他们给我的印象是软件“架构”和“框架”被混为一谈,而我也曾经经历过这个阶段。在理解上
从模糊混淆到有所认识是需要投入足够的时间来进行理解的。软件“架构”和“框架”是两个不同的概念,但它们也是相互关联的。

软件框架是一特殊的软件,由实际的代码构建而成,是软件系统、子系统的半成品。软件框架为具体的解决方案提供了基础,提供了基础服务和可扩展点,同时软件框架也建立了一些约束,开发人员在此基础上进行特定业务功能的定制开发。例如,在J2EE企业级应用程序开发中,经常使用struts+spring+hibernate来搭建一个基本的项目结构,在没有其他特殊系统需求的前提下,这就是一个软件框架。

软件架构是引导如何设计软件框架的重要决策。它决定了软件系统如何划分,在一定程度上描述了被划分的各个部分之间的静态、动态关系。软件架构的决策体现在软件系统的框架中。

框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。可以说,一个框架是一个可复用的设计构件,它规定了应用的体系结构,阐明了整个设计、协作构件之间的依赖关系、责任分配和控制流程,表现为一组抽象类以及其实例之间协作的方法,它为构件复用提供了上下文(Context)关系。因此构件库的大规模重用也需要框架。

构件领域框架方法在很大程度上借鉴了硬件技术发展的成就,它是构件技术、软件体系结构研究和应用软件开发三者发展结合的产物。在很多情况下,框架通常以构件库的形式出
现,但构件库只是框架的一个重要部分。框架的关键还在于框架内对象间的交互模式和控制流模式。

框架比构件可定制性强。在某种程度上,将构件和框架看成两个不同但彼此协作的技术或许更好。框架为构件提供重用的环境,为构件处理错误、交换数据及激活操作提供了标准
的方法。

应用框架的概念也很简单。它并不是包含构件应用程序的小片程序,而是实现了某应用领域通用完备功能(除去特殊应用的部分)的底层服务。使用这种框架的编程人员可以在一
个通用功能已经实现的基础上开始具体的系统开发。框架提供了所有应用期望的默认行为的类集合。具体的应用通过重写子类(该子类属于框架的默认行为)或组装对象来支持应
用专用的行为。

应用框架强调的是软件的设计重用性和系统的可扩充性,以缩短大型应用软件系统的开发周期,提高开发质量。与传统的基于类库的面向对象重用技术比较,应用框架更注重于面
向专业领域的软件重用。应用框架具有领域相关性,构件根据框架进行复合而生成可运行的系统。框架的粒度越大,其中包含的领域知识就更加完整。

框架,即framework。其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟
的,不断升级的软件。

框架目前还没有统一的定义,其中Ralph Johnson所给出的定义基本上为大多数研究人员所接受:

一个框架是一个可复用设计,它是由一组抽象类及其实例间协作关系来表达的 【Johnson 98】。

这个定义是从框架内涵的角度来定义框架的,当然也可以从框架用途的角度来给出框架的定义:

一个框架是在一个给定的问题领域内,一个应用程序的一部分设计与实现【Bosch 97】。

从以上两个定义可以看出,框架是对特定应用领域中的应用系统的部分设计和实现的整体结构。框架将应用系统划分为类和对象,定义类和对象的责任,类和对象如何互相协作
,以及对象之间的控制线程。
这些共有的设计因素由框架预先定义,应用开发人员只须关注于特定的应用系统特有部分。框架刻画了其应用领域所共有的设计决策,所
以说框架着重于设计复用,尽管框架中可能包含用某种程序设计语言实现的具体类。

一个基于框架开发的应用系统包含一个或多个框架,与框架相关的构件类,以及与应用系统相关的功能扩展。与应用系统相关的扩展包括与应用系统相关的类和对象。应用系统可
能仅仅复用了面向对象框架的一部分,或者说,它可能需要对框架进行一些适应性修改,以满足系统需求。

面向对象的框架作为一种可复用的软件,在基于框架的软件开发过程中会涉及到框架的开发和利用两个方面的工作。框架的开发阶段在于产生领域中可复用的设计。该阶段的主要
结果是框架以及与框架相关的构件类。该阶段的一个重要活动是框架的演变和维护。象所有软件一样,框架也易于变化。产生变化的原因很多,如应用出错,业务领域变化,等等
。

不论是哪一种技术,最终都是为业务发展而服务的。从业务的角度来讲。首先,框架的是为了企业的业务发展和战略规划而服务的,他服从于企业的愿景(vision);其次
,框架最重要的目标是提高企业的竞争能力,包括降低成本、提高质量、改善客户满意程度,控制进度等方面。最后,框架实现这一目标的方式是进行有效的知识积累。软件开发
是一种知识活动,因此知识的聚集和积累是至关重要的。框架能够采用一种结构化的方式对某个特定的业务领域进行描述,也就是将这个领域相关的技术以代码、文档、模型等方
式固化下来。

详细介绍一下框架的作用

一、框架要解决的问题

框架要解决的最重要的一个问题是技术整合的问题,在J2EE的框架中,有着各种各样的技术,不同的软件企业需要从J2EE中选择不同的技术,这就使得软件企业
最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的
实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。

要理解这一点,我们来举一些例子:

一个做视频流应用的软件企业,他为电广行业提供整体的解决方案。他的优势在于将各种各样的视频硬件、服务器、和管理结合起来,因此他扮演的是一个集成商的角色。因此他
的核心价值在于使用软件技术将不同的硬件整合起来,并在硬件的整合层面上提供一个统一的管理平台。所以他的精力应该放在解决两个问题:

如何找到一种方法,将不同的硬件整合起来,注意,这里的整合并不是技术整合,而是一种思路上的整合。首先要考虑的绝对不是要使用什么技术,而是这些硬件需要提供哪些
服务,需要以什么样的方式进行管理
。因此,这时候做的事情实际上是对领域进行建模。例如,我们定义任何一种硬件都需要提供两种能力,一种是统一的管理接口,用于对所
有硬件统一管理;另一种是服务接口,系统平台可以查询硬件所能够提供的服务,并调用这些服务。所以,设计的规范将会针对两种能力进行。

另一个问题是如何描述这个管理系统的规范。你需要描述各种管理活动,以及管理中所涉及的不同实体。因为管理系统是针对硬件的管理,所以它是构架在硬件整合平台之上的。

在完成业务层面的设计之后,我们再来看看具体的技术实现。光有规范和设计是不够的,我们还需要选择一个优秀的技术。由于是对不同硬件的整合,我们想到采用Java提供
的JMX技术。JMX技术适合用来进行系统整合,它定义了一个通用的规范,并给出了远程管理端口的一些默认实现。JMX已经经过了实践的检验,不少的应用服务器都采用
了以JMX为基础的结构,例如有名的JBoss。JMX已经是一个很好的开始了,但是我们还需要在JMX的基础上再做一些工作。

二、什么要用框架?

因为软件系统发展到今天已经很复杂了,特别是服务器端软件,设计到的知识,内容,问题太多。在某些方面使用别人成熟的框架,就相当于让别人帮你完成一些基础工作,你只
需要集中精力完成系统的业务逻辑设计。而且框架一般是成熟,稳健的,他可以处理系统很多细节问题,比如,事物处理,安全性,数据流控制等问题。还有框架一般都经过很多
人使用,所以结构很好,所以扩展性也很好,而且它是不断升级的,你可以直接享受别人升级代码带来的好处。

框架一般处在低层应用平台(如J2EE)和高层业务逻辑之间的中间层。软件为什么要分层?为了实现“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易
于延展,易于分配资源…总之好处很多啦。

三、为什么要进行框架开发?

框架的最大好处就是重用。面向对象系统获得的最大的复用方式就是框架,一个大的应用系统往往可能由多层互相协作的框架组成。

由于框架能重用代码,因此从一已有构件库中建立应用变得非常容易,因为构件都采用框架统一定义的接口,从而使构件间的通信简单。

框架能重用设计。它提供可重用的抽象算法及高层设计,并能将大系统分解成更小的构件,而且能描述构件间的内部接口。这些标准接口使在已有的构件基础上通过组装建立各种
各样的系统成为可能。只要符合接口定义,新的构件就能插入框架中,构件设计者就能重用构架的bsp;
框架还能重用分析。所有的人员若按照框架的思想来分析事务,那么就能将它划分为同样的构件,采用相似的解决方法,从而使采用同一框架的分析人员之间能进行沟通。

采用框架技术进行软件开发的主要特点包括:

  • 领域内的软件结构一致性好
  • 建立更加开放的系统
  • 重用代码大大增加,软件生产效率和质量也得到了提高
  • 软件设计人员要专注于对领域的了解,使需求分析更充分
  • 存储了经验,可以让那些经验丰富的人员去设计框架和领域构件,而不必限于低层编程
  • 允许采用快速原型技术
  • 有利于在一个项目内多人协同工作
  • 大粒度的重用使得平均开发费用降低,开发速度加快,开发人员减少,维护费用降低,而参数化框架使得适应性、灵活性增强

四、与框架相关的概念

1\. 白盒与黑盒框架

框架可分为白盒(White-Box)与黑盒(Black-Box)两种框架。

基于继承的框架被称为白盒框架。所谓白盒即具备可视性,被继承的父类的内部实现细节对子类而言都是可知的。利用白盒框架的应用开发者通过衍生子类或重写父类的成员方法来开发系统。子类的实现很大程度上依赖于父类的实现,这种依赖性限制了重用的灵活性和完全性。但解决这种局限性的方法可以是只继承抽象父类,因为抽象类基本上不提供具体的实现。白盒框架是一个程序骨架,而用户衍生出的子类是这个骨架上的附属品。

基于对象构件组装的框架就是黑盒框架。应用开发者通过整理、组装对象来获得系统的实现。用户只须了解构件的外部接口,无须了解内部的具体实现。另外,组装比继承更为灵活,它能动态地改变,继承只是一个静态编译时的概念。

在理想情况下,任何所需的功能都可通过组装已有的构件得到,事实上可获得的构件远远不能满足需求,有时通过继承获得新的构件比利用已有构件组装新构件更容易,因此白盒
和黑盒将同时应用于系统的开发中。不过白盒框架趋向于向黑盒框架发展,黑盒框架也是系统开发希望达到的理想目标。

2\. 热点、食谱以及好莱坞原则

成功的框架开发需要确定领域专用的“热点” (Hot spot)。应用开发者在框架的基础上进行开发,只须扩展框架的某些部分,“热点”就是在应用领域的一种扩展
槽,开发者根据自己的需要填充这些扩展槽
。“热点”使框架具有灵活性,如在具体的实现中,扩展槽可以被看成是一些抽象类,开发者通过重写抽象方法获得具体实现。

“食谱” (Cookbook)就是描述如何使用框架方法的文档。在“食谱”中包含了许多“烹饪”方法,这些“烹饪”方法相当于一些具体的操作步骤,描述了为解决某一
专门问题如何使用框架的详细方法。框架的内部设计和实现细节通常不出现在“食谱”中。

框架的一个重要特征就是用户定义的方法经常被框架自身调用,而不是从用户的应用代码中调用。这种机制常称为“好莱坞原则”(Hollywood
Principle)或“别调用我们,我们会调用您”。

豪翔天下

在Github上设置webhook钩子

发表于 2015-03-22 | 分类于 编程之路 |

又搞了一天才搞好,主要原因是没看懂错误提示信息。

webhook:简单地说,使用webhook可以让每次在本地push到github上去后,让服务器自动pull下来,这样就不用每次提交然后手动pull的过程了。

下面是详细配置过程(基于laravel):

  1. 首先,得在laravel里添加一个路由,然后指向某个文件,例如路由为’/webhook’,然后指向PHP文件为’webhook.php’,然后在该文件内添加如下内容,该文件即是服务器响应Github webhook请求的文件:

    <?php
        $dir = '/var/www/test';
        echo shell_exec("cd /var/www/test");
        echo shell_exec("sudo git pull -f git@github.com:haoflynet/guake.git 2>&1");
    ?>
    
  2. 用户权限问题,由于执行该PHP文件的用户是apache的默认用户www-data,所以我在上面的命令中使用的是’sudo’,这样可以不用给www-
    data用户生成ssh,然后又出现各种混乱的问题。但是www-
    data又怎么免输密码执行git命令呢,还好linux提供了这么一个方面的操作,在’/etc/sudoers’文件里有如下内容:

    # User privilege specification
    root ALL=(ALL:ALL) ALL  # 默认的root用户可以在所有平台使用所有权限
    www-data ALL=NOPASSWD:/usr/bin/git # 这里给www-data赋予免密码执行git的权限
    
  3. 在仓库中添加钩子,执行vim .git/hooks/post-receive,然后添加如下内容,最后还要加入可执行权限:

    GIT_WORK_TREE=/var/www git checkout -f
    
  4. 在Github上进行设置

    如图所示添加webhook,设置中可以选择各种出发事件event,一般默认是push事件,在Payload
    URL中设置自己刚才所设置的URL,最后添加即可完成。

  5. 测试
    测试钩子的时候,可以直接在Github上面看到执行的列表,还可以看到每一个POST的详细信息以及响应信息:

TroubleShooting:

  • Laravel 5中如果要设置webhook,该POST路由不能使用CSRF
  • Django同样要禁用csrf:

    import os
    from django.views.decorators.csrf import csrf_exempt
    from django.http import HttpResponse
    
    @csrf_exempt
    def webhook(request):
        os.system('cd /var/www/admin')
    os.system('git pull -f git@github.com:haoflynet/admin.git 2>&1')
        return HttpResponse('ok')
    
1…567…15
haofly

haofly

豪翔天下的个人博客

147 日志
6 分类
RSS
GitHub 微博
小伙伴们
  • Phodal
  • zkzhao
  • 倾国倾城的博客
© 2016 haofly
由 Hexo 强力驱动
主题 - NexT.Pisces