目录
前言
导读
项目准备
集群准备
父工程引入子项目
服务调用方HelloService准备
pom文件
yml文件
Controller文件
服务提供方HelloWorld准备
pom文件
yml文件
Controller文件
运行此两个工程
hello_world组集群
集群调用测试
RestTemplate换成Dubbo行不行,怎么换
两个pom文件引入依赖
两个启动文件添加新注解
结构调整
helloWorld调整
helloService调整
编辑
重新运行
权重
结语
前言
前文中,对Redis的集群做了详细的讲解,能一路读到这里的相信你们对集群的概念已经基本了解,今天博主要带给大家的是集群之间的相互调用,同时还带负载均衡,这也是目前比较流行的集群使用方法,可以有效避免故障时对整个系统造成影响。既然有集群,那一定少不了RPC的使用,没错,这里会简单涉及,后续会单独讲解更详细的使用,下面,就让我们一起来学习这篇集群之间的相互调用吧。
导读
本篇的重点不在于讲RPC,而是单纯的一种集群调用思想和调用结构!!!
在开始学习前,不知道你对Nacos了解多少?本文主要依靠的便是Nacos注册中心,如果不还不太了解,下面这篇博客将带你初步了解:
Java开发 - 配置中心初体验
项目准备
在开始讲解之前,我们需要先准备一个基础项目其结构大概如下:
你不必像创建过多子项目,圈起来的俩红色的是博主创建的,service是业务调用方,helloworld是业务提供方,好吧,world还写错了,请不要嘲笑博主,手滑,真的是手滑。
关于微服务项目基本结构不知道大家了解多少,如果你对微服务项目里父子项目的基本结构不了解的话,那么建议你先去了解下面下,博主给大家准备好了,点下面链接:
Java开发 - 数风流人物,还看“微服务”
赶紧去看,趁博主还没收费哦~
不选择在这里给大家讲解是因为篇幅过长,不利于大家学习,学习应该是一个循序渐进的过程,而不是一蹴而就。
下面就是一个集群的异地容灾结构图,大家看看:
集群准备
上面主要是让大家熟悉微服务父子项目的配置,这里也会涉及点,但基本上是一笔带过,若果你了解父子项目结构,那么请继续。
父工程引入子项目
详细的结构大家还是要通过上面那篇去学习,这里不再赘述。
服务调用方HelloService准备
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>com.codingfire</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.codingfire</groupId>
<artifactId>HelloService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>HelloService</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.codingfire</groupId>
<artifactId>Cloud-helloword</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
yml文件
spring:
application:
# 当前Springboot项目的名称,用于注册中心服务的名称
name: hello_service
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: HZ # 集群名称
hello_world:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
其中,hello_world是我们要调用的业务的名字,在对方的pom文件中。下面有负载均衡策略,是随机的。
Controller文件
我们简单搞个Controller,用来调用hello_world服务:
package com.codingfire.helloservice.controller;
import com.codingfire.cloud.helloword.controller.HHelloController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/hello")
public class SHelloController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/service")
public String hello(){
//hello_world也可以用ip:port替代,但你要集群调用,就不能写死,要写对方的集群的服务名
String helloWorld = "http://hello_world/hello/world";
return "XiaoMing says:" + helloWorld;
}
}
此处我们先用最简单的RestTemplate来进行RPC,此处肯定报错,还需要在启动类中添加此方法:
package com.codingfire.helloservice.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
到此,服务调用方的准备就已经就绪。
服务提供方HelloWorld准备
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>com.codingfire</groupId>
<artifactId>cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.codingfire</groupId>
<artifactId>Cloud-helloword</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Cloud-helloword</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
yml文件
spring:
application:
# 当前Springboot项目的名称,用于注册中心服务的名称
name: hello_world
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: SH # 集群名称
Controller文件
package com.codingfire.cloud.helloword.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HHelloController {
@GetMapping("/world")
public String hello(){
return "Hello World!";
}
}
到此,服务提供方准备就绪。
运行此两个工程
在此之前,请大家确保Nacos已经启动,否则无法运行项目!!!
由于本地运行项目时,没有指定端口,所以tomcat端口8080会冲突,我们给hello_world指定版本号8081:
接下来分别运行这两个项目,并在Nacos后台查看:
此时看到两个服务都已经成功注册进来,但我们要做的是调用集群服务,所以还需要给hello_world增加一个服务,组成集群。
hello_world组集群
请大家点击此处:
在下图中复制多个启动文件出来:
并分别指定端口为8082,8083,8084。
下面我们先运行8082:
在运行8083和8084之前,我们需要把yml文件中的集群名HZ,改为SH,已经运行的项目不要暂停,本地模拟需要,生产环境多台服务器的时候不需要这样。
接着分别运行8083和8084的启动文件,然后去Nacos后台刷新查看:
此时hello_world的集群有两个,实例有四个,符合我们的预期,我们点击“详情”按钮查看:
完美。
集群调用测试
下面我们调用接口:http://localhost:8080/hello/service
它会去调用hello_world里面的接口,最终会显示出来一段话,额,你们有没有报500错误?控制台提示下面这段话:
Request URI does not contain a valid hostname: http://hello_world/hello/world
如果有的话,恭喜你中奖了,这里要插入一个小小的知识点,spring.application.name中不能带下划线,博主的命名是不是都带了下划线?这是一个非常小的错误,但也是一个非常容易被忽略的错误,问题是你压根不知道发生了什么,所以,大家现在回过头,去yml文件里,把名字改了,改成:helloWorld和helloService,Controller里面的RestTemplate调用处也需要改,改完之后,重新按照博主上面的步骤运行。一个HZ集群,一个SH集群,不要漏一个。
下面就是见证奇迹的时刻,让我们访问:http://localhost:8080/hello/service
输出这段话,就表示你的应用运行成功了。下面我们清空四个控制台:
刷新url并查看控制台:
你会发现在这四个控制台的输出像是轮询,一个一个,我们注意到,helloServcie的集群是HZ,我们希望它能够优先访问本集群内的服务,而不是访问其他集群的服务。
但默认的ZoneAvoidanceRule
不能实现根据同集群优先来实现负载均衡,所以Nacos中提供了一个NacosRule
的实现,可以优先从同集群中挑选实例。下面修改负载均衡的策略:
helloWorld:
ribbon:
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
使用新的,原来的注释起来,再次运行helloService,重新调用上面的接口,就会优先访问本集群的服务了。
为了测试集群挂掉后是否能访问其他的集群,你需要把HZ集群的两个实例下线掉:
然后继续访问上面的接口,只要能成功访问,就代表我们的集群策略是没问题的。
RestTemplate换成Dubbo行不行,怎么换
RestTemplate是一种比较老的用法,我们现在做RPC会更多的用Dubbo,那么Dubbo该怎么使用呢?现在,我们把RestTemplate替换成Dubbo来看看,能不能和上面的集群调用有一样的表现。
两个pom文件引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
两个yml文件引入配置:
dubbo:
protocol:
port: -1 # 设置Dubbo服务调用的端口 设置-1能够实现动态自动设置合适端口,生成规则是从20880开始递增
name: dubbo # 设置端口名称,一般固定就叫dubbo
registry:
address: nacos://localhost:8848 # 配置当前Dubbo注册中心的类型和地址
consumer:
check: false # 设置为false表示当前项目启动时,不检查要调用的远程服务是否可用,避免报错
两个启动文件添加新注解
// 如果当前项目是Dubbo服务的生产者,必须添加这个注解
@EnableDubbo
生产者就是服务提供方,消费方不需要,为了防止大家搞错,所以就两个启动类都添加,实际使用中要区分。但也存在即是服务提供方,又是服务消费方的情况。
结构调整
为了能更贴近于真实使用场景,也是为了不在Controller中直接使用Dubbo(可能会带来一些问题),所以我们对项目结构做一些简单的调整,增加service层,包括接口和实现类 。
helloWorld调整
IHelloWorldService类:
package com.codingfire.cloud.helloword.service;
public interface IHelloWorldService {
String helloWorld();
}
HelloWorldServiceImpl类:
package com.codingfire.cloud.helloword.service.impl;
import com.codingfire.cloud.helloword.service.IHelloWorldService;
import org.springframework.stereotype.Service;
@Service
public class HelloWorldServiceImpl implements IHelloWorldService {
@Override
public String helloWorld() {
return "Hello World!";
}
}
HHelloController类:
package com.codingfire.cloud.helloword.controller;
import com.codingfire.cloud.helloword.service.IHelloWorldService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HHelloController {
@Autowired
private IHelloWorldService helloWorldService;
@GetMapping("/world")
public String hello(){
return helloWorldService.helloWorld();
}
}
helloService调整
IHelloService类:
package com.codingfire.helloservice.service;
public interface IHelloService {
String getHelloWorld();
}
HelloServiceImpl类:
package com.codingfire.helloservice.service.impl;
import com.codingfire.cloud.helloword.service.IHelloWorldService;
import com.codingfire.helloservice.service.IHelloService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Service;
@DubboService
@Service
public class HelloServiceImpl implements IHelloService {
@DubboReference
private IHelloWorldService dubboHelloService;
@Override
public String getHelloWorld() {
String helloWorld = dubboHelloService.helloWorld();
return helloWorld;
}
}
SHelloController类:
package com.codingfire.helloservice.controller;
import com.codingfire.helloservice.service.IHelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class SHelloController {
@Autowired
private IHelloService helloService;
@GetMapping("/service")
public String hello(){
String helloWorld = helloService.getHelloWorld();
return "XiaoMing Says:" + helloWorld;
}
}
重新运行
俩集群,不要忘了啊,手动修改,和上面一样的方式。
运行成功后查看Nacos中状态:
原来的俩服务还在,但是多了其他的,他们分别是我们注册的dubbo服务,有提供者provider,也有消费者consumer,我们下面点进去看看helloWorld集群启动成功没有:
元数据这一列多了好多数据啊,我们看到是dubbo相关的,集群也都正常,图太长,就不一一截了,下面去访问接口:http://localhost:8080/hello/service
能得到数据,就说明我们的配置没问题,Dubbo引入就是成功的:
查看控制台:
org.apache.dubbo.rpc.RpcException: No provider available from registry localhost:8848 for service com.codingfire.cloud.helloword.service.IHelloWorldService on consumer 192.168.0.102 use dubbo version 2.7.8, please check status of providers(disabled, not registered or in blacklist).
说是com.codingfire.cloud.helloword.service.IHelloWorldService这个类没有注册到白名单?我们看下这个类,这是个接口,所以还是要看其实现类HelloWorldServiceImpl,果然,这个类没有添加Dubbo注解,在类上添加注解:
@DubboService
下面重新运行helloWorld的四个启动类,记得集群哦,这时候,博主仿佛听到有人在吐槽:博主,你不玩我们会死啊?能不能一步到位?
哈哈哈哈~不好意思,这是为了让大家更好的理解细节处,大家还是先运行项目吧。
a few moments later~
好了,大家都运行成功了吧,下面,重新访问接口:http://localhost:8080/hello/service
漂亮!!!
但是关于Dubbo单独设置负载均衡策略和同集群优先这些就不在这里介绍了,博主还在进行研究,后续会发详细介绍。
权重
关于集群访问,还可设置权重:
各集群权重可自行根据需要设置。
结语
到此,这篇关于集群间相互调用的博客就写好了,唯一觉得有些遗憾的是关于Dubbo的详细使用没有给大家讲清楚,后续单独出一篇来进行讲解,请大家稍微期待一下,其实很多人开发中未必用到异地容灾的场景,这篇博客算是给大家一个入门的门票,算不上特别精,但一定对你会有些帮助。未来,会分享更多的集群相关的博客,让我们一起学习,早日成为架构师。