# 简介
# 概述
非主流微服务架构解决方案 Spring Cloud Alibaba + Dubbo + Vue,该章节为拓展章节,主要目的是想实现之前说的对内 RPC,对外 REST 并利用 Vue Element Admin 实现一个相对完整的前后分离效果。
# 目标
Spring Cloud Alibaba Dubbo 项目的目标是将 Dubbo 融入到 Spring Cloud Alibaba 生态中,使微服务之间的调用同时具备 RESTful 和 Dubbo 调用的能力。做到对业务代码无侵入,无感知;引入 JAR 包则微服务间调用使用 Dubbo,去掉 JAR 包则使用默认的 RESTful;实现参考的是 Spring Cloud 中文社区的 Spring Cloud Dubbo (opens new window) 项目。
# Nacos
既然已经将 Dubbo 集成进了 Spring Cloud Alibaba,那么理所当然的我们的注册中心也不再采用 Zookeeper 方案而是转为 Nacos 方案了,部署 Nacos : 服务注册与发现
注:截止到博客发表时间 2019 年 03 月 13 日,Nacos 已发行版为 0.9.0,在 0.8.0 时登录 Nacos 需要使用账号密码,默认账号密码为 nacos/nacos
# 实现基于 Feign 的注册方案
由于 Nacos、Dubbo、Spring Cloud Alibaba 都是阿里系产品,所以我们可以很容的将 Dubbo 和 Http 服务注册到 Nacos 中。
此时服务提供者即注册了 Dubbo 又注册了 Http 服务,服务消费者根据配置方式可以在 Dubbo 与 Http 调用中随意切换
# 服务提供者
服务提供者在使用 Dubbo 注册服务时是需要使用 @Service
注解将服务注册到注册中心的,现在改用 @FeignClient
注解来注册
以 spring-cloud-alibaba-dubbo-provider-api
项目中定义的 API 接口为例
package com.cmcc.alibaba.dubbo.provider.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient("provider")
public interface ProviderService {
@GetMapping("/hi")
String hi();
}
2
3
4
5
6
7
8
9
10
以 spring-cloud-alibaba-dubbo-provider-service
项目中实现接口为例
package com.cmcc.alibaba.dubbo.provider.service.impl;
import com.cmcc.alibaba.dubbo.provider.service.ProviderService;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProviderServiceImpl implements ProviderService {
@Override
public String hi() {
return "Hi Spring Cloud Alibaba Dubbo";
}
}
2
3
4
5
6
7
8
9
10
11
12
以上代码是一个典型的 Spring Cloud RESTFul API,服务提供者需要做的就是引入 Dubbo 相关依赖,扫描包含 @FeignClient
注解的类并注册到 Nacos 即可,关键代码在 spring-cloud-alibaba-dubbo-core
项目的 FeignClientToDubboProviderBeanPostProcessor
类中
/**
* Registers Beans whose classes was annotated {@link FeignClient}
*
* @param packagesToScan The base packages to scan
* @param registry {@link BeanDefinitionRegistry}
*/
private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class, true, true));
for (String packageToScan : packagesToScan) {
// Registers @Service Bean first
scanner.scan(packageToScan);
// Finds all BeanDefinitionHolders of @Service whether @ComponentScan scans or not.
Set<BeanDefinitionHolder> beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
registerServiceBean(beanDefinitionHolder, registry, scanner);
}
if (logger.isInfoEnabled()) {
logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " +
beanDefinitionHolders +
" } were scanned under package[" + packageToScan + "]");
}
} else {
if (logger.isWarnEnabled()) {
logger.warn("No Spring Bean annotating Dubbo's @Service was found under package["
+ packageToScan + "]");
}
}
}
}
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
# 服务消费者
服务消费者需要依赖 spring-cloud-alibaba-dubbo-provider-api
,并直接使用 @Autowired
注解即可实现注入,可以不使用 Dubbo 提供的 @Reference
注解
package com.cmcc.alibaba.dubbo.consumer.service;
import com.cmcc.alibaba.dubbo.provider.service.ProviderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestService {
@Autowired
private ProviderService providerService;
@GetMapping("/test")
public String test() {
return providerService.hi();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
以上代码是一个典型的 Spring Cloud Feign 调用。我们只需要替换 Feign 的实现。产生 ProviderService
接口的 ProxyBean
时,使用 Dubbo 产生的 Bean 替换默认的 Feign 产生的 RESTFul 调用的 Bean 即可,关键代码在 spring-cloud-alibaba-dubbo-core
项目的 DubboFeignBuilder
类中
@Override
public <T> T target(Target<T> target) {
ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder
.create(defaultReference, target.getClass().getClassLoader(), applicationContext)
.interfaceClass(target.type());
try {
T object = (T) beanBuilder.build().getObject();
return object;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 消费者使用 RESTFul 调用
只需将 Dubbo 相关依赖排除即可
<dependency>
<groupId>com.cmcc</groupId>
<artifactId>spring-cloud-alibaba-dubbo-starter</artifactId>
<exclusions>
<exclusion>
<groupId>com.cmcc</groupId>
<artifactId>spring-cloud-alibaba-dubbo-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 附:阿里为何放弃 Zookeeper
# CAP
有个思考,从 CAP 角度考虑,服务注册中心是 CP 系统还是 AP 系统呢?
- 服务注册中心是为了服务间调用服务的,那么绝对不允许因为服务注册中心出现了问题而导致服务间的调用出问题
- 假如有 node1,node2,node3 集群节点。保存着可用服务列表 ip1,ip2,ip3,试想如果此时不一致,比如 node1 只保存了ip1,ip2,此时服务读取 node1 的节点,那么会造成什么影响?
调用 node1 的服务,顶多就是负载均衡时不会有流量打到 ip3,然后等 node1 同步回 ip3 后,又一致了,这对服务其实没什么太大影响。所以,推测出服务注册中心应该是个 AP 系统。
注:CAP 定理可参考我 面试宝典 中的 说说 CAP 定理、 BASE 理论 章节
# Zookeeper 是个 CP 系统,强一致性
- 场景1,当 master 挂了,此时 Zookeeper 集群需要重新选举,而此时服务需要来读取可用服务,是不可用的。影响到了服务的可用性当然你可以说服务本地有缓存可用列表。然而下面这种方式就更无法处理了。
- 场景2,分区可用。试想,有 3 个机房,如果其中机房 3 和机房 1,2 网络断了,那么机房 3 的注册中心就不能注册新的机器了,这显然也不合理从健康检查角度来看
Zookeeper 是通过 TCP 的心跳判断服务是否可用,但 TCP 的活性并不代表服务是可用的,如:连接池已满,DB 挂了等
注意:Zookeeper 可以参考我 Apache Dubbo Zookeeper 解决方案的 Apache Zookeeper 章节
# 理想的注册中心
- 服务自动注册发现。最好有新的服务注册上去时还能推送到调用端
- 能对注册上来的机器方便的进行管理,能手动删除(发送信号让服务优雅下线)、恢复机器
- 服务的健康检查,能真正的检测到服务是否可用
- 可以看到是否有其他调用服务正在订阅注册上来的服务
- 能够带上些除了 IP 外的其它信息