一、什么是分布式
传统的老式架构就是把所有的服务都集中在一个系统中 然后统一一起部署在一台服务器上 这样会带来很多问题 例如系统功能的扩展等 而分布式系统就是将各个服务抽取出来放在一个单独的服务器上 然后通过这些服务器之间的交互来实现整个系统功能
二、几种系统架构
ORM
单一应用架构:系统的所有功能都放在一个项目中 再将项目部署在 服务器上
缺点:1.如果要添加某一个功能的话就要把一个项目重新打包,在分别部署到每一个服务器当中去。2.如果后期项目越来越大的话单台服务器跑一个项目压力会很大的。
MVC
垂直应用架构:将系统的服务抽取出来 把每个服务都做成一个完整的小系统(都包括前端 数据库 后端)在把每个小系统部署在服务器上。
RPC
分布式应用架构(远程过程调用):当垂直应用(MVC架构中的小系统)越来越多,应用之间交互不可避免,将核心业务和前端页面都抽取出来,作为独立的服务都可以独立运行,再在各个系统之间进行交互来形成系统的功能。
SOA
流动计算架构:在RPC架构中 每个服务之间的流量可能不尽相同 如果将每个服务的服务器数量 作为不变的 那么是很不利的 因此做好可以动态的调整各个服务的服务器数量 这就是SOA 资源调度和治理中心在SOA的核心。
三、RPC简介
1.原理思想:
假设A服务器需要调用B服务器上的一个方法 那么就需要在A服务器与B服务器之间建立连接(通常的socket网络连接) 然后将A服务器调用所需要的参数传递给B服务器 然后B服务器再将方法的返回值传递给A服务器。
2.RPC框架要素:
1.两个服务器进行通讯就需要建立连接 那么能否快速的在服务器之间建立连接就是一个很重要的标准
2.在网络之间进行数据传递 数据都需要进行序列化 那么RPC框架的序列化机制是否快速就是一个很重要的标准
四、Dubbo基础概念
1、dubbo作用:
实现各个服务器之间的远程调用 数据传递进程通讯等问题
(负载均衡问题:简单的来说 假设A服务部署在多台服务器上 且这些服务器处理的请求数各不相同 那么再收到请求就应该让相对空闲的服务器去处理这个请求)
2.注册中心:
就是可以服务注册在这个注册中心中 然后再各个服务器需要这个服务的时候 可以去注册中心中进行查找看那些服务器中有这个服务 再进行相应的调用
3.设计架构:
执行流程:
1.容器启动 服务提供者会将服务注册到注册中心
2.如果服务消费者需要这个服务 就会去注册中心订阅这个服务 同时可以同步的就去调用服务提供者的这个服务
3.如果服务提供者的某些服务发生了变化 那么注册中心还可以将这变化反馈给服务消费者
4.服务提供者与服务消费者之间的交互信息 都会再监控中心被记录
五、Dubbo初体验 环境搭建
1.下载注册中心zookeeper,搭建监控中心
2.搭建环境
新建服务提供者和服务消费者以及公共部分
服务提供者实现接口 服务消费者消费接口
公共部分是服务接口以及javabean
3.让各个服务可以通信
将服务提供者注册到注册中心
服务消费者去注册中心订阅服务
将服务提供者注册到注册中心 配置提供者
提供者中导入dubbo依赖 引入操作zookeeper的客户端
<!--引入dubbo依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<!--引入操作zookeeper的客户端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
配置服务提供者 暴露服务
<!--指定当前服务的名字 同样的服务名字相同 不要跟别的服务名字相同-->
<dubbo:application name="provider"></dubbo:application>
<!--指定注册中心的位置-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>
<!--指定通信规则 包括通信的协议和端口-->
<dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>
<!--指定暴露的服务 interface要暴露的接口名 ref该接口的实现-->
<bean class="com.ldb.serviceimp.UserServiceImpl" id="userService"></bean>
<dubbo:service interface="com.ldb.service.UserService" ref="userService"></dubbo:service>
服务消费者去注册中心订阅服务 配置消费者
在消费者中引入dubbo依赖以及zookeeper的操作客户端
<!-- 引入dubbo -->
<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<!-- 注册中心使用的是zookeeper,引入操作zookeeper的客户端端 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
进行配置消费者
<!-- 指定应用的名字-->
<dubbo:application name="order-service-consumer"></dubbo:application>
<!-- 指定注册中心地址-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>
<!-- 配置本地存根-->
<!--声明需要调用的远程服务的接口;会生成远程服务代理 -->
<!-- interface就是要调用的远程服务-->
<dubbo:reference interface="com.atguigu.gmall.service.UserService"
id="userService" >
</dubbo:reference>
连接监控中心:
dubbo配置的加载顺序:
虚拟机参数,XML文件,properties文件
常用配置:
启动时检查:会先检查消费者所需要的服务是否在注册中心里面 默认 check="true"
可以通过 check="false"
关闭检查
配置当前引用服务
<dubbo:reference interface="com.foo.BarService" check="false" />
配置所有服务的统一检查规则:
<dubbo:consumer check="false" />
配置超时 timeout 单位毫秒 默认值是1000:
<dubbo:reference interface="com.atguigu.gmall.service.UserService"
id="userService" timeout="5000" ></dubbo:reference>
全体设置:
<dubbo:consumer check="false" timeout="5000"></dubbo:consumer>
配置优先级:
- 方法级优先,接口级次之,全局配置再次之。
- 如果级别一样,则消费方优先,提供方次之。
- 越精确的越优先 级别一样的时候 消费者优先
配置重试次数:不包含第一次调用 0代表不重试 当一个服务器上的服务不可用而另一个服务器上也有这个服务的话 会自动的去调用另一台机器上的服务
<dubbo:reference interface="com.atguigu.gmall.service.UserService"
id="userService" retries="3" >
</dubbo:reference>
幂等 指无论这个操作执行多少次执行结果都是一样的 这样的一般会设置重试次数(查询、删除、修改)、非幂等 指操作多次会导致不同的执行结果 一般不设置重试次数(新增)
多版本设置:version 实现灰度发布
首先在服务提供者里面要提供多版本号 然后再调用里面要指定版本号 * 就是随机调用
<dubbo:reference interface="com.atguigu.gmall.service.UserService"
id="userService" version="*">
</dubbo:reference>
本地存根:
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑
先要在消费者端写一个接口实现要调用的远程服务的接口并提供一个有参构造器
public class BarServiceStub implements BarService {
private final BarService barService;
// 构造函数传入真正的远程代理对象
public BarServiceStub(BarService barService){
this.barService = barService;
}
public String sayHello(String name) {
// 此代码在客户端执行, 可以先进行一些小型操作
try {
逻辑操作
} catch (Exception e) {
}
}
}
然后要在消费者的配置文件中进行配置 stub指定本地实现的类的全类名:
<dubbo:service interface="com.foo.BarService" stub="com.foo.BarServiceStub" />
整合springboot的是三种方式:
同样是消费者,提供者和公共部分,写controller处理请求调用远程服务
方法一:同样是先导入dubbo的start 在全局配置文件中对dubbo进行配置,然后开启基于注解的dubbo模式 再在需要进行配种子的类和接口上加注解
配置服务提供者:
导入dubbo的start以及其他依赖
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
以前在spring配置文件中进行配置的内容就可以在springboot的全局配置文件中进行配置
配置应用名称和注册中心等
应用名称
dubbo.application.name=user-service-provider
注册中心的地址
dubbo.registry.address=127.0.0.1:2181
注册中心
dubbo.registry.protocol=zookeeper
注册中心的协议端口号
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881
dubbo.monitor.protocol=registry
##dubbo.scan.base-packages=com.atguigu.gmall
暴露服务:
先要开启基于注解的dubbo服务 使用注解:@EnableDubbo
要暴露的服务上加注解 使用dubbo下的注解:@service
配置消费者:
导入dubbo的start依赖:
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
以前在spring配置文件中进行配置的内容就可以在springboot的全局配置文件中进行配置
配置应用名称和注册中心等
dubbo.application.name=boot-order-service-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.monitor.protocol=registry
加注解标识进行调用哪个服务:
先要开启基于注解的dubbo服务 使用注解:@EnableDubbo
@EnableDubbo
@SpringBootApplication
public class BootOrderServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(BootOrderServiceConsumerApplication.class, args);
}
}
加注解 @Reference(以前是用@Autowired进行注入) 表示对哪个远程服务进行调用:
@Service
public class OrderServiceImpl implements OrderService {
@Reference//表示调用这个接口服务
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);
return addressList;
}
}
方法二:
同样是先导入dubbo的start
保留dubbo的xml配置文件 就不在全局配置文件中进行配置了 然后使用@ImportResource导入一个dubbo的配置文件 不用开启基于注解的dubbo配置
暴露服务也就不用再加注解了 因为在xml文件中已经配置好了
@ImportResource(locations="classpath:provider.xml")
@SpringBootApplication
public class BootUserServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(BootUserServiceProviderApplication.class, args);
}
}
方法三:将所有的配置都做成一个组件 返回到容器中 再来扫描对应组件即可
@EnableDubbo(scanBasePackages="com.atguigu.gmall")
@Configuration
public class MyDubboConfig {
@Bean //对应<dubbo:application name="boot-user-service-provider">
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("boot-user-service-provider");
return applicationConfig;
}
//对应<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181">
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("127.0.0.1:2181");
return registryConfig;
}
//对应<dubbo:protocol name="dubbo" port="20882">
@Bean
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20882);
return protocolConfig;
}
/**
*对应<dubbo:service interface="com.atguigu.gmall.service.UserService"
ref="userServiceImpl01" timeout="1000" version="1.0.0">
<dubbo:method name="getUserAddressList" timeout="1000"></dubbo:method>
</dubbo:service>
*/
@Bean
public ServiceConfig<UserService> userServiceConfig(UserService userService){
ServiceConfig<UserService> serviceConfig = new ServiceConfig<>();
serviceConfig.setInterface(UserService.class);
serviceConfig.setRef(userService);
serviceConfig.setVersion("1.0.0");
//配置每一个method的信息
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("getUserAddressList");
methodConfig.setTimeout(1000);
//将method的设置关联到service配置中
List<MethodConfig> methods = new ArrayList<>();
methods.add(methodConfig);
serviceConfig.setMethods(methods);
//ProviderConfig
//MonitorConfig
return serviceConfig;
}
}
高可用场景:就是指通过一些手段减少系统不可用的时间
zookeeper宕机:
多个注册中心的时候如果有某一个注册中心宕机就会自动切换其他注册中心来运行 如果都宕机消费者也还是回有本地缓存可以使用
dubbo直连:就是指不通过注册中心直接与提供者进行通信 直接在消费者调用提供者的服务上加注解 这样就可以绕过注册中心
@Reference(url="127.0.0.1:200881") //dubbo直连
UserService userService;
负载均衡:
几个负载均衡的策略:
Random LoadBalance 默认就是随机的机制
随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
轮循,按权重设置轮循比率。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash LoadBalance
一致性 Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
可以在服务暴露的时候或者是服务调用的时候进行一个设置
@Reference(loadbalance="roundrobi")
UserService userService;
权重可以在服务暴露@service中进行设置 也可以在控制台动态调整
服务降级:当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作
两种方式:
在调用某些服务的时候直接就在客户端返回为空 不去调用远程服务
调用失败的时候返回为空
设置:
可以直接在控制台进行设置屏蔽 就是指不会发起远程调用直接就在客户端返回为空
也可以对服务进行设置容错 就是调用失败的时候会返回为空
集群容错:就是调用远程服务出错了的一种处理策略
几种容错模式:
Failover Cluster 默认模式
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息
设置:
<dubbo:service cluster="failsafe" />
或
<dubbo:reference cluster="failsafe" />
实际开发中通常是整合Hystrix来实现的
整合Hystrix
Hystrix 旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能
先导入Hystrix 在开启Hystrix功能即可
1、配置spring-cloud-starter-netflix-hystrix
在pom.xml里加入Hystrix依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>1.4.4.RELEASE</version> </dependency> |
开启服务容错功能 在Application类上增加@EnableHystrix来启用hystrix:
@SpringBootApplication @EnableHystrix public class ProviderApplication {
|
2、配置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."); } } |
3、配置Consumer端
出错也是在消费者放进行调用的时候出错的
同在在消费者端也要引入Hystrix的依赖 然后开启Hystrix功能 然后同样在可能出错的方法上加注解@HystrixCommand 使用参数
fallbackMethod 来指定出错后的处理方法
@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"; } |
RPC原理:
1)服务消费方(client)调用以本地调用方式调用服务;
2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
3)client stub找到服务地址,并将消息发送到服务端;
4)server stub收到消息后进行解码;
5)server stub根据解码结果调用本地的服务;
6)本地服务执行并将结果返回给server stub;
7)server stub将返回结果打包成消息并发送至消费方;
8)client stub接收到消息,并进行解码;
9)服务消费方得到最终结果。
RPC框架就是要把中间的这些细节封装起来
dubbo底层是使用netty的通信框架的