Note 1:
Feign integrates Sentinel:
The first 1 Step : introducing Feign and sentinel dependent:
<!--fegin组件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--Sentinel组件-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
server:
port: 8091
# tomcat:
# max-threads: 10 #tomcat的最大并发值修改为10,默认是200
spring:
application:
name: service-order
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shop?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
cloud:
nacos: #nacos的作用就是一个注册中心
discovery:
server-addr: localhost:8848
sentinel:
# Sentinel是个springboot项目的控制台, 提供服务容错方案,通过流量控制、熔断降级、系统负载保护来维持服务的稳定性
# cmd命令窗口启动Sentinel控制台命令:(cd 到 jar包所在目录)
# java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.2.jar
# -Dserver.port 是sentinel服务地址, -Dcsp.sentinel.dashboard.server是控制台服务地址
# 若启动多个应用,则需要通过 -Dcsp.sentinel.api.port=xxxx 指定客户端监控 API 的端口
transport:
port: 9998 # 指定流控服务的地址, 不同微服务的该端口不可一致
# 跟控制台交流的端口,随意指定一个未被占用的端口即可
dashboard: localhost:8080 # Sentinel控制台地址
filter: # 关闭sentinel的CommonFilter实例化,否则无法实现 链路流控 规则
enabled: false
# 流控规则持久化到nacos
datasource:
dsl:
nacos: #规则持久化目前支持:file,nacos,zk,redis这五种类型
server-addr: localhost:8848
data-id: ${spring.application.name}
group-id: DEFAULT_GROUP
data-type: json
rule-type: flow # 规则类型,取值见:org.springframework.cloud.alibaba.sentinel.datasource.RuleType
#在nacos中心新建配置: data-id无所谓, 创建json格式的配置:
# [
# {
# "resource": "/order/message5", //resource:资源名,即限流规则的作用对象
# "limitApp": "default", //limitApp:流控针对的调用来源,若为default则不区分调用来源
# "grade": 1, //grade:限流阈值类型(1:QPS, 0:线程数)
# "count": 5, //count:限流阈值
# "strategy": 0, //strategy:调用关系限流策略(0:直接, 1:关联, 2:链路)
# "controlBehavior": 0, //controlBehavior:流量控制效果(0:直接拒绝, 1:WarmUp, 2:匀速排队)
# "clusterMode": false //clusterMode: 是否集群
# }
# ]
feign: # 开启feign对sentinel的支持,作用:可通过fallback实现简单容错,通过fallbackFactory实现容错异常处理
sentinel:
enabled: true
service-product: # 调用外部服务的名称
ribbon: #配置 ribbon
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
#设置负载均衡策略 BestAvailableRule 访问最小并发请求的服务; RandomRule 随机访问服务等策略
jpa:
properties:
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
Of 3 steps : creating a fault-tolerant class ( must implement the fault-tolerant interface , and fault tolerance scheme for each method )
@Component
//@Slf4j//作用是可以使用log.xxx();实现控制台打印信息,但这里没用到
//Fallback回滚类/异常处理类的作用:可以处理异常,但不可以获取产生该异常的对象
//容错类要求必须实现被容错的接口,并为每个方法实现容错方案
public class ProductServiceFallBack implements ProductService {
@Override
public Product findByPid(Integer pid) {
Product product = new Product();
product.setPid(-1);
return product;
}
}
The first 4 steps : as being fault-tolerant interface specifies the class of the container
//创建一个interface接口类,用@FeignClient注解指定需要调用的微服务名
//value用于指定调用nacos下哪个微服务 //fallback用于指定容错类
@FeignClient(value = "service-product", fallback = ProductServiceFallBack.class)
public interface ProductService {
//指定调用微服务的具体接口
@GetMapping(value = "/product/{pid}")//http://service- product/product/{pid}
Product findByPid(@PathVariable("pid") Integer pid);
}
The first 5 steps : Modify controller
package com.stephen.shoporder.controller;
import com.alibaba.fastjson.JSON;
import com.stephen.shopcommon.model.Order;
import com.stephen.shopcommon.model.Product;
import com.stephen.shoporder.service.Feign.ProductService;
import com.stephen.shoporder.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
@Slf4j
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderService orderService;
/*
DiscoveryClient是专门负责服务注册和发现的,我们可以通过它获取到注册到注册中心的所有服务
当启动类添加了@EnableDiscoveryClient注解后,就会直接注册DiscoveryClient,因此这里可以直接注入
*/
@Autowired
private DiscoveryClient discoveryClient;
//注入接口类,用于调用该接口指定的外部微服务的接口
@Autowired
private ProductService productService;
//准备买1件商品
@GetMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
// log.info(">>客户下单,这时候要调用商品微服务查询商品信息");
//通过restTemplate调用商品微服务
/*
get请求:
restTemplate.getForEntity():
返回通过http方式的get请求后返回的全部信息
restTemplate.getForObject():
用法和restTemplate.getForEntity()一样,但只返回通过http方式的get请求后返回的请求体body的信息
post请求:
restTemplate.postForObject():
Map<String, Object> object = restTemplate.postForObject("http://www.xxx.com/testSend?name={name}&gender={gender}", null, Map.class,"xiao ming",1);
restTemplate.postForLocation():
restTemplate.postForLocation(URI.create("http://www.xxx.com/test"), requestEntity);
*/
// 缺点: 不能随着服务地址的修改而动态的修改,必须手动修改,因此不方便,使用nacos注册服务中心可以解决
// Product product = restTemplate.getForObject("http://localhost:8081/product/" + pid, Product.class);
//从nacos中获取服务地址:
// discoveryClient.getInstances("").get(0):获取名为 service-product 的微服务的实例集合的第一个服务对象(ServiceInstance对象)
// ServiceInstance serviceInstance = discoveryClient.getInstances("service-product").get(0);
// 如果固定调用第一个服务对象,当访问量过大就很可能发生系统繁忙的崩溃问题,因此需要实现负载均衡:
// List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
// 通过随机挑选端口来实现负载均衡
// new Random().nextInt(instances.size()):随机产生一个区间在 [0,instances长度) 的正整数
// 但是这种方式随机性太大,不能很好的实现负载均衡,因此我们可以通过给RestTemplate添加@LoadBalanced注解来实现自动化负载均衡
// ServiceInstance serviceInstance = instances.get(new Random().nextInt(instances.size()));
//获取服务对象的地址和端口号组成的字符串
// String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
//当RestTemplate添加@LoadBalanced注解后就实现了自动化负载均衡,此时不再需要指定具体地址了,
//因为服务名就是域名,域名映射成地址,而这些不同端口的地址都映射到同一个服务名,因此访问直接传入服务名即可
// String url = "service-product";
// log.info(">>从nacos中获取到的微服务地址为:" + url);
//通过restTemplate调用获取到的服务对象的接口
// Product product = restTemplate.getForObject("http://" + url + "/product/" + pid, Product.class);
//通过Fegin调用商品微服务
// Product product = productService.findByPid(pid);
// log.info(">>商品信息,查询结果:" + JSON.toJSONString(product));
//通过restTemplate调用商品微服务
//模拟一次网络延时
// try {Thread.sleep(3000); } catch (InterruptedException e) {
// e.printStackTrace();
// }
//通过fegin整合sentinel后调用商品微服务
//这种写法是将后面的参数值取代到第一个参数的{}
log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息", pid);
Product product = productService.findByPid(pid);
if ( product.getPid() == -1 ) {
Order order = new Order();
order.setPname("下单失败");
return order;
}
log.info("查询到{}号商品的信息,内容是:{}", pid, JSON.toJSONString(product));
//下单(创建订单)
Order order = new Order();
order.setOid(8L);
order.setUid(1);
order.setUsername("测试用户");
order.setPid(product.getPid());
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
orderService.save(order);
return order;
}
@RequestMapping("/order/message")
public String message() { return "高并发下的问题测试"; }
}
The first 6 steps : Stop all Shop - Product Service , restart the Shop - the Order service , access request , to observe the effect of fault tolerance:
Console log:
Extension : If you want to get specific errors in the fault-tolerant class , you can use FallBackFactory
7. Create ProductServiceFallBackFactory
@Component
//FallbackFactory<>回滚工厂的作用:即可以处理异常,又可以获取产生该异常的对象
public class ProductServiceFallBackFactory implements FallbackFactory<ProductService> {
@Override
public ProductService create(Throwable throwable) {
return new ProductService() {
@Override
public Product findByPid(Integer pid) {
throwable.printStackTrace();//控制台打印错误信息
Product product = new Product();
product.setPid(-1);
return product;
}
};
}
}
8.Modify the ProductService interface, change the fallback attribute to fallbackFactory and specify it as ProductServiceFallBackFactory.class
//创建一个interface接口类,用@FeignClient注解指定需要调用的微服务名
//value用于指定调用nacos下哪个微服务 //fallback用于指定容错类
@FeignClient(value = "service-product",
//fallback = ProductServiceFallBack.class,
fallbackFactory = ProductServiceFallBackFactory.class)
public interface ProductService {
//指定调用微服务的具体接口
@GetMapping(value = "/product/{pid}")//http://service- product/product/{pid}
Product findByPid(@PathVariable("pid") Integer pid);
}
9. Restart and test the interface, you can see the error message: