微服务搭建
Eureka注册中心
背景
服务发现是基于微服务的体系结构的关键原则之一。尝试手工配置每个客户端或某种形式的约定可能很困难,而且很脆弱。Eureka是Netflix的服务发现服务器和客户端。可以将服务器配置和部署为高可用性,每个服务器将注册服务的状态复制到其他服务器。
简介
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。
Eureka组件
Eureka Server
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
原理探究
Eureka特点
Eureka服务注册与发现
服务注册
- 1将实例注册信息放入或者更新registry
- 2.将实例注册信息加入最近修改的记录队列
- 3.主动让Response缓存失效
服务取消
- 1.从registry中剔除这个实例
- 2.将实例注册信息加入最近修改的记录队列
- 3.主动让Response缓存失效
EurekaClient 缓存
EurekaClient第一次全量拉取,定时增量拉取应用服务实例信息,保存在缓存中。
EurekaClient增量拉取失败,或者增量拉取之后对比hashcode发现不一致,就会执行全量拉取,这样避免了网络某时段分片带来的问题。
同时对于服务调用,如果涉及到ribbon负载均衡,那么ribbon对于这个实例列表也有自己的缓存,这个缓存定时从EurekaClient的缓存更新
自我保护机制
自我保护机制:默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。
Eureka通过“自我保护模式”来解决这个问题——当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
但是,在我们实际生产中,我们云环境同一个Region下不会发生大规模网络分区状况,所以没有启用自我保护。
服务搭建
父级项目依赖
使用IDEA创建父maven的pom类型项目, ,然后仅仅往pom.xml添加如下依赖即可,可参考如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| <?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>
<groupId>com.springcloud.test</groupId> <artifactId>SpringCloudTest</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>eureka-server</module> <module>user-server</module> <module>order-server</module> </modules>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.7.RELEASE</version> </parent>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR4</spring-cloud.version> </properties>
<dependencies> <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>
<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>
<repositories> <repository> <id>central</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <layout>default</layout> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
</project>
|
Eureka Ser ver搭建
添加POM依赖
<parent>指定为父级项目
该模块必须引入spring-boot-starter-web,否则无法启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?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"> <parent> <artifactId>SpringCloudTest</artifactId> <groupId>com.springcloud.test</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<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> </dependencies> </project>
|
注意:查资料时看到maven引入部分使用了spring-cloud-starter-eureka-server,部分使用了spring-cloud-starter-netflix-eureka-server。特意去查了下springcloud更新换代比较快,可能1.5可以使用,到了2.0就不用了。所以做项目或者练习时要看清自己使用的版本。1.5版本使用spring-cloud-starter-eureka-server还是没问题的。2.0以上建议使用 spring-cloud-starter-netflix-eureka-server。
配置配置文件
配置文件application.yml的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| server.port=8888 spring.application.name=cloud-eureka-server
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false eureka.server.enable-self-preservation=true
spring.security.user.name=admin spring.security.user.password=admin
|
创建启动类
在启动类中加入注解@EnableEurekaServer证明是个启动服务注册。
1 2 3 4 5 6 7
| @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class); } }
|
启动测试
如果访问 localhost:8888 能够出现这个页面说明Eureka启动成功
Eureka Client的搭建
创建一个订单服务
添加POM依赖
<parent>指定为父级项目
该模块必须引入spring-boot-starter-web,否则无法启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?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"> <parent> <artifactId>SpringCloudTest</artifactId> <groupId>com.springcloud.test</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<artifactId>order-server</artifactId> <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.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.10</version> </dependency> </dependencies> </project>
|
注意:该模块引入spring-cloud-starter-netflix-eureka-client
配置配置文件
配置application.yml连接到erueka-server端
1 2 3
| server.port=8082 spring.application.name=order-server eureka.client.serviceUrl.defaultZone=http://localhost:8888/eureka/
|
创建启动类
启动类@EnableEurekaClient指定为客户端
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableEurekaClient public class OrderApplication {
public static void main(String[] args) { SpringApplication.run(OrderApplication.class); } }
|
启动测试
当发现项目启动成功后 访问 http://localhost:8888 就会发现该服务已经在注册中心注册了
创建用户服务
具体细节参考 上文
添加POM依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?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"> <parent> <artifactId>SpringCloudTest</artifactId> <groupId>com.springcloud.test</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<artifactId>user-server</artifactId> <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>
</project>
|
配置配置文件
配置application.yml连接到erueka-server端
1 2 3
| server.port=8081 spring.application.name=user-server eureka.client.serviceUrl.defaultZone=http://localhost:8888/eureka/
|
创建启动类
启动类@EnableEurekaClient指定为客户端
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableEurekaClient public class UserApplication {
public static void main(String[] args) { SpringApplication.run(UserApplication.class); } }
|
启动测试
当发现项目启动成功后 访问 http://localhost:8888 就会发现该服务已经在注册中心注册了
RestTemplate服务间调用
RestTemplate 简介
在微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端。我们可以使用JDK原生的URLConnection、Apache的Http Client、Netty的异步HTTP Client, Spring的RestTemplate。但是,用起来最方便、最优雅的还是要属Feign了。这里介绍的是RestTemplate。
什么是RestTemplate
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。
ClientHttpRequestFactory接口主要提供了两种实现方式
基本使用
上一篇文章我们创建了三个服务 Eureka Server,和连个Eureka Client 分别是 用户服务和订单服务,我们用订单服务作为提供方,用户服务作为调用方
服务提供方
订单服务提供服务
前置工作
增加实体类
1 2 3 4 5 6 7 8 9 10 11 12
| public class Order {
private Long orderId;
private String userid;
private Float money;
private Date creatreDate; }
|
服务提供方代码
OrderService
1 2 3 4 5 6 7 8
| @RestController @RequestMapping("/order") public interface OrderService {
@GetMapping("/createorder/{userid}") public Order createOrder(@PathVariable("userid") String userId); }
|
OrderServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Component public class OrderServiceImpl implements OrderService {
@Override public Order createOrder(String userId) { Order order = new Order(); order.setUserid(userId); order.setMoney(0.0F); order.setCreatreDate(new Date()); order.setOrderId(111111111111111111L); return order; } }
|
测试
通过浏览器直接测试 http://localhost:8082/order/createorder/xx
响应结果
1 2 3 4 5 6
| { "orderId": 111111111111111111, "userid": "xx", "money": 0.0, "creatreDate": "2020-05-14T02:36:44.320+0000" }
|
服务调用方
用户服务调用订单服务
前置工作
增加实体类
注意该实体和Order服务的实体是两个不同的实体
1 2 3 4 5 6 7 8 9 10 11
| public class Order {
private Long orderId;
private String userid;
private Float money;
private Date creatreDate; }
|
RestTemplate配置类
1 2 3 4 5 6 7 8
| @Configuration public class RestTemplateConfiguration {
@Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } }
|
服务调用方代码
UserService
1 2 3 4 5 6 7
| @RestController @RequestMapping("/user") public interface UserService { @RequestMapping("/addorder") public String addOrder(); }
|
UserServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Component public class UserServiceImpl implements UserService {
@Autowired private RestTemplate restTemplate;
@Override public String addOrder() { Order order = restTemplate.getForObject("http://localhost:8082/order/createorder/1", Order.class); System.out.println(order); return order.toString(); } }
|
测试调用
调用订单服务 http://localhost:8081/user/addorder
响应结果
1 2 3
| Order { orderId = 111111111111111111, userid = '1', money = 0.0, creatreDate = Thu May 14 10: 42: 41 CST 2020 }
|
到此位置 完成了RestTemplate对跨服务应用的调用,但是我们发现我们的地址是写死的,不能进行负载均衡等方式的调用。
客户端负载均衡
负载均衡是我们处理高并发、缓解网络压力和进行服务端扩容的重要手段之一,但是一般情况下我们所说的负载均衡通常都是指服务端负载均衡
客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置。在客户端负载均衡中,所有的客户端节点都有一份自己要访问的服务端清单,这些清单统统都是从 Eureka 服务注册中心获取的。在 springcloud 中我们如果想要使用客户端负载均衡,方法很简单,开启@LoadBalanced
注解即可,这样客户端在发起请求的时候会先自行选择一个服务端,向该服务端发起请求,从而实现负载均衡
在Spring Cloud框架中,负载均衡服务本身也要作为一个发现客户端注册到Eureka服务器上。客户发起一个请求时,需要在Eureke服务器上发现负载均衡服务,负载均衡服务通过RestTemplate调用微服务的接口时,会通过负载均衡器进行负载均衡。这样,不同的服务请求会由负载均衡机制分别调用微服务的不同实例。
服务端集群
首先我们通过修改服务的端口来启动两个订单服务
我们启动连个 订单服务 一个端口号 8082 一个8083
更改代码
更改一些代码为了演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Component public class OrderServiceImpl implements OrderService {
@Value("${server.port}") private int serverPort;
@Override public Order createOrder(String userId) { Order order = new Order(); order.setUserid(userId); order.setMoney(0.0F); order.setCreatreDate(new Date()); order.setOrderId((long) serverPort); System.out.println(serverPort + "创建订单"); return order; } }
|
idea启动多实例
查看配置中心
我们发现注册了两个Order服务
8082 和 8083两个服务构成简单的集群
服务调用
在 springcloud 中我们如果想要使用客户端负载均衡,方法很简单,开启@LoadBalanced
注解即可。
配置LoadBalanced
在用户服务的RestTemplate配置类加上 @LoadBalanced
1 2 3 4 5 6 7 8 9
| @Configuration public class RestTemplateConfiguration {
@Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
|
修改调用代码
如果想要使用负载均衡必须使用服务名的调用形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Component public class UserServiceImpl implements UserService {
private static final String ORDER_SERVICE_NAME = "order-server";
@Autowired private RestTemplate restTemplate;
@Override public String addOrder() { String url = "http://"+ORDER_SERVICE_NAME + "/order/createorder/1"; Order order = restTemplate.getForObject(url, Order.class); System.out.println(order); return order.toString(); } }
|
测试
通过 http://localhost:8081/user/addorder 方式调用查看返回结果回来会在 8082-8083 之间跳转,完成了负载均衡