第七章中在 ProductController 和 OrderController 中都使用了局部服务降级,但同时也导致两个问题, 通过观察两个局部降级的案例,可以发现:
业务逻辑方法和处理服务异常降级方法混在一起,不便于维护,为解决此问题,可以使用注解 @FeignClient(value = "PRODUCT-SERVICE",fallback = xxx.class) 在调用远端服务的接口上进行指定服务降级方法解耦,并实现调用远端服务的接口的实现类,在实现类中统计管理服务降级解耦的方法。
进行全局解耦,以 订单服务 OrderController 调用 商品服务 ProductController 为案例,过程如下:
1、订单服务 和 商品服务引入依赖
订单服务引入依赖 openfegin , 商品服务分别引入依赖 Hystrix
2、清除 OrderController 和 ProductController 所有降级方法
清除 OrderController 所有降级方法
import com.hwadee.springcloud.entity.Product; import com.hwadee.springcloud.service.IOrderFeignService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/order") public class OrderController { @Autowired IOrderFeignService orderFeignService; @RequestMapping("/buy/{id}") public Product buy(@PathVariable Long id) { System.out.println("进入OrderController的buy方法, orderFeignService 准备调用远端接口 findById"); Product product = orderFeignService.findOrderById(id); return product; } @RequestMapping(value = "/delete/{id}") public Product deleteOrderById(@PathVariable Long id) { System.out.println("进入OrderController的deleteOrderById方法, orderFeignService 准备调用远端接口deleteOrderById"); Product product = orderFeignService.deleteOrderById(id); return product; } }
清除 ProductController 所有降级方法
注意,若有全局或者专属降级可以自己增加。此处为方便演示,不使用全局或者专属。
import com.hwadee.springcloud.entity.Product; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.math.BigDecimal; @RestController @RequestMapping("/product") public class ProductController { //方便后面讲负载均衡,查看ip,此处获取配置中的端口号和ip @Value("${server.port}") private String port; @Value("${spring.cloud.client.ip-address}") private String ip; @RequestMapping("/buy/{id}") public Product findById(@PathVariable Long id) { Product product = new Product(); product.setId(id); // 后面需要测试负载均衡,所以返回 ip 地址及端口号 product.setName("当前访问服务地址:" + ip + ":" + port + " " + "查询商品订单,订单号:" + id); product.setPrice(new BigDecimal(10000.0)); System.out.println(product); //测试超时熔断 try { Thread.sleep(5000); //测试并发熔断 //Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } return product; } @RequestMapping(value = "/delete/{id}") public Product deleteOrderById(@PathVariable Long id) { Product product = new Product(); product.setId(id); // 后面需要测试负载均衡,所以返回 ip 地址及端口号 product.setName("当前访问服务地址:" + ip + ":" + port + " " + "从购物车删除订单,订单号:" + id); product.setPrice(new BigDecimal(10000.0)); System.out.println(product); //测试异常熔断 System.out.println(10 / 0); return product; } }
2、定义Feign接口的实现类
因为订单服务 OrderController 中通过 接口IOrderFeignService 和 注解@FeignClient 远程调用商品服务ProductController ,因此当 订单服务 OrderController 出现超时或异常,可以通过 订单服务 OrderController 中通过 接口IOrderFeignService 的 实现类OrderFeignServiceFallBack 将务逻辑的处理方法 和 降级方法进行解耦。
import com.hwadee.springcloud.entity.Product;
import com.hwadee.springcloud.service.IOrderFeignService;
import org.springframework.stereotype.Component;
@Component
public class OrderFeignServiceFallBack implements IOrderFeignService {
@Override
public Product findOrderById(Long id) {
Product product = new Product();
product.setId(id);
product.setName("当前订单服务访问/order/buy/1 超时:"+id);
return product;
}
@Override
public Product deleteOrderById(Long id) {
Product product = new Product();
product.setId(id);
product.setName("当前订单服务访问/order/delete/1 10/0异常:"+id);
return product;
}
}
3、修改 IOrderFeignService 代码
在 接口IOrderFeignService 的 注解@FeignClient中指定服务解耦的类 OrderFeignServiceFallBack 。
@Component // 让 spring 可以识别,不加也行,但是在注入的时候 IDEA 会报错,不会影响运行,但有条红线让自己不舒服
@FeignClient(value = "PRODUCT-SERVICE",fallback = OrderFeignServiceFallBack.class)
public interface IOrderFeignService {
@RequestMapping(value = "/product/buy/{id}")
Product findOrderById(@PathVariable Long id);
@RequestMapping(value = "/product/delete/{id}")
Product deleteOrderById(@PathVariable Long id);
}
4、定义 订单服务 和 商品服务主启动类
由于订单服务中引入的 spring-cloud-starter-openfeign
依赖中已经集成了 feign-hystrix
,有了对 Hystrix 的支持,所以不需要额外引入依赖项。如果需要开启 订单服务端的 hystrix服务,只需要
在订单服务的配置文件配置 feign-hystrix 进行激活Hystrix,无需在主启动类中添加注解 @EnableHystrix 或 @EnableHystrix 来开启Hystrix服务。订单服务 和 商品服务主启动类如下:
订单服务主启动类 OrderServerApplication
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableEurekaClient// 启动 eureka 客户端 @EnableFeignClients // 启动 feign public class OrderServerApplication { public static void main(String[] args) { SpringApplication.run(OrderServerApplication.class, args); } }
商品服务主启动类 ProductServerApplication
@SpringBootApplication @EnableEurekaClient// 启动 eureka 客户端 public class ProductServerApplication { public static void main(String[] args) { SpringApplication.run(ProductServerApplication.class, args); } }
4、开启openfeign在调用服务过程中开启hystrix支持
由于前面引入的 spring-cloud-starter-openfeign
依赖中已经集成了 feign-hystrix
,有了对 Hystrix 的支持,所以不需要额外引入依赖项。只需要在订单服务的 application.yml 配置文件中开启openfeign在调用服务过程中开启hystrix支持。
server: port: 9000 spring: application: name: order-service # 为当前订单服务命名为 order-service # 配置eureka客户端信息 eureka: client: service-url: # 配置eureka客户端服务order-service的注册地址,与 eureka-server 中暴露地址要保持一致 defaultZone: http://localhost:8000/eureka/ instance: prefer-ip-address: true # 是否使用 IP 地址注册,默认 false # instance-id: order-service # 实例 id,服务的唯一标识,会自动的找到order-service的ip和端口 instance-id: ${spring.cloud.client.ip-address}:${server.port} # 如果想在控制页面看到服务地址与端口,可以将 instance-id 这样配置 feign: hystrix: enabled: true
5、修改订单服务配置文件
由于订单服务中引入的 spring-cloud-starter-openfeign
依赖中已经集成了 feign-hystrix
,有了对 Hystrix 的支持,所以不需要额外引入依赖项。如果需要开启 订单服务端的 hystrix服务,只需要
在订单服务的配置文件配置 feign-hystrix 进行激活Hystrix,修改application.yml如下:
server:
port: 9000
spring:
application:
name: order-service # 为当前订单服务命名为 order-service# 配置eureka客户端信息
eureka:
client:
service-url:
# 配置eureka客户端服务order-service的注册地址,与 eureka-server 中暴露地址要保持一致
defaultZone: http://localhost:8000/eureka/
instance:
prefer-ip-address: true # 是否使用 IP 地址注册,默认 false
# instance-id: order-service # 实例 id,服务的唯一标识,会自动的找到order-service的ip和端口
instance-id: ${spring.cloud.client.ip-address}:${server.port} # 如果想在控制页面看到服务地址与端口,可以将 instance-id 这样配置feign:
hystrix:
enabled: true
6、进行测试
分别访问 http://localhost:9000/order/buy/1 和 http://localhost:9000/order/delete/1 查看浏览器结果如下: