1.分布式系统中的相关概念
1.1 大型互联网项目架构目标
- 互联网项目的特点
用户多;流量大,并发高;海量数据;易受攻击;功能繁琐;高性能(提供快速的访问体验)。 - 衡量网站的性能指标
- 响应时间:指执行一个请求从开始到最后收到响应数据所花费的总体时间。
- 并发数:指系统同时能处理的请求数量。
- 并发连接数:指的是客户端向服务器发起请求,并建立了TCP连接。每秒钟服务器连接的总TCP数量
- 请求数:也称为 QPS(Query Per Second) 指每秒多少请求.
- 并发用户数:单位时间内有多少用户
- 吞吐量:指单位时间内系统能处理的请求数量。
- QPS:Query Per Second 每秒查询数。
- TPS:Transactions Per Second 每秒事务数。
- 一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。
- 一个页面的一次访问,只会形成一个TPS;但一次页面请求,可能产生多次对服务器的请求,就会有多个QPS
- 高性能:提供快速的访问体验。
- 高可用:网站服务一直可以正常访问。
- 可伸缩:通过硬件增加/减少,提高/降低处理能力。
- 高可扩展:系统间耦合低,方便的通过新增/移除方式,增加/减少新的功能/模块。
- 安全性:提供网站安全访问和数据加密,安全存储等策略。
- 敏捷性:随需应变,快速响应。
1.2 集群和分布式
- 集群:一个业务模块,部署在多台服务器上。
(很多“人”一起 ,干一样的事) - 分布式:一个大的业务系统,拆分为小的业务模块,分别部署在不同的机器上。
(很多“人”一起,干不一样的事。这些不一样的事,合起来是一件大事) - 集群分布式的优点:高性能+高可用+可伸缩+高可扩展
1.3 架构演进
1.单体架构
- 优点:简单:开发部署都很方便,小型项目首选。
- 缺点:项目启动慢;可靠性差;可伸缩性差;扩展性和可维护性差;性能低。
2.垂直架构
- 垂直架构是指将单体架构中的多个模块拆分为多个独立的项目。形成多个独立的单体架构。
- 单体架构存在的问题:项目启动慢;可靠性差;可伸缩性差;扩展性和可维护性差;性能低。
- 垂直架构存在的问题:重复功能太多。
3.分布式架构
- 分布式架构是指在垂直架构的基础上,将公共业务模块抽取出来,作为独立的服务,供其他调用者消费,以实现服务的共享和重用。
- RPC: Remote Procedure Call 远程过程调用。有非常多的协议和技术来都实现了RPC的过程。
比如:HTTP REST风格,Java RMI规范、WebService SOAP协议、Hession等等。 - 垂直架构存在的问题:重复功能太多
- 分布式架构存在的问题:服务提供方一旦产生变更,所有消费方都需要变更。
4.SOA架构
- SOA(Service-Oriented Architecture/面向服务的架构)
是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和契约联系起来。 - ESB(Enterparise Servce Bus)企业服务总线,服务中介:主要是提供了一个服务于服务之间的交互。
ESB包含的功能如:负载均衡,流量控制,加密处理,服务的监控,异常处理,监控告急等等。 - 分布式架构存在的问题:服务提供方一旦产生变更,所有消费方都需要变更。
5.微服务架构
- 微服务架构是在SOA做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。
- 微服务架构 = 80%的SOA服务架构思想 + 100%的组件化架构思想 + 80%的领域建模思想。
- 特点
- 服务实现组件化:开发者可以自由选择开发技术。也不需要协调其他团队。
- 服务之间交互一般使用REST API。
- 去中心化:每个微服务有自己私有的数据库持久化业务数据
- 自动化部署:把应用拆分成为一个一个独立的单个服务,方便自动化部署、测试、运维
- 缺点
- 粒度太细导致服务太多,维护成本高。
- 分布式系统开发的技术成本高,对团队的挑战大。
Dubbo 是 SOA 时代的产物,SpringCloud 是微服务时代的产物
2.Dubbo概述
2.1 Dubbo简介
- Apache Dubbo是一款高性能的Java RPC框架。
其前身是阿里巴巴公司开源的一个高性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成。 - 什么是RPC?
RPC全称为remote procedure call,即远程过程调用。比如两台服务器A和B,A服务器上部署一个应用,B服务器上部署一个应用,A服务器上的应用想调用B服务器上的应用提供的方法,由于两个应用不在一个内存空间,不能直接调用,所以需要通过网络来表达调用的语义和传达调用的数据。
需要注意的是RPC并不是一个具体的技术,而是指整个网络远程调用过程。
RPC是一个泛化的概念,严格来说一切远程过程调用手段都属于RPC范畴。各种开发语言都有自己的RPC框架。
Java中的RPC框架比较多,广泛使用的有RMI、Hessian、Dubbo等。 - Dubbo官网地址:https://dubbo.apache.org/zh/index.html
- Dubbo提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
2.2 Dubbo架构
节点角色说明:
- Provider:暴露服务的服务提供方
- Container:服务运行容器
- Consumer:调用远程服务的服务消费方
- Registry:服务注册与发现的注册中心
- Monitor:统计服务的调用次数和调用时间的监控中心
虚线都是异步访问,实线都是同步访问
蓝色虚线:在启动时完成的功能
红色虚线(实线)都是程序运行过程中执行的功能
调用关系说明
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
3.Dubbo快速入门
3.1 zookeeper介绍
通过前面的Dubbo架构图可以看到,Registry(服务注册中心)在其中起着至关重要的作用。
Dubbo官方推荐使用Zookeeper作为服务注册中心。
Zookeeper 是 Apache Hadoop 的子项目,是一个树型的目录服务。
其支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用 。
流程说明
- 服务提供者(Provider)启动时: 向
/dubbo/com.foo.BarService/providers
目录下写入自己的 URL 地址。 - 服务消费者(Consumer)启动时: 订阅
/dubbo/com.foo.BarService/providers
目录下的提供者 URL 地址。
并向/dubbo/com.foo.BarService/consumers
目录下写入自己的 URL 地址。 - 监控中心(Monitor)启动时: 订阅
/dubbo/com.foo.BarService
目录下的所有提供者和消费者 URL 地址。
3.2 zookeeper下载安装
- 上传zookeeper安装包
- 解压zookeeper安装包
# 创建zooKeeper目录 mkdir /opt/zooKeeper # 将zookeeper安装包移动到/opt/zooKeeper mv apache-zookeeper-3.5.6-bin.tar.gz /opt/zookeeper/ # 解压 tar -zxvf apache-ZooKeeper-3.5.6-bin.tar.gz
- 配置zoo.cfg
# 进入到conf目录 cd /opt/zooKeeper/apache-zooKeeper-3.5.6-bin/conf/ # 拷贝 cp zoo_sample.cfg zoo.cfg # 打开目录 cd /opt/zooKeeper/ # 创建存储目录 mkdir zkdata mkdir zkdatalog # 修改zoo.cfg vim /opt/zooKeeper/apache-zooKeeper-3.5.6-bin/conf/zoo.cfg 添加两行 dataDir=/opt/zooKeeper/zkdata dataLogDir=/opt/zooKeeper/zkdatalog
- 启动zookeeper
进入zookeeper的bin目录,启动服务命令 ./zkServer.sh start 停止服务命令 ./zkServer.sh stop 查看服务状态 ./zkServer.sh status
3.3 Dubbo快速入门
项目结构
tree -I target
.
├── dubbo-interface
│ ├── pom.xml
│ └── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── itheima
│ │ │ └── service
│ │ │ └── UserService.java
│ │ └── resources
│ └── test
│ └── java
├── dubbo-service
│ ├── pom.xml
│ └── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── itheima
│ │ │ └── service
│ │ │ └── impl
│ │ │ └── UserServiceImpl.java
│ │ ├── resources
│ │ │ ├── log4j.properties
│ │ │ └── spring
│ │ │ └── applicationContext.xml
│ │ └── webapp
│ │ └── WEB-INF
│ │ └── web.xml
│ └── test
│ └── java
├── dubbo-web
│ ├── pom.xml
│ └── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── itheima
│ │ │ └── controller
│ │ │ └── UserController.java
│ │ ├── resources
│ │ │ ├── log4j.properties
│ │ │ └── spring
│ │ │ └── springmvc.xml
│ │ └── webapp
│ │ └── WEB-INF
│ │ └── web.xml
│ └── test
│ └── java
└── logs
1.创建公共的接口模块dubbo-interface
创建UserService接口,并创建sayHello方法
package com.itheima.service;
public interface UserService {
public String sayHello();
}
2.创建服务提供者dubbo-service
编写pom.xml
<dependencies>
<!--spring的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--springmvc的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--Dubbo的依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--ZooKeeper客户端实现 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${zookeeper.version}</version>
</dependency>
<!--ZooKeeper客户端实现 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${zookeeper.version}</version>
</dependency>
<!--依赖公共的接口模块-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>dubbo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--tomcat插件-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>9000</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
创建UserServiceImpl实现类,并实现sayHello方法
package com.itheima.service.impl;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service // 导入dubbo的service注解,将这个类提供的方法对外发布,注册到注册中心中。
public class UserServiceImpl implements UserService {
public String sayHello() {
return "hello dubbo hello!~";
}
}
配置applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--dubbo的配置-->
<!--1.配置项目的名称,唯一-->
<dubbo:application name="dubbo-service"/>
<!--2.配置注册中心的地址:当前使用的是zookeeper-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!--3.配置dubbo包扫描-->
<dubbo:annotation package="com.itheima.service.impl"/>
</beans>
配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- spring -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
3.创建服务消费者dubbo-web
编写pom.xml
<dependencies>
<!--spring的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--springmvc的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--Dubbo的依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--ZooKeeper客户端实现 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${zookeeper.version}</version>
</dependency>
<!--ZooKeeper客户端实现 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${zookeeper.version}</version>
</dependency>
<!--依赖公共的接口模块-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>dubbo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--tomcat插件-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>8000</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
创建UserController类,并创建sayHello方法
package com.itheima.controller;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Reference;
@RestController
@RequestMapping("/user")
public class UserController {
/*
Reference远程注入的原理:
1. 从zookeeper注册中心获取userService的访问url
2. 进行远程调用RPC
3. 将结果封装为一个代理对象,给变量赋值。
*/
@Reference // 远程注入。
private UserService userService;
@RequestMapping("/sayHello")
public String sayHello(){
return userService.sayHello();
}
}
配置springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--dubbo的配置-->
<!--1.配置项目的名称,唯一-->
<dubbo:application name="dubbo-web"/>
<!--2.配置注册中心的地址:当前使用的是zookeeper-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!--3.配置dubbo包扫描-->
<dubbo:annotation package="com.itheima.controller"/>
</beans>
配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- Springmvc -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
4.测试
访问 http://localhost:8000/user/sayHello 查看效果
4.Dubbo管理控制台
dubbo-admin 是一个前后端分离的项目。前端使用vue,后端使用springboot,安装 dubbo-admin 其实就是部署该项目。
我们将dubbo-admin安装到开发环境上,要保证开发环境有jdk,maven,nodejs。
5.Dubbo的特性
5.1 序列化
- 问:两个机器传输数据,如何传输 Java 对象?
Dubbo 内部已经将 序列化 和 反序列化 的过程内部封装了。
我们只需要在定义pojo类时实现Serializable接口即可。
一般会定义一个公共的pojo模块,让生产者和消费者都依赖该模块。
5.2 地址缓存
- 问:注册中心挂了,地址是否可以正常访问?
可以。因为Dubbo服务消费者在第一次调用时,会将服务提供方地址缓存到本地,以后在调用则不会访问注册中心。
当服务提供者地址发生变化时,注册中心会通知服务消费者。
5.3 超时与重试
服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
在某个峰值时刻,大量的请求都在同时请求服务消费者,会造成线程的大量堆积,势必会造成雪崩。
Dubbo 利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
使用timeout属性配置超时时间,默认值1000,单位毫秒。
Dubbo提供重试机制来避免类似问题的发生。
通过retries属性来设置重试次数。默认为 2 次。
可以在服务提供者一方配置(service)。
@Service(timeout = 3000, retries = 2) // 当前服务超过3000秒,重试2次,一共是3次。
也可以在服务消费者一方配置(contorller)。
@Reference(timeout = 1000) // 远程注入。
5.4 多版本发布
灰度发布:当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。
Dubbo中使用version属性来设置和调用同一个接口的不同版本。
比如这是serviceImpl_1
@Service(version = "v1.0")
比如这是serviceImpl_2
@Service(version = "v2.0")
比如这是controller
@Reference(version = "v1.0")
5.5 负载均衡
负载均衡(Load Balance):其实就是将请求分摊到多个操作单元上进行执行,从而共同完成工作任务。
在集群负载均衡时,Dubbo 提供了多种均衡策略(包括随机、轮询、最少活跃调用数、一致性Hash),默认为random随机调用。
配置负载均衡策略,既可以在服务提供者一方配置,也可以在服务消费者一方配置。
比如:
@Controller
@RequestMapping("/demo")
public class HelloController {
// 在服务消费者一方配置负载均衡策略。
@Reference(check = false,loadbalance = "random")
private HelloService helloService;
@RequestMapping("/hello")
@ResponseBody
public String getName(String name){
//远程调用
String result = helloService.sayHello(name);
System.out.println(result);
return result;
}
}
// 在服务提供者一方配置负载均衡。
@Service(loadbalance = "random")
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "hello " + name;
}
}
可以通过启动多个服务提供者来观察Dubbo负载均衡效果。
我们也可以设置权重
@Service(weight = 100)
注意:因为我们是在一台机器上启动多个服务提供者,所以需要修改tomcat的端口号和Dubbo服务的端口号来防止端口冲突。
在实际生产环境中,多个服务提供者是分别部署在不同的机器上,所以不存在端口冲突问题。
- 负载均衡策略(4种):
- Random:按权重随机,默认值。按权重设置随机概率。
- RoundRobin:按权重轮询。
- LeastActive:最少活跃调用数,相同活跃数的随机。
- ConsistentHash:一致性Hash,相同参数的请求总是发到同一提供者。
5.6 集群容错
- 集群容错模式
- Failover Cluster:失败重试。默认值。当出现失败,重试其它服务器 ,默认重试2次,使用 retries 配置。一般用于读操作
- Failfast Cluster :快速失败,只发起一次调用,失败立即报错。通常用于写操作。
- Failsafe Cluster :失败安全,出现异常时,直接忽略。返回一个空结果。
- Failback Cluster :失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking Cluster :并行调用多个服务器,只要一个成功即返回。
- Broadcast Cluster :广播调用所有提供者,逐个调用,任意一台报错则报错。
比如:
@Reference(cluster = "failover")
5.7 服务降级
服务降级方式
mock = force:return null
:- 表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
mock = fail:return null
:- 表示消费方对该服务的方法调用在失败后,在返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
比如:
@Reference(mock = "force:return null") // 相当于不再调用 UserService的服务。