七.eureka 注册与发现
1.标题创建eureka项目,并添加eureka server依赖
2.配置依赖 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 https://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.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>sp05-eureka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sp05-eureka</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR7</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</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>
3.配置 application.yml
#服务名称
spring:
application:
name: eureka-server
#端口号
server:
port: 2001
eureka:
instance:
#eureka 集群服务器之间,通过 hostname 来区分
hostname: eureka1
#eureka 的自我保护状态:心跳失败的比例,在15分钟内是否超过85%,
#如果出现了超过的情况,Eureka Server会将当前的实例注册信息保护起来,
#同时提示一个警告,一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,
#不再删除服务注册表中的数据。也就是不会注销任何微服务
server:
enable-self-preservation: false
client:
register-with-eureka: false #不向自身注册
fetch-registry: false #不从自身拉取注册信息
4,主程序启用 eureka 服务器
@EnableEurekaServer
@SpringBootApplication
public class Sp05EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(Sp05EurekaApplication.class, args);
}
}
5.修改 hosts 文件,添加 eureka 域名映射
127.0.0.1 eureka1
127.0.0.1 eureka2
6.启动,并访问测试
八.service provider 服务提供者
修改 item-service、user-service、order-service,把微服务注册到 eureka 服务器
1.pom.xml 添加eureka依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.application.yml 添加eureka注册配置
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka
3.主程序启用服务注册发现客户端
@EnableDiscoveryClient //让注册中心能够发现,扫描到该服务
@EnableDiscoveryClient //让注册中心能够发现,扫描到该服务
@SpringBootApplication
public class Sp02ItemserviceApplication {
public static void main(String[] args) {
SpringApplication.run(Sp02ItemserviceApplication.class, args);
}
}
4.启动服务,在eureka中查看注册信息
http://eureka1:2001
九、eureka 和 “服务提供者”的高可用
9.1item-service 高可用
配置Sp02启动参数, --server.port 可以覆盖yml中的端口配置
9.1.1点击Edi Configurations
9.1.2配置启动参数,在Progran arguments选项填写:–server.port=8001如下图所示
--server.port=8001
9.1.3复制Sp02itemservice-8001,名称改成Sp02itemservice-8001,参数设置成:–server.port=8002,如下图
--server.port=8002
9.1.4访问 eureka 查看 item-service 注册信息
访问两个端口测试
http://localhost:8001/35
http://localhost:8002/35
9.2 eureka 高可用
9.2.1添加两个服务器的 profile 配置文件
application-eureka1.yml
eureka:
instance:
hostname: eureka1
client:
register-with-eureka: true #profile的配置会覆盖公用配置
fetch-registry: true #profile的配置会覆盖公用配置
service-url:
defaultZone: http://eureka2:2002/eureka #eureka1启动时向eureka2注册
application-eureka2.yml
eureka:
instance:
hostname: eureka2
client:
register-with-eureka: true #profile的配置会覆盖公用配置
fetch-registry: true #profile的配置会覆盖公用配置
service-url:
defaultZone: http://eureka1:2001/eureka #eureka2启动时向eureka1注册
9.2.2配置启动参数 --spring.profiles.active 和 --server.port
eureka1 启动参数设置,点击Edi Configurations,做如图所示设置
--spring.profiles.active=eureka1 --server.port=2001
eureka2 启动参数,步骤如上,效果如下图
--spring.profiles.active=eureka2 --server.port=2002
9.2.3访问 eureka1和eureka2 服务器,查看注册信息
http://eureka1:2001/
http://eureka2:2002/
9.2.4 Sp02,Sp03,Sp04向uereka2注册
修改三个服务的yml文件
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
9.2.5访问uereka1和uereka2
十.ribbon 服务消费者-实现远程调用
ribbon 提供了负载均衡和重试功能, 它底层是使用 RestTemplate 进行 Rest api 调用
10.1RestTemplate
RestTemplate 是StringBoot提供的一个Rest远程调用工具
它的常用方法:
getForObject() - 执行get请求
postForObject() - 执行post请求
之前的系统结构是浏览器直接访问后台服务
后面我们通过一个Demo项目演示 Spring Cloud 远程调用
下面我们先不使用ribbon, 单独使用RestTemplate来执行远程调用
10.2新建 ribbon 项目
选中file-new-Module,创建SpringBoot项目
10.3pom.xml
pom文件添加sp01依赖
<?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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>sp06-ribbon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sp06-ribbon</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR7</spring-cloud.version>
</properties>
<dependencies>
<!--spring web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Eureka client依赖-->
<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--sp01依赖-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>sp01-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</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>
10.3application.yml
spring:
application:
name: ribbon
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
10.4主程序
创建 RestTemplate 实例
RestTemplate 是用来调用其他微服务的工具类,封装了远程调用代码,提供了一组用于远程调用的模板方法,例如:getForObject()、postForObject() 等
package cn.tedu.sp06;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient //让注册中心能够发现,扫描到该服务
@SpringBootApplication
public class Sp06RibbonApplication {
public static void main(String[] args) {
SpringApplication.run(Sp06RibbonApplication.class, args);
}
//创建 RestTemplate 实例,并存入 spring 容器
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate(sf);
}
}
10.5controller层
创建RibbonController类
package cn.tedu.sp06.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
@RestController
public class RibbonController {
@Autowired
private RestTemplate rt;
@GetMapping("/item-service/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
//向指定微服务地址发送 get 请求,并获得该服务的返回结果
//{1} 占位符,用 orderId 填充
return rt.getForObject("http://localhost:8001/{1}", JsonResult.class, orderId);
}
@PostMapping("/item-service/decreaseNumber")
public JsonResult decreaseNumber(@RequestBody List<Item> items) {
//发送 post 请求
return rt.postForObject("http://localhost:8001/decreaseNumber", items, JsonResult.class);
}
/
@GetMapping("/user-service/{userId}")
public JsonResult<User> getUser(@PathVariable Integer userId) {
return rt.getForObject("http://localhost:8101/{1}", JsonResult.class, userId);
}
@GetMapping("/user-service/{userId}/score")
public JsonResult addScore(
@PathVariable Integer userId, Integer score) {
return rt.getForObject("http://localhost:8101/{1}/score?score={2}", JsonResult.class, userId, score);
}
/
@GetMapping("/order-service/{orderId}")
public JsonResult<Order> getOrder(@PathVariable String orderId) {
return rt.getForObject("http://localhost:8201/{1}", JsonResult.class, orderId);
}
@GetMapping("/order-service")
public JsonResult addOrder() {
return rt.getForObject("http://localhost:8201/", JsonResult.class);
}
}
10.6启动,并访问测试
http://eureka1:2001
http://localhost:3001/item-service/35
http://localhost:3001/item-service/decreaseNumber
使用postman,POST发送以下格式数据:
[{“id”:1, “name”:“abc”, “number”:23},{“id”:2, “name”:“def”, “number”:11}]
http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100
http://localhost:3001/order-service/123abc
http://localhost:3001/order-service/
十一ribbon 负载均衡和重试
11.1Ribbon 负载均衡
修改 sp06-ribbon 项目
11.1RestTemplate 设置 @LoadBalanced
@LoadBalanced 负载均衡注解,会对 RestTemplate 实例进行封装,创建动态代理对象,并切入(AOP)负载均衡代码,把请求分发到集群中的服务器
package cn.tedu.sp06;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
@SpringBootApplication
public class Sp06RibbonApplication {
@LoadBalanced //负载均衡注解
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Sp06RibbonApplication.class, args);
}
}
11.2访问路径设置为服务id
package cn.tedu.sp06.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
@RestController
public class RibbonController {
@Autowired
private RestTemplate rt;
@GetMapping("/item-service/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId) {
//这里服务器路径用 service-id 代替,ribbon 会向服务的多台集群服务器分发请求
return rt.getForObject("http://item-service/{1}", JsonResult.class, orderId);
}
@PostMapping("/item-service/decreaseNumber")
public JsonResult decreaseNumber(@RequestBody List<Item> items) {
return rt.postForObject("http://item-service/decreaseNumber", items, JsonResult.class);
}
/
@GetMapping("/user-service/{userId}")
public JsonResult<User> getUser(@PathVariable Integer userId) {
return rt.getForObject("http://user-service/{1}", JsonResult.class, userId);
}
@GetMapping("/user-service/{userId}/score")
public JsonResult addScore(
@PathVariable Integer userId, Integer score) {
return rt.getForObject("http://user-service/{1}/score?score={2}", JsonResult.class, userId, score);
}
/
@GetMapping("/order-service/{orderId}")
public JsonResult<Order> getOrder(@PathVariable String orderId) {
return rt.getForObject("http://order-service/{1}", JsonResult.class, orderId);
}
@GetMapping("/order-service")
public JsonResult addOrder() {
return rt.getForObject("http://order-service/", JsonResult.class);
}
}
11.3访问测试
访问测试,ribbon 会把请求分发到 8001 和 8002 两个服务端口上
http://localhost:3001/item-service/34
11.2ribbon 重试
11.2.1pom.xml 添加 spring-retry 依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
11.2.2application.yml 配置 ribbon 重试
spring:
application:
name: ribbon
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
ribbon:
MaxAutoRetries: 1 #当前实例重试次数,尝试失败会更换下一个服务
MaxAutoRetriesNextServer: 2 #更换服务的次数
#OkToRetryOnAllOperations: true #默认只对GET请求重试, 当设置为true时, 对POST等所有类型请求都重试
11.2.3主程序设置 RestTemplate 的请求工厂的超时属性
package cn.tedu.sp06;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient //让注册中心能够发现,扫描到该服务
@SpringBootApplication
public class Sp06RibbonApplication {
public static void main(String[] args) {
SpringApplication.run(Sp06RibbonApplication.class, args);
}
//创建 RestTemplate 实例,并存入 spring 容器
@LoadBalanced //负载均衡注解
@Bean
public RestTemplate getRestTemplate() {
//RestTemplate 中默认的 Factory 实例中,两个超时属性默认是 -1,
//-1的意思是未启用超时
SimpleClientHttpRequestFactory sf=new SimpleClientHttpRequestFactory();
sf.setConnectTimeout(1000);//建立链接等待超时时间
sf.setReadTimeout(1000);//连接已建立并已发送请求,等待接受响应的超时时间
return new RestTemplate(sf);
}
}
11.2.4item-service 的 ItemController 添加延迟代码,以便测试 ribbon 的重试机制
package cn.tedu.sp02.item.controller;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.service.ItemService;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Random;
@RestController
@Slf4j
public class ItemController {
@Autowired
private ItemService itemService;
@Value("${server.port}")
private int port;
// 获取订单中的商品
@GetMapping("/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId) throws InterruptedException {
log.info("orderId="+orderId+", port="+port);
///--设置随机延迟
double d=Math.random();
log.info("d="+d);
if(d<0.6) {
long t = new Random().nextInt(5000);
log.info("item-service-"+port+" - 暂停 "+t);
Thread.sleep(t);
}
List<Item> items = itemService.getItems(orderId);
return JsonResult.ok().data(items).msg("port="+port);
}
// 减少商品库存
@PostMapping("/decreaseNumber")
public JsonResult<?> decreaseNumber(@RequestBody List<Item> items) {
itemService.decreaseNumber(items);
return JsonResult.ok().msg("减少商品库存成功");
}
}
11.2.5访问,测试 ribbon 重试机制
通过 ribbon 访问 item-service,当超时,ribbon 会重试请求集群中其他服务器
http://localhost:3001/item-service/35
十二、Hystrix 断路器
https://github.com/Netflix/Hystrix/wiki