12. 分布式理论
12.1 简介
-
分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。
-
分布式是一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统,其目的是利用更多的机器,处理更多的数据。
-
小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群。为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式,一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群。
—— 张鹏飞
-
分布式架构四个核心问题
- 多服务,客户端如何访问?— API 网关,服务路由
- 多个服务之间如何通信?— HTTP,RPC框架,异步调用
- 多服务如何治理?— 服务注册与发现,高可用
- 服务挂了,怎么办?— 熔断机制,服务降级
12.2 Dubbo
(1) 背景
● 架构
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
单一应用架构(All in One)
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
垂直应用架构(Vertical Application)
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
分布式服务架构(Distributed Service)
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构(Elastic Computing)
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
● RPC
-
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务
-
本地过程调用
直接调用,函数体通过函数指针来指定。
-
远程过程调用
// Client端 1. 将调用映射为 (函数,进程 ID)。 2. 将 (函数,进程 ID) 序列化,以二进制形式打包 3. 把 2 中得到的数据包发送给 Server,这需要使用网络传输层 4. 等待服务器返回结果 5. 如果服务器调用成功,那么就将结果反序列化.
// Server端 1. 在本地维护 (函数,进程ID) 到函数指针的映射,可以用 Map<String, Method> map 2. 等待客户端请求 3. 得到一个请求后,将其数据包反序列化,得到 (函数,进程ID) 4. 通过在 map 中查找,得到相应的函数指针 5. 本地调用函数,得到结果 6. 将结果序列化后通过网络返回给 Client
(2) 概念
- Dubbo 是一个高性能优秀的服务框架,它使应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成。
- 节点角色说明
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
(3) 安装
-
下载地址:apache/dubbo-admin at master (github.com)
-
指定 zookeeper 注册地址
# dubbo-admin-master-0.2.0\dubbo-admin\src\main\resources\application.properties server.port=7001 spring.velocity.cache=false spring.velocity.charset=UTF-8 spring.velocity.layout-url=/templates/default.vm spring.messages.fallback-to-system-locale=false spring.messages.basename=i18n/message spring.root.password=root spring.guest.password=guest # 注册地址 dubbo.registry.address=zookeeper://127.0.0.1:2181
-
在项目目录下打包 dubbo-admin
mvn clean package -Dmaven. test. skip=true
- 配置 maven 的环境变量
- 以管理员身份运行 cmd
- 在 dubbo 目录下运行cmd,执行命令
(4) 测试
-
启动 zookeeper
-
启动 dubbo
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
-
浏览器访问 http://localhost:7001/,默认用户名和密码都为 root
12.3 Zookepper
● 简介
- ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
- 下载地址:http://zookeeper.apache.org/releases.html#download
● 启动服务
-
以管理员身份运行 zkService.cmd
-
服务闪退问题
- 没有配置 zoo.cfg 配置文件
-
解决方案
-
编辑 zkService.cmd 文件,末尾添加
pause
,暂停服务,便于查找错误。 -
程序未找到 zoo.cfg
-
复制 conf/zoo_sample.cfg 改名为 zoo.cfg
-
再次以管理员身份启动 zkService.cmd
-
测试
-
打开用户端 bin/zkCli.cmd
-
-
12.4 springboot+dubbo+zookeeper
(1) 建立空工程,创建两个 springboot 服务模块
(2) 为模块添加服务
● provider-server
<!--pom.xml-->
<!--dubbo-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!--zkCli-->
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!--zookeeper-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.1.0</version>
</dependency>
<!--解决报错:java.lang.NoClassDefFoundError: org/apache/curator/framework/recipes/cache/TreeCacheListener-->
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!--排除 slf4j-log4j12,避免日志冲突-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
# application.properties
server.port=8001
# 服务应用名字
dubbo.application.name=provider-server
# 注册中心地址,即存放服务的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
# 待注册的服务
dubbo.scan.base-packages=com.why.service
// service
public interface TicketService {
public String getTicket();
}
// service
// zookeeper:服务注册与发现
// 项目已启动就自动注册到注册中心
// 注意使用 dubbo 的 Service 注解
@Service
@Component
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "Train Ticket";
}
}
● consumer-server
<!--pom.xml-->
<!--dubbo-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!--zkCli-->
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!--zookeeper-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.1.0</version>
</dependency>
<!--解决报错:java.lang.NoClassDefFoundError: org/apache/curator/framework/recipes/cache/TreeCacheListener-->
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!--排除 slf4j-log4j12,避免日志冲突-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
# application.properties
server.port=8002
# 消费者取服务,需要暴露自己的名字
dubbo.application.name=consumer-server
# 注册中心的地址,即取服务的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
// service
public interface UserService {
public void buyTick();
}
// service
// 此处的 @Service 是将实现类注册到 Spring 中,注意导包路径
@Service
public class UserServiceImpl implements UserService {
// 在注册中心拿到 provider-server 的服务
// 本地可定义与 provider-server 端路径相同的接口,使用 @Reference 引用 provider-server 端注册在注册中心的服务
@Reference
private TicketService ticketService;
@Override
public void buyTick() {
String ticket = ticketService.getTicket();
System.out.println("在注册中心拿到=>" + ticket);
}
}
// service
public interface TicketService {
public String getTicket();
}
● 启动服务
-
启动 zkService
-
启动 dubbo-admin 程序
-
启动 provider-server 服务
-
编写并启动 consumer-server 测试
@Test void testBuyTick() { userService.buyTick(); }
consumer-server 服务并未编写实现类实现 TicketService 接口,此为 consumer-server 在注册中心拿到的结果。即执行了如下过程: