1. 实现高可用
1.1 商品服务高可用
复制Sp01Item配置
测试效果:
1.2 eureka高可用实现
1.2.1 编辑配置文件
eureka:
instance:
#主机名,在集群中区分不同服务器
hostname: eureka1
client:
register-with-eureka: true
fetch-registry: true
#连接eureka2
service-url:
defaultZone: http://eureka2:2002/eureka
eureka:
instance:
#主机名,在集群中区分不同服务器
hostname: eureka2
client:
register-with-eureka: true
fetch-registry: true
#连接eureka1
service-url:
defaultZone: http://eureka1:2001/eureka
1.2.2 编辑启动配置
–spring.profiles.active=eureka1 --server.port-2001
1.2.3 测试效果
http://eureka1:2001/
http://eureka2:2002/
2. ribbon
2.1 RestTemplate
ribbon 提供了负载均衡和重试功能, 它底层是使用 RestTemplate 进行 Rest api 调用
RestTemplate 是SpringBoot提供的一个Rest远程调用工具,对Rest API调用做了高度地封装,提供了渐变的调用接口
它的常用方法:
- getForObject(url,转换类型,提交的参数数据) - 执行get请求
- postForObject(url,提交的协议体数据,转换类型) - 执行post请求
2.2 创建sp06-ribbon项目
2.2.1 添加依赖
<?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.8.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.SR9</spring-cloud.version>
<vaadin.version>14.4.6</vaadin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.eureka</groupId>
<artifactId>eureka-client</artifactId>
<version>1.10.11</version>
</dependency>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>sp01-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</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>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.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>
2.2.2 配置yml
spring:
application:
name: ribbon
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
2.2.3 启动类新建RestTemplate
package cn.tedu.sp06;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Sp06RibbonApplication {
public static void main(String[] args) {
SpringApplication.run(Sp06RibbonApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.2.4 编辑RibbonController
package cn.tedu.sp06.controller;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@Slf4j
public class RibbonController {
@Autowired
private RestTemplate rt;
//调用获取商品列表
@GetMapping("/item-service/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId){
//调用远程的商品服务,获取商品列表 http://localhost:8001/{orderId}
//{1} - RestTemplate 定义的占位符格式
return rt.getForObject("http://localhost:8001/{1}",JsonResult.class,orderId);
}
//减少商品库存
@PostMapping("/item-service/decreaseNumber")
public JsonResult decreaseNumber(@RequestBody List<Item> items){
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);
}
}
2.2.5 测试
http://localhost:3001/item-service/34
2.3 实现负载均衡
2.3.1 添加@LoadBalanced注解
在RestTemplate上添加注解@LoadBalanced
2.3.2 编辑RibbonController
设置返回url为动态地址
package cn.tedu.sp06.controller;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@Slf4j
public class RibbonController {
@Autowired
private RestTemplate rt;
/**
* eureka注册表:
* item-service --- localhost:8001,localhost:8002
* @param orderId
* @return
*/
//调用获取商品列表
@GetMapping("/item-service/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId){
//调用远程的商品服务,获取商品列表 http://localhost:8001/{orderId}
//{1} - RestTemplate 定义的占位符格式
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);
}
}
2.3.3 负载均衡测试
2.4 实现重试
2.4.1 添加依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
2.4.2 配置重试参数
- MaxAutoRetries - 单台服务器重试次数
- MaxAutoRetriesNextServer - 更换服务器次数
- OkToRetryOnAllOperations - 是否对所有类型请求都重试,默认只对get重试
- ConnectTimeout - 建立连接的超时时间
- Readtimeout - 连接建立后请求已发送,等待响应超时时间
ribbon:
MaxAutoRetriesNextServer: 2
MaxAutoRetries: 1
OkToRetryOnAllOperations: true
2.4.2 编辑启动类
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory f = new SimpleClientHttpRequestFactory();
f.setConnectTimeout(1000);
f.setReadTimeout(1000);
return new RestTemplate(f);
}
2.4.3 编辑ItemController
//获取订单中的商品列表
@GetMapping("/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId) throws InterruptedException {
List<Item> items = itemService.getItems(orderId);
log.info("获取商品列表, orderId"+orderId);
//随机延迟
if(Math.random()<0.9){
//产生随机延迟时长
int t = new Random().nextInt(5000);
System.out.println("延迟"+t);
Thread.sleep(t);
}
return JsonResult.ok().msg("server.port="+port).data(items);
}
2.4.4 重试效果测试
http://localhost:3001/item-service/34
3. Hystrix 断路器
系统容错/系统限流工具
- 降级
- 熔断
3.1 降级
调用远程服务失败,可以执行当前服务中的一段代码,来向客户端返回结果
3.1.1 添加 hystrix 起步依赖
在sp06-ribbon项目的pom.xml文件添加hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3.1.2 启动类添加@EnableCircuitBreaker注解
在sp06-ribbon项目的启动类上添加@EnableCircuitBreaker注解
3.1.3 添加降级代码
package cn.tedu.sp06.controller;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@Slf4j
public class RibbonController {
@Autowired
private RestTemplate rt;
/**
* eureka注册表:
* item-service --- localhost:8001,localhost:8002
* @param orderId
* @return
*/
//调用获取商品列表
@HystrixCommand(fallbackMethod = "getItemsFB")//如果远程调用失败,调用降级方法
@GetMapping("/item-service/{orderId}")
public JsonResult<List<Item>> getItems(@PathVariable String orderId){
//调用远程的商品服务,获取商品列表 http://localhost:8001/{orderId}
//{1} - RestTemplate 定义的占位符格式
return rt.getForObject("http://item-service/{1}",JsonResult.class,orderId);
}
//减少商品库存
@HystrixCommand(fallbackMethod = "decreaseNumberFB")
@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}")
@HystrixCommand(fallbackMethod = "getUserFB")
public JsonResult<User> getUser(@PathVariable Integer userId){
return rt.getForObject("http://user-service/{1}", JsonResult.class, userId);
}
@HystrixCommand(fallbackMethod = "addScorerFB")
@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);
}
@HystrixCommand(fallbackMethod = "getOrderFB")
@GetMapping("/order-service/{orderId}")
public JsonResult<Order> getOrder(@PathVariable String orderId){
return rt.getForObject("http://order-service/{1}", JsonResult.class, orderId);
}
@HystrixCommand(fallbackMethod = "addOrderFB")
@GetMapping("/order-service")
public JsonResult addOrder(){
return rt.getForObject("http://order-service/", JsonResult.class);
}
//降级方法的参数和返回值,需要和原始方法一致,方法名任意
public JsonResult<List<Item>> getItemsFB(String orderId){
return JsonResult.err("获取订单商品列表失败");
}
public JsonResult decreaseNumberFB(List<Item> items) {
return JsonResult.err("更新商品库存失败");
}
public JsonResult<User> getUserFB(Integer userId) {
return JsonResult.err("获取用户信息失败");
}
public JsonResult addScoreFB(Integer userId, Integer score) {
return JsonResult.err("增加用户积分失败");
}
public JsonResult<Order> getOrderFB(String orderId) {
return JsonResult.err("获取订单失败");
}
public JsonResult addOrderFB() {
return JsonResult.err("添加订单失败");
}
}
3.1.4 降级测试效果
3.1.5 配置hystrix 超时设置
Hystrix超时和Ribbon超时需要配合设置,否则ribbon重试可能无效
说明: hystrix等待超时后, 会执行降级代码, 快速向客户端返回降级结果, 默认超时时间是1000毫秒
- Hystrix超时时间>=Ribbon总的超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
3.2 熔断
说明: 访问压力增大,出现大量失败时,可以断开链路,对后台服务不再发生请求
熔断自动触发发生条件:
- 10秒发生20次请求(必须首先成立)
- 50%请求都失败,执行降级代码
半开状态:
- 断路器打开后几秒,会进入半开状态,在半开状态下会尝试向后台服务发送一次调用,如果成功则关闭断路器,恢复正常;如果仍然调用失败,则继续保持打开状态几秒钟,再进入半开状态,尝试向后台服务发送一次调用,循环往复.
3.2.1 熔断测试
访问网页: http://localhost:3001/item-service/4
F5多次刷新,发现无需等待直接降级返回,等一会测试发现又需要等待,则熔断测试成功