微服务是指开发一个单个小型的但有业务功能的服务,每个服务都有自己的处理和轻量通讯机制,简单的说就是对系统进行拆分,拆分多个服务,每个服务运行在其独立的进程中,服务和服务之间采用轻量级的通信机制相互沟通(通常是基于HTTP的Restful API).每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境、类生产环境等
开发环境:JDK8 Spring Boot版本2.1.3 首先我们创建两个Spring Boot项目provider-server 服务提供者
pom.xml文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.li</groupId> <artifactId>provider-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>provider-server</name> <description>Demo project for Spring Boot</description>
<properties> <java.version>1.8</java.version> </properties>
<dependencies> <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>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
在程序入口定义一个hello接口 方便调用
package com.li.providerserver;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;
/** * 服务提供者 */@RestController@SpringBootApplicationpublic class ProviderServerApplication {
public static void main(String[] args) { SpringApplication.run(ProviderServerApplication.class, args); }
/** * 提供hello接口 * @param name * @return */ @RequestMapping("/hello") public String hello(String name){ System.out.println("被调用"); return "hello,"+name; }}
启动项目,访问接口: http://localhost:8080/hello?name=lhd
consumer-server 服务消费者也就是调用者pom.xml文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.li</groupId> <artifactId>consumer-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>consumer-server</name> <description>Demo project for Spring Boot</description>
<properties> <java.version>1.8</java.version> </properties>
<dependencies> <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>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
配置文件中修改端口号 8081
在程序入口定义一个hello接口 我们通过RestTemplate远程调用
package com.li.consumerserver;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;
/** * 服务消费者也就是调用者 */@RestController@SpringBootApplicationpublic class ConsumerServerApplication {
public static void main(String[] args) { SpringApplication.run(ConsumerServerApplication.class, args); }
/** * 注入bean 才可以使用 * @return */ @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }
/** * 使用注解@Autowired进行注入即可 */ @Autowired private RestTemplate restTemplate; /** * * @param name * @return */ @RequestMapping("/hello") public String hello(String name){ // 直接调用provider-server服务的hello接口 ip 端口号 参数等 String hello = restTemplate.getForObject("http://localhost:8080/hello?name=" + name, String.class); return "调用端口号8080服务成功,返回数据:"+hello; }}
启动项目, 访问接口: http://localhost:8081/hello?name=lhd
一个简单的远程服务调用案例就实现了
那么问题来了
在consumer中,我们把url地址硬编码到了代码中,不方便后期维护
服务管理
服务如何实现负载均衡? 服务如何解决容灾问题? 服务如何实现统一配置? 以上的问题,我们都将在SpringCloud中得到答案
在刚才的案例中,provider-server对外提供服务,需要对外暴露自己的地址。而consumer(调用者)需要记录服务提供者的地址。将来ip地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的
举个生活中的例子:
在没有滴滴打车之前, 我们出门都是坐出粗车, 有些私家车司机也想去拉客人去挣钱,因为没有网约车这种概念,被称为黑车,而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少中间人啊,就好比中介 自从滴滴打车这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。 此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务
那么问题来了,和我们说的Eureka有什么关系?
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。 同时,服务提供方与Eureka之间通过“心跳”机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。 这就实现了服务的自动注册、发现、状态监控等
原理图:
开发工具: IDEA Spring Boot版本: 2.1.3 Spring Cloud版本: Greenwich
File -> New -> Project - Maven
创建完结构如下
pom.xml文件先不用写东西 小伙伴等不及了吧 下面开始Spring Cloud
右击你的项目 New - Module - Spring Initializr
创建完之后 目录结构如下
我们来进行修改我们的pom文件
把我们刚刚创建的eureka-server服务下的pom文件
Spring Boot版本 以及一些属性复制粘贴到SpringCloudLearn的pom下 也就是父pom文件
然后eureka-server服务下的pom.xml继承父pom文件 如下进行更改
ps: 我比较喜欢把web依赖也放到父pom文件,因为我认为每个模块都必须要有
只需要一个注解@EnableEurekaServer,这个注解需要在springboot工程的启动application类上加:
package com.li.eurekaserver;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer@SpringBootApplicationpublic class EurekaServerApplication {
public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); }
}
eureka是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳(因此可以在内存中完成),在默认情况下erureka server也是一个eureka client ,必须要指定一个 server eureka-server的配置文件 application.properties:
#服务注册中心端口号server.port=8761#服务注册中心实例的主机名eureka.instance.hostname=localhost#是否向服务注册中心注册自己eureka.client.register-with-eureka=false#是否检索服务eureka.client.fetch-registry=false#服务注册中心的配置内容,指定服务注册中心的位置eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
通过eureka.client.registerWithEureka:false和fetchRegistry:false来表明自己是一个eureka server.
配置完成后 尝试启动项目,它是有界面的, 打开浏览器访问:http://localhost:8761
到这里服务注册中心就搞定了, 接下按照前面模拟的服务调用进行创建服务提供者以及服务消费者
当client向server注册时,它会提供一些元数据,例如主机和端口,URL,主页等。Eureka server 从每个client实例接收心跳消息。 如果心跳超时,则通常将该实例从注册server中删除
步骤和前面的一样 依赖变了
创建完后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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.li</groupId> <artifactId>SpringCloudLearn</artifactId> <version>1.0-SNAPSHOT</version> <relativePath>../</relativePath> </parent> <artifactId>eureka-client</artifactId> <version>0.0.1-SNAPSHOT</version> <name>eureka-client</name> <description>Demo project for Spring Boot</description>
<properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties>
<dependencies> <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>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
通过注解@EnableEurekaClient 表明自己是一个eureka client
package com.li.eurekaclient;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication@EnableEurekaClientpublic class EurekaClientApplication {
public static void main(String[] args) { SpringApplication.run(EurekaClientApplication.class, args); }
}
使用注解@EnableEurekaClient只是表明他是一个客户端, 我们还需要在配置文件中注明自己的服务注册中心的地址以及一些配置
application.properties:
# 端口号server.port=8762# 需要指明spring.application.name 这个很重要# 这在以后的服务与服务之间相互调用一般都是根据这个namespring.application.name=eureka-client#服务注册中心实例的主机名eureka.instance.hostname=localhost#服务注册中心端口号eureka.port=8761#在此指定服务注册中心地址eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${eureka.port}/eureka/
在程序入口定义一个接口
package com.li.eurekaclient;
import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication@EnableEurekaClient@RestControllerpublic class EurekaClientApplication {
public static void main(String[] args) { SpringApplication.run(EurekaClientApplication.class, args); }
@Value("${server.port}") String port;
@RequestMapping("/hello") public String home(@RequestParam(value = "name", defaultValue = "lhd") String name) { return "hello: " + name + " ,from port:" + port; }
}
启动项目,打开浏览器访问:http://localhost:8761 eureka-client服务已经注册上去了
继续访问: http://localhost:8762/hello?name=lhd
可以正常访问 并返回结果
创建consumer-server工程 步骤和eureka-client一模一样
在工程的启动类中,通过添加注解@EnableEurekaClient向服务中心注册
package com.li.consumerserver;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient@SpringBootApplicationpublic class ConsumerServerApplication {
public static void main(String[] args) { SpringApplication.run(ConsumerServerApplication.class, args); }
}
配置application.properties:
# 端口号server.port=8763# 需要指明spring.application.name 这个很重要# 这在以后的服务与服务之间相互调用一般都是根据这个namespring.application.name=consumer-server#服务注册中心实例的主机名eureka.instance.hostname=localhost#服务注册中心端口号eureka.port=8761#在此指定服务注册中心地址eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${eureka.port}/eureka/
在程序入口定义接口并且向程序的ioc注入一个bean: restTemplate 用DiscoveryClient类的方法,根据服务名称,获取服务实例 restTemplate来消费eureka-client服务的“/hello”接口
package com.li.consumerserver;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.discovery.DiscoveryClient;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;import org.springframework.context.annotation.Bean;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController@EnableEurekaClient@SpringBootApplicationpublic class ConsumerServerApplication {
public static void main(String[] args) { SpringApplication.run(ConsumerServerApplication.class, args); }
@Bean RestTemplate restTemplate() { return new RestTemplate(); }
// eureka客户端,可以获取到eureka中服务的信息 @Autowired private DiscoveryClient discoveryClient;
@Autowired RestTemplate restTemplate;
@RequestMapping("/hello") public String hello(String name) {
// 根据服务名称,获取服务实例。有可能是集群,所以是service实例集合 List<ServiceInstance> instances = discoveryClient.getInstances("eureka-client"); // 因为只有一个eureka-client 所以获取第一个实例 ServiceInstance instance = instances.get(0); // 获取ip和端口信息,拼接成服务地址 String baseUrl = "http://" + instance.getHost() + ":" + instance.getPort() + "/hello?name="+name; return restTemplate.getForObject(baseUrl, String.class); }
}
启动程序 打开浏览器访问: http://localhost:8761/ consumer-server服务已经注册进来
继续访问: http://localhost:8763/hello?name=lhd
这样就可以调用成功
Debug跟踪运行:
生成的URL:
Eureka详解
服务注册中心
Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server
服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的eureka-client
服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的consumer-server
SpringCloud Eureka服务发现与注册讲解完毕
源码下载: https://github.com/LiHaodong888/SpringCloudLearn