一、分布式基础知识
1.1 分布式系统的理解
把大型系统,分成一个个小的功能模块,再将小的模块部署在不同的服务器上,这些服务器合并而成一个大型系统。
1.2 发展演变
- ORM单一应用架构:将所有的功能放在一个服务器上。不容易拓展,当更改系统的时候,需要整体重新部署,也不利于协作开发;
- MVC垂直应用架构:哪个应用访问量大,就多部署在几台服务器上。例如商城项目,用户应用,订单应用,商品应用,可以用户和商品多部署几个服务器,每个应用服务器都做一套完整的流程,前端页面+业务逻辑+持久化。缺点是大量的应用之间需要交互,不可能完全独立,公用模块无法重复利用,开发性浪费。
- RPC分布式服务架构:远程过程调用,各个功能分离, A服务器上放前端页面,B服务器上处理业务1,C服务器处理业务2等等。然而A服务器调用B服务器的接口,这个过程就是RPC调用。
- SOA流动计算架构:当服务越来越多,造成小服务资源的浪费,就需要调度中心做资源调度和治理。
1.3 RPC基本原理
- 客户端想要调用另一个服务器的接口,首先A发送给Client Stub,Client Stub将传递的参数进行序列化,并发送socket消息给B服务器;
- B服务器也有一个Server Stub接收到A的消息后,将参数反序列化成对象,并调用B的本地服务的接口,去Server中处理服务,并返回结果;
- Server Stub拿到B返回的结果,进行序列化处理,并发送消息给A的Client Stub,Client Stub将数据反序列化后,返回给A的Client。
二、Dubbo环境搭建
2.1 Dubbo架构
- Provider:暴露服务的服务提供方
- Container:服务运行容器
- Consumer:调用远程服务的服务消费方
- Registry:服务注册与发现的注册中心
- Monitor:统计服务的调用次数和调用时间的监控中心
- 0-start,将服务提供者提供的服务启动起来;
- 1-register,将服务注册到Registry中,即注册中心;
- 2-subscribe,消费者向Registry订阅服务;
- 3-notify,Registry将IP端口和服务的调用地址给消费者;
- 4-invoke,RPC的过程,调用生产者的服务;
- 5-count,监控中心Monitor去监控调用次数和调用时间等信息。
2.2 安装zookeeper
- zookeeper用作dubbo的注册中心,下载zookeeper。下载网址:zookeeper下载
- 查看zoo.cfg配置文件。在conf文件夹下将zoo_sample.cfg复制并改名为zoo.cfg。
- 启动zookeeper。在bin目录下的cmd中,执行zkServer.cmd。
- 测试zookeeper。在bin目录下的cmd中,执行zkCli.cmd,进入敲命令。
ls / :列出zookeeper根下保存的所有节点
create -e /atguigu 123 :创建一个atguigu节点,值为123
get /atguigu :获取/atguigu节点的值
2.3 Dubbo安装
dubbo-admin是一个前后端分离的项目,前端使用vue,后端使用SpringBoot,如果要安装dubbo到本地,则需要首先安装jdk,maven,nodejs。
2.3.1 下载源码
https://github.com/apache/dubbo 在这个地址下载dubbo-admin-develop到本地
2.3.2 修改配置文件
进入dubbo-admin-server目录,找到src/main/rescources/application.properties文件,修改此文件的内容。
- 服务的端口、dubbo协议的端口
- admin.registry.address为注册中心的地址和端口
- admin.config-center为配置中心的地址和端口
2.3.3 打包项目
在dubbo-admin-develop目录下,执行maven clean package命令,进行打包。
2.3.4 启动后端
进入dubbo-admin-distribution目录的target文件夹下,使用java -jar启动dubbo-admin-0.5.0-SNAPSHOT.jar包。
2.3.5 启动前端
进入dubbo-admin-ui目录下,进行npm run dev命令,启动前端。
2.3.6 访问页面
进入Local地址,输入用户名密码,默认root/root,进入网页。
三、Dubbo入门案例
这里模拟下在不同模块中,如何利用dubbo调用不同模块的接口。
3.3.1 项目背景
一个电商系统,订单服务需要调用用户服务获取某个用户的所有地址。现在需要创建两个模块,模块一是订单服务,提供功能创建订单,模块二是用户模块,提供功能查询用户地址。
3.3.2 项目结构
项目是通过简单的spring搭建,通过xml的方式进行配置文件配置。
其中gmall-interface是注册中心,里面注册了所有的接口;order-service-consumer是消费者服务,user-service-provider是提供者服务。
3.3.3 user-service-provider
UserServiceImpl.java
package com.study.gmall.service.impl;
import com.study.gmall.bean.UserAddress;
import com.study.gmall.service.UserService;
import java.util.Arrays;
import java.util.List;
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市海淀区", "1", "王", "010-555555", "Y");
UserAddress address2 = new UserAddress(2, "广东省珠海市", "1", "李", "010-666666", "N");
return Arrays.asList(address1, address2);
}
}
provider.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://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 1. 指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名) -->
<dubbo:application name="user-service-provider"/>
<!-- 2. 指定注册中心的位置(二选一) -->
<!-- <dubbo:registry address="zookeeper://127.0.0.1:2181"/>-->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
<!-- 3. 指定通信规则 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 4. 暴露服务 ref:指向服务是真正实现对象 -->
<dubbo:service interface="com.study.gmall.service.UserService" ref="userServiceImpl"/>
<!-- 服务的配置 -->
<bean id="userServiceImpl" class="com.study.gmall.service.impl.UserServiceImpl"/>
</beans>
MainApplication.java
package com.study.gmall;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class MainApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("provider.xml");
ioc.start();
System.in.read();
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.study</groupId>
<artifactId>user-service-provider</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.study</groupId>
<artifactId>gmall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.86.Final</version>
</dependency>
<!-- 引入dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.12</version>
</dependency>
<!-- 引入操作zookeeper的客户端 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
</project>
3.3.4 order-service-consumer
OrderDServiceImpl.java
package com.study.gmall.service.impl;
import com.study.gmall.bean.UserAddress;
import com.study.gmall.service.OrderService;
import com.study.gmall.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 1. 将服务提供者注册到注册中心(暴露服务)
* 1)导入dubbo依赖和操作zookeeper的客户端依赖
* 2)配置服务提供者
* 2. 让服务消费者去注册中心订阅服务提供者的服务地址
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
@Override
public void initOrder(String userId) {
System.out.println("用户id:" + userId);
// 1. 查询用户的收货地址
List<UserAddress> addressList = userService.getUserAddressList(userId);
for (UserAddress userAddress : addressList) {
System.out.println(userAddress.getUserAddress());
}
}
}
consumer.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://code.alibabatech.com/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://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<context:component-scan base-package="com.study.gmall.service.impl"/>
<dubbo:application name="order-service-consumer"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!-- 声明需要调用的远程服务的端口,生成远程服务代理 -->
<dubbo:reference interface="com.study.gmall.service.UserService" id="userService"/>
</beans>
MainApplication.java
package com.study.gmall;
import com.study.gmall.service.OrderService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class MainApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("consumer.xml");
OrderService orderService = applicationContext.getBean(OrderService.class);
orderService.initOrder("1");
System.out.println("调用结束!");
System.in.read();
}
}
pom.xml文件同2.3.3的pom文件。
3.3.5 gmall-interface
UserAddress.java
package com.study.gmall.bean;
import java.io.Serializable;
public class UserAddress implements Serializable {
private Integer id;
private String userAddress;
private String userId;
private String consignee; // 收货人
private String phoneNum;
private String isDefault; // 是否为默认地址
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getConsignee() {
return consignee;
}
public void setConsignee(String consignee) {
this.consignee = consignee;
}
public String getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(String phoneNum) {
this.phoneNum = phoneNum;
}
public String getIsDefault() {
return isDefault;
}
public void setIsDefault(String isDefault) {
this.isDefault = isDefault;
}
public UserAddress(Integer id, String userAddress, String userId, String consignee, String phoneNum, String isDefault) {
this.id = id;
this.userAddress = userAddress;
this.userId = userId;
this.consignee = consignee;
this.phoneNum = phoneNum;
this.isDefault = isDefault;
}
@Override
public String toString() {
return "UserAddress{" +
"id=" + id +
", userAddress='" + userAddress + '\'' +
", userId='" + userId + '\'' +
", consignee='" + consignee + '\'' +
", phoneNum='" + phoneNum + '\'' +
", isDefault='" + isDefault + '\'' +
'}';
}
}
UserService.java
package com.study.gmall.service;
import com.study.gmall.bean.UserAddress;
import java.util.List;
public interface UserService {
/**
* 按照用户ID返回所有的收获地址
*/
List<UserAddress> getUserAddressList(String userId);
}
OrderService.java
package com.study.gmall.service;
public interface OrderService {
void initOrder(String userId);
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.study</groupId>
<artifactId>gmall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
3.3.6 运行
消费者调用生产者注册到注册中心的接口,实现调用到其他模块的接口。启动user-service-provider,order-service-consumer模块中的MainApplication类即可。
四、SpringBoot整合Dubbo
4.1 项目结构
pom.xml 生产者和消费者的pom文件均为此
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.11</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.study.gmall</groupId>
<artifactId>boot-user-service-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-user-service-provider</name>
<description>boot-user-service-provider</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.study</groupId>
<artifactId>gmall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.12</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.2 生产者
UserServiceImpl.java
生产者添加@DubboService注解,将实现类注册到spring容器。
package com.study.gmall.service.impl;
import com.study.gmall.bean.UserAddress;
import com.study.gmall.service.UserService;
import org.apache.dubbo.config.annotation.DubboService;
import java.util.Arrays;
import java.util.List;
@DubboService
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市海淀区", "1", "王", "010-555555", "Y");
UserAddress address2 = new UserAddress(2, "广东省珠海市", "1", "李", "010-666666", "N");
return Arrays.asList(address1, address2);
}
}
启动类
将启动类上加上@EnableDubbo注解
package com.study.gmall;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableDubbo
@SpringBootApplication
public class BootUserServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(BootUserServiceProviderApplication.class, args);
}
}
application.properties
# 应用名称
spring.application.name=boot-user-service-provider
# 应用服务 WEB 访问端口
server.port=8082
# 配置注册中心的地址,指定注册中心的类型和地址信息
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881
4.3 消费者
OrderController.java
package com.study.gmall.controller;
import com.study.gmall.bean.UserAddress;
import com.study.gmall.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller
public class OrderController {
@Autowired
OrderService orderService;
@ResponseBody
@RequestMapping("/initOrder")
public List<UserAddress> getOrder(@RequestParam("userId") String userId) {
return orderService.initOrder(userId);
}
}
OrderServiceImpl.java
package com.study.gmall.service.impl;
import com.study.gmall.bean.UserAddress;
import com.study.gmall.service.OrderService;
import com.study.gmall.service.UserService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import java.util.List;
@DubboService
public class OrderServiceImpl implements OrderService {
@DubboReference(interfaceClass = UserService.class)
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
System.out.println("用户id:" + userId);
List<UserAddress> addressList = userService.getUserAddressList(userId);
return addressList;
}
}
启动类
package com.study.gmall;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableDubbo
@SpringBootApplication
public class BootOrderServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(BootOrderServiceConsumerApplication.class, args);
}
}
application.properties
# 应用名称
spring.application.name=boot-order-service-consumer
# 应用服务 WEB 访问端口
server.port=8081
# 配置注册中心的地址,指定注册中心的类型和地址信息
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.registry.protocol=zookeeper
4.4 SpringBoot注解方式整合
4.1~4.3章节,是SpringBoot整合dubbo的配置方式进行整合,即
五、Dubbo配置
Dubbo配置文件网站,包含支持的所有配置组件及每个配置组件支持的所有配置项。
六、高可用
6.1 zookeeper宕机
- 当zookeeper注册中心宕机了,还可以消费dubbo暴露的服务;
- 监控中心宕掉不影响使用,只丢失了部分采样数据;
- 数据库宕掉,注册中心仍能通过缓存提供服务列表查询,但不能注册服务;
- 注册中心对等集群,任意一台宕机,将自动切换到另一台;
- 注册中心全部宕机,服务提供者和消费者仍能通过本地缓存通讯;
- 服务提供者无状态,任意一台宕机,不影响使用;
- 服务提供者全部宕机,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复。
6.2 dubbo直连
没有注册中心也能调用服务,可以通过dubbo直连的方式进行。
6.3 dubbo集群下的负载均衡
集群负载均衡时,dubbo提供了多种均衡策略,默认为random随即调用。
1. Random LoadBalance
随机,按权重设置随机概率。【在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重】
2. RoundRobin LoadBalance
轮询,按公约后的权重设置轮询比率。存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
3. LeastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
4. ConsistentHash LoadBalance
一致性 Hash,相同参数的请求总是发到同一提供者。
6.4 服务降级
当服务器压力剧增,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
- 方式一:消费方对该服务的方法调用,直接返回null值,不发起远程调用,用来屏蔽不重要的服务不可用时对调用方的影响
- 方式二:消费方对该服务的方法调用在失败后,再返回null值,不抛异常,用来容忍不重要服务不稳定时对调用方的影响
6.5 集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
集群容错模式
- Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。
重试次数配置如下:
<dubbo:service retries="2" />
或
<dubbo:reference retries="2" />
或
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
-
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 -
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 -
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 -
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。 -
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息。 -
集群模式配置
按照以下示例在服务提供方和消费方配置集群模式
<dubbo:service cluster="failsafe" />
或
<dubbo:reference cluster="failsafe" />
七、Dubbo原理
7.1 RPC原理
一次完整的RPC调用流程:
- 服务消费方(client)调用以本地调用方式调用的服务;
- client stub接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体;
- client stub找到服务地址,并将消息发送到服务端;
- server stub收到消息后进行解码;
- server stub根据解码结果调用本地的服务;
- 本地服务执行并将结果返回给server stub;
- server stub将返回结果打包成消息并发送至消费方;
- client stub接收到消息,并进行解码;
- 服务消费方得到最终结果。
RPC框架的目标就是要2~8这些步骤都封装起来,这些细节对用户来说是不可见的。
7.2 Dubbo原理
Dubbo架构图可参考:架构图