01-Spring中BeanFacory和ApplicationContext的功能与实现

导图

flowchart LR
A(Spring) --> B(BeanFacory)
A --> C(ApplicationContext)
C --> 1(功能)
C --> 2(实现)
B --> 3(功能)
B --> 4(实现)
A --> 5(差异)

差异

org.springframework.beansorg.springframework.context 是Spring框架IoC容器的基础包。 BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象。ApplicationContext是BeanFactory的子接口,添加了如下功能:

  • 更容易与Spring的AOP功能集成
  • 消息资源处理(用于国际化)
  • 事件发布
  • 特定应用层的上下文,如用于web应用程序的WebApplicationContext
Table 9. Feature Matrix
Feature BeanFactory ApplicationContext

Bean instantiation/wiring Bean的实例化和自动装配

Yes

Yes

Integrated lifecycle management 集成bean生命周期管理

No

Yes

Automatic BeanPostProcessor registration Bean后置处理器自动注册

No

Yes

Automatic BeanFactoryPostProcessor registration BeanFactory后置处理器自动注册

No

Yes

Convenient MessageSource access (for internationalization) 国际化资源访问

No

Yes

Built-in ApplicationEvent publication mechanism 内置事件发布机制

No

Yes

常见实现

ApplicationContext:

  • AnnotationConfigApplicationContext 基于注解配置的A加载上下文
  • ClassPathXmlApplicationContext 基于xml配置的加载上下文
  • FileSystemXmlApplicationContext 基于xml配置的加载上下文
  • GenericApplicationContext 通用的上下文,更加灵活加载配置

BeanFactory:

  • DefaultListableBeanFactory 默认BeanFactory

代码示例

beanFactory 不会做的事情:

  1. 不会主动调用 beanFactory的后处理器
  2. 不会主动添加Bean的后处理器
  3. 不会主动初始化单例
  4. 不会解析beanFactory,还不会解析 ${} 占位符 #{} EL表达式

通过 DefaultListableBeanFactory 实现 ApplicationContext的功能:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package engineer.spring.ioc.context;

import javax.annotation.Resource;

public class AppBeanFactory {

protected final Log log = LogFactory.getLog(getClass());

public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// bean的定义 初始化 销毁 class scope 生命周期定义
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class)
.setScope(BeanDefinition.SCOPE_SINGLETON).getBeanDefinition();
beanFactory.registerBeanDefinition("config", beanDefinition);

// 给beanFactory添加常用的后置处理器
// 设置排序方法
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
// beanFactory 执行后置处理器类 补充一些bean的定义
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().stream().sorted(beanFactory.getDependencyComparator()).forEach(beanFactoryPostProcessor -> {
System.out.println("BeanFactoryPostProcessor: " + beanFactoryPostProcessor.getClass());
beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
});

// bean 执行后置处理器类 补充一些bean的扩展处理 如 @autowired @resource
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanPostProcessor -> {
System.out.println("BeanPostProcessor: " + beanPostProcessor.getClass());
beanFactory.addBeanPostProcessor(beanPostProcessor);
});

// 预处理单例类
beanFactory.preInstantiateSingletons();

System.out.println(beanFactory.getBean(Bean1.class).getBean2());


for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}

// beanFactory 后处理器有排序的逻辑
System.out.println(beanFactory.getBean(Bean1.class).getInter());

}

@Configuration
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}

@Bean
public Bean2 bean2() {
return new Bean2();
}

@Bean
public Bean3 bean3() {
return new Bean3();
}

@Bean
public Bean4 bean4() {
return new Bean4();
}
}

static class Bean1 {
@Autowired
private Bean2 bean2;
@Autowired
@Resource(name = "bean4")
private Inter bean3;

public Bean1() {
System.out.println("Bean1构造器初始化");
}

public Bean2 getBean2() {
return bean2;
}

public Inter getInter() {
return bean3;
}
}

static class Bean2 {
public Bean2() {
System.out.println("Bean2构造器初始化");
}
}

interface Inter {
}

static class Bean3 implements Inter {

}

static class Bean4 implements Inter {

}
}


参考

把工作当做热爱

一个人每周至少要工作40个小时,而平均每个人的工作年限是35年,也就是说除了睡觉以外,占据你最多时间的是——工作。
你是要浑浑噩噩度过一天中精力最充足的时间,还是集中精力解决一个个抛过来的难题磨练自己的技能,选择权在于你。

稻盛和夫曾说过:“劳动的意义不仅在于追求业绩,更在于完善人的内心。”

而你会发现,当你不再“逃跑”,而是选择面对直视问题的时候,你的人格就得到了磨练,你逐渐从从一个喜欢逃避、厌恶麻烦、随意任性的人变成一个直接面对问题、有责任感、值得信任的成熟社会人。

就是如果你一心扑在工作上,不管吃饭也好、睡觉也好,都在想着工作,那么那个困扰你很久的问题就可能在某个时刻得到启示。

每天吃吃喝喝的生活,在第一个星期的时候或许会觉得享受,但如果把这样的生活持续一个月、甚至半年,你会发现,你根本感觉不到快乐了。
因为幸福是一种对比,或者说是一种感受的落差。

不可否认的是,工作能够给人带来意义感。

正如稻盛和夫所说,“拼命工作的背后隐藏着快乐和欢喜,正像慢慢长夜结束后,曙光就会到来一样。”

在《干法》中,稻盛和夫将人分为三种类型:

1)不燃型:点火也烧不起来的人;
2)可燃型:点火就着的人;
3)自燃型:没人点自己就能熊熊燃烧的人。

自燃型的他们有极强的自主驱动力,长期处于学习区,他们不认为工作是一种任务,相反的是,他们认为工作很有意思,他们从来不会等别人吩咐了才去干,而是在被人吩咐之前就自发去干。

把工作当做热爱的认识能够持久的发光发热,温柔度过漫长的日子。

摘录自: 《人为什么要热爱工作?这是我听过最好的答案》https://mp.weixin.qq.com/s/_CNAsITDLyTMpOuVJrYh4Q

06-追踪微服务调用

概要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@startmindmap
* 追踪微服务调用
** 服务追踪的作用
*** 优化系统瓶颈
*** 优化链路调用
*** 生成网络拓扑
*** 透明传输数据
** 服务追踪系统原理
*** traceId,用于标识某一次具体的请求 ID。
*** spanId,用于标识一次 RPC 调用在分布式请求中的位置。
*** annotation,用于业务自定义埋点数据。
** 服务追踪系统实现
*** 数据采集层,负责数据埋点并上报。
*** 数据处理层,负责数据的存储与计算。
*** 数据展示层,负责数据的图形化展示。
@endmindmap

微服务架构下,由于进行了服务拆分,一次请求往往需要涉及多个服务,每个服务可能是由不同的团队开发,使用了不同的编程语言,还有可能部署在不同的机器上,分布在不同的数据中心。

服务追踪的作用

  • 第一,优化系统瓶颈。
    通过记录调用经过的每一条链路上的耗时,我们能快速定位整个系统的瓶颈点在哪里
  • 第二,优化链路调用。
    通过服务追踪可以分析调用所经过的路径,然后评估是否合理。
  • 第三,生成网络拓扑。
    通过服务追踪系统中记录的链路信息,可以生成一张系统的网络调用拓扑图,它可以反映系统都依赖了哪些服务,以及服务之间的调用关系是什么样的。
  • 第四,透明传输数据。

服务追踪系统原理

Dapper, a Large-Scale Distributed Systems Tracing Infrastructure:调用链:通过一个全局唯一的 ID 将分布在各个服务节点上的同一次请求串联起来,从而还原原有的调用关系,可以追踪系统问题、分析调用数据并统计各种系统指标。

  • traceId,用于标识某一次具体的请求 ID。当用户的请求进入系统后,会在 RPC 调用网络的第一层生成一个全局唯一的 traceId,并且会随着每一层的 RPC 调用,不断往后传递,这样的话通过 traceId 就可以把一次用户请求在系统中调用的路径串联起来。
  • spanId,用于标识一次 RPC 调用在分布式请求中的位置。当用户的请求进入系统后,处在 RPC 调用网络的第一层 A 时 spanId 初始值是 0,进入下一层 RPC 调用 B 的时候 spanId 是 0.1,继续进入下一层 RPC 调用 C 时 spanId 是 0.1.1,而与 B 处在同一层的 RPC 调用 E 的 spanId 是 0.2,这样的话通过 spanId 就可以定位某一次 RPC 请求在系统调用中所处的位置,以及它的上下游依赖分别是谁。
  • annotation,用于业务自定义埋点数据,可以是业务感兴趣的想上传到后端的数据,比如一次请求的用户 UID。

服务追踪系统实现

  • 数据采集层,负责数据埋点并上报。
  • 数据处理层,负责数据的存储与计算。
  • 数据展示层,负责数据的图形化展示。

参考

  • [从 0 开始学微服务 - 极客时间 - 胡忠想]

05-监控微服务调用

概要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@startmindmap
* 监控微服务
** 监控对象
*** 用户端监控
*** 接口监控
*** 资源监控
*** 基础监控
** 监控指标
*** 请求量
*** 响应时间
*** 错误率
** 监控维度
*** 全局维度
*** 分机房维度
*** 单机维度
*** 时间维度
*** 核心维度
** 监控系统原理
*** 数据采集
*** 数据传输
*** 数据处理
*** 数据展示
@endmindmap

与单体应用相比,在微服务架构下,一次用户调用会因为服务化拆分后,变成多个不同服务之间的相互调用,这也就需要对拆分后的每个服务都监控起来。

监控对象

  • 用户端监控。通常是指业务直接对用户提供的功能的监控。
  • 接口监控。通常是指业务提供的功能所依赖的具体 RPC 接口的监控。
  • 资源监控。通常是指某个接口依赖的资源的监控。
  • 基础监控。通常是指对服务器本身的健康状况的监控。

监控指标

  • 请求量。请求量监控分为两个维度,一个是实时请求量,一个是统计请求量。实时请求量用 QPS(Queries Per Second)即每秒查询次数来衡量,它反映了服务调用的实时变化情况。统计请求量一般用 PV(Page View)即一段时间内用户的访问量来衡量,比如一天的 PV 代表了服务一天的请求量,通常用来统计报表。
  • 响应时间。大多数情况下,可以用一段时间内所有调用的平均耗时来反映请求的响应时间。
  • 错误率。错误率的监控通常用一段时间内调用失败的次数占调用总次数的比率来衡量,比如对于接口的错误率一般用接口返回错误码为 503 的比率来表示。

监控维度

  • 全局维度。从整体角度监控对象的的请求量、平均耗时以及错误率,全局维度的监控一般是为了让你对监控对象的调用情况有个整体了解。
  • 分机房维度。
  • 单机维度。即便是在同一个机房内部,可能由于采购年份和批次的不同,位于不同机器上的同一个监控对象的各种指标也会有很大差异。
  • 时间维度。同一个监控对象,在每天的同一时刻各种指标通常也不会一样,这种差异要么是由业务变更导致,要么是运营活动导致。
  • 核心维度。业务上一般会依据重要性程度对监控对象进行分级,最简单的是分成核心业务和非核心业务。核心业务和非核心业务在部署上必须隔离,分开监控,这样才能对核心业务做重点保障。

监控系统原理

监控系统主要包括四个环节:数据采集、数据传输、数据处理和数据展示。

数据采集

通常有两种数据收集方式:

  • 服务主动上报,这种处理方式通过在业务代码或者服务框架里加入数据收集代码逻辑,在每一次服务调用完成后,主动上报服务的调用信息。
  • 代理收集,这种处理方式通过服务调用后把调用的详细信息记录到本地日志文件中,然后再通过代理去解析本地日志文件,然后再上报服务的调用信息。

数据传输

数据传输最常用的方式有两种:

  • UDP 传输,这种处理方式是数据处理单元提供服务器的请求地址,数据采集后通过 UDP 协议与服务器建立连接,然后把数据发送过去。
  • Kafka 传输,这种处理方式是数据采集后发送到指定的 Topic,然后数据处理单元再订阅对应的 Topic,就可以从 Kafka 消息队列中读取到对应的数据。

一般数据传输时采用的数据格式有两种:

  • 二进制协议,最常用的就是 PB 对象,它的优点是高压缩比和高性能,可以减少传输带宽并且序列化和反序列化效率特别高。
  • 文本协议,最常用的就是 JSON 字符串,它的优点是可读性好,但相比于 PB 对象,传输占用带宽高,并且解析性能也要差一些。

数据处理

数据处理是对收集来的原始数据进行聚合并存储。数据聚合通常有两个维度:

  • 接口维度聚合。把实时收到的数据按照接口名维度实时聚合在一起,这样就可以得到每个接口的实时请求量、平均耗时等信息。
  • 机器维度聚合。把实时收到的数据按照调用的节点维度聚合在一起,这样就可以从单机维度去查看每个接口的实时请求量、平均耗时等信息。

聚合后的数据需要持久化到数据库中存储,所选用的数据库一般分为两种:

  • 索引数据库,比如 Elasticsearch,以倒排索引的数据结构存储,需要查询的时候,根据索引来查询。
  • 时序数据库,比如 OpenTSDB,以时序序列数据的方式存储,查询的时候按照时序如 1min、5min 等维度来查询。

数据展示

数据展示是把处理后的数据以 Dashboard 的方式展示给用户。数据展示有多种方式,比如曲线图、饼状图、格子图展示等。

  • 曲线图。一般是用来监控变化趋势的。
  • 饼状图。一般是用来监控占比分布的。
  • 格子图。主要做一些细粒度的监控。

参考

  • [从 0 开始学微服务 - 极客时间 - 胡忠想]

04-实现RPC远程服务调用

概要

1
2
3
4
5
6
7
8
9
10
11
12
@startmindmap
* RPC远程服务调用
** 客户端和服务端建立网络连接
*** HTTP 通信
*** Socket 通信
** 服务端处理请求
*** 同步阻塞方式(BIO)
*** 同步非阻塞方式 (NIO)
*** 异步非阻塞方式(AIO)
** 数据传输协议
** 数据序列化和反序列化
@endmindmap

在单体应用时,一次服务调用发生在同一台机器上的同一个进程内部,也就是说调用发生在本机内部,因此也被叫作本地方法调用。在进行服务化拆分之后,服务提供者和服务消费者运行在两台不同物理机上的不同进程内,它们之间的调用相比于本地方法调用,可称之为远程方法调用,简称 RPC(Remote Procedure Call)。

把服务消费者叫作客户端,服务提供者叫作服务端,两者通常位于网络上两个不同的地址,要完成一次 RPC 调用,就必须先建立网络连接。建立连接后,双方还必须按照某种约定的协议进行网络通信,这个协议就是通信协议。双方能够正常通信后,服务端接收到请求时,需要以某种方式进行处理,处理成功后,把请求结果返回给客户端。为了减少传输的数据大小,还要对数据进行压缩,也就是对数据进行序列化。

客户端和服务端建立网络连接

客户端和服务端之间基于 TCP 协议建立网络连接最常用的途径有两种。

  1. HTTP 通信
  2. Socket 通信

HTTP 通信

HTTP 通信是基于应用层 HTTP 协议的,而 HTTP 协议又是基于传输层 TCP 协议的。一次 HTTP 通信过程就是发起一次 HTTP 调用,而一次 HTTP 调用就会建立一个 TCP 连接,经历一次下图所示的“三次握手”的过程来建立连接。

tcp-connection-establishment

完成请求后,再经历一次“四次挥手”的过程来断开连接。

tcp-connection-termination

Socket 通信

Socket 通信是基于 TCP/IP 协议的封装,建立一次 Socket 连接至少需要一对套接字,其中一个运行于客户端,称为 ClientSocket ;另一个运行于服务器端,称为 ServerSocket 。Socket 通信的过程分为四个步骤:服务器监听、客户端请求、连接确认、数据传输。

  • 服务器监听:ServerSocket 通过调用 bind() 函数绑定某个具体端口,然后调用 listen() 函数实时监控网络状态,等待客户端的连接请求。
  • 客户端请求:ClientSocket 调用 connect() 函数向 ServerSocket 绑定的地址和端口发起连接请求。
  • 服务端连接确认:当 ServerSocket 监听到或者接收到 ClientSocket 的连接请求时,调用 accept() 函数响应 ClientSocket 的请求,同客户端建立连接。
  • 数据传输:当 ClientSocket 和 ServerSocket 建立连接后,ClientSocket 调用 send() 函数,ServerSocket 调用 receive() 函数,ServerSocket 处理完请求后,调用 send() 函数,ClientSocket 调用 receive() 函数,就可以得到得到返回结果。

服务端处理请求

客户端和服务端已经建立了网络连接,服务端处理客户端的请求有三种方式。

  • 同步阻塞方式(BIO),客户端每发一次请求,服务端就生成一个线程去处理。当客户端同时发起的请求很多时,服务端需要创建很多的线程去处理每一个请求,如果达到了系统最大的线程数瓶颈,新来的请求就没法处理了。
  • 同步非阻塞方式 (NIO),客户端每发一次请求,服务端并不是每次都创建一个新线程来处理,而是通过 I/O 多路复用技术进行处理。就是把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使系统在单线程的情况下可以同时处理多个客户端请求。这种方式的优势是开销小,不用为每个请求创建一个线程,可以节省系统开销。
  • 异步非阻塞方式(AIO),客户端只需要发起一个 I/O 操作然后立即返回,等 I/O 操作真正完成以后,客户端会得到 I/O 操作完成的通知,此时客户端只需要对数据进行处理就好了,不需要进行实际的 I/O 读写操作,因为真正的 I/O 读取或者写入操作已经由内核完成了。这种方式的优势是客户端无需等待,不存在阻塞等待问题。

数据传输协议

最常用的有 HTTP 协议,它是一种开放的协议,各大网站的服务器和浏览器之间的数据传输大都采用了这种协议。还有一些定制的私有协议,比如阿里巴巴开源的 Dubbo 协议,也可以用于服务端和客户端之间的数据传输。

通常协议契约包括两个部分:消息头和消息体。其中消息头存放的是协议的公共字段以及用户扩展字段,消息体存放的是传输数据的具体内容。

数据序列化和反序列化

一般数据在网络中进行传输前,都要先在发送方一端对数据进行编码,经过网络传输到达另一端后,再对数据进行解码,这个过程就是序列化和反序列化。

常用的序列化方式分为两类:文本类如 XML/JSON 等,二进制类如 PB/Thrift 等,而具体采用哪种序列化方式,主要取决于三个方面的因素。

  • 支持数据结构类型的丰富度。数据结构种类支持的越多越好,这样的话对于使用者来说在编程时更加友好,有些序列化框架如 Hessian 2.0 还支持复杂的数据结构比如 Map、List 等。
  • 跨语言支持。序列化方式是否支持跨语言也是一个很重要的因素,否则使用的场景就比较局限,比如 Java 序列化只支持 Java 语言,就不能用于跨语言的服务调用了。
  • 性能。主要看两点,一个是序列化后的压缩比,一个是序列化的速度。以常用的 PB 序列化和 JSON 序列化协议为例来对比分析,PB 序列化的压缩比和速度都要比 JSON 序列化高很多,所以对性能和存储空间要求比较高的系统选用 PB 序列化更合适;而 JSON 序列化虽然性能要差一些,但可读性更好,更适合对外部提供服务。

参考

02-发布和引用服务

1
2
3
4
5
6
@startmindmap
* 服务描述协议
** RESTful API
** XML 配置
** IDL 文件
@endmindmap

想要构建微服务,首先要解决的问题是,服务提供者如何发布一个服务,服务消费者如何引用这个服务。最常见的服务发布和引用的方式有三种:

  • RESTful API
  • XML 配置
  • IDL 文件

RESTful API

RESTful API 的方式,主要被用作 HTTP 或者 HTTPS 协议的接口定义。

RESTful API 的优点:

  • 因为 HTTP 协议本身是一个公开的协议,对于服务消费者来说几乎没有学习成本,比较适合用作跨业务平台之间的服务协议。
  • 行为和资源分离,更容易理解。
  • 提出使用版本号(例如v1、v2),更加规范。

XML 配置

XML 配置方式的服务发布和引用主要分三个步骤:

  • 服务提供者定义接口,并实现接口。
  • 服务提供者进程启动时,通过加载 server.xml 配置文件将接口暴露出去。
  • 服务消费者进程启动时,通过加载 client.xml 配置文件来引入要调用的接口。

一般是私有 RPC 框架会选择 XML 配置这种方式来描述接口,因为私有 RPC 协议的性能要比 HTTP 协议高,所以在对性能要求比较高的场景下,采用 XML 配置的方式比较合适。

IDL 文件

IDL 就是接口描述语言(interface description language)的缩写,通过一种中立的方式来描述接口,使得在不同的平台上运行的对象和不同语言编写的程序可以相互通信交流。

IDL 主要是用作跨语言平台的服务之间的调用,有两种最常用的 IDL:一个是 Facebook 开源的 Thrift 协议,另一个是 Google 开源的 gRPC 协议。
一般是私有 RPC 框架会选择 XML 配置这种方式来描述接口,因为私有 RPC 协议的性能要比 HTTP 协议高,所以在对性能要求比较高的场景下,采用 XML 配置的方式比较合适。

gRPC 协议的服务描述是通过 proto 文件来定义接口的,然后再使用 protoc 来生成不同语言平台的客户端和服务端代码,从而具备跨语言服务调用能力。

参考

  • [从 0 开始学微服务 - 极客时间 - 胡忠想]

03-注册中心原理和实现方式

概要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@startmindmap
* 注册中心
** 注册中心原理
*** 服务提供者(RPC Server)
*** 服务消费者(RPC Client)
*** 和服务注册中心(Registry)
** 注册中心实现方式
*** 注册中心 API
**** 服务注册接口
**** 服务反注册接口
**** 心跳汇报接口
**** 服务订阅接口
**** 服务变更查询接口
**** 服务查询接口
**** 服务修改接口
*** 集群部署
*** 目录存储
*** 服务健康状态检查
*** 服务状态变更通知
@endmindmap

注册中心原理

在微服务架构下,主要有三种角色:服务提供者(RPC Server)、服务消费者(RPC Client)和服务注册中心(Registry)。

  • RPC Server 提供服务,在启动时,根据服务发布文件 server.xml 中的配置的信息,向 Registry 注册自身服务,并向 Registry 定期发送心跳汇报存活状态。
  • RPC Client 调用服务,在启动时,根据服务引用文件 client.xml 中配置的信息,向 Registry 订阅服务,把 Registry 返回的服务节点列表缓存在本地内存中,并与 RPC Sever 建立连接。
  • 当 RPC Server 节点发生变更时,Registry 会同步变更,RPC Client 感知后会刷新本地内存中缓存的服务节点列表。
  • RPC Client 从本地缓存的服务节点列表中,基于负载均衡算法选择一台 RPC Sever 发起调用。

微服务架构

当 RPC Server 节点发生变更时,RPC Client 可以通过拉取的方式获取最新服务节点。Registry 也可以推送节点变化的消息。

注册中心实现方式

1. 注册中心 API

  • 服务注册接口:服务提供者通过调用服务注册接口来完成服务注册。
  • 服务反注册接口:服务提供者通过调用服务反注册接口来完成服务注销。
  • 心跳汇报接口:服务提供者通过调用心跳汇报接口完成节点存活状态上报。
  • 服务订阅接口:服务消费者通过调用服务订阅接口完成服务订阅,获取可用的服务提供者节点列表。
  • 服务变更查询接口:服务消费者通过调用服务变更查询接口,获取最新的可用服务节点列表。

2. 集群部署

注册中心作为服务提供者和服务消费者之间沟通的桥梁,它的重要性不言而喻。所以注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。

3. 目录存储

注册中心存储服务信息一般采用层次化的目录结构。

4. 服务健康状态检测

注册中心除了要支持最基本的服务注册和服务订阅功能以外,还必须具备对服务提供者节点的健康状态检测功能,这样才能保证注册中心里保存的服务节点都是可用的。

5. 服务状态变更通知

一旦注册中心探测到有服务提供者节点新加入或者被剔除,就必须立刻通知所有订阅该服务的服务消费者,刷新本地缓存的服务节点信息,确保服务调用不会请求不可用的服务提供者节点。

参考

  • [从 0 开始学微服务 - 极客时间 - 胡忠想]

01-微服务架构定义

基本组件

微服务架构下,服务调用主要依赖下面几个基本组件:

  • 服务描述
  • 注册中心
  • 服务框架
  • 服务监控
  • 服务追踪
  • 服务治理

微服务架构

服务描述

服务调用首先要解决的问题就是服务如何对外描述。

常用的服务描述方式包括 RESTful API、XML 配置以及 IDL 文件三种。

注册中心

有了服务的接口描述,下一步要解决的问题就是服务的发布和订阅,就是说你提供了一个服务,如何让外部想调用你的服务的人知道。

注册中心的工作流程是:

  1. 服务提供者在启动时,根据服务发布文件中配置的发布信息向注册中心注册自己的服务。
  2. 服务消费者在启动时,根据消费者配置文件中配置的服务信息向注册中心订阅自己所需要的服务。
  3. 注册中心返回服务提供者地址列表给服务消费者。
  4. 当服务提供者发生变化,比如有节点新增或者销毁,注册中心将变更通知给服务消费者。

服务框架

通过注册中心,服务消费者就可以获取到服务提供者的地址,有了地址后就可以发起调用。发起调用之前,服务框架需要解决问题如下:

  1. 服务通信采用什么协议。服务提供者和服务消费者之间以什么样的协议进行网络通信,是采用四层 TCP、UDP 协议,还是采用七层 HTTP 协议。
  2. 数据传输采用什么方式。服务提供者和服务消费者之间的数据传输采用哪种方式,是同步还是异步,是在单连接上传输,还是多路复用。
  3. 数据压缩格式。通常数据传输都会对数据进行压缩,来减少网络传输的数据量,从而减少带宽消耗和网络传输时间,比如常见的 JSON 序列化、 Java 对象序列化以及 Protobuf 序列化等。

服务监控

一旦服务消费者与服务提供者之间能够正常发起服务调用,你就需要对调用情况进行监控,以了解服务是否正常。通常来讲,服务监控主要包括三个流程。

  • 指标收集。就是要把每一次服务调用的请求耗时以及成功与否收集起来,并上传到集中的数据处理中心。
  • 数据处理。有了每次调用的请求耗时以及成功与否等信息,就可以计算每秒服务请求量、平均耗时以及成功率等指标。
  • 数据展示。数据收集起来,经过处理之后,还需要以友好的方式对外展示,才能发挥价值。通常都是将数据展示在 Dashboard 面板上,并且每隔 10s 等间隔自动刷新,用作业务监控和报警等。

服务追踪

除了需要对服务调用情况进行监控之外,你还需要记录服务调用经过的每一层链路,以便进行问题追踪和故障定位。

服务追踪的工作原理大致如下:

  • 服务消费者发起调用前,会在本地按照一定的规则生成一个 requestid,发起调用时,将 requestid 当作请求参数的一部分,传递给服务提供者。
  • 服务提供者接收到请求后,记录下这次请求的 requestid,然后处理请求。如果服务提供者继续请求其他服务,会在本地再生成一个自己的 requestid,然后把这两个 requestid 都当作请求参数继续往下传递。

服务治理

服务监控能够发现问题,服务追踪能够定位问题所在,而解决问题就得靠服务治理了。服务治理就是通过一系列的手段来保证在各种意外情况下,服务调用仍然能够正常进行。

  • 单机故障。通常遇到单机故障,都是靠运维发现并重启服务或者从线上摘除故障节点。然而集群的规模越大,越是容易遇到单机故障,在机器规模超过一百台以上时,靠传统的人肉运维显然难以应对。而服务治理可以通过一定的策略,自动摘除故障节点,不需要人为干预,就能保证单机故障不会影响业务。
  • 单 IDC 故障。你应该经常听说某某 App,因为施工挖断光缆导致大批量用户无法使用的严重故障。而服务治理可以通过自动切换故障 IDC 的流量到其他正常 IDC,可以避免因为单 IDC 故障引起的大批量业务受影响。
  • 赖服务不可用。比如你的服务依赖依赖了另一个服务,当另一个服务出现问题时,会拖慢甚至拖垮你的服务。而服务治理可以通过熔断,在依赖服务异常的情况下,一段时期内停止发起调用而直接返回。

参考

  • [从 0 开始学微服务 - 极客时间 - 胡忠想]

00-微服务定义

12因素应用和微服务

12-Factor 为构建如下的 SaaS 应用提供了方法论:

  • 使用标准化流程自动配置,从而使新的开发者花费最少的学习成本加入这个项目。
  • 和操作系统之间尽可能的划清界限,在各个系统中提供最大的可移植性。
  • 适合部署在现代的云计算平台,从而在服务器和系统管理方面节省资源。
  • 将开发环境和生产环境的差异降至最低,并使用持续交付实施敏捷开发。
  • 可以在工具、架构和开发流程不发生明显变化的前提下实现扩展。

满足12 因素的app大概是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
I. 基准代码
一份基准代码,多份部署
II. 依赖
显式声明依赖关系
III. 配置
在环境中存储配置
IV. 后端服务
把后端服务当作附加资源
V. 构建,发布,运行
严格分离构建和运行
VI. 进程
以一个或多个无状态进程运行应用
VII. 端口绑定
通过端口绑定提供服务
VIII. 并发
通过进程模型进行扩展
IX. 易处理
快速启动和优雅终止可最大化健壮性
X. 开发环境与线上环境等价
尽可能的保持开发,预发布,线上环境相同
XI. 日志
把日志当作事件流
XII. 管理进程
后台管理任务当作一次性进程运行

微服务作为一个概念是满足12因素的多应用方案,是一个可以具体落地的方案。

单体服务与微服务

单体服务,以 MVC 架构为例,业务通常是通过部署一个 WAR 包到 Tomcat 中,然后启动 Tomcat,监听某个端口即可对外提供服务。早期在业务规模不大、开发团队人员规模较小的时候,采用单体应用架构,团队的开发和运维成本都可控。存在问题如下:

  • 部署效率低下。
  • 团队协作开发成本高。
  • 系统高可用性差。
  • 线上发布变慢。

服务化,就是把传统的单机应用中通过 JAR 包依赖产生的本地方法调用,改造成通过 RPC 接口产生的远程方法调用。通过服务化,可以解决单体应用膨胀、团队开发耦合度高、协作效率低下的问题。

  1. 单体服务的线上服务稳定容易被其他功能模块影响。
  2. 单体服务在进行多个功能模块开发的时候混合进行开发、测试部署。不同功能之间容易影响。

服务化的思想进一步演化,演变为今天我们所熟知的微服务。微服务相比于服务化的不同:

  1. 服务拆分粒度更细。
  2. 服务独立部署。
  3. 服务独立维护。
  4. 服务治理能力要求高。

常见的服务拆分方式:

  1. 纵向拆分,从业务角度拆分,按照业务的关联独立程度来决定。独立功能,关联较为深的拆分微服务。
  2. 横向拆分,从公共且独立功能维度拆分。多个公共服务被调用,且依赖资源独立不与其他业务耦合。

参考

  • 12-factors app
  • [从 0 开始学微服务 - 极客时间 - 胡忠想]

微服务核心组件分析

单体服务与微服务

  1. 单体服务的线上服务稳定容易被其他功能模块影响。
  2. 单体服务在进行多个功能模块开发的时候混合进行开发、测试部署。不同功能之间容易影响。

服务拆分方式:

  1. 纵向拆分,从业务角度拆分,按照业务的关联独立程度来决定。独立功能,关联较为深的拆分微服务。
  2. 横向拆分,从公共且独立功能维度拆分。多个公共服务被调用,且依赖资源独立不与其他业务耦合。

微服务框架解决的问题

服务定义

对于单体应用来说,不同功能模块之前相互交互时,通常是以类库的方式来提供各个模块的功能。无论采用哪种通讯协议,是 HTTP 还是 RPC,服务之间的调用都通过接口描述来约定,约定内容包括接口名、接口参数以及接口返回值。

服务管理

  • 服务管理
    • 服务发现
    • 服务订阅

单体应用由于部署在同一个 WAR 包里,接口之间的调用属于进程内的调用。而拆分为微服务独立部署后,服务提供者该如何对外暴露自己的地址,服务调用者该如何查询所需要调用的服务的地址呢?这个时候你就需要一个类似登记处的地方,能够记录每个服务提供者的地址以供服务调用者查询,在微服务架构里,这个地方就是注册中心。

服务监控

通常对于一个服务,我们最关心的是 QPS(调用量)、AvgTime(平均耗时)以及 P999(99.9% 的请求性能在多少毫秒以内)这些指标。这时候你就需要一种通用的监控方案,能够覆盖业务埋点、数据收集、数据处理,最后到数据展示的全链路功能。

服务治理

拆分为微服务架构后,服务的数量变多了,依赖关系也变复杂了。比如一个服务的性能有问题时,依赖的服务都势必会受到影响。可以设定一个调用性能阈值,如果一段时间内一直超过这个值,那么依赖服务的调用可以直接返回,这就是熔断,也是服务治理最常用的手段之一。

故障定位

在单体应用拆分为微服务之后,一次用户调用可能依赖多个服务,每个服务又部署在不同的节点上,如果用户调用出现问题,你需要有一种解决方案能够将一次用户请求进行标记,并在多个依赖的服务系统中继续传递,以便串联所有路径,从而进行故障定位。

微服务框架解决方案

  • 服务管理
    • 服务注册
    • 服务订阅
  • 服务调用
    • 外部调用(网关)
    • 内部调用(ESB)
  • 服务框架
    • 开发框架
    • 远程调用
    • 交互协议
  • 服务治理
    • 配置中心
    • 熔断
    • 负载均衡
  • 服务定义
    • RESTful API(http协议)
    • XML配置(PRC协议)
    • IDL文件(跨语言服务调用)
  • 服务追踪
    • 调用链路
    • 调用记录

Spring Cloud

  • Spring CLoud Netflix
    • zuul
      • 服务路由
      • 服务过滤器
    • eureka
      • 服务注册
      • 服务发现
    • Ribbon
      • 客户端负载均衡
    • 断路器
      • 服务降级调用 Hystrix
      • 控制面板
  • spring cloud config (配置中心)
  • spring boot (开发框架)
  • Spring Cloud Alibaba
    • gateway
    • nacos
    • dubbo/springboot

Spring Cloud Netflix

  • 服务管理 Eureka
    • 服务注册
    • 服务订阅
  • 服务调用
    • 外部调用(网关) Zuul
    • 内部调用(ESB)Feign
  • 服务框架
    • 开发框架 Spring boot
    • 远程调用 Feign
    • 交互协议 Rest协议
  • 服务治理
    • 配置中心 无,可使用spring cloud config (配置中心)
    • 熔断 Hystrix工具
    • 负载均衡 Ribbon客户端负载均衡
  • 服务定义
    • RESTful API(http协议)
  • 服务追踪 SkyWaliking\ELK等框架集成
    • 调用链路
    • 调用记录

netflix

发展前景

  • Service Mesh: Service Mesh 是一种新型的用于处理服务与服务之间通信的技术,尤其适用以云原生应用形式部署的服务,能够保证服务与服务之间调用的可靠性。在实际部署时,Service Mesh 通常以轻量级的网络代理的方式跟应用的代码部署在一起,从而以应用无感知的方式实现服务治理。
  • Docker 能帮助解决服务运行环境可迁移问题的关键,就在于 Docker 镜像的使用上,实际在使用 Docker 镜像的时候往往并不是把业务代码、依赖的软件环境以及操作系统本身直接都打包成一个镜像,而是利用 Docker 镜像的分层机制,在每一层通过编写 Dockerfile 文件来逐层打包镜像。这是因为虽然不同的微服务依赖的软件环境不同,但是还是存在大大小小的相同之处,因此在打包 Docker 镜像的时候,可以分层设计、逐层复用,这样的话可以减少每一层镜像文件的大小。

参考