系统架构
在没有微服务之前有已经有跨服务调用了,比如ServiceB去调用ServiceA中的服务 , 传统模式可以直接在ServiceB中写ServiceA中的服务但是这样是写死了的,不够灵活。
下图就是传统的调用
微服务下的跨系统调用应该是这样的:
此时服务的调用应该是分两个步骤的:
ServiceB 去服务中心拿到ServiceA的地址,如果ServiceA是单机部署,那么这个地址就只有一个,如果ServiceA是集群是集群环境部署,那么发现的地址就是多个。
拿到了ServiceA的地址后,ServiceB再去调用ServiceA的相关服务了。
这样做其实是有很多好处的,首先互相调用的地址可以不用写死,需要的时候直接去服务中心获取,并且服务之间也可以很方便的部署、集群等。
服务注册与消费搭建
- 首先我们创建一个ServiceRegister的普通maven项目,然后在创建一个Eureka的SpringBoot项目作为子项目
下面是Eureka项目的pom.xml 配置
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
下面是application.yml 配置文件
# 别名
spring:
application:
name: eureka
#端口
server:
port: 1111
# eureka config
eureka:
client:
register-with-eureka: false
fetch-registry: false
注意 这里配置好了后需要在Eureka这个子项目的启动类上加入下面这个注解
@EnableEurekaServer
这个注解的意义是代表这个项目成为一个注册中心
- 创建一个Provider
创建一个叫Provider的SpringBoot项目作为子项目,pom.xml配置
<properties>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
下面是application.yml 配置文件
# 别名
spring:
application:
name: provider
#端口
server:
port: 4001
# provider config
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka
spring.application.name 是服务的名称,你可以理解成是别名,其他的服务调用的时候需要使用这个name来调用
server.port 是端口号
eureka.client.service-url.defaultZone 是这个服务需要注册到服务中心地址,这里需要注意的是,如果服务中心是一个集群,这里也可以只写服务中心的一个节点,eureka会自动集群对服务进行同步。
在微服务中,你的项目的pom.xml 中如果存在 spring-cloud-starter-netflix-eureka-client的依赖,并且提供了eureka注册中心的地址那么会默认注册到 Eureka Server 中。
然后我们在 provider 中提供一个简单的服务
@RestController
public class SayHelloController {
@GetMapping("/sayHello")
public String SayHello(String name) {
return "sayHello" + name + "!";
}
}
这样我们就创建好我们的服务提供者,并且提供了一个简单的服务接口了。
- 创建consumer
创建好了服务提供者,那么我们就来创建服务消费者consumer,创建一个SpringBoot的子项目工程 pom依赖如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
这个配置和provider 配置几乎是一样的,下面是consumer的application.yml 配置文件
# 别名
spring:
application:
name: consumer
#端口
server:
port: 4002
# provider config
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka
唯一变了就是服务的名称。
配置好了后,我们在consumer的启动类中添加一个RestTemplate的实例,RestTemplate是Spring提供的一个Http请求工具,下面是这个RestTemplate实例的代码
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
然后我们在consumer 中添加一个UserSayHelloController,在这里去调用provider 提供的服务。
@RestController
public class SayUserHelloController {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
@GetMapping("/sayHello")
public String hello(String name) {
List<ServiceInstance> list = discoveryClient.getInstances("provider");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
String s = restTemplate.getForObject("http://" + host + ":" + port + "/hello?name={1}", String.class, name);
return s;
}
}
上面代码的意思是解释如下:
首先我们在这个Controller中注入了一个DiscoveryClient ,DiscoveryClient可以从Eureka或者从Consul上根据服务名查询一个服务的详细信息,注意DiscoveryClient是下面这个包中的
org.springframework.cloud.client.discovery.DiscoveryClient
@Autowired
RestTemplate restTemplate;
RestTemplate 就是Spring 给我们提供用来发送Http请求的,这个大多数人应该都是知道的。
List<ServiceInstance> list = discoveryClient.getInstances("provider");
discoveryClient.getInstances 就是调用服务的名称,为什么用List去接受? 那是因为有可能provider 是单机部署 也有可能是集群部署,如果是集群部署的话,那么provider的实例就有多个
ServiceInstance 保存了provider 中 详细的信息、如主机地址、端口号、实例id等。ServiceInstance是一个接口,它有很多给实现类,我们本次的这个项目使用的是EurekaServiceInstance。
ServiceInstance instance = list.get(0);
因为我们只有一个provider 实例,所以我们就用list.get(0) 来获取实例了
获取主机地址
String host = instance.getHost();
获取端口号
int port = instance.getPort();
RestTemplate 的 getForObject 方法接收三个参数。第一个参数是请求地址,请求地址中的 {1} 表示一个参数占位符,第一个参数 String.class 表示返回的参数类型,第三个参数则是一个第一个占位符的具体值。
String s = restTemplate.getForObject("http://" + host + ":" + port + "/hello?name={1}", String.class, name);
最后我们返回这个s 就完成了consumer的编写
下面我们依次的启动Eureka(注册中心)和 服务提供者(provider) 以及 消费者(consumer)
然后在浏览器上输入:
localhost:4002/sayHello?name=jishu
结果如下图所示:
这样我们就顺利的从consumer 去调用了provider 的服务了
DiscoveryClient是哪里来的
归根结底 DiscoveryClient作用就是可以从Eureka 或者Consul 中查询服务的实例,不过DiscoveryClient就是一个接口而已,但是还是有一个实现类, 这个具体的实现类就是CompositeDiscovery,当我们的微服务启动的时候,就会在CompositeDiscoveryClientAutoConfiguration类中配置一个CompositeDiscovery的实例,
下面这个就是大名鼎鼎的CompositeDiscoveryClientAutoConfiguration的源码
//这里就是DiscoveryClient的源码了
@Configuration
@AutoConfigureBefore(SimpleDiscoveryClientAutoConfiguration.class)
public class CompositeDiscoveryClientAutoConfiguration {
//这里返回的是一个实例
@Bean
@Primary
public CompositeDiscoveryClient compositeDiscoveryClient(
List<DiscoveryClient> discoveryClients) {
return new CompositeDiscoveryClient(discoveryClients);
}
}
其实真正调用的是CompositeDiscoveryClient类中的discoveryClients 属性提供的 DiscoveryClient,而discoveryClients 属性默认集合中只有一条数据,就是EurekaDiscoveryClient,最终在EurekaDiscoveryClient类中,通过EurekaClient来获取一个微服务的实例信息了
总结
在不用微服务调用,服务之间的调用是相当繁琐的,并且地址是写死了,那么部署的话是非常的不方便,但是我们提供了注册中心的话,那么我们让服务注册到我们的注册中心中,然后在从注册中心去获取我们的服务信息,这样就有大大的好处,降低了调用的难度。