1、zookeeper宕机与dubbo直连
现象:zookeeper注册中心宕机,还可以消费dubbo暴露的服务。
原因:
健壮性
监控中心宕掉不影响使用,只是丢失部分采样数据
数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
注册中心对等集群,任意一台宕掉后,将自动切换到另一台
注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
服务提供者无状态,任意一台宕掉后,不影响使用
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
高可用:通过设计,减少系统不能提供服务的时间;
案例 :① 比如zookeeper注册中心宕机了,消费者以上面的红色部分连接到消费者的服务,因为有缓存,
② zookeeper宕机了(我是将zookeeper服务停掉了),还可以通过dubbo直连的方式绕过注册中心找到提供者
@Service
public class OrderServiceImpl implements OrderService{
// @Autowired
@Reference(url="127.0.0.1:20882")
UserService userService;
public List<UserAddress> initOrder(String userId){
List<UserAddress> addressList = userService.getUserAddressList(userId);
System.out.println(addressList);
return addressList;
}
}
只需要在Reference 中加上提供者的地址,就可以找到消费者,实现了高可用
2、集群下dubbo负载均衡配置
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用
<dubbo:consumer loadbalance="random"/>
1.Random:安权重随机
2.RoundRobin: 轮询
3.LeastActive :最少活跃数(正在处理的数)慢的机器,收到的请求少
负载均衡策略
①Random LoadBalance
随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
我们看到每台机器后面都设置了权重,总的权重就是350 ,1号机器占了2/7,2号占了4/7,3号占了1/7 ,大量的访问来了,就有对应概率的访问量到达每一台机器,每次请求随机到那一台机器是不确定的
②RoundRobin LoadBalance
轮循,按公约后的权重设置轮循比率。
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
1)如果先不考虑后面设置的权重,只按照轮询的方式第一次访问到1号机器,第二次访问2号,第三次访问3号,依次循环
2)如果考虑后面的权重,第一次访问1号,第二次访问2号,第三次访问2号,第一轮结束了,第二轮继续,第四次访问1号,第五次方位2号,第六次本应该方位3号,可以按照权重比,3号只会被访问一次,第一轮已经访问过一次,所以会这次来到2号,第7次还是访问2号。这就是基于权重的轮询
③LeastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
orderService 会统计每台服务器上一次的时间,总会访问活动时间最小的机器访问
④ConsistentHash LoadBalance
一致性 Hash,相同参数的请求总是发到同一提供者。
第一次id=1 访问到1号机器,id=2访问到2号,id=3访问到3号机器,因为参数一直没有变化过,无论多少次访问每次访问都会落到同一台机器上。
测试负载均衡机制实验
boot-user-service-provider 项目中需要修改2出代码
@Component
@Service
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
//只增加一行打印,用于控制台便于区分 分别 1 ,2 ,3
System.out.println("UserService.....3....");
UserAddress address1 = new UserAddress(1,"东长安街89号","1","王老师","13278977","1");
UserAddress address2 = new UserAddress(2,"西长安街90号","1","李老师","13278988","1");
return Arrays.asList(address1,address2);
}
}
MyDbubboConfig只修改protocolConfig() 方法中的端口号,分别是20880,20881,20882,分别对应上面的UserServiceImpl 打印的1,2,3 修改一次,启动一次
@Configuration
public class MyDubboConfig {
/**
* <dubbo:application name="boot-user-service-provider">
* </dubbo:application>
* 对应下面的代码
* @return
*/
@Bean
public ApplicationConfig applicationConfig(){
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("boot-user-service-provider");
return applicationConfig;
}
/**
* <dubbo:registry protocol="zookeeper" address="10.5.96.48:2181">
* </dubbo:registry>
* @return
*/
@Bean
public RegistryConfig registryConfig(){
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("10.5.96.48:2181");
return registryConfig;
}
/**
* <!-- 3 指定通信规则 通信协议,通信端口-->
<dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>
* @return
*/
@Bean
public ProtocolConfig protocolConfig(){
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20882);
return protocolConfig;
}
/**
* <dubbo:service interface="com.atguigu.gmail.service.UserService"
ref="userServiceImpl01" version="1.0.0">
<dubbo:method name="getUserAddressList" timeout="3000"></dubbo:method>
</dubbo:service>
*/
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);
return serviceConfig;
}
}
主启动类
@EnableDubbo(scanBasePackages="com.atguigu.gmail")
@SpringBootApplication
public class ProviderApp
{
public static void main( String[] args )
{
SpringApplication.run(ProviderApp.class, args);
}
}
System.out.println("UserService.....1...."); 对应 protocolConfig.setPort(20880);启动一次主启动类,一次类推,一共启动三次
可视化控制台可以看到有三个服务提供者
然后我们浏览器访问访问 http://localhost:8083/initOrder?uid=1 ,多次刷新,然后可以看到dubbo是随机访问每台服务提供者的
如果修改负载均衡的机制,具体的值可以参考http://dubbo.apache.org/zh-cn/docs/user/demos/loadbalance.html
@Service
public class OrderServiceImpl implements OrderService{
//修改负载均衡的机制
@Reference(loadbalance="roundrobin")
UserService userService;
public List<UserAddress> initOrder(String userId){
List<UserAddress> addressList = userService.getUserAddressList(userId);
System.out.println(addressList);
return addressList;
}
}
如果是安全权重修改服务提供者的 在@Service中设置对应的权重,这样的是写死的方法
@Component
@Service(weight=100)//暴露服务
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
System.out.println("UserService.....3....");
UserAddress address1 = new UserAddress(1,"东长安街89号","1","王老师","13278977","1");
UserAddress address2 = new UserAddress(2,"西长安街90号","1","李老师","13278988","1");
return Arrays.asList(address1,address2);
}
}
利用可视化控制台,红色框中的按钮,就可以设置对应服务提供中的权重进行负载均衡按权重轮询了,还是访问上面的连接,然后看控制台的信息,可以区分访问了那台机器,可以通过权重调整机器被访问的次数。
3、整合hystrix,服务熔断与降级处理
1、服务降级
什么是服务降级?
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
向注册中心写入动态配置覆盖规则:
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
服务降级的两种方式
- mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
- 还可以改为 mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
第一种测试
我们值提供一个Provider 的启动类,OrderService 调用UerService ,
服务消费者只有一个,此时我们点击屏蔽,也是是第一种我们OrderService 就不会去远程调用服务直接反悔了null
浏览器访问http://localhost:8083/initOrder?uid=1 所有的控制台都不会有打印的信息,浏览器返回空白的页面
第一种测试
还是上面的截图,我们先回复,然后点击容错
OrderServiceImpl 我们设置超时时间1000ms
@Service
public class OrderServiceImpl implements OrderService{
@Reference(loadbalance="roundrobin",timeout=1000)
UserService userService;
public List<UserAddress> initOrder(String userId){
List<UserAddress> addressList = userService.getUserAddressList(userId);
System.out.println(addressList);
return addressList;
}
}
UserServiceImpl 设置超时2000,让调用超时
@Component
@Service//暴露服务
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1,"东长安街89号","1","王老师","13278977","1");
UserAddress address2 = new UserAddress(2,"西长安街90号","1","李老师","13278988","1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return Arrays.asList(address1,address2);
}
}
浏览器访问http://localhost:8083/initOrder?uid=1 所有的控制台都不会有打印的信息,浏览器返回空白的页面
如果要是不容错,访问超时页面会报错
2、集群容错
在集群调用失败时,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" />
3、整合hystrix
Hystrix 旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能
1、配置spring-cloud-starter-netflix-hystrix
spring boot官方提供了对hystrix的集成,直接在服务提供者boot-user-service-provider项目的pom.xml里加入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
<!-- 上面是依赖的最后的部分,前面的已经有了就不写了 -->
<!-- 下面也是新增的配置-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后在服务提供者Application类上增加@EnableHystrix来启用hystrix starter:
@EnableDubbo(scanBasePackages="com.atguigu.gmail")
@SpringBootApplication
@EnableHystrix //开启服务容错
public class ProviderApp
{
2、配置Provider端
在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.");
}
}
3、配置Consumer端
对于Consumer端,则可以增加一层method调用,并在method上配置@HystrixCommand。当调用出错时,会走到fallbackMethod = "reliable"的调用里。
@Component
@Service//暴露服务
public class UserServiceImpl implements UserService {
@HystrixCommand //这个方法被Hystrix 代理
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1,"东长安街89号","1","王老师","13278977","1");
UserAddress address2 = new UserAddress(2,"西长安街90号","1","李老师","13278988","1");
if(Math.random() > 0.5){
throw new RuntimeException();
}
return Arrays.asList(address1,address2);
}
}
服务消费方导入依赖pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>
spring-cloud-starter-netflix-hystrix
</artifactId>
</dependency>
</dependencies>
<!-- 上面是依赖的最后的部分,前面的已经有了就不写了 -->
<!-- 下面也是新增的配置-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
服务方消费方开启服务容错
@EnableDubbo
@SpringBootApplication
@EnableHystrix //开启服务容错
public class ConsumerApp
OrderServiceImpl
@Service
public class OrderServiceImpl implements OrderService{
// @Autowired
@Reference
UserService userService;
/**
* 一旦远程调用方法失败,调用hello方法
*/
@HystrixCommand(fallbackMethod="hello")
public List<UserAddress> initOrder(String userId){
List<UserAddress> addressList = userService.getUserAddressList(userId);
System.out.println(addressList);
return addressList;
}
//远程服务错误 回调hello方法返回数据
public List<UserAddress> hello(String userId){
return Arrays.asList(new UserAddress(10, "测试地址", "1", "测试", "测试", "Y"));
}
}
启动服务提供者,启动服务消费者,浏览器多次访问http://localhost:8083/initOrder?uid=1 ,会有不同的结果!运行将可以自行查看。
4、Dubbo 声明式缓存
参数与返回值:key/value形式
<doubbo:reference interface="com.xxx.ProduceService" cache="lru"/>
<!--1 lru:基于最近最少使用原则删除多余缓存 -->
<!--2 threadlocal:当前线程缓存 -->
1 lru:基于最近最少使用原则删除多余缓存
2 threadlocal:当前线程缓存 只把缓存 放在客户端请求的缓存中
5、Dubbo 异步调用(待完善)**********
<dubbo:reference id="orderService" interface="com.xxx.OrderService">
<dubbo:method name="submit" async="true" />
<dubbo:method name="cancle" async="true" />
</dubbo:reference>
并发请求多个服务 submit和cancel 是两个异步调用的方式,官方不建议这么使用异步调用
6、Dubbo 事件通知(回调)
<dubbo:method name="xxx" async="true" onreturn="callBack.onOrderSubmit"/>
注册到ioc容器中
<bean id="callBack" class="com.xxx.callback.CallBack"></bean>
异步回调类
/**
* 回调方法类
**/
public class CallBack{
public void onOrderSubmit(OrderEntiry result,OrderEntiry form){
System.out.println("生成了一单,金额" +result.getMoney());
}
}
1.正常业务返回时,配置onreturn ,异常抛出时onthrow
7、Dubbo 回声测试 测试阶段用
8、泛化调用(应急的办法)
泛化--抽象
当项目A没有得到项目B接口描述,它还想要rpc调用它,有没有办法?
java反射:不知道对象B的class是什么,想要调用,用反射
<dubbo:reference id="otherService" interface="com.xxx.OtherService" generic="true"/>