关于alias和alias_method的真相

alias和alias_method

alias是什么?它是一个方法么?当然不是啦,它是一个逼格很高的keyword并且它永远指向当前作用域的self。keyword是语言的一部分不可被ruby程序覆盖。例如有下面这段代码:

1
2
3
4
5
6
7
def alias(&scope)
scope.call('1024')
end
alias :io, :puts
io "hola"
# 输出 hola,可见这里alias并没有被覆盖

永远指向当前作用区的self

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A
def hola
"hola"
end
def self.add_hello
alias :hello :hola
end
add_hello
end
class B < A
def hola
"你好"
end
add_hello
end
puts A.new.hello # 输出 "hola"
puts B.new.hello # 输出 "hola"
# 可见 add_hello 方法里的alias并没有因为调用的scope变了导致hello方法变了

alias是引用还是创建?(以下是我的机器上输出的结果):

1
2
3
4
a = A.new
a.method(:hola).object_id # 输出 70144731241200
a.method(:hello).object_id # 输出 70144731223640
# object_id并不一样 由此可以推断是创建了一个的对象

聊聊alias_method。它是一个ruby标准库的方法(它默认写在Kernel的私有实例方法下面)。不是词法的一部分,是标准库的一部分。它很灵活,而且是一个on call的self绑定。它可以被重写(当然一般我们不这么乱用膜法):
什么是on call?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A
def hola
"hola"
end
def self.add_hello
alias_method :hello, :hola # 由于是方法因此这里是要给参数的,当参数和形参不匹配的时候会报错的
add_hello
end
end
class B < A
def hola
"你好"
end
add_hello
end
puts A.new.hello # 输出 "hola"
puts B.new.hello # 输出 "你好"
# 这就是on call 因为B类里重写了hola方法 所以B类里的alias_method会根据B的方法生成hello方法

bcrypt-rubyの探索

bcrypt-ruby

这是一个简而美的gem,用于实现加密功能:(如用户密码)。因为简单易用的特性,被rails官方团队加入了Gemfile里。

简而美

1
2
3
4
5
6
7
8
9
10
11
12
# Gemfile
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'
## 这是一段在rails里简单的使用demo
# app/models/user.rb
class User < ApplicationRecord
has_secure_password # 默认需要有一个password_digest字段来存密码
end
User.find(1).password_digest
# $2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy

WTF就这样?

你可能会吐槽,握草!居然不需要使用者指定salt,莫非是公用同一个salt?这种加密跑个简单的rainbow table不就好了?
握草太可怕了,我再也不敢用gem了,我要去造轮子。骚等,先喝杯咖啡静静。
再回到之前的一段密文字符串里:

1
"$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"

根据wikipedia里的描述,bcrypt加密后是由于$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy这3段字符串构成的密文密码。
$2a表示所用的bcrypt算法的的版本,$10表示需要计算的次数。下面一段字符串逼格比较高,也是本例密文里的核心部分。这段密文其实是一段混合密文密码+salt的字符串。
需要换个姿♂势解读我们把$忽略,留下N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy。前者是一段密文,而后者是一段salt。
WTF?日了狗了?这段salt哪里来的?!莫非是有人黑了我的代码?!莫方。它的诞生其实是来源于我们的bcrypt-rubygem。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# lib/bcrypt/engine.rb
def self.generate_salt(cost = self.cost)
cost = cost.to_i
if cost > 0
if cost < MIN_COST
cost = MIN_COST
end
if RUBY_PLATFORM == "java"
Java.bcrypt_jruby.BCrypt.gensalt(cost)
else
prefix = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
__bc_salt(prefix, cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
end
else
raise Errors::InvalidCost.new("cost must be numeric and > 0")
end
end

是不是瞬间被这个gem所折服了?
http://okh71i0eg.bkt.clouddn.com/yangwangjugenshaonv.jpg

参考资料

https://en.wikipedia.org/wiki/Bcrypt
http://stackoverflow.com/a/6833165
https://github.com/codahale/bcrypt-ruby

2016年终总结

2016 11/28

最终还是没有忍住,比15年提前了一个月写了这篇年终总结。这一年发生了太多的事情。
因为个人原因我五月末离开了暴漫,加入了新公司(第一财经新媒体)。几个月后暴漫发生了大规模的裁员,上海、成都、南京等部门都几乎没了人。
感叹现实是多么的无情,因为裁员的原因,我开始和以前不怎么关注的同事小聚交流,我们一起吐槽在暴漫的日子,后来发现他们身上都有着不少闪光点(如果你看到了在暴漫工作的简历,请不要犹豫、约他/她来面谈)。
简单的review去年的总结,好像什么都没做,也好像都做了很多计划之外的东西,但是都不够完整,不能拿出来吹逼。

2016年我的收获

  • kindle
  • 鼠绘板
  • 一些书
  • 一些正版软件
  • 一家全新的公司
  • 尝试了用js做后端开发,学习了Elixir,生产我也用上了docker
  • 一个独特的朋友
  • 一些idea
  • 一个新目标(运营一个公众号)

生活不只是眼前的苟且,还有诗和远方

我不知道这句话是否适合做标题,但它在我最需要的时候点醒了我。生活不能看着眼前,你还有更多需要去尝试的东西等着你去挖掘,有些是期待你去挖掘,有些则可能与你无缘。
但是不尝试,赖在原地怎么会知道生活的味道?
在加入新公司之后,经历了一些不愉快,分析下来80%是自己的问题20%是公司的环境,生活不只是眼前的苟且,还有诗和远方,我在交际方面存在许多问题,但是逃避好像并不能治本。
应该把在技术上的热情放在生活上,这才是我现在的钢需。在一些好朋友的帮助和鼓励下,我开始慢慢去学习跳出自己的圈子,开始尝试一些曾经不喜欢的事情(看非技术书,尝试运营公众号和
一些外向的人交流,去人多的商场,学习手绘)。发掘原来我的生活除了网络,还有更多的东西值得去花时间和精力去投入。这是一个难忘的成长阶段。

要成为什么样的人?

我要成为火影!我是要成为海贼王的男人!。总是会被问到,将来想成为什么样子的人之类的话题。
这些话虽然显得很中二,但因为我并不是很了解自己,我也不敢乱给自己定无法实现的目标。不过正是因为有这样的问题,我才会反思自己。然而总结出来的自己却是模糊的。
按照某本书上的话来说,人在生活中总是会遇到吸引你的事物,如果它是人,你可能会去模仿他,如果它是物,你可能会想办法获取它。因为这样才可能发展出你的特点。
虽然不知道自己未来能成为什么样子的人,但是未来的性格和爱好,多半是由这些事物所影响的。人们都喜欢收藏这些事物,可能是人,也可能是物。可以把这些东西看作是一个圈子。

来上海有一年多了,我圈子小,接触的大多数都是搞技术的,有艺术型爆栈程序员,有创业大佬,还有天才型程序员以及野路子程序员。他们身上都有吸引我的点,颠覆了我曾经眼中的程序员模样。
于是我也开始模仿,曾经我觉得程序员大多数思想jiang化,比较木呐。直到有一天我发现他们真的不只是会coding,有的是因为从小的教育所导致,爱好广泛(手绘,旅游,写作,音乐),编程只是爱好之一。
有的是眼界广,不光着迷与技术,还热爱人文科学。从他们身上我总结出来,一个人的思维活跃度,取决于他对这个社会的关注点,然而关注点则取决于眼界。

对于目前的我来说,需要去跳出自己的圈子,去接触更多的人,尝试更多的陌生事物,才会有所成长。
以前我对技术看的特别的重要,我会花费大量的精力和时间去填补自己的缺漏,最后发现,努力决定下限,天赋决定上限。这句话还是蛮有道理的。
我好像没什么做技术的天赋,但是我会去努力提高自己的下限。我没什么显著的优点,我在努力方面还是愿意花费精力的,2017年希望把更多时间放在做有意思的事情,提高眼界。

2017的计划表

  • 坚持用Linux编程
  • 坚持刷题
  • 用Elixir做个side project
  • 多读书
  • 学习手绘
  • 学习写作
  • 学习运营公众号
  • 去舟山吃海鲜,去杭州品真宗的杭帮菜

致新的自己

做有趣的工程师。

译-what-is-reactive-programming

翻译what-is-reactive-programming

什么是响应式编程?

要理解响应式编程,理解它的编程范式以及它背后的机制,可以帮助我们了解当今的开发者以及公司所面临的挑战,与十年前的挑战的差距。
这些年对于开发者和公司来说有两个很明显的变化:

  • 硬件
  • 互联网
    虽然”围着篝火讨论过去”被认为是最lowbee的一种对话形式,但我们应该通过探索我们职业的历史,来解决每个开发者即将面临的问题。

有什么东西都不同于现在了?

有大量的知识,被我们在处理几十年前的计算机领域知识中发掘。响应式编程是一种尝试推广的知识,为了讲它应用在下一代软件中。

1999年

我开始学习java的时候是1999年,当时在Canadian Imperial Bank of Commerce实习:

  • 那时候互联网有2亿8千万的用户。
  • J2EE那时候只是Sun MicroSystems心中的梦想。
  • 网银应用属于刚起步阶段 - 5岁所有
    早在1999年以前我就开始遇到了并发的问题。那时候的解决方案涉及到线程和锁,让复杂的东西变得正常仅针对于开发者中的老司机。
    当时Java的主要卖点在于”write one, run anywhere”(一次编写到处运行), 但是anywhere是指安装了JVM的操作系统里,
    那时候还没有云的概念,以及大量的并发连接,我们现在正在设计的东西在互联网时代。

2005年

2005年过去的时间并不长,但是计算机和互联网却有了不小的改变,正是J2EE,SOA(面向服务架构)和XML热潮,Ruby on Rails的诞生
对于痛苦的J2EE容器部署模型是巨大的冲击。
一些有趣的事实:

  • 互联网以及有了十亿的用户。
  • Facebook 以及有了5.5亿的用户。
  • YouTube 在2005年的二月诞生了。
  • Twitter 还没出生,(诞生于2006年)
  • Netflix 还没有介绍视频流(诞生于2007年)

2014年

在2014年,也正是写这篇博客的时间,按照Internet Live Stats的统计大约有将近29亿5千万互联网用户。
中国就有6亿4千万的互联网用户。美国有2亿8千万。
如今两个最流行的网站:

  • Facebook 有13亿用户。
  • Twitter 有2亿7千万用户。
    时代在变化。一个单一的网站如今可以尽可能的处理更多地流量,然而互联网变化还不到10年。
    https://cdn-images-2.medium.com/max/1200/1*HAoewX140mJ8JnogRGPr5g.png
    很容易看出,我们正在面临技术栈趋势的问题,期望,以及软件在我们生活中的重要性。还可以看出,一些范式没有继续上升到现在,当然未来也不会。

响应式的四大原则

响应式应用基于四大指导原则。

  • 响应式程序是目标
  • 响应式程序具备可伸缩以及弹性。响应没有可伸缩性以及弹性是没法实现响应性的。
  • 消息驱动架构是可伸缩性、弹性以及最终响应系统的基础。
    让我们探索以高层次来探索每一个原则,以便于理解为什么它们必须一起应用,以提高现代环境软件开发的质量。

响应式

当我们说一个应用程序是响应式的时候,我们想表达什么?
响应式系统是迅速对所有用户做出反应,为了确保固定且正确地用户体验。
在不同的场景下,快速以及正确地用户体验依赖于响应式应用的两个特性:弹性以及可伸缩性。
消息驱动架构是响应式系统的根基。
为什么响应式系统架构如此重要?
在异步的世界里,有以下这样一个场景:你正在烘焙一壶咖啡,但是你突然发现缺少奶油和糖。

  • 一种解决方案:

    • 开始烘焙一壶咖啡
    • 当咖啡在烘焙的时候,走到商店
    • 买奶油和糖
    • 回到家
    • 咖啡刚好烘焙好,开始喝咖啡
    • 生活如此美好
  • 另一种解决方案:

    • 去商店
    • 买奶油和糖
    • 回到家
    • 开始烘焙咖啡
    • 耐心的看着咖啡在烘焙
    • 对咖啡因开始反感
    • 烘焙真TM麻烦

    如你所见,在消息驱动的架构下异步边界提供了时间和空间的解耦。让我们继续探索异步边界的理念。

一致性在沃尔玛加拿大的应用

在加入类型安全之前,我正带领着一个用Scala语言和Play框架的团队开发沃尔玛加拿大的电子商务平台
我们的目标是一致性的用户体验,无论在一下的任何场景:

  • 在桌面,平板以及移动设备的浏览器打开welmart.ca
  • 在流量达到高峰时,不论是在秒杀级别还是在持续保持
  • 一个重大的基础设施故障,例如数据中心

    在以上的场景下,无论是响应时间和还是用户体验必须保持一致。一致性的重要性是你网站交付的根本,考虑到如今网站能代表你的牌面。
    一个糟糕的体验比起一个破落不堪的店铺更难以被忘记或忽略,因为它们都发生在网上。

Gilt的零售的响应式

在电子商务领域一致性不允许出现一次意外,在Gilt中有这样一个场景,每天中午都会放上即使抢购会经历秒杀级的流量。
让我们设想一下即使抢购的网站应该有的用户体验,如果你的浏览器在上午11:58和下午的12:01访问时,你应当得到同样地用户体验,但这时Gilt网站的访量正处于高峰期。
Gilt实现一致的用户体验以及响应体验,通过实现Reactive(响应式)实现。想要更多地了解Gilt从Rails迁移到基于Scala的微服务架构,可以了解一下采访Gilt的vp Eric Bowman的文章

弹性

大多数的应用程序被乐观的设计和开发着,没有一些特殊场景的考量。这看上去会有较少的几天会出现程序gg的情况或者被黑客攻击到宕机,数据丢失,有损名誉。
弹性系统应当有良好的设计以及合理的架构原则,为了保证在极端的场景下也能有和普通场景一样的体验。
虽然Java和JVM可以无缝的将单独一个应用程序部署到不同的操作系统。如今(201x)年的互联应用,都是程序级别的组合,要保证相互的可连接性,以及安全性。
现在的应用程序会有许多个子程序构成,整合成一个web服务或者是别的什么网络协议。如今要构建一个程序,可能会依赖好多个外部的服务。它可能会服务给大部分的客户端,有用户也有别的系统。
还有多少开发者,把用心去整合这些复杂的东西:

  • 有没有分析并建模是否需要有外部的依赖?
  • 有没有Document应在整合的每一个服务的理想时间内响应。并且做在高峰值流量以及持久访问的性能测试,合理的评估是否符合预期。
  • 有没有将所有的性能,失败的场景和其他的非功能性的期望都作为核心应用程序的逻辑的一部分。
  • 有没有分析并且测试每个服务可能失败的场景
  • 有没有分析外部依赖的安全性,考虑整合是否会造成漏洞?
    弹性在大部分应用中是最薄弱的环节,尤其是业务复杂的,但是弹性作为一种以后才考虑的传说很快就要GG。现代应用程序必须是有弹性的才能保证在真实地世界里和不理想的环境里保证能够即使的响应。
    性能,安全性和稳定性都面临着弹性问题。你的应用必须做到所有方面的弹性,而不只是一部分。

消息驱动中的弹性

构件合理的消息驱动好处,你会轻松的许多有价值的基础构件块。
隔离被作为系统中的自我修复方案存在。适当的隔离,让我们可以把每个类型的工作和组件分离,以便于了解它失败的风险,性能的特点,占用的CPU以及内存等等。
一个单独的构件的出错不会对整个系统产生影响,同时我们可以针对这个组件来完成修复。
位置透明性(原文Location transparency)让我们有能力在不同的进程中的不同的集群节点里交互,就像在同一台机器的进程里。
划分单独的错误频道允许我们把错误信号重定向到某个地方,要比直接throw到调用栈会更好一些
这些因素帮助我们实现可靠的错误处理以及容错能力纳入到我们的应用。这些因素表现的实际实现像是[Akka的supervisor层次结构]。
核心构件块提供了一套消息驱动的架构,这不仅有利于弹性,同时还具有响应性,不论是良好的场景下还是极端的场景下。

没有弹性造成一个价值4亿4千万美元的伤

回顾一下2012年Knight Capital Group软件故障方面的积累
在软件的升级过程中,另一个休眠的整合程序在不经意间被激活了,开始放大了交易量。
接下来的45分钟发生的事是一场噩梦。
Knight自动的交易系统被纳斯达克的错误交易所淹没,把公司的数十亿美元投入到了不合理的地方。它让Knight公司花了4亿4千万美元才扭转回来。
在Knight公司的小故障期间,Knight公司的交易淹没了纳斯达克,最终被纳斯达克叫停。
在当天Knight的股票就下跌了63%,他们勉强的生存了下来。只有投入股市的股票被卖出去的才收回一些价值。
Knight系统虽然性能很高,但是它并不具备弹性。光有性能没有弹性,这导致了Knight公司碰到了这种问题,缺没有安全的终止方案。避免bug造成这样的严重的损失。
这样导致他们在45分钟就损失了大量的资金。
这就是因为开发的过程都只有站在理想的角度考虑。如今软件已经成为我们生活和公司核心的一部分,如果没有良好的设计,在不理想的环境下,即使是一小时,损失也会非常的严重。

伸缩性

弹性和伸缩性可以协力帮你创建具有一致性的响应式应用。
一个具有伸缩性的系统,可以在多负载的情况下轻松的升级,并且保证服务的响应性。
在网上买东西的骚年都清楚一个事实:当你的东西热销的时候,流量会很大。通常情况下,除了刻意的网络攻击。
正常情况大量流量的操作是被允许的。当出现有大量的流量需要给你支付金额。
因此你要如何处理突如其来的任持续增长的请求?
首先选择好方案,第二选择好语言工具来实现你的方案。
然而大多数的程序员认为应该怎么做是按照语言和框架所推崇的。大多数人因为工具的约定,让他们难以去有更多的思维,来决定如何处理更多的情况。因为一些工具致使他们无法站在自己的环境下做出决定。
如果你在技术层面有独立的分析能力以及设计原则,那么你已经远远领先了他们。

基于线程并发的限制

选择一种并发模型的框架是很重要的一个技术决策。有两种类型的高级并发模型:

  • 传统的基于调用栈和内存共享的线程并发模型。
  • 消息驱动模型
    一些流行的MVC框架例如Rails就是基于线程的并发模型。这种类型的框架特点包括:
  • 共享可变状态
  • 一个线程处理一个请求
  • 并发的访问可变的变量和对象实例时通过管理锁和其他的复杂同步机制。
    结合以上的特性以及动态类型,像ruby这样的解释性语言,你会很快的达到性能以及伸缩性的瓶颈。
    对于任何一种语言根本上也是一样的,毕竟它的核心实现是一个脚本语言。

伸缩?是横向还是纵向?

让我们考虑一下几种伸缩的方式。
纵向伸缩涉及单独一个CPU或者服务器,通常会需要资金的支持,升级硬件。
横向伸缩涉及分布式计算跨越集群,可以在不增加设备成本的情况下(比如说可以用云平台),由于你的系统是基于时间和空间的概念,很难实现。
正如之前我们说说,异步边界解耦了空间和时间,让我们的更好的实现横向伸缩,这样也称之为弹性。
横向伸缩的优势是可以更充分的利用好服务器资源,而且弹性可以根据你的需求来增加,纵向伸缩的能力是响应式应用的最终目的。
响应式应用很难基于多线程的框架构造,因为横向扩展一个基于共享可变状态、多线程和锁是非常困难的。开发者需要考虑在单机的情况下利用好多核的机器,还需要在集群里充分利用。是
共享可变状态会增大横向伸缩的实现难度。学过多线程的人大家都清楚共享变量在多个不同的线程里会出现线程安全的问题。

消息驱动

消息驱动架构是响应式程序的基础。消息驱动可以用事件驱动,基于actor,或者混合二者实现。

一个事件驱动系统通过监控一个或者多个观察者实现。它不同于命令式的程序设计,它不会因为调用会阻塞程序而等待直到它响应调用的结果。
事件不会指向同一个对象,而是根据监听它的变化,这有一定的影响,接下来让我们。

基于Actor的并发模型是基于消息传递架构的衍生,消息会指向一个接收者 - 一个actor。
消息也许会穿越线程边界,也可能在不同的物理机上之间传递到另一个actor的信箱。Actor的弹性特性可以按照需求横向伸缩
actors可以在分布跨越在网络里,如果他们可以通过相互通信,共享同样的内存。

消息以及事件的主要区别在于消息可以指向性的,事件是触发性的。消息有一个明确的目标,然而事件可能有多个观察者。
接下来让我们探索一下事件驱动以及基于actor的并发模型的细节。

事件并发模型

传统的应用程序采用命令式的风格开发 - 一系列有步骤的操作 基于当前的调用栈调用栈的主要目的是保持跟踪子程序(routine)的调用者,然后执行子程序(routine),会阻塞当前的主线程,等待子程序的执行,最终会随着子程序的返回值把控制权返回当前调用者的线程。
从表面上来看,事件驱动的应用程序并不关注调用栈,它只是触发事件。事件也许会被编码成消息放置在用来监听多个观察者的队列里。事件驱动和命令方式的两者的区别在于一个调用者是非阻塞的,一个则是需要阻塞线程等待响应结果。
事件循环虽然可能是单线程的,但是并发仍然是可行的。可以通过调用子程序实现相关业务,当收到一个请求的时候会有事件循环的线程处理(有时候事件线程是一个单独的线程),而不是收到请求之后阻塞线程指导处理完之后才返回结果。

基于事件驱动架构的可能会引起callback hell影响代码的可维护性。发生callback hell的原因是,消息的接收方是基于异步回调传递的,而不是直接的通过引用找到的。
callback hell又被称作the Pyramid of Doom,它的解决方案在于代码的编写,然而这样可能会造成难于调试。

基于Actor的并发模型

Actor的并发模型是通过异步的传递消息在多个actors之间实现。

一个Actor具有如下的几种属性:

  • 一个接受消息的信箱
  • actor的逻辑是会根据pattern matching的方式决定处理收到哪种类型的消息。
  • 状态隔离而不是共享状态,状态只会存在自己的上下文中。

    Actor有点类似事件驱动模型,它支持通过调用栈传递轻量级的消息。Actors之间可以来回传递消息,甚至可以给它们自己传递消息。actor可以给自己传递消息,如果消息处理时间很长,它会等待处理结束后从信箱里提取消息,继续处理。
    基于Actor的模型比事件驱动更多的地方有,能够实现跨网络节点可扩展性的计算,每条消息指向每个Actor,可避免callback hell。这些特性可以让你更好的设计、构建和维护伸缩性更高的程序,而不是纠结于时间和空间,深层的回调嵌套。
    你只需要关注Actor之间的消息传递。
    Actor架构下的另一个好处是组件的松耦合化,这让调用者不在因为响应结果阻塞线程,因此调用者会很快的继续回到他们的岗位工作上。被调用的子程序(routine)会封入一个Actor,只有必要的时候才会调用子程序(routine)。
    这样创造了更多的可行性,例如跨集群的分布式的子程序,因为调用栈没有和应用程序的内存空间耦合,Actor让部署抽象的配置化,而不是程序化。
    Akka是一个基于Actor的工具和runtime,它是类型安全的应式平台的一部分(原文:  part of the Typesafe Reactive Platform),它是为了构建JVM上的高并发,分布式,可容错的,且基于Actor的响应式程序。
    Akka有一些难以执行的功能来用于构建响应式应用,例如supervisor hierarchies提供了可伸缩性的分布式worker和弹性。如果想深入挖掘Akka的,已经超出本文的范围,我强烈推荐去看Let it Crash blog之类的更多Akka的内容。
    也同样推荐 Benjamin Erb’s 的毕业论文,Concurrent Programming for Scalable Web Architectures

总结(译者本人的一些观念)

作者并不是想告诉大家,Reactive Programming是一种当今时代在开发者圈子里一种流行的趋势,不管你使用什么语言什么工具,只要你的用户量是持续增长的,服务需要是高可用的。
你就应该在设计、开发的角度时,考虑未来要如何提高可扩展性,如何做容错方案,如何做到弹性。这是每一个开发者应该学习的而不是把Reactive Programming当做一种新的趋势或者说当做新技术。
这种东西其实很早以前就已经出现了。因为当时我们身边的机器没有现在发展的迅速,猜测是因为这个原因没有得到广大的推广,选择一种编程Pattern或者选择某种架构的时候,我们应当从当前的资源(例如基础设施,硬件),考虑以便于充分利用资源,做绿色设计。
而不是由于你的某种框架的Pattern死板,促使你写的代码也很死板,无法满足需求。所以基础还是很重要的,程序员应该多注重基础修行,而不是为了追求某个框架的Best Practise而编写代码。

nodejs陪我过国庆

为什么突然用node(为了装逼啊…)

由于之前用ruby基于多线程做了一个服务(等有空专门为这个服务新写一篇博客),遇到了种种的race condition带来的坑。虽说rails本身是线程安全,
我用的又是MRI(有GIL,不会因为native extension造成线程安全的影响),理论上是说只要项目里的ruby代码(包括gem)都是线程安全的就不会出
现各种race condition的问题。早就听说nodejs的牛逼,只让使用者知道它只有一个主线程,通过协作式多任务来避开多线程的race condition,
并弥补了自己只有一个主线程造成的一些人生遗憾,然而一直没有机会去用,于是决定国庆假日抽时间,去写个demo练练手,如果体验好的话,打算最近的一个项目
就用node写了。

痛苦的开始

很久以前对nodejs也有所了解,还有对nodejs api已经生疏,加上从来没用它写过完整的服务端应用,在与公司js大神们的交流之后,
我打算尝试用koa写个cms的demo,因为已经习惯rails的convention,cms的架子也是仿rails搭了一套,
在这个痛苦的过程中了解了koa深深的让我知道了它的牛逼之处。

Koa

koa是一个基于wsgi协议实现的一个轻量级的nodejs web框架,它要求使用者实现generator函数,并且把它作为一个中间件,use到koa里。
来接受以及响应请求。

  • generator:

    1
    2
    3
    4
    5
    6
    7
    function *middleware() {
    const n = yield 1;
    return n;
    }
    gen = middleware();
    val = gen.next(); // { value: 1, done: false }
    end = gen.next(val.value); // { value: 1, done: true }

    koa会根据use的顺序以及generator的特性把几个中间件,串连在一期实现中间件之间的切换,koa通过引入了co,来实现异步控制流。
    generator的yield只是表明它会等待右边的表达式执行完之后才会继续往下执行。
    co会把yield后面的异步函数的执行结果保存并且带入到直到被再次暂停或者执行结束。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function *middleware() {
    const n = yield PromisifyAsyncCallback();
    return n;
    }
    gen = middleware();
    val = gen.next();
    val.then((data) => {
    console.log(gen.next(data)), // { value: data, done true}
    });

由于generator的yield特性,如果yield后面是一个generator函数,当前的yield会等待这个generator全部执行完之后才会返回执行权限给yield,koa就是通过这一特性实现,middlware之间的切换。
然而这些都是经过我耗时很久的理解、读源码、debug以及看博客之后的小总结,在不知道cogenerator的情况下用koa有一种被狗日的感觉。。

debug

在node debug模式下的repl里手动调用异步函数,只能看到repl返回了当前对象的值,并没有去执行异步的代码,我以为是代码写的不对,结果经过好长时间的排查,发现原来是node debug模式repl的运行机制有点独特。
正常开node的repl里写同样地代码来调用异步函数是可以看到异步函数的执行过程。(问题还在调查中..未完待续..)

失败的决策以及踩的坑

近期来了一堆紧急需求,然而我为了有更多地时间摸鱼,我做了几个愚蠢的决定

  1. 使用active_model_serializers的jsonapi adapter(为了追求jsonapi spec)
  2. 使用cancancan作角色权限验证
  3. 编写大量的测试

jsonapi spec

本身spec的宗旨是希望通过一定的convetion(约定)来帮助开发者规范json schema 统一好json schema的格式。
然而我对这个spec也只是停留在了解的程度,还没能够对它的整套规范做出良好的据测,由于不清楚它所谓的几个“MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL”。
再加上为了偷懒,我选择了active_model_serializers,发现并没有想象中的真的节约了我多少时间,还是大部分时间再去看它的源码以及开源社区里的issue支撑我现在的需求。
在读源码&jsonapi spec的过程中发现,项目里也只有序列化的部分用到了jsonapi的spec,接口的更新,创建,查询,排序等还是依着需求,写的也比较随意。

权限验证的坑

之前在暴漫做的项目,权限验证用是pundit,后来随着需求不断的更改,大家觉得写policy很折腾,于是同事把它逐渐换成了cancancan,为了满足变化快的需求,我们把权限数据放入了数据库里,给管理员提供了一套编辑的功能。
使用了一段时间发现这套方案在cancancan的帮助下还是很方便的,把permission挂载ability下面。对于需要验证的部分加macro就可以了。
由于之前的场景是内部系统,用户不会很多,也不会因为每次权限都要从数据库里查询带来多少体验上的影响。
于是决定继续使用数据表+cancancan的方案,但是现在场景是面对互联网用户,为了让服务器充分的发挥它的三维,让用户感受到服务器爸爸的伟大。决定给权限验证加缓存。
起初我以为根据用户的cache_key把ability实例丢进了缓存里就可以了,结果被服务器告知实例里有一些无法被序列化的对象。为了给权限加缓存,又去看了下cancancan的源码(真的是浪费时间。。)。
发现cancancan内部有一个rules对象,通过把权限记录在rules里来实现权限验证,rules是一个简单地对象符合ruby的序列化二进制数据的规范。只要把rules序列化缓存就好了。

写测试的意义在哪里?

权限做好了,下面要把相关的接口都测一遍,因为懒不喜欢用GUI点来点去觉得脚本测试很方便,又怕考虑的不全面,于是我模拟了我考虑到的所有场景。浪费太多时间在写测试上面。
在后面对需求和接口格式的时候发现,不少测试都是无用功,有的是因为功能变了,有的是json结构不满足前端的交互,jsonapi那套schema也没覆盖到。

总结

面对紧急需求,应当选择自己熟悉工具,以后不滥用gem,选择遵守某种约定的时候应当从实现、使用以及维护的角度来决策。只针对稳定or复杂的需求编写覆盖性的测试用例。抽时间读常用gem的源码。

部署单页面应用踩到nginx的坑

最近在公司里配置一个单页面应用遇到的问题。

需求:由于单页面应用使用js做路由,并且还需要从Rails应用的接口里拿数据,因此需要把单页面应用的路由和Rails的路由分开做代理。

场景:SPA静态HTML在Rails的public目录下。需要配置Nginx把请求转发到SPA的静态文件。同时还需要把api请求代理到Rails应用里。

研究发现可以通过使用try_files指令来实现SPA静态文件的代理。

在测试服上可以工作的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
upstream app_name {
server <app_ipaddr>;
}
server {
listen 80 default deferred;
server_name domain;
location / {
try_files $uri $uri/ /index.html;
proxy_pass http://app_name;
proxy_redirect off;
}
location ~ ^/(api/v1|api/auth/v1)/ {
proxy_pass http://app_name;
proxy_set_header Accept "application/vnd.example.v1";
proxy_redirect off;
}
location ~ ^/(api/v2|api/auth/v2)/ {
proxy_pass http://app_name;
proxy_set_header Accept "application/vnd.example.v2";
proxy_redirect off;
}
}

同样地配置在生产环境的Nginx中try_files报了如下的错误

1
rewrite or internal redirection cycle while internally redirecting to "/index.html"

初步判断以为是vps的防火墙ban了nginx的代理请求。经过排查发现,是生产环境的nginx因为版本比较高的原因,try_files并没有通过proxy_pass 而只是在本地try_files了,因此会找不到文件报错

猜测nginx的try_files实际上会在nginx上寻找try_files参数里的location,当找不到的时候会去寻找本地的文件。如果仍然找不到的话那就会出现之前的报错。
加上location之后发现这个try_files是会找到location并且根据location里的proxy_pass代理拿到try_files的’/index.html’。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
upstream app_name {
server <app_ipaddr>;
}
server {
listen 80 default deferred;
server_name domain;
location /index.html {
proxy_pass http://app_name;
proxy_redirect off;
}
location / {
try_files $uri $uri/ /index.html;
proxy_pass http://app_name;
}
location ~ ^/(api/v1|api/auth/v1)/ {
proxy_pass http://app_name;
proxy_set_header Accept "application/vnd.example.v1";
proxy_redirect off;
}
location ~ ^/(api/v2|api/auth/v2)/ {
proxy_pass http://app_name;
proxy_set_header Accept "application/vnd.example.v2";
proxy_redirect off;
}
}

基于http1.1的缓存机制探索

项目里用了rails的一些基于http 1.1协议的cache探索,然而对http cache远离还是不够理解,于是查询了一些资料。打算写一篇博客来总结一下。

首先HTTP是什么?为何要强调semantics

HTTP(Hypertext Transfer Protocol =: 超文本传输协议)。
既然是文本自然离不开文字。文字要表达的意思可以用semantics(语义)这个词来代替。
HTTP由request(请求)和response(响应)两种消息构成。
HTTP协议工作机制则是根据requestresponse的消息里的字段(这些字段包括:HTTP的方法,HTTP的状态码,HTTP的头)
解读这些消息里有价值的语义信息实现request关联response

理解什么是semantically transparent

rfc2616里的原文描述是这样的:

1
2
3
4
5
6
7
A cache behaves in a "semantically transparent" manner, with
respect to a particular response, when its use affects neither the
requesting client nor the origin server, except to improve
performance. When a cache is semantically transparent, the client
receives exactly the same response (except for hop-by-hop headers)
that it would have received had its request been handled directly
by the origin server.

大意是这样的:
缓存表现在语义透明的情况。语义透明就是说,当特定的响应,既不影响接受它的客户端,也不影响发起它的服务端时。
当缓存表现为语义透明的时候,客户端得到的响应是服务器以前就处理过(除了hop-by-hop头不一样以外,其他部分完全一样)

HTTP 1.1协议中是如何通过cache提升性能的呢?

rfc2616里描述,http1.1协议里caching的目的是在特定的业务场景下减少冗余的http请求or冗余的http响应。

在必要的时候http1.1允许源服务器、缓存、客户端显示的减少透明度。

Cache-control

Cache-control是一个用于覆盖默认缓存算法的http header字段。当默认缓存和Cache-control缓存冲突时

  • http1.1中通过Expiration Model(过期模型)实现减少多余的http请求
    *
  • http1.1中通过Validation Model(验证模型)实现减少多余的http响应

HTTP 1.1中提供的缓存response的机制

*

###

参考资料

rfc2616#section-13
总结 Web 应用中常用的各种 Cache

译-postgresql-at-a-glance

翻译postgresql-at-a-glance

PostgreSQL展示了它丰富的功能以及不错的性能。考虑到它的质量高,但奇怪的是PostgreSQL并没有流行起来。
这并不影响到它的前进。本文讲安利一下它的发展史以及特性。

为什么你需要了解PostgreSQL

PostgreSQL 是一个关系型数据库管理系统,在北美和11区相当的流行。在棒子国并不是很流行,但它依然是个具备强大功能
和卓越性能的一个优秀的关系型数据库,它是值得去研究和学习的。

PostgreSQL (读音为:[Post-Gres-Q-L])是一个对象关系型数据库。并且它还是一个开源代码的数据库,提供了企业级
的关系型数据库具备的功能。PostgreSQL作为开源数据库具有与Oracle一样的知名度,也有和Oracle相似的功能。

发展史

PostgreSQL 由许多先驱数据库演变而来。Ingres是PostgreSQL的鼻祖。
Michael Stonebraker是Ingres数据库的发起人。
他是数据库领域的大师,至今都在努力研究数据库领域。
Ingres这个项目是在1997年,伯克利大学发起的。在Ingres启动后Michael Stonebraker开始了另一个项目,Postgres(Post-Ingres)。
1991年发布了Postgres第三版,它的用户群体变的非常庞大。但对随着用户提供支持的负担太高,在1993年项目被叫停了。
(Postgres对当时的Informix产品产生了巨大的影响,甚至在项目被叫停的时候。)

尽管项目被叫停了。使用Postgres的用户和学生他们借着当前的版本创建了一个叫做Postgres95的项目,并且提升了
40%的性能,还提供了比Postgres更强大的SQL支持并且改善了数据结构。

在1996年Postgres95开发了源码,并且改名为PostgreSQL,也就是它现在的名字。这个名字体现出它Postgres的成功以及对SQL的支持。
(早期PostgreSQL采用的是QUEL语言,而不是SQL语言)。1997年,PostgresSQL发布了更名之后的第一个版本6.0

从那时起,PostgreSQL在开源社区的发展下,到了2013年5月的9.2版本。此外它的开源许可类似BSD和MIT许可。
PostgreSQL允许商业使用和修改,但是许可也声明源开发者并不承担任何发生在使用中的法律责任。PostgresSQL超过20多种不同版本的
fork。其中一些影响PostgresSQL,一些则已经消失。

PostgresSQL的图标是一个叫做Slonik的大象(这是来源于俄语,大象宝宝的意思)。然而并没有准确的理由可以证明为何使用大象作为图标,
有人说是在它开放源码后,它的用户从Agatha Christie’s小说中受到启发,并且提议使用大象作为图标。从那以后大象标志作为PostgresSQL
官方的图标。

大象还象征着庞大,强大,可靠和有好的记忆,Hadoop和Evernode也是用大象作为官方图标。

功能性以及局限性

PostgresSQL提供事务的支持和ACID,这都是作为关系型数据最基本的功能。此外PostgresSQL还支持许多先进的功能和扩展功能对于
学术研究提供了基本的可靠性和稳定性。PostgresSQL具有相当丰富的功能列表。

  • 嵌套的事务(savepoints)
  • 时间恢复点
  • 在线热备份,并行还原
  • 规则系统(query write system)
  • B-tree, R-tree, hash, GiST 方法索引
  • 多版本并发控制(MVCC)
  • 表空间
  • Procedural Language
  • Information Schema
  • I18n, L10N(国际化,本地化)
  • 列级排序规则
  • 数组,XML,UUID(数据类型)
  • 序列(用于值的自增)
  • 异步复制
  • limit/offset
  • 全文检索
  • SSL, IPv6
  • key-value(键值) 存储
  • 表继承

除了这些以外,它还具有多种功能和企业级数据库的功能。
总体上来看,PostgresSQL还存在以下的许多存储限制:

Limit Value
数据库存储最大上限 无限制
单表最大存储上限 32TB
单表行最大存储上限 1.6GB
字段最大存储上限 1GB
单表行数上限 无限制
单表列数上限 250~1600
单表最索引数 无限制

PostgresSQL里程碑

版本 发布时间 主要功能
0.01 1995 Postgres95 发布
1.0 1995 版权变动,开放源码
6.0~6.5 1997~1999 更名 PostgreSQL,加入索引、试图、规则,序列,触发器,查询优化器,约束,子查询,MVCC和JDBC接口
7.0~7.4 2000~2010 外键,SQL92语法加入,日志预写,Information schema和国际化
8.0~8.4 2005~2012 提供windows平台原生支持,Savepoint, Point-in-time,recovery,Two-phase commit,表空间, 切分,全文检索,Common table expressions (CTE),SQL/XML, ENUM, UUID Type,Window functions,Per-database collation,复制,备份
9.0 2010-09 流式复制, 热备份,提供64位windows系统软件支持,基于列的触发器
9.1 2011-09 功能分化,同步复制,列排序,Unlogged 表,K-nearest-neighbor 索引,序列化隔离级别,Writeable CTE (WITH),SQL/MED External Data,SE-Linux的集成
9.2 2012-09 性能优化,linear scalability to 64 cores,降低CPU层面的耗能,Cascade 流式复制,JSON, Range 数据类型,优化了锁的管理,Space-partitioned GiST index,索引扫描(覆盖)

下一个PostgreSQL版本是9.3,在2013年第三季发布。此版本具有许多功能,包括增强的管理功能:并行查询,合并、更改,插入,多版本主复制,物理化试图,增强多语言支持。

内部结构

以下是PostgreSQL的内部构造:
图片木有加载出来

当客户端请求和服务器建立连接,通过步骤(1)接口库(接口包括,libpg,JDBC和ODBC)。
PostMaster程序通过步骤(2)和服务器传播,然后客户端通过和所分配的服务器连接,执行查询。

以下是PostgreSQL服务的查询过程:
图片木有加载出来
当服务器收到客户发来的查询请求时,系统会创建一个语法解析树,系统会根据创建的语法解析树(1)分析,创建一个查询树。
接着查询树根据服务器定义的规则(3)找到多个可执行的计划,最终会根据最优的计划生成查询树。
当服务器执行查询时候,数据库系统里的catalog使用频繁,在系统内置的catalog中,用户可以直接定义函数和数据结构。以及索引访问方法和规则。在PostgreSQL里catalog起着很重要的作用,用于增加和扩展它的功能。

在PostgreSQL里一个文件可以由多个文件构成,一个页面有一个可扩展开槽页结构:
图片木有加载出来
图片木有加载出来

未完待续…

Linux中的多任务

什么是computer multitasking?

computer multitasking俗称多任务处理。是指计算机同时处理多个任务的能力。
多任务的实现方式,一般是运行第一个任务的一部分代码,保存上下文,再执行第二个任务,保存上下文。
然后恢复第一个程序的上下文继续执行……

Linux里的Process

Process俗称进程。是一个装载程序代码的容器。

进程还包括了一系列资源:打开文件待定的信号量内部的内核数据
处理器状态内存地址空间线程数据区块:包含了很多全局变量

如何通过Processes(多个进程)实现computer multitasking?

首先我们看下进程的调度策略:
一种方案是Cooperative multitasking(协作式多任务)
通过霸占当前CPU分配的时间,由进程自己决定是否爽够了把时间让给别的进程工作。
当进程爽够了的时候,它会告知kernel把时间让给下一个进程,kernel会提前处理
这样的缺点是,一个进程如果一直霸占会导致kernel卡顿,优点是进程的调度时间都是预期的(不会被强制终止)

还有一种方案是Preemption multitasking(抢占式多任务)
这种方案目前的*nix和windows系统都是采用这套设计。
通过kernel来作进程调度,kernel有自己的一套规则,看着不顺眼的进程就把它的工作挂起。
这样的缺点是,提高了编写进程任务的复杂度,优点是可以通过抢占争取更多的时间在有效的工作任务上。

如何实现多任务?为什么要有多任务?
当执行一个大程序的时候,它有一个非常大的任务,又追求处理速度。那么可以把大任务分成几个不同的子任务。
分配给不同的进程。那么多个进程如何一起处理一个大任务呢?可以通过共享上下文的办法。
通过IPC(进程调用)传递之间的上下文。
当要同时执行多个程序的时候也需要多任务。可以把多个程序分配给多个不同的进程。
当进程挂起的时候保存自己的上下文,当进程又获得重生的时候,载入保存的上下文继续工作。

Thread俗称线程。是进程的一部分。

Linux里线程也是进程的一种实现。进程的实现是从虚拟内存里分配一部分空间给当前进程。并且每个进程的上下文完全独立。
然而线程是由进程分配。一个进程里不同线程共享当前进程的上下文。
线程依赖进程。当进程被挂起,那么线程任务也全部挂起。(其实线程就是进程里的小生命)

进程调度的初探

进程的调度可以被分为I/O-bound(IO密集型)和Processor-bound(处理器密集型)
I/O-bound负责处理进程里的IO请求(IO请求的提交以及等待IO执行结果)
Processor-bound则是只负责执行进程里读取text section里的代码

所以你发现进程被抢占了那是因为I/O-bound处理了一个阻塞的请求。
当进程在工作的时候,那是Processor-bound在执行代码。

参考资料