分布式系统概述
- 分布式系统是若干计算机独立的集合,这些计算机对用户来说就好像单个系统.
- 分布式系统是未来的大趋势,但这种大趋势也必定带来管理的困难,所以duboo系统便应运而生,用来管理分布式系统的运作.
RPC远程过程调用
-
但分布式系统的管理中,怎样在服务器之间建立连接,是一个重要的问题.
-
rpc简称远程过程调用,是一种技术的思想,而不是一种规范.
-
在上面的过程中,我们可以看出,绝对RPC思想的效率一是通讯的效率二是RPC中序列化和反序列化的效率,这关于数据格式之间的传输,是xml传输还是json格式传输.
简介
Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者(Consumer):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心:注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者.
监控中心:服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
windows下安装Zookeeper(注册中心)
下载apache-zookeeper-3.5.7-bin.tar.gz直接解压
然后找到conf目录中的zoo_sample.cfg文件,复制一份,更改名字为zoo.cfg
之后在conf同级目录下,新建一个名为data的包,然后打开刚才的zoo.cfg,编辑其中一项内容为:
dataDir=../data
之后在bin目录下,cmd进入doc窗口,输入zkServer.cmd没报错便可以启动,启动不要关闭窗口,再次进入一遍doc窗口,然后zkCli.cmd便可以启动zk服务.
服务管理中心安装
https://github.com/apache/dubbo-admin下载项目.
第二步:修改端口,进入dubbo-admin\dubbo-admin-server\src\main\resources下的application.properties文件,修改本机端口号
修改好后,在dubbo-admin\目录下,cmd进入doc窗口,然后输入mvn -v查看是否安装的maven,若没有安装,请安装并配置好环境变量.其次安装好maven后,输入命令mvn clean package,然后等待,出现下者,表示成功.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W4snjkAu-1653707010834)(C:\Users\46894\AppData\Local\Temp\WeChat Files\73e3faf4ef4e56bb8ec777fe52b1b7f.png)]
成功后,进入admin-server文件中的target文件夹,然后再次进入doc.
之后输入命令:java -jar dubbo-admin-server-0.4.0.jar 启动项目.项目启动完成.
去浏览器输入地址http://127.0.0.1:端口号/,用户名账号和密码都是root.
Dubbo入门,提供者和消费者
提出需求,在一个电商系统中,可分为订单模块和用户模块,这两个模块可能处在不同的服务器中,但每次生成订单的时候都得向用户模块去获取信息.
那么这里我们可以将用户模块看做提供者,提高用户地址信息,二订单模块可以分为消费者,需求用户的订单信息.
我们在idea中新建一个java工程,然后在工程里新建两个Maven模块,这就可以模拟不同模块之间的通讯.
但值得构思的是,这两个模块之间有一些共同之处,那就是都需要两个接口,一个是消费者接口,一个是提供者接口,所以我们可以将这里两个模块之间的一些抽象类或者接口也放在一个模块里.
这里便产生了三个模块,消费者模块,提供者模块,两者共有接口模块.
我们先编写接口模块,首先是将三个Maven工程的模块的打包方式都改成jar形式,用户地址信息类
package com.hyb.bean;
import java.io.Serializable;
/**
* 用户地址
* @author lfy
*
*/
public class UserAddress implements Serializable {
private Integer id;
private String userAddress; //用户地址
private String userId; //用户id
private String consignee; //收货人
private String phoneNum; //电话号码
private String isDefault; //是否为默认地址 Y-是 N-否
public UserAddress() {
super();
// TODO Auto-generated constructor stub
}
public UserAddress(Integer id, String userAddress, String userId, String consignee, String phoneNum,
String isDefault) {
super();
this.id = id;
this.userAddress = userAddress;
this.userId = userId;
this.consignee = consignee;
this.phoneNum = phoneNum;
this.isDefault = 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;
}
}
编完接口模块后,我们要编写提供者和消费者的接口了.
package com.hyb.service;
import com.hyb.bean.UserAddress;
import java.util.List;
public interface OrderService {
/**
* 初始化订单
* @param userId
*/
public List<UserAddress> initOrder(String userId);
}
package com.hyb.service;
import java.util.List;
import com.hyb.bean.UserAddress;
/**
* 用户服务
* @author lfy
*
*/
public interface UserService {
/**
* 按照用户id返回所有的收货地址
* @param userId
* @return
*/
public List<UserAddress> getUserAddressList(String userId);
}
这些接口一个用来消费服务,一个用来发送服务.
编写完接口模块后,需要编写提供者模块,该提供者模块中,首先需要导入依赖,一个是Dubbo的jar,该jar依赖了SSM的jar,所以不需要导入ssm的jar.除了这个jar,还需要与Zookeeper连接的客户端jar
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.5</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
其次,因为我们将调用接口都抽取在接口模块,所以该提供者模块是要实现接口模块的提供接口,所以要导入接口模块的jar.
<dependency>
<groupId>com.hyb.goods</groupId>
<artifactId>goods-interfaces</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
之后才是写实现提供接口的代码
package com.hyb.Impl;
import com.hyb.bean.UserAddress;
import com.hyb.service.UserService;
import java.util.Arrays;
import java.util.List;
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
// TODO Auto-generated method stub
System.out.println("UserServiceImpl..3.....");
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
return Arrays.asList(address1,address2);
}
}
到这里,肯定还没有写完,我们还需要一个向服务中心去注册自己的提供者服务.我们需要在resource目录下新建一个spring 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"
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">
<!--指定当前服务应用的名字,服务名称是唯一的-->
<dubbo:application name="user-service-provider"></dubbo:application>
<!--指定注册中心的位置-->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>
<!--指定通讯规则-->
<dubbo:protocol name="dubbo" port="20881"></dubbo:protocol>
<!--暴露服务 ref=指向该服务的实现-->
<dubbo:service interface="com.hyb.service.UserService" ref="userServiceImpl"></dubbo:service>
<bean name="userServiceImpl" class="com.hyb.Impl.UserServiceImpl"></bean>
</beans>
写完后,我们先来测试提供者这个模块.
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("provider.xml");
classPathXmlApplicationContext.start();
System.in.read();
}
启动该测试类你就会发现,在我们的注册中心,会多出一个服务,这个服务便是提供者本身(从服务管理中心的网址便可以查看变化情况)
最后,我们才编写消费者模块.导入的jar包与提供者一致.
不一致的有,我们要实现接口模块中的消费接口,即订单接口,拿取提供者的提供的用户地址信息
package com.hyb.Impl;
import com.hyb.bean.UserAddress;
import com.hyb.service.OrderService;
import com.hyb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 1、将服务提供者注册到注册中心(暴露服务)
* 1)、导入dubbo依赖(2.6.2)\操作zookeeper的客户端(curator)
* 2)、配置服务提供者
*
* 2、让服务消费者去注册中心订阅服务提供者的服务地址
* @author lfy
*
*/
//用spring的service
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
// TODO Auto-generated method stub
System.out.println("用户id:"+userId);
//1、查询用户的收货地址
List<UserAddress> addressList = userService.getUserAddressList(userId);
for (UserAddress userAddress : addressList) {
System.out.println(userAddress.getUserAddress());
}
return addressList;
}
}
其次,便是对应的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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置扫描组件,扫描OrderServiceImpl-->
<context:component-scan base-package="com.hyb.Impl"></context:component-scan>
<!--配置消费者名字-->
<dubbo:application name="order-service-consumer"></dubbo:application>
<!--注册中心地址-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>
<!--消费-->
<dubbo:reference interface="com.hyb.service.UserService" id="userService"></dubbo:reference>
</beans>
之后便是测试
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("consumer.xml");
OrderService orderService=classPathXmlApplicationContext.getBean(OrderService.class);
orderService.initOrder("1");
System.out.println("调用完成");
// 让控制台不停止,方便观察情况
System.in.read();
}
启动后便会发现,该消费者拿到了提供者的用户信息.这便解决了分布式系统的通讯问题.同事,一旦拿到了消息,服务管理中心的控制台的提供者服务便会自动下线.
给予Springboot的提供者和消费者
该例子原理和前一章一样,只不过换成了SpringBoot的方式,有一些小区别,
首先是导入将提供者和消费者换成两个Springboot工程,然后导入Dubbo和Springboot整合的依赖.
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
要注意版本的适配
然后在application.properties文件里配置
提供者配置:
dubbo.application.name=gmall-user
dubbo.registry.protocol=zookeeper
dubbo.registry.address=192.168.67.159:2181
dubbo.scan.base-package=com.atguigu.gmall
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#监控中心配置
dubbo.monitor.protocol=registry
application.name就是服务名,不能跟别的dubbo提供端重复
registry.protocol 是指定注册中心协议
registry.address 是注册中心的地址加端口号
protocol.name 是分布式固定是dubbo,不要改。
base-package 注解方式要扫描的包
消费者配置:
dubbo.application.name=gmall-order-web
dubbo.registry.protocol=zookeeper
dubbo.registry.address=192.168.67.159:2181
dubbo.scan.base-package=com.atguigu.gmall
dubbo.protocol.name=dubbo
其次,配置上是三个Dubbo提供的注解
@Service在提供者这一方类上代替暴露服务的配置.@Reference在消费者这一方,代替@Autowired自动注入.
@EnableDubbo 两者都要提供注解,开启基于Dubbo的注解配置,该注解和dubbo.scan.base-package对应,有了后者前者便不用配置
Dubbo配置顺序
在Dubbo中dubbo.protocol.port执行dubbo的端口,但这个端口有三个可以配置的地方,一个是项目运行的时候直接配置虚拟机参数,这是 优先级最高的,二是在application.properties里配置,三是在dubbo.properties配置公共属性.
启动时检查设置
在远程调用中,如果提供者没有开启,而消费者开启了,消费者就会报错.但在现实中,这个过程有时候是会发生的.所以我们要关闭启动时检查.
关闭检查设置有几个地方,首先是
dubbo:reference 标签,该标签在消费者这一方设置其check=false属性便可以避开检查对应的提供者.
但若是有多个提供者,该属性设置未免显得过于冗杂.
第二个是dubbo:registry标签,也可以设置check属性.这个表示避免检查注册中心,但这也是针对性的,不对所有起作用.
最后一个便是dubbo:consumer标签,该标签设置的属性便是以自身的角度,避开所有为其提供服务的提供者检查.
超时设置
- 如果在提供者一方线程延迟,消费者可以根据自己设置的超时时间而判断是否进行接下去的步骤.
- 首先,在dubbo:reference标签中设置timeout属性,单位为毫秒,便可以设置超时时间.
- 其次,在dubbo:reference标签中,再写上dubbo:method标签,指定提供者哪个方法为参考而设置超时时间.
- 最后,是在全局配置中.dubbo:consumer标签中的timeout属性设置超时时间.
- 这些超时时间的设置,优先级为:method标签最高,其次便是reference标签,然后才是全局配置.如果级别一致,则消费者优先,提供者次之.
- 注意,该超时时间若是不设置,会有个默认值,为1000毫秒.
- 该设置以消费者为例,提供者也可以设置,标签对应消费者就可以了.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VAjj6IGM-1653707010835)(C:\Users\46894\AppData\Local\Temp\WeChat Files\04d035c7630b0222f6fb73fa4f2d389.png)]
- 看到上面的图,便是优先顺序实例,不仅对于超时设置,对于其他任何配置都是这个顺序.
重置次数
- retries 远程服务调用重试次数,不包括第一次调用,不需要重试请设为0.
- 重置设置只对幂等操作有效.幂等操作表示执行多次结果返回一样,例如数据库的查询,删除,修改.这些查询,删除,修改的操作执行多次都是一样的效果.而数据表的增加则不能设置重置次数,如果重置多次,便会执行多次,该操作就会重复很多遍,增加多条数据.
多版本
- 在分布式系统中,若有某个模块升级,便会产生旧版本,但新版本需要一部分的模块真实测试而其他模块则还是需要旧版本,所以便可以提供多版本的设置.
- 比如,提供者一方有两个版本的服务暴露了出来,版本号可以在dubbo:service标签中用version属性设置.而在消费者一方,我们映射提供者服务的时候(dubbo:reference),也可以用version属性进行选择版本,或者直接让version属性值为*号,这样系统就会随机切换版本.
本地存根
本地存根提供了开发者在远程调用服务之前可以其他事情的功能,如本地逻辑,本地缓存等等.
这里我们启动前面的提供者和消费者案例,我们在消费者模块,添加一个Userservice的实现类
package com.hyb.Impl;
import com.hyb.bean.UserAddress;
import com.hyb.service.UserService;
import java.util.List;
public class UserServiceImpl implements UserService {
private final UserService userService;
/*dubbo会将远程代理对象传入,所以必须要有这个有参构造器*/
public UserServiceImpl(UserService userService) {
this.userService = userService;
}
/*在这个方法之中,我们便可以在调用远程的代理对象之前,做一些有意义的事情.*/
@Override
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("远程代理对象之前调用了方法");
return userService.getUserAddressList(userId);
}
}
该实现类表示我们在调用提供者的服务之前,自己先实现一遍,然后做一些操作,再去返回.
但我们要告诉dubbo,这是一个本地存根
<dubbo:reference stub="com.hyb.Impl.UserServiceImpl" interface="com.hyb.service.UserService" id="userService">
这样,当我们调用提供者服务的时候,这个存根便会先执行.
与Springboot整合的三种方式
第一种,是前面所讲的,将配置配置到application.properties文件中.然后加上暴露服务的注解和参考服务的注解便可以省去配置文件,但这种方式在配置文件中含有二级标签,例如dubbo:method标签的时候,无法实现.
第二种,是xml配置文件和Springboot的方式,我们将配置文件写好,然后再Springboot的启动类中,加上@ImportResource(locations=“classpath:…”)的注解来将配置文件加载在容器中.
第三种是Springboot结合Spring注解驱动开发的模式,将xml文件的标签一一映射成对象.
@Configuration
public class DubboConfig {
@Bean
public ReferenceConfig<UserService> referenceConfig(){
ReferenceConfig<UserService> userServiceReferenceConfig = new ReferenceConfig<>();
userServiceReferenceConfig.setStub("com.hyb.Impl.UserServiceImpl");
MethodConfig methodConfig = new MethodConfig();
methodConfig.setTimeout(1000);
List<MethodConfig> methodConfigs = new ArrayList<>();
userServiceReferenceConfig.setMethods(methodConfigs);
return userServiceReferenceConfig;
}
}
然后在启动类上加上@EnableDuboo(scanBasePackage=“全类名”)
zookeeper宕机与dubbo直连
zookeeper注册中心宕机,还可以消费dubbo暴露的服务。
l 监控中心宕掉不影响使用,只是丢失部分采样数据
l 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
l 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
l 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
l 服务提供者无状态,任意一台宕掉后,不影响使用
l 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
可以不使用注册中心,用dubbo直连的方式进行通讯.
@Reference(url =指定直连地址 )
负载均衡机制
基于权重的负载均衡
- 假如有多个提供者,可以给多个提供者设置不同的权重,到时候消费者请求不同提供者的时候,就可以根据提供者占据权重的比例进行选随机择,尽量达到负载均衡.请求越大,便越均衡.
基于权重的轮询负载均衡
- 同样给权重,算出权重比例,一开始按照顺序进行请求,但一旦某个模块接受请求的比例达到自身权重的比例,便会直接跳过该模块,寻找下一个模块进行请求.
- 但该方式存在累计请求问题,如果有某个提供者效率慢,就会造成请求堆积.
基于最少活跃数负载均衡
- 每次请求都会根据提供者上一次请求效率高的进行请求.
一致性哈希负载均衡
根据提供者的哈希分布进行均衡.
相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
例子
- 我们可以在提供者模块,修改不同的端口号,然后分别启动服务,这就模拟了多台服务器.
- 然后在暴露服务的标签或注解(@Service)中,进入weight属性的设,该属性可以设置权重,但一般不那么设置,而是在注册中心手动调节.
- 分别在不同端口号的提供者输出一句话,然后执行请求多个,就会发现系统默认是基于权重的负载均衡机制.而我们设置了权重比较大的提供者,得到的请求比例便会增大.但最后会达到各自提供者权重的比例平衡.
服务降级
- 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
- 在注册中心的便可以设置服务级别模式,一个是禁止服务:该模式是在请求的时候直接返回为null,不进行调用.二是容错服务:该模式表示在请求的时候如果出错了,不打印错误,而是返回为空,该模式可配合超时时间使用,一旦有请求超时了,那么便容错,不报错,但数据返回为空.
集群容错
容错分类
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" />
容错框架
Hystrix 旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能.
首先,导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
然后在Application类上增加@EnableHystrix来启用hystrix starter
@SpringBootApplication
@EnableHystrix
public class ProviderApplication {
在Dubbo的Provider上增加@HystrixCommand配置,这样子调用就会经过Hystrix代理。
@Service(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") })
@Override
public String sayHello(String name) {
// System.out.println("async provider received: " + name);
// return "annotation: hello, " + name;
throw new RuntimeException("Exception to show hystrix enabled.");
}
}
对于Consumer端,则可以增加一层method调用,并在method上配置@HystrixCommand。当调用出错时,会走到fallbackMethod = "reliable"的调用里。
@Reference(version = "1.0.0")
private HelloService demoService;
@HystrixCommand(fallbackMethod = "reliable")
public String doSayHello(String name) {
return demoService.sayHello(name);
}
public String reliable(String name) {
return "hystrix fallback value";
}