Nepxion Discovery学习笔记6 Sentinel流量防卫兵/服务容错综合方案

笔记1:

Feign整合Sentinel:

1: 引入Feign和sentinel的依赖:

        <!--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>
2: 在配置文件中开启FeignSentinel的支持
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

 

3: 创建容错类 (必须实现被容错的接口,并为每个方法实现容错方案)

@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;
	}
}

 

4: 为被容器的接口指定容错类

//创建一个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);
}

 

5: 修改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 "高并发下的问题测试"; }
}

 

6: 停止所有 shop-product 服务,重启 shop-order 服务,访问请求,观察容错效果:

控制台日志:

 

扩展: 如果想在容错类中拿到具体的错误,可以使用FallBackFactory

7.创建 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.修改 ProductService接口,将 fallback属性改为 fallbackFactory 并指定为 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.重启并测试接口,就能看见错误信息了:

 

猜你喜欢

转载自blog.csdn.net/weixin_42585386/article/details/109245770