阿里面经

阿里面试总结

简历过了

“您好,请问是xx吗?我是天猫前端部的xxx。我收到了您的简历。balabala”。(简历终于通过了筛选!开心!!)……

在杭州找工作的日子里结实了一位年轻的猎头基友,当时在家里投简历都快绝望了,投了N多家没有一家回应我的。和许多朋友交流之后,大家都吐槽我的简历烂。在他的点播下我学会了写简历(吹工作经历)。还帮我投了之后我入职杭州的第一家公司,最后能接到那家公司的面试邀请,也是因为猎头职业在圈子内的影响力吧。

面试

因为时隔久远,细节我记得不是特别清楚了,大致的内容还依稀残留于脑内。满满的伤。。XD
简单的让我自我介绍了一下。聊了聊做的项目和遇到的坑。破冰问题问了项目中媒体文件处理的大小和时间(因为时间隔的比较久,我完全忘记了…)。由于简历没有写前端的经验。所以大部分都在问后端的内容。还问了nodejs底层架构,讲真我nodejs实际使用时间只有2个月。(算现在的话 也只有4个月)

总结

天猫的面试让我看到了自己很多的不足。nodejs底层细节不了解,前端不熟,岗位是前端开发。面试过程很愉快。我总共经过了两轮面试,每一轮的面试官人都特别nice。因为那段时间我是在职期间,还特地问我说话是否方便,以及为什么离职。虽然我在离职理由层面表达的都不是特别好,但是面试官还是很理解,并表示很愿意理解我的情况。
天猫这个部门似乎是新成立的,做一些创新类产品研发。有强大的java后端支撑,同时有专门研究ar技术的组,面试还问了C10k类的问题,这里的前端应该也需要参与双11的秒杀系统的设计。
非常感谢我的朋友帮我内推天猫以及能和天猫的大牛们面试切磋。平定了我浮躁、傲慢的内心,路还有很长要走,我对js生态的认知以及了解深度,还仅是停留在打开新世界的大门阶段。

留给自己

好好学js,了解它的过去,认识它的现在。弥补我欠缺的计算机科学知识,多和大牛切磋,而不是混在自己的小圈子。

【译】PLop-95 -- half-sync-half-async

翻译的动机

任职服务端开发也有2年之久,个人是个野路子程序员,使用的都是Lowbee脚本语言。并没有过真·计算机领域知识学习。
++严重欠缺计算机科学背景,但是由于本人工作和生活中长期使用Linux,并且希望向女装程序员水平上靠拢,加上对于并发一直是懵懵懂。为了日♂后可以欢乐的play。因此尝试通过翻译这篇paper来作为一个知识点的学习。毕竟计算机是实战科学,因为工作目前不会接触高访问量的需求,所以后续有时间我会补上代码加以巩固知识点。
本文中部分章节可能会有缺漏,本人英译有限,有什么错误或者缺漏可以私我yooobuntu@163.com(反正也不会有人看。。)

正题

动机

如图[1]所示,一个基于BSD UNIX的软件架构的网络子系统。BSD UNIX 内核会调度异步通信设备(如:网络适配器和终端)和正跑在系统的程序,当网络数据包到达通信设备后,会由硬件发起一个电信号(也就是我们俗称的interrupt[中断])异步的发送给中断处理程序(也就是说发送这个电信号的操作,并不会立即执行并返回响应给发信号的硬件大爷),然后数据包会被中断控制器发送给操作系统内核(我们可以把操作系统前后端分离,前端视为用户态,后端视为内核,用户态只具有用户权限和申请获取资源能力,内核具备最高权限和资源分配能力)。这些中断处理程序会接受来自设备的数据包,并且会处理成高层协议(如:IP,TCP,UDP)。合法的数据包包含的应用程序数据会存在Socket层的队列中。内核酱会分发给等待消费数据的用户进程。这些受到数据包的骚年,会调用read内核api,用同步的方式读数据包里的内容。用户进程任何时候都可以调用read,当数据抵达时,当前进程会睡眠,直到数据成功抵达时。
在BSD架构中,内核异步地执行I/O响应给设备中断。相反的是,在用户态程序中同步地执行I/O。这种分离的关注点被称为”半同步、半异步”并发IO结构用于处理下面的两个问题:
1.简化编程:
使用异步I/O模型编程会比较复杂,因为输入和输出操作是中断触发的。异步会引发导致时钟问题和竞争问题,当前的线程可能控制权可能会被中断程序抢占,此外,中断驱动的程序运行栈会依赖额外的数据结构。这些数据结构用于显示地保存和恢复状态,这些状态是由事件异步地产生。再就是,调试异步程序比较困难,由于在程序运行时,在代码不同地点,产生了来自外部的事件。
相反,基于同步IO编写的程序相对于简单,因为I/O操作会在定义处产生,代码会按序处理。此外,程序会被同步I/O阻塞,等待I/O操作完成。使用阻塞I/O允许程序维护状态信息、调用历史,在运行时的栈活动记录里,而不是需要额外的数据结构管理。因此强烈建议使用同步I/O模型简化编程模型。
2.高效率地运行:
异步I/O模型有效地映射到硬件设备上,通过发送中断驱动。异步I/O可以同时处理通信和计算。另外,上下文切换开销小,因为维护程序所需的信息的状态数量相对比较小。因此,这里也是强烈建议使用异步I/O模型提高运行性能。
相反。完全地使用同步I/O可能是低效的,当每一个事件源(如:网络适配器,终端,计时器)相关的单独的活动对象(如:进程,线程),每一个这些对象都包含了一些资源(如:栈,一组寄存器)会发生阻塞,这时候就需要等待产生阻塞的事件源响应完成。因此,同步I/O模型会减少时间,用于创建必要的空间,调度,分发,结束一个活动对象。

解决方案

为了解决简化编程模型和提高运行效率之间产生的担忧,我们使用Half Sync/Half Async模式。这个模式以有效且良好的方式集成同步I/O和异步I/O模型。在Half Sync/Half Async模式中,
高级任务(如:数据库查询和文件传输)使用同步I/O模型,可以达到简化编程模型。相反,低级任务(如:处理网络控制器的中断)使用异步I/O模型,从而强化执行效率。因为在系统中的高级任务比低级任务更多,这个模式将异步处理的复杂性放置软件架构中的单独一层内。同步通信任务和异步通信任务会被编号并且有序的放入队列层。

适用性

使用Half Sync/Half Async模式

  • 系统处理过程有这些特点:

    • 这个系统必须执行任务响应异步产生的外部事件。
    • 对于事件源使用独立的线程处理同步I/O是低效率的。
    • 系统中的高级任务在同步I/O模型下,编程模型非常的简单。
  • 一个或多个任务必须在单个线程控制下执行,而其他任务可能会从多线程里得到收益。

    • 例如,历史库X-windows、SUN RPC 通常不可重入执行(non-reentrant)。因此多线程控制不能在并发情况下安全的调用这些库函数。但是,为了确保服务质量以及享受多CPU的好处,把大量数据传输和数据库查询操作放入独立的线程中执行可能是必要的。Half Sync/Half Async模式会抽离多线程程序中的单个线程,这个解耦可以满足不可重复执行函数的需求,不需要写额外的维护代码,以至于它们可以正确的执行。

结构和参与者

如图[2]所示是Half Sync/Half Async模式的设计参与者设计结构。参与者分为以下几种

  • 同步任务层(用户态线程)
    • 这层执行高级I/O会是用同步的方式传输数据,这些任务会放入队列层的消息队列中。不像异步层,同步层的任务是活动对象,有自己的允许栈和寄存器。因此当执行同步I/O时他们会阻塞。
  • 队列层(Socket层)
    • 这层为同步任务层和异步任务层,提供了同步和缓冲区。I/O事件被缓冲在队列层的消息队列中的异步任务处理,以便后续用同步任务检索。
  • 异步任务层(BSD UNIX内核)
    • 这层为处理来自外部事件源(如:网络接口、终端)的低级任务。不像同步层,异步层的任务是被动对象,所以他们没有自己的运行栈和寄存器。因此他们不会无止境地阻塞在任何单个事件源上。
  • 外部事件源(网络接口)
    • 外部设备(如:网络接口、磁盘控制器)生成事件的会被异步任务层接收并处理。

合作

如图[3]所示,Half Sync/Half Async模式下的参与者之间的活动合作过程:当有输入事件抵达外部事件源(输出事件也是一样的方式处理)。这些合作过程被分成三个阶段:

  • 异步阶段 - 该阶段,外部事件源会与异步任务层通过发送中断和异步事件通知通信。
  • 队列阶段 - 该阶段,队列层提供明确的同步点,这样缓冲区消息会把在同步和异步任务层之间传递以响应给输入事件。
  • 同步阶段 - 该阶段的任务在同步层会检索放在队列层中的异步任务。注意协议决定数据以什么方式被传递,队列层负责调解相交的同步和异步任务层相交的通信。
    异步和同步任务层如图[3],通过”生产者与消费者”模式通过消息通信。理解这个模式的关键点是理解同步任务是活动模型。因此他们调用读写会引起阻塞,在任何时候都取决于他们的协议。如果数据还不可用,这个活动任务会进入睡眠,直到数据达到。反之,异步层的任务是被动对象,被动对象会触发来自外部事件源的通知或者中断。

总结

Half Sync/Half Async模式有以下几种好处:

  • 高级任务编程模型简单,因为他们屏蔽了低级异步I/O。复杂并发模型,信号中断处理,计时器分发委托给异步任务层。异步任务层负责处理低级任务的细节,异步I/O编程模型(信号中断处理)。异步层同时也调度具体的硬件组件(如:DMA,内存管理和设备寄存器)。
  • 同步方案在每一层都低耦合。因此每层不需要使用相同的并发控制策略。例如,在单线程的BSD UNIX内核,异步任务层通过低级任务机制(如,升降CPU的中断级别)实现并发控制,相反处于同步任务层的用户态,通过高级同步构造(如:信号量,消息队列,竞争资源,锁记录)实现并发控制。
  • 内部层通信通过同一个端,因为所有的交互都由队列层调度。队列层缓冲的消息会被发送到同步和异步任务两层。这样就消除了锁定和序列化的复杂度,不管是同步还是异步任务层,都可以直接的访问他们自己的内存。
  • 多处理器改善性能。使用同步I/O模型可以简化编程模型,并且在多处理器上会有性能提升。例如:持续时间长的数据传输(如:从数据库下载较大的医疗方面的图片)可以通过同步I/O模型,简便并高效的处理。一个处理器可以分配给传输数据的线程,促使相关联的CPU让整个传输操作享有CPU的指令和高速缓存。
    Half Sync/Half Async 模式有以下几个缺点:
  • 同步数据拷贝和上下文切换的开销可能会存在引起交叉的边界惩罚。这个开销通常产生于当同步、异步任务的队列层传输数据时。特别是,大多数操作系统适用Half Sync/Half Async模式,在用户态和内核态领域边界安置队列层。当发生边界交叉时,会有明显性能上的惩罚。例如:基于BSD UNIX的Socket层,占有大量比例基于TCP/IP的网络开销。
  • 缺乏面向高级任务的异步I/O。由于异步I/O依赖系统接口的设计,因此可能高级任务不能利用提供了低级异步I/O设备。因此系统I/O结构可能会避免应用程序高效的利用硬件,尽管外部设备支持异步重叠计算和通信。

实现

1. 定义耗时长的任务并且使用同步I/O实现

许多系统的任务可以很容易的使用同步I/O实现。通常,耗时任务传输大的数据流或者执行数据库操作时可能会阻塞并长时间等待服务器响应。
实现这些耗时任务可以采用活动对象模型。因为活动对象有属于他们自己的运行栈和寄存器,当执行同步I/O时,他们会阻塞。实现活动对象机制,依赖一个切换上下文控制的方法。在底层,这意味着有空间存储当前线程状态的硬件(例如,所有值,包括线程的栈指针,都会存在线程对应的寄存器中)并且加载一个新线程的状态。这样足以实现一个不需要保护内存的非抢占式线程机制。”用户态线程”包通常提供了这样的功能。
但是,需要依赖更多多任务操作系统的特性,将活跃对象模型健壮的线程化和进程化。在这个案例中,每一个线程控制都有属于自己的地址空间,这些地址空间会被处理器的内存管理单元(MMU)管理。当切换之间的线程时,新的进程地址空间信息必须加载到MMU中。也可能还需要缓存刷新,特别是某种类型的虚拟地址缓存。除了地址空间,操作系统的进程通常有”用户标识”,这样操作系统才知道这个进程的访问权限以及有多少资源可以使用。
为了避免单个进程无止尽的霸占系统,因此变有了抢占的方式。抢占通常由计时器完成。计时器会定期地(例如:0.01秒)生成一个时钟中断信号。在这个中断期间操作系统会查看当前执行的进程是否需要被抢占。如果需要,它会保存这个进程的状态并且加载下一个进程的状态执行。当中断信号返回,那么新进程将开始运行。

2. 定义耗时短的任务并且使用异步I/O实现

系统中的某个任务不能长期阻塞住。通常这些任务会使用耗时短且通过与外部事件源交互实现(例如图形界面、中断信号驱动的硬件网络接口)。提高性能并且确保响应时间,这些事件源必须无阻赛的提供服务。
实现这些耗时短的任务使用响应式,被动对象模型。被动对象会借助来自任何地方的线程控制(例如:调用者、独立的中断栈)。因此,这些任务必须使用异步I/O因为它们不会长时间阻塞。主要的动机是不会阻塞,确保充足的时机响应别的任务。(例如:优先级高的硬件中断,时钟计时器)。
这里有几种方式开发一个结构不错的异步I/O框架:

  • 使用Reactor模式多路分解事件 - Reactor模式调度单个事件循环线程 - 该线程支持多路分解和分发多个任务管理者,支持并发地触发多个事件。这个模式将简单的一个单线程事件循环模型和基于面向对象编程扩展模式相互组合。Reactor模式序列化事件处理在一个进程或者一个线程里并且它通常不需要更复杂的线程、同步或者加锁。
    Reactor可以通过中断执行同步和异步事件源实现。Reactor会提供一个异步的事件处理,因此这个事件处理不会阻塞不会妨碍到响应其他事件源。
  • 实现多级别中断方案 - 这些实现允许非时间关键被高优先级任务处理(例如:硬件终端),当有高优先级事件时必须提前执行。异步层的数据结构必须被保护(例如:通过提高处理的优先级或者使用信号量)避免中断处理器同时被访问从而破坏共享状态。
    例如,在操作系统内核中,对多级中断方案的需求受到硬件中断服务时间的强烈影响。如果这个时间可以显着降低,则在硬件中断级别执行所有处理可能会更有效,以避免额外的软件中断的开销。 TCP / IP的实现降低了入站分组协议处理开销,使得两级中断方案的成本支配整个分组处理时间。

3. 实现队列层

队列层提供了同步点用于缓冲交换同步任务层和异步任务层之间的消息。以下的几个论点会围绕设计队列层的寻址:

  • 并发控制 - 当一个异步任务层和同步任务层并发执行时(既可能是多个CPU决定也可能是中断决定)有必要确保并发访问共享队列的状态是会避免资源竞争的。因此队列层通常会使用并发控制机制实现,例如信号量,互斥,竞争资源。这些机制会确保信息可以被队列层插入和删除并且不被内部数据破坏队列内数据的结构。
  • 层和层之间的控制流 - 系统不能无限制的将很多资源奉献给队列层的消息缓冲区。因为同步任务层和异步任务层的数据传输是有必要调节的。例如:层和层之间的控制流程应当避免同步任务因为消息过多淹没异步层,影响网络接口的传输。
    同步层任务会阻塞,因此当同步任务正在处理等待更多的数据时,正常的流程控制方案是会让它进入睡眠。当异步任务层在离开队列时,下面的某个级别的同步任务会被继续唤醒。反之,异步任务不会阻塞,因此,如果它们产生过多的数据,则公共流控制策略将使排队层丢弃消息, 因此,如果它们产
    生的数据的量过多共用流控制策略是有排队层丢弃消息。当消息有关联的依赖面向可靠连接的网络协议时,它的发起者最终会超时,并且重发。
  • 数据拷贝开销 - 一些系统(例如:BSD UNIX)会在用户态和内核态之间存放一个队列层。通常的解耦方式是通过拷贝它们之前的消息。然而,这样会增加系统总线和内存的读取,当有大消息的传递时,会明显地降低性能。
    一种方式是减少数据拷贝的,分配一块内存用于共享在同步和异步任务层。这样允许两个层之间数据直接交换,不需要通过拷贝到队列层。例如,提供一个I/O子系统,通过轮训中断信号来改善处理连续的媒体I/O流。这种方法还提供了一个缓冲管理系统,允许高效的分页重新映射同时共享内存机制可以跨用户态,内核,以及设备。

参考资料

PLoP-95.pdf

关于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