Dubbo
Dubbo
基础知识
1.1什么时是分布式系统?
是若干独立计算机的集合,这些计算机对于用户来说就像是单个相关系统,分布式系统是建立再网络之上的软件系统
1.2 发展演变
单一应用架构:当网站流量很小,只需要一个应用,将所有功能部署在一起,以减少部署节点和成本,
垂直应用架构
分布式服务架构 RPC 远程过程调用
流动计算架构:基于访问压力实时管理集群容量,提高集群利用效率
1.3RPC remote procedure call
指远程调用,是一种进程健通信方式,是一种技术理想,它允许程序调用另一个地址空间的过程或函数,而不用程序员显示编码这个远程调用的细节,即程序员无论是调用本地的还是远程的函数本质上编写的调用代码基本相同
特性:
面向接口代理的高性能RPC调用,智能负载均衡,服务自动注册与发现,高度可扩展能力,
运行期流量调度,可视化的服务治理与运维
配置
首先需要有一个地方去存放这些服务的信息,nacos和zookeeper其实都可以,这里先选择zookeeper吧,
先安装了一个zookeeper,把服务端和客户端都启动
安装监控中心,dubbo有配套的项目,可以用来管理这个项目的监控,直接去github上面下载下来,然后打包成jar包,运行就可以看到,但是这个过程要保证zookeeper,或者是其他的使用的注册中心正在启动
dubbo配置起来实在是有点麻烦,
首先来一个服务的公共服务,这个服务里面存放着公共的bean和接口,这个服务是最基础的服务,其他的服务都需要引入这个服务才能使用它里面的实体类什么的
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserAddress implements Serializable {
private Integer id;
private String userAddress;
private String userId;
private String consignee; //收货人
private String phoneNum;
private String isDefault; //是否为默认地址
}
public interface OrderService {
/**
* 初始化订单
* @param userId
*/
public void initOrder(String userId);
}
public interface UserService {
/**
* 按照用户id返回所有的收获地址
* @param userId
* @return
*/
List<UserAddress> getUserAddressList(String userId);
}
服务的提供者和消费者之间其实没有一个明确的界定,这里面用户信息是提供者,订单时消费者,产生订单需要使用到用户的信息,根据这个划分出来的
下面时服务的提供者
<dependencies>
<dependency>
<groupId>com.atguigu.gmail</groupId>
<artifactId>gmail-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<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>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>compile</scope>
<version>1.7.24</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
这比较笨拙,还需要配置文件,指定一些最基本的信息
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
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">
<!--1.指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名)-->
<dubbo:application name="user-service-provider"/>
<!--2.指定注册中心的位置-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!--3.指定通信规则,(通信协议,通信端口)-->
<dubbo:protocol name="dubbo" port="20890"/>
<!--4.暴露服务 ref:指向服务的真正的实现对象-->
<dubbo:service interface="com.atguigu.gmall.service.UserService" ref="userServiceImpl"/>
<!--服务的实现-->
<bean id="userServiceImpl" class="com.atguigu.gmall.service.impl.UserServiceImpl"/>
</beans>
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserServiceImpl.....old...");
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);
}
}
把自己的写的实现类暴露出去,这是实现的公共服务里面的接口,消费者也有公共服务,所以知道这个接口的名字,直接调用就可以,实际实现会通过zookeeper去找我们这个服务的提供者
public class MainApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:application.xml");
ioc.start();
System.in.read();
}
}
服务的消费者:消费者主要就是使用别人的服务,也需要引入jar包,然后使用配置文件去声明一些配置
<dependencies>
<dependency>
<groupId>com.atguigu.gmail</groupId>
<artifactId>gmail-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
<?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-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描注解-->
<context:component-scan base-package="com.atguigu.gmall.service.impl"></context:component-scan>
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="order-service-consumer"/>
<!-- 使用multicast广播注册中心暴露发现服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!-- 声明需要调用的远程服务的接口,生成远程服务代理 -->
<dubbo:reference id="userService" interface="com.atguigu.gmall.service.UserService"/>
</beans>
主要就是需要连接上注册中心,然后声明需要调用的远程服务接口,
然后我们就可以使用自动注入进行使用这个接口的方法了,实际上会调用提供者
/**
* User:曹帅
* Date:2021/1/19
* Version:1.0
* 1.将服务提供者注册到注册中心(暴露服务)
* 1.导入dubbo依赖(2.6.2)
* 2.配置服务提供者,使用配置文件将自己的服务暴露出去
* 2.让服务消费者去注册中心订阅服务提供者的服务地址
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
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());
}
}
}
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();
}
}
使用SpringBoot来改造
服务提供者
dubbo:
application:
name: user-service-provider
registry:
address: 127.0.0.1:2181
protocol: zookeeper
protocol:
name: dubbo
port: 20880
monitor:
address: registry
@EnableDubbo //开启基于注解的dubbo功能
@SpringBootApplication
public class BootUserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BootUserServiceApplication.class, args);
}
}
@Service //暴露出去
@Component
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserServiceImpl.....old...");
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);
}
}
服务消费者
dubbo:
registry:
# 注册中心地址,
address: zookeeper://127.0.0.1:2181
# 监控中心的协议,自己去注册中心去找监控
monitor:
protocol: registry
application:
name: order-service-consumer
server:
port: 8081
/**
* 导入依赖
* 1.dubbo-starter
* 2.导入dubbo的其他依赖
*
*/
@EnableDubbo
@SpringBootApplication
public class BootOrderServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(BootOrderServiceConsumerApplication.class, args);
}
}
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.gmail</groupId>
<artifactId>gmail-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<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>
</dependencies>
@Controller
public class OrderController {
@Autowired
OrderService orderService;
@ResponseBody
@RequestMapping("/initOrder")
public List<UserAddress> initOrder(@RequestParam("uid") String userId) {
return orderService.initOrder(userId);
}
}
@Service
public class OrderServiceImpl implements OrderService {
@Reference
UserService userService;
public List<UserAddress> initOrder(String userId) {
System.out.println("用户Id" + userId);
//1.查询用户的收货地址
List<UserAddress> addressList = userService.getUserAddressList(userId);
return addressList;
}
}
dubbo默认的情况下消费者启动的时候会自动检查它所需要的服务提供者,如果服务提供者没有启动它会报错,可以设置先不检查,等到用的时候再说
<dubbo:reference id="userService" interface="com.atguigu.gmall.service.UserService"
check="false"/>
上面的是不检查其中的一个bean
<!--配置当前消费者的统一规则-->
<dubbo:consumer check="false"/>
这个意思就是所有的都不会再检查了
服务启动默认就会去找注册中心,如果没有注册中心就会报错,我们可以设置启动的时候不让进行注册中心检查
<!--关闭注册中心启动时检查(注册订阅失败时报错)-->
<dubbo:registry check="false"/>
<!-- 声明需要调用的远程服务的接口,生成远程服务代理 -->
<!--timeout='3000' 超时自动返回,默认是1000-->
<dubbo:reference id="userService" interface="com.atguigu.gmall.service.UserService"
timeout="3000">
<dubbo:method name="getUserAddressList" timeout="1000"/>
</dubbo:reference>
类里面还有方法,方法也可以设置超时时间
方法级优先,接口次之,全局配置再次之
如果级别一样,则消费方设置的优先,提供者配置的次之
<!--重试次数 retries,这是一个整数,但是不包含第一次调用-->
如果相同的服务有多个服务器,还会去其他的服务里面去调用,这些服务只要有一个给出返回内容就可以正确
幂等(设置重试次数): 方法无论执行多少次,返回的结果都是确定的,(查询,删除,修改)
非幂等(不能设置重试次数):新增
灰度发布
版本代码实现不同
可以在暴露出去的接口上面声明这个接口的版本号
@Service(version = "1.0.0") //暴露出去
然后再消费者方调用的时候声明要使用的版本号,这样就可以实现灰度发布了
@Reference(version = "2.0.0")
如果调用的时候version=“*”,那么就代表着所有暴露出去的版本都有机会被使用到
多版本的负载均衡
SpringBoot整合Dubbo
1.导入dubbo-starter,在application.properties配置属性,使用@Service【暴露服务】,使用@Reference【引用服务】
2.@EnableDubbo开启dubbo注解功能,或者 @ImportResource导入dubbo的配置文件
3.使用注解API的方式:
将每一个组件手动创建到容器中,
@Configuration
public class MyDubboConfig {
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("boot-user-service-provider");
return applicationConfig;
}
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("127.0.0.1:2181");
return registryConfig;
}
@Bean
public ProtocolConfig protocolConfig(){
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20882);
return protocolConfig;
}
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(3000);
//将method的设置关联到service配置中
serviceConfig.setMethods(Arrays.asList(methodConfig));
//ProviderConfig
//MonitorConfig
return serviceConfig;
}
}
@EnableDubbo(scanBasePackages = "com.atguigu.gmall")
@SpringBootApplication
public class BootUserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BootUserServiceApplication.class, args);
}
}
个人感觉没有必要
高可用
1.zookeeper宕机与dubbo直连
现象:zookeeper注册中心宕机,还可以消费dubbo暴露的服务
原因:
健壮性:
监控中心宕掉不影响使用,只是丢掉部分采样数据
数据库宕机之后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
注册中心对等集群,任意一台宕机,将自动切换到另一台
注册中心全部宕机,服务提供者和服务消费者仍能通过本地缓存通讯
服务提供者无状态,任意一台宕机,不影响使用
服务提供者全部宕机,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
高可用:通过设计,减少系统不能提供服务的时间
集群下面dubbo的负载均衡配置
Random LoadBalance :随机,按权重设置随机概率,
RoundRobin LoadBalance: 轮询,按公约后的权重设置轮询比率,存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台就卡在那,久而久之,所有的请求都卡在了第二台,这个问题其实也可以解决,只要设置合适的权重即可,但是这并不是最好的,万一有一次出现了一些问题,那接下来这个服务都会比别人慢半拍,主要还是要看熔断降级策略设置的好不好,可以让当前的这个服务不影响其他的
1231222
LeastActive LoadBalance : 最少活跃数,负载均衡机制
权重其实也可以在界面里面进行配置
服务降级
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运转
可以通过服务降级功能l临时屏蔽某个出错的非关键业务,并定义降级后的返回策略,
1.从客户端直接返回null,也不用去远程调用提供者了, mock=force:return+null
2.mock=fail:return+null,表示消费方对该服务的提供方调用失败后,再返回null值,不抛出异常,用来容忍不重要的服务不稳定时对调用方的影响
集群容错
Failover Cluster:失败自动切换,当出现失败,重试其他服务器,通常用于读操作,但重试会带来更长延迟,通过 retries=“2” 来设置重试次数
Failfast Cluster:快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作,比如新增记录,
Failsafe Cluster : 失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作,
Failback Cluster: 失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作
Forking Cluster:并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的操作,但需要浪费更多服务资源,可通过forks=“2” 来设置最大并行数
Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错就报错,通常用于通知所有提供者更新缓存或日志等本地资源信息
<dubbo:service interface="com.atguigu.gmall.service.UserService" cluster="failsafe"/>
整合hystrix
旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力,Hystrix具有回退机制和断路功能和信号隔离,请求缓存和请求打包,以及监控和配置等功能
@EnableHystrix
@EnableDubbo
@SpringBootApplication
public class BootOrderServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(BootOrderServiceConsumerApplication.class, args);
}
}
@Service
public class OrderServiceImpl implements OrderService {
@Reference
UserService userService;
@HystrixCommand(fallbackMethod = "hello")
public List<UserAddress> initOrder(String userId) {
System.out.println("用户Id" + userId);
//1.查询用户的收货地址
List<UserAddress> addressList = userService.getUserAddressList(userId);
return addressList;
}
public List<UserAddress> hello(String userId) {
return Arrays.asList(new UserAddress(2, "测试", "1", "小草", "010-56253825", "N"));
}
}
@Service //暴露出去
@Component
public class UserServiceImpl implements UserService {
@HystrixCommand
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserServiceImpl.....1...");
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
if (Math.random() > 0.5) {
throw new RuntimeException();
}
return Arrays.asList(address1, address2);
}
}
dubbo原理
一次完整的RPC调用流程:
1.服务消费方以本地方式调用方式调用服务
2.client stub 接收到调用后负责将方法,参数等组装成能够进行网络传输的消息体
3.cleint stub 找到服务地址,并将消息发送到服务端
4.server stub 接收到消息后进行解码
5.server stub 根据解码结果调用本地的服务
6.本地服务执行并将结果返回给server stub
7.server stub 将返回结果打包成消息并发送至消费方
8.client stub 接收到消息,并进行解码
9.服务消费方得到最终结果
RPC框架的目标就是要把 2-8 步骤封装起来,这些细节对用户来说是透明的,不可见的
Netty通信原理
netty是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端,它极大地简化了TCP和UDP套接字服务器等网络编程
BIO 阻塞式编程
NIO 非阻塞编程
Service
Config
Proxy
Registry
Cluster
Monitor
Protocol
Exchange
Transport
Serialize