RPC框架的技术架构和未来

图片来自markusspiske会员

RPC(远程服务调用)框架是现代互联网公司中最基础、最关键的微服务技术架构组件,它涉及到微服务的注册和发现、高可用、服务治理、服务监控,直接关系到应用服务的研发流程,对持续交付和运维直接相关联,是一个牵一发而动全身的基础技术组件。如何选择和设计好一个RPC框架,是一个互联网公司微服务技术架构中的百年大计工程。

本文对业界开源RPC框架的调研基础上,分析RPC框架的技术架构和各个关键技术模块组成,同时对各个RPC框架进行了相关技术需求数据的收集,以帮助考察对比各个RPC框架的技术参数。

  • 阿里的Dubbo,开源于2011年10月
  • 微博的Motan,开源于2016年5月
  • Spring Cloud,开源于2014年10月

由于Spring Cloud的目标也是实现微服务架构,提供很多相关的组件,在RPC框架对比中,我们也将其加入了分析行列。

但是,本文并没有将gRpc/Thrift加入对比行列,主要是这两个框架更多关注跨语言的服务调用,不提供服务治理功能,所以对微服务技术架构无法全面支持。而本文主要是讨论能够支撑微服务架构落地的RPC框架及其技术架构。

最后讨论当前各个主流RPC框架所面临的问题,及其未来5-10年Service Mesh在引领RPC框架上的创新变化。

1. RPC架构

一个典型的RPC架构如下图所示,

一个RPC框架的典型架构

它包括了如下几个组件,

  • 服务提供者:远程服务的被调用方,提供服务实现。
  • 服务消费者:远程服务的调用方。
  • 注册中心:提供服务的注册和发现。
  • 调用监控:监控远程服务调用情况。

在服务提供者/消费者内部,一般会包括如下5个组件,

  • proxy:服务接口代理,将服务接口的本地调用转接为远程调用。
  • service:服务的定义,这个Service的定义决定了支持的服务粒度。在Dubbo和Motan的缺省实现中,一个Service被定义为一个服务类实例的一个方法(Method),通过反射的方式调用服务的Method。
  • register:服务的注册和发现,在服务启动时,注册服务URL地址到注册中心,启动后会定时从注册中心获取最新服务列表。
  • protocol:对RPC的封装抽象,主要包含两个功能:一个服务导出(exporter),用于服务提供者,将服务导出并注册URL,RPC时获取服务定义并调用实现;还有就是服务引用(referer),用于服务消费者,获取远程服务URL并发起RPC。
  • transport:远程服务通信层,里面包括用于远程通信的服务器端和客户端,常见使用的有Netty/Apache Mina/Grizzly等网络通信框架,简单的有直接使用OkHttp/Unirest进行Http通信。

上述是RPC的典型架构,各个RPC框架基本上都提供了上述RPC架构的各个组件,不一样之处在于语言实现及其相关组件模块。

2. RPC框架组件

业界的RPC框架百花齐放,种类繁多,各个RPC框架基本上都实现了上述典型RPC架构,不一样之处在于实现,比如,

  • 不同的实现语言,比如Java语言的Dubbo和Motan,Go语言的rpcx等等
  • 支持不同的注册中心,从consule、zookeeper、eureka、自带注册中心等
  • 服务提供者和消费者的架构组件

其中RPC框架在服务提供者和消费者的架构组件上的实现,最能体现其框架对微服务架构的功能支持粒度和深度。

下面两个图主要列出了Dubbo和Motan的服务提供者和消费者架构组件,对比其实现模块,可以看到,两者的基本模块主要还是围绕着Service/Proxy/Protocol/Transport进行展开。理解这两个图对于解读Dubbo和Motan的开源代码会有帮助。

2.1 Dubbo的架构实现

Dubbo的架构实现

2.2 Motan的架构实现

Motan的架构实现

2.3 一个RPC框架的简单实现

请点击这里查看一个RPC的简单演示项目,也可以通过如下命令克隆项目代码到本地。

git clone git@gitee.com:pphh/simple-rpc.git

这个演示项目参考motan/dubbo的架构实现,以尽量简洁的方式演示一个RPC框架中所使用的核心组件技术,包括,

  • 服务提供方和消费方实现
  • 服务的注册和发现机制
  • 服务集群的高可用和负载均衡
  • 远程调用的底层通信实现
  • 等等。

3. RPC的技术需求分析

一个远程服务调用的开发和上线,按流程先后顺序会接触到,

  • 服务定义
  • 服务开发
  • 服务调用
  • 服务通信
  • 服务监控运行

下面将从这几个方面逐一分析RPC框架所涉及的各项技术需求。

1)服务的定义

RPC是一个两方(消费方和提供方)的通信调用关系,这需要在两方之间建立一个通信契约,服务定义就是实现这个目的。服务定义的内容主要包括服务名、入参请求和出参响应。

gRpc/Thrift是以一个规范的文件格式定义服务,

syntax = “proto3”;
package helloworld;

// The greeting service definition.
Service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

而Dubbo和Motan则使用Java Interface的形式。

Public interface Greeter {
    String sayHello(String name);
}

好的服务定义方式能够简洁清除地表达服务名、入参请求和出参响应,方便服务的开发和调用,服务的定义对服务的版本升级兼容也应有相应的考虑。

2)服务的开发

在服务定义好了之后,马上服务提供者需要根据定义进行服务的开发。

大多数RPC框架都对服务的开发进行了非常友好的支持,例如gRpc/Thrift提供代码的自动生成,开发者只需要继承生成的服务基类,添加相应的逻辑代码即可,Dubbo和Motan由于直接使用Java Interface,开发也是非常容易。

下面是一个Java语言服务的开发,其实现了Greeter Interface所定义的服务接口,

public class GreeterImpl implements Greeter {
    public String sayHello(String name) {
        return “Hello “ + name;
    }
}

服务的开发一般需要考虑编程语言的支持,Dubbo和Motan主要支持Java语言,有些RPC框架支持Go语言(比如rpcx),而有些则支持多语言,这个需要根据实际情况考虑。

3)服务的调用

服务开发之后,接下来就要看服务如何被使用了。

服务调用比较多样化,主要考虑包括如下几个方面,

  • 同步和异步调用的支持。
  • 对容错、负载均衡的支持,对服务的多版本和分组支持,以保证高可用的目的。
  • 对服务熔断和服务降级的支持,以保证服务故障隔离的目的。

容错策略解决的是,一旦发现远程调用失败,如果进行下一步操作以减少错误影响,常见的容错策略有,

  • FailFast 快速失败:当消费者调用远程服务失败时,立即报错,消费者只发起一次调用请求。
  • FailOver 失败自动切换:当消费者调用远程服务失败时,重新尝试调用服务,重试的次数一般需要指定,防止无限次重试。
  • FailSafe 失败安全:当消费者调用远程服务失败时,直接忽略,请求正常返回报成功。一般用于可有可无的服务调用。
  • FailBack 失败自动恢复:当消费者调用远程服务失败时,定时重发请求。一般用于消息通知。
  • Forking 并行调用:消费者同时调用多个远程服务,任一成功响应则返回。

负载均衡机制解决的是如何在多个可用远程服务提供者中,选择下一个进行调用,常见的负载均衡策略有,

  • Random 随机选择:在可用的远程服务提供者中,随机选择一个进行调用。
  • RoundRobin 轮询选择:在可用的远程服务提供者中,依次轮询选择一个进行调用。
  • LeastActive 按最少活跃调用数选择:在可用的远程服务提供者中,选择最少调用过的远程进行调用。
  • ConsistentHash 按一致哈希选择:获取调用请求的哈希值,根据哈希值选择远程服务提供者,保证相同参数的请求总是发到同一提供者。

服务熔断是指一旦服务调用无法正常工作,在故障时间内,尽量减少对服务的调用,直接返回null或抛异常,直到服务恢复正常。

各个RPC框架对服务调用的支持程度不一,但基本的高可用和熔断措施都会提供,而且一般会有相应的扩展点供自定义。在高并发量的情况下,对高可用和熔断措施会是相当大的考验,能否正常稳定运行,是一个RPC框架走向生产环境的关键,一般会和远程服务通信模块一并进行性能测试,详细的性能测试参数见后续的远程服务通信。

4)应用开发框架集成

为了方便服务的开发和使用,RPC框架还会集成业界主流应用开发框架,例如Dubbo和Motan集成了对Spring Framework的集成。

下面是Motan以注解的形式提供服务,

@MotanService(export = “demoMotan:8002”)
public class GreeterImpl implements Greeter {

    public String sayHello (String name) {
        return “Hello “ + name;
    }

}

下面是Motan以注解的形式消费服务,

public class HelloController {

    @MotanReferer(basicReferer = “clientConfig”, directUrl = “127.0.0.1:8002”)
    Greeter greeter;

    public String home() {
        return greeter.sayHello(“test”);
    }

}

可以看到,RPC框架所集成的应用开发框架,及其集成的支持程度,其能够很大程度上影响服务开发的效率,这在评价RPC框架时也是重要考虑因素。

5)远程服务通信

服务通信组件是底层,其通信吞吐量和性能,决定了整个微服务框架的高并发,其通信一般通过性能测试报告获得。

一般的测试场景包括,

数据传输性能测试场景(考察最大通信吞吐量TPS,响应时间,10并发)

  • 传入1k string,原样返回
  • 传入50k string,原样返回
  • 传入200k string,原样返回
  • 传入1k POJO,原样返回,考察序列化模块
  • 传入的string size在1k-200k随机变化,原样返回

高并发性能测试场景(考察最大通信吞吐量TPS,响应时间,负载均衡)

  • 10并发1k string,原样返回
  • 20并发 1k string,原样返回

稳定性测试场景(考察框架的稳定性, 通信吞吐量TPS、成功率、响应时间的趋势)

  • 时长:24小时,7天,一个月
  • 吞吐量:10并发 1k string,原样返回

服务消费方性能测试(考察消费方的稳定性和高可用)

  • 熔断:每隔1分钟触发一次熔断,1分钟后恢复,一次轮询24小时
  • 负载均衡:对每个负载均衡策略,持续运行24小时

更多的测试场景需依据业务情景进行考虑。

一份Dubbo的性能测试报告见如下链接,

  • https://dubbo.gitbooks.io/dubbo-user-book/content/perf-test.html

6)服务运行监控

服务调用监控是微服务运营的一个关键环节,通过服务监控,我们可以获取服务的调用次数,调用时长,成功和失败数,及其分析整个服务调用链,获取远程服务调用的健康状态,辅助改进远程服务调用。

RPC框架对监控指标的收集和分析,是RPC框架本身重要的治理功能。

4. 开源RPC框架的技术需求指标

下表统计了Dubbo和Motan各项技术需求指标情况,同时加入了Spring Cloud在微服务架构的相关功能支持,方便对比,

RPC框架 Dubbon Spring Cloud Motan
公司 阿里 微博
开源时间 2012.1 2014.10 2016.5
框架开发语言 Java Java Java
项目代码量 120K ? 140K
社区活跃度 15000+ ? 4000+
服务开发语言 Java Java Java/PHP
服务开发框架集成 Spring Framework 4.3.10.RELEASE
分布式服务-注册中心 Default, Multicast, Redis, Zookeeper, Simple Registry, Direct Netflix Eureka Consul, Zookeeper, Direct
分布式服务-高可用 Failfast, Failover, FailSafe, FailBack, Forking, Broadcast 通过Netflix Zuul提供FailOver Failfast, Failover
分布式服务-负载均衡 Random, Roundrobin, LeastActive, ConsistentHash 通过Netflix Zuul提供: Random, Roundrobin Random, Roundrobin, LocalFirst
分布式服务-服务的定义 Java Interface 无强契约, 支持Swagger API Java Interface
分布式服务-服务粒度 服务类方法Method 服务类方法Method
分布式服务-多版本 支持 支持
分布式服务-配置 无,外接 Spring Cloud Config 无,外接
分布式服务-服务故障 提供服务降级, 直接返回null Netflix Hystrix 服务故障隔离
远程服务调用定义-Protocol Dubbo协议(Netty NIO), Hessian协议, Injvm协议(内部调用), Rmi协议(TCP), Memcached协议, Redis协议, Thrift协议, HTTP协议 Restful API Motan2协议 (Netty NIO), Injvm协议(内部调用), Yar(PHP), Restful (JAX-RS), Grpc
远程服务通信 Transport Netty, Netty4, Apache Mima (TCP), Grizzly, P2p Netty, Netty4
服务调用跟踪 Spring Cloud Sleuth OpenTracing
性能报告 1k String,并发量10K+ 1k String,并发量10K+
序列化协议 Hessian2序列化, Dubbo序列化, JSON序列化 Simple序列化, Hessian2序列化
可扩展性 SPI,扩展点丰富 SPI,扩展点丰富
特色 开源较早的服务化框架,业界应用广泛,功能丰富 和Dubbo相比,提供更加简化的框架模块和架构

上述表格的数据统计于2018年1月25日。
注:有?标记说明该项指标待查。

5. RPC框架选择

每一个RPC框架都有其自身的特点和适应性,只有符合当前业务场景需要的RPC框架,才是最好的选择。前文对服务定义、服务开发、服务调用、服务通信和服务监控运行各个方面需要考虑的技术因素进行了描述,除此之外,我们还需要考虑更多的方面。

若准备选择已开源的RPC框架上进行自研,则还需要考虑如下几个方面,

  • 生产应用:只有经过生产环境的运行和流量冲击,才能证明RPC框架的稳定性
  • 架构和代码复杂度:这个关系到后续框架的可维护性和可扩展性
  • 社区活跃度:好的活跃社区,有助于框架的成熟,支撑未来发展

如果选择全新自研RPC,则建议全面考虑上述因素。

6. RPC框架的未来

RPC框架发展至今,其基本的架构组件没有太大变化,服务的注册和发现、服务的通信和治理、故障熔断恢复、配置、监控等等,都是一个RPC框架需要提供实现的核心技术组件。但是2017年微服务业界对Service Mesh的热烈讨论,让我们看到对RPC框架未来5-10年的变化。

1) 当前RPC框架面临的问题

上面对RPC框架的技术需求分析中,可以看到RPC框架涉及技术广泛,架构模块组件繁多,在对微服务架构提供技术支持的同时,其框架本身的治理问题也亟待解决,这是问题之一。

还有一个更大的问题是,RPC架构本身是和语言无关的,但为了开发效率,各个RPC框架都对某一个开发语言有特定的支持。目前通过RPC框架开发微服务时,带来最大的问题便是其框架侵入性,开发者在项目的开发过程中,不仅引入框架组件,还不得不在业务代码中添加各种“胶水”配置代码。

请见一个Spring Cloud项目,在引入各个组件之后,还需通过注解方式打开各个组件开关,

@SpringBootApplication
@EnableConfigServer
@EnableEurekaServer
@EnableEurekaClient
@EnableHystrix
@EnableHystrixDashboard
@EnableTurbine
@EnableDiscoveryClient
@EnableFeignClients
@EnableZuulProxy
@EnableZuulServer
Public class Application {
   SpringApplication.run (Application.class, args);
}

Dubbo和Motan对组件的注解和配置也是如此。下面是一个简化的motan服务端配置,

<beans>
    <!-- 业务具体实现类 -->
    <bean id=" motanDemo" class="MotanDemo"/>
    <!-- 注册中心配置 -->
    <motan:registry regProtocol="zookeeper" name="registry" address="127.0.0.1:2181" />
    <!-- 协议配置。-->
    <motan:protocol id="demoMotan" default="true" name="motan"  maxConn="80000" />
    <!-- 具体rpc服务配置,声明实现的接口类。-->
    <motan:service interface="Demo" ref="motanDemo" export="demo:8001" >
    </motan:service>
</beans>

可以看到,即使开发一个简单的项目,各个框架组件一个都不能少,框架对业务代码的侵入性强,各种服务配置的更新会直接涉及项目代码变化。特别是一旦涉及框架的升级,当前“嵌入式”的RPC框架将需要各个应用项目配合,升级时间长,依赖冲突,分散,不可控制,RPC框架的升级治理完全靠“手工”,是一项非常困难、痛苦的过程。

2) 未来5-10年

上述RPC框架面临的问题正是Service Mesh所要解决的,Service Mesh是一个基础设施层,其独立运行在应用服务之外,提供应用服务之间安全、可靠、高效的通信,并为服务通信实现了微服务运行所需的基本组件功能,包括服务注册发现、负载均衡、故障恢复、监控、权限控制等等。

Service Mesh并没有脱离RPC典型架构,它提出一个Sidecar的Proxy组件,实现RPC典型架构中的服务提供者和服务消费者功能,注册中心将成为一个更加强大的控制管理平台。

一个典型的Service Mesh部署网络拓扑图如下,

其中绿色方块为应用服务,蓝色方块为Sidecar Proxy,应用服务之间通过Sidecar Proxy进行通信,整个服务通信形成图中的蓝色网络连线,图中所有蓝色部分就形成了Service Mesh。

可以看到,通过sidecar将本地服务调用转为远程服务调用,sidecar的部署和运行独立于应用服务之外,通过这种方式减少了框架的侵入性,应用的开发语言无限制,能够做到真正的跨语言,同时整个sidecar的运行治理也将自成系统。

未来的5-10年,Service Mesh在引领RPC框架上的创新变化,为下一代微服务架构提供全新技术支持。

7. 参考文献

  1. Dubbo的技术文档 https://www.gitbook.com/@dubbo
  2. Motan的技术文档 https://github.com/weibocom/motan/wiki
  3. 微博轻量级RPC框架Motan正式开源:支撑千亿调用
  4. WHAT'S A SERVICE MESH? AND WHY DO I NEED ONE? ,作者William Morgan,为BuoYant公司CEO,讲述其对Service Mesh的定义和意义。
  5. Pattern: Service Mesh,作者Phil Calçado,该文章以TCP/IP在网络通信的发展历程和作用作为类比,来解释Service Mesh要解决的问题及在微服务中的作用。

如何使用Javac/Jar/Java工具对源代码进行编译打包执行

图片来自pixabay.com的qimono会员

Javac/Jar/Java是JDK的发布包中带的三个基本工具,用于对Java源码进行编译打包运行,本文将介绍这几个工具的使用方法。

1. 代码和演示环境

代码地址:https://gitee.com/pphh/blog/tree/master/171221_java_compile_run/
演示环境:Windows 7 SP1, JDK 9.0.1

下面的所有命令演示都是在目录./171221_java_compile_run/中进行,文件的目录结构如下,

+ Simple.java
+ src
  - App.java
  + common
    - java/com/pphh/demo/Logger.java
  + main
    - java/com/pphh/demo/AppPkg.java
+ src2
  - AppLogger.java
+ src3
  - AppJ7.java
  - AppJ8.java
+ target
  - app.jar
  - common.jar
  + classes

2. Java源码编译

在开发完Java代码后,首先需要对源码进行编译。JDK中带的编译工具为Javac。

javac的命令格式为,

javac [options] [sourcefiles]

其中:
options 命令行选项
sourcefiles 待编译的代码文件

a) 简单的编译

只需要输入代码的文件地址即可

javac ./Simple.java
java Simple

b) 指定编译后的输出目录

使用-d选项,指定编译后的class文件输入目录

javac -d ./target/classes Simple.java
javac -d ./target/classes ./src/main/java/com/pphh/demo/AppPkg.java

c) 编译某个目录下的多个代码文件

使用-sourcepath选项,指定一个代码目录,然后通过通配符*.java获取指定目录下的所有代码文件进行编译,

set src_dir=./src/common/java/com/pphh/demo/
javac -d ./target/classes -sourcepath %src_dir% %src_dir%/*.java

上述命令会将指定目录下Logger.java文件进行编译,并按照package(com.pphh.demo)放置编译好的class文件,具体位置为,
./target/classes/com/pphh/demo/Logger.class

d) 有依赖的编译

可以通过-cp选项,指定依赖的class/Jar文件或所在目录。
例如程序代码src2/AppLogger.java有对前一步产生的Logger.class有依赖,其编译命令为,

javac -cp ./target/classes -d ./target/classes src2/AppLogger.java

上述命令指定了在依赖文件目录为/target/classes,编译代码后,将在目录./target/classes中产生一个AppLogger.class文件。

e) CLASSPATH

在编译的时候,Javac会从如下几个地方查找依赖class文件,

  1. 环境变量CLASSPATH定义的依赖文件和目录
  2. 当前执行命令的目录
  3. 通过-cp/-classpath指定的依赖文件和目录

在指定依赖的时候,可以通过上述几个方法定义依赖的class文件。

f) 编译时指定源码和目标代码的Java版本

可以通过-source/-target选项指定。

javac -d ./target/classes ./src3/AppJ7.java -source 7 -target 7
javac -d ./target/classes ./src3/AppJ8.java -source 8 -target 8

在JDK 9中Javac支持的Java版本包括6, 7, 8, 9,JDK9不再支持-source 5之前的版本。

需要注意的是,源发行版必须高于目标发行版,换句话说Java 7语言级别的代码,可以编译成Java 7/8/9语言级别的目标class文件,但不能编译成Java 6语言级别目标代码。

3. class文件打包

对代码编译之后,为了方便发布和使用,需要使用工具jar对这些class文件进行打包成Jar包。Jar是Java Archives的缩写,意思为Java归档文件。

jar的命令格式为,

jar [options] [ [--release VERSION] [-C dir] files] ...

其中:
options 命令行选项
--release VERSION创建多版本的Jar包,这是JDK9新功能
-C DIR 更改目录路径

a) 简单的打包命令

在打包前,先切换到class文件目录,然后执行打包命令,

cd ./target/classes
jar cvf ../common.jar ./

上述命令将./target/classes目录下所有文件打好包为common.jar,并放置在上一层目录中,也就是./target/下面。

将common.jar放置在classes目录之外,这是为了区分class和jar包文件,防止在下一次运行命令时,将刚刚生成的common.jar也打入下一个包中,形成循环。

b) 打包时更改目录路径到根目录

很多时候我们希望直接指定class的目录路径,但是我们不要使用如下的命令,

jar cvf ./target/common.jar ./target/classes(不推荐此命令,请使用-C选项)

因为上面的命令没有更改目录结构。如果通过解压工具解压common.jar包,会发现里面classes文件目录结构是不对的,classes文件并没有放置在根目录,而且放置在target/classes/中,这个是不对的。

这个时候要使用-C选项,更改目录路径到根目录

jar -cvf ./target/common.jar -C ./target/classes/ .

c) 设置主程序

每一个Jar包都可以设置一个运行主程序,当使用Java -jar命令运行Jar包时,会自动执行这个主程序。

设置主程序要使用-e选项,

jar -cvfe ./target/common1.jar Simple -C ./target/classes/ .
jar -cvfe ./target/common2.jar AppLogger -C ./target/classes/ .
jar -cvfe ./target/common3.jar com.pphh.demo.AppPkg -C ./target/classes/ .

上面的命令将Jar包中的主程序分别设置为Simple\AppLogger\com.pphh.demo.AppPkg,包的运行见后面。

d) 选择指定的class文件打包

除了指定一个class目录,我们还可以指定某些class文件打成Jar包。

下面将指定的一个App.class打入app.jar包中,

jar -cvf ./target/app.jar -C ./target/classes/ Simple.class

下面将一个Logger.class归档为一个日志类库包中,

jar -cvf ./target/logger.jar -C ./target/classes/ ./com/pphh/demo/Logger.class

4. 执行Java程序

在将源码编译成可执行的class文件后,接下来就可以使用java工具来启动运行。

java的命令格式为,

java [options] mainclass [args...]

其中:
options 命令行选项
mainclass 主程序类文件
args 命令行参数

a) 简单的编译运行

javac Simple.java
java Simple

b) 指定class文件或目录
javac -d ./target/classes Simple.java
java -classpath ./target/classes Simple
java -classpath "./target/classes/Simple.class;" Simple
c) 运行一个在指定package的运行程序

javac -d ./target/classes ./src/main/java/com/pphh/demo/AppPkg.java
java -classpath ./target/classes com.pphh.demo.AppPkg

d) 运行一个已配置主程序的Jar包

java -jar ./target/common1.jar
java -jar ./target/common2.jar
java -jar ./target/common3.jar

e) 运行一个未配置主程序的Jar包,通过命令指定运行的主程序
java -classpath "./target/common.jar" Simple
java -classpath "./target/common.jar" AppLogger
java -classpath "./target/common.jar" com.pphh.demo.AppPkg

5. 参考文献

  1. Java 9 SE技术文档:Javac工具命令
  2. Java 9 SE技术文档:Jar工具命令
  3. Java 9 SE技术文档:Java工具命令
  4. Java 7 SE技术文档:Javac工具命令
  5. Java 7 SE技术文档:Jar工具命令
  6. Java 7 SE技术文档:Java工具命令

永远的民国风华-林徽因

年轻时林徽因

最近读了孟斜阳著的一本书,《一生盛放如莲花:林徽因传》,书中对林徽因的一生有着详细的记述,为爱痴狂的徐志摩,愿白首得一人心的梁思成,柏拉图式爱情的金岳霖,都分别单独一个卷章,记录了那个时代围绕着民国风华人物林徽因发生的故事。

林徽因

林徽因的诗,她的直率纯真,她的才华美貌,让所有接触过她的人都发出赞叹,也是留给我们后人对那个时代最值得去回忆的一个符号。

林徽因在1904年出生在一个名门之家,父亲林长民曾留学日本早稻田大学,在清末民国初期,社会动荡,思想活跃,林长民带着开明的才学和热血的激情进入政坛,直到后来在反奉反内战、征讨张作霖中,中流弹而失去了自己的生命。父亲对林徽因的影响是一生的,同时父亲对林徽因也寄予了深厚的期望,他曾说到,“作为一名天才女儿的父亲,不是容易享的福”,这种福不仅仅是幸运,更多的是责任。在林徽因14岁的时候,林长民带着她游历欧洲英国,徽因因此接触到了国外的进步,了解了外面的世界,练就了纯正流利的英语,慢慢深得中西方艺术文化真谛。也就是这个时候,她见到了徐志摩。

徐志摩

林徽因,对于徐志摩来说,是诗情的开始,也是诗情的结束。在伦敦剑河的桥边,两人一路相伴,畅想阔论,徐志摩从第一眼看见徽因开始,便知道自己一直寻找的灵魂伴侣就在眼前,于是抛妻离家,为了爱情,他不顾一切的去追求自己理想的她,每一首诗,每一行字,都倾注他对爱的渴望、期待、痛苦、坚持和执着,直到再一次飞往心中的那个她时,带着悠悠的思念,只为再一次和徽因相遇,却永远消失在天边。

1931年,徐志摩在收到林徽因的学术报告邀请,在飞行的途中飞机出事,徐志摩永远地消失在天边的云彩。轻轻的他来了,轻轻的他走了,有人说,徐志摩先生最好的一首诗是他的人生。

徐志摩走后,林徽因让梁思成从出事的地方带回一个飞机残片,挂在房屋床头,一直陪伴她到终点。

梁思成

林徽因和梁思成,始于父辈梁启超和林长民的安排,才子佳人相见很早,对彼此都留下深刻的印象。当林徽因1921年从伦敦回来,两个人便有了媒妁之言,而后1925年父母的安排下,一起赴美学习,而后在1927年行文定礼成婚。

梁思成不善言辞,却不乏幽默感,为人宽容厚重,和林徽因的俏皮可爱形成鲜明的对比,两者取长补短,生活中渐渐相互融合。在事业上,两个人共同选择了建筑,从1930年到1945年,梁思成林徽因夫妇二人共同走了中国的15个省,190多个县,考察测绘了2738处古建筑物,让很多中国古建筑重见天日,获得世界的认识,两人通过这些考察,最后一起破解了中国古书《营造法式》,为中国古代建筑学研究奠基。正是有了这共同的事业,两个人一起学习,一起成长,度过了艰难的战争岁月,一路走来,两个人的爱情愈加深沉圆润。梁思成为拥有徽因而珍惜不已,处处为徽因着想,而徽因为思成的胸怀大度所折服,佳偶天成。

金岳霖

在梁思成和林徽因的生活中,有一个人始终陪伴左右,那就是金岳霖,他终生未娶,把对徽因的爱恋沉在心底。老金在1932年搬到梁家所在胡同后,开始了二十多年的“逐林而居”,每天和梁林生活在同一屋檐下。金老对徽因的爱,发乎情止于礼,体现君子之风。在徽因逝世多年后,有一天,金老郑重其事地邀请了一些至交好友到北京饭店赴宴,没有说任何理由,正当大家在琢磨宴请为何时,金老在开席时站了起来,说:“今天是徽因的生日。”一句话让大家感叹不已。金岳霖对徽因的爱一生未变,后来梁思成和另一女子结婚,重温二人世界,金老对此一声不吭,每年逢徽因忌日,他总是要去拜访徽因墓,献上自己的一束花。在金老的晚年,有人请他为徽因诗集出版说一些话,他想了很久,没有啃声,最后他一字一顿地、毫无含糊地说:“我所有的话,都应该同她自己说,我不能说,我没有机会同她说的话,我不愿意说,也不愿意有这种话。”说完便默。

金岳霖独身一人,直到自己生命尽头,把自己对徽因的爱恋带到了最后。值得大家称赞的是,金岳霖对徽因的孩子视如己出,徽因的孩子也把金老当成自己的家人,照顾他到最后老去。

随着金老的离去,民国那些年围绕着林徽因的故事渐渐落下了帷幕,时间带走了那个时代对爱情的表达,留下了人们对那个时代风华的记忆。

参考资料

  • 《一生盛放如莲花:林徽因传》,孟斜阳著
  • 百度百科:林徽因