zoukankan      html  css  js  c++  java
  • [Alibaba微服务技术入门]_Sentinel服务熔断_第17讲

    Sentinel的 block 机制是专门处理限流规则,降级规则,热点参数规则。但是当系统内部出现异常,比如:NullPointerException,IIlegalArgumentException等,block就不能很好的处理

    于是我们可以采用Sentinel服务熔断 fallback 机制来有效处理系统内部异常,或者说系统业务功能异常

    Sentinel服务熔断实战案例

    第一:案例需求准备

    • 服务方(9001,9002)提供 5条模拟数据
    • 消费方(8001)接口需要通过 ID 参数获取服务提供的数据
    • 消费方(8001)处理两种异常NullPointerException、IIlegalArgumentException,并对异常进行抛出
    • 抛出的异常,需要通过Sentinel进行服务熔断

    第二:在服务方(9001,9002)和消费方(8001),分别创建一个实体类

    package com.liuyangjava.entity;
    
    public class User {
        private String id;
        private String name;
        private Integer age;
        private String sex;
    
        public User() {
        }
    
        public User(String id, String name, Integer age, String sex) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.sex = sex;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    ", sex='" + sex + '\'' +
                    '}';
        }
    }

    第三:在消费方(8001)创建统一返回的ResultData类,此类专门提供数据响应给前端

    扩展知识点:

    项目中我们会将响应封装成 json 返回,一般我们会将所有接口的数据格式统一, 使前端对数据的操作更一致、轻松。

    一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以。但是一般会包含 响应状态、 状态码、返回消息、数据 这几部分内容

    例如,我们的系统要求返回的基本数据格式如下

    列表数据

    {
    	"success": true,
    	"code": 20000,
    	"message":"成功",
    	"data": {
    		"items": [
    			{
    				"id":"1",
    				"name":"李白",
    				"intro":"李白(701年-762年),字太白,号青莲居士,又号“谪仙人”,唐代伟大的浪漫主义诗人"
    			}
    		]
    	}
    }

    单条数据:

    {
    	"success": true,
    	"code": 20000,
    	"message":"成功",
    	"data": {
    		"id":"1",
    		"name":"李白",
    		"intro":"李白(701年-762年),字太白,号青莲居士,又号“谪仙人”,唐代伟大的浪漫主义诗人"
    	}
    }

    没有返回数据:

    {
    	"success": true,
    	"code": 20000,
    	"message":"成功",
    	"data": {}
    }

    回调错误数据:

    {
    	"success": false,
    	"code": 20001,
    	"message":"失败",
    	"data": {}
    }

    因此,我们可以定义一个统一的数据返回结果(每个公司对返回结果定义会有些不一样,大家不要对下面的代码太较真

    {
    “success": 布尔,//响应是否成功
    "code": 数字,//响应码
    “message": 字符串,//返回消息
    "data": HashMap //返回数据,放在键值对中
    }

     创建ResultCodeEnum类,记录系统异常的相关信息

    package com.liuyangjava.common.enums;
    
    public enum ResultCodeEnum {
        SUCCESS(true, 20000, "成功"),
        UNKNOWN_REASON(false,20001,"未知错误"),
        BAD_SQL_GRAMMAR(false, 21001,"sql语法错误"),
        JSON_PARSE_ERROR(false,21002,"json解析异常"),
        PARAM_ERROR(false,21003,"参数不正确");
    
        private Boolean success;
        private Integer code;
        private String message;
    
        ResultCodeEnum(Boolean success, Integer code, String message) {
            this.success = success;
            this.code = code;
            this.message = message;
        }
    
        public Boolean getSuccess() {
            return success;
        }
    
        public void setSuccess(Boolean success) {
            this.success = success;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        @Override
        public String toString() {
            return "ResultCodeEnum{" +
                    "success=" + success +
                    ", code=" + code +
                    ", message='" + message + '\'' +
                    '}';
        }
    }

    创建ResultData类,返回统一定义数据结果

    package com.liuyangjava.common.result;
    
    import com.liuyangjava.common.enums.ResultCodeEnum;
    
    public class ResultData<T> {
        private Boolean success;
        private Integer code;
        private String message;
        private T data;
    
        private ResultData() {}
    
        /**
         * 返回成功
         * @return
         */
        public static ResultData ok() {
            ResultData resultData = new ResultData();
            resultData.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
            resultData.setCode(ResultCodeEnum.SUCCESS.getCode());
            resultData.setMessage(ResultCodeEnum.SUCCESS.getMessage());
            return resultData;
        }
    
        /**
         * 返回失败
         * @return
         */
        public static ResultData error() {
            ResultData resultData = new ResultData();
            resultData.setSuccess(ResultCodeEnum.UNKNOWN_REASON.getSuccess());
            resultData.setCode(ResultCodeEnum.UNKNOWN_REASON.getCode());
            resultData.setMessage(ResultCodeEnum.UNKNOWN_REASON.getMessage());
            return resultData;
        }
    
        /**
         * 重新自定义的返回数据信息
         * @param resultCodeEnum
         * @return
         */
        public static ResultData setResultData(ResultCodeEnum resultCodeEnum) {
            ResultData resultData = new ResultData();
            resultData.setSuccess(resultCodeEnum.getSuccess());
            resultData.setCode(resultCodeEnum.getCode());
            resultData.setMessage(resultCodeEnum.getMessage());
            return resultData;
        }
    
        public Boolean getSuccess() {
            return success;
        }
    
        public void setSuccess(Boolean success) {
            this.success = success;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }

     第四:在服务方(9001,9002)创建NacosUserProviderController接口,且在此接口中提供5条User的模拟数据

    package com.liuyangjava.controller;
    
    import com.liuyangjava.entity.User;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @RestController
    public class NacosUserProviderController {
    
        private Map<String, User> map = new HashMap<>();
    
        {
            map.put("1", new User("1", "李白", 28, "男"));
            map.put("2", new User("2", "杜甫", 25, "男"));
            map.put("3", new User("3", "苏轼", 23, "男"));
            map.put("5", new User("5", "辛弃疾", 22, "男"));
            map.put("6", new User("6", "范仲淹", 21, "男"));
        }
    
        @GetMapping("/nacos/user/provider/{id}")
        public User getUserInfo(@PathVariable String id) {
            return map.get(id);
        }
    
    }

    第五:在消费方(8001)上创建NacosUserConsumerController接口,通过Feign去调用服务方(9001, 9002)的数据

    package com.liuyangjava.controller;
    
    import com.liuyangjava.common.result.ResultData;
    import com.liuyangjava.entity.User;
    import com.liuyangjava.service.NacosConsumerService;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    @RestController
    public class NacosUserConsumerController {
    
        @Value("${service-url.nacos-provider}")
        private String serverUrl;
    
        @Resource
        private NacosConsumerService nacosConsumerService;
    
        @GetMapping("/nacos/user/consumer/{id}")
        public ResultData<User> getUserInfo(@PathVariable String id) {
            User userInfo = nacosConsumerService.getUserInfo(id);
            if(userInfo == null) {
                throw new NullPointerException();
            }
            ResultData resultData = ResultData.ok();
            resultData.setData(userInfo);
            return resultData;
        }
    
    }
    package com.liuyangjava.service;
    
    import com.liuyangjava.entity.User;
    import com.liuyangjava.service.fallback.NacosConsumerServiceFallBack;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient(value = "nacos-provider", fallback = NacosConsumerServiceFallBack.class)
    public interface NacosConsumerService {
    
        @GetMapping("/nacos/provider")
        String getInfoWithFeign();
    
        // 通过 feign 远程调用9001 或 9002 提供的数据
        @GetMapping("/nacos/user/provider/{id}")
        User getUserInfo(@PathVariable String id);
    }

    第六步:我们加入Sentinel的依赖,并对消费方(8001)的配置文件 application.yml 加入 Sentinel 的配置 

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    
    <!--springcloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    server:
      port: 8001
    
    spring:
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8858
        sentinel:
          transport:
            port: 8719
            dashboard: 127.0.0.1:8080
      application:
        name: nacos-consumer
    
    management:
      endpoints:
        web:
          exposure:
            include: '*'
    
    service-url:
      nacos-provider: http://nacos-provider

    第七步:修改消费方(8001)的接口,加入 @SentinelResource 注解,完成系统业务功能出现异常后的处理方法

    package com.liuyangjava.controller;
    
    import com.alibaba.csp.sentinel.annotation.SentinelResource;
    import com.liuyangjava.common.result.ResultData;
    import com.liuyangjava.entity.User;
    import com.liuyangjava.service.NacosConsumerService;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    @RestController
    public class NacosUserConsumerController {
    
        @Value("${service-url.nacos-provider}")
        private String serverUrl;
    
        @Resource
        private NacosConsumerService nacosConsumerService;
    
        @GetMapping("/nacos/user/consumer/{id}")
        @SentinelResource(value = "getUserInfo", fallback = "handlerFallback")
        public ResultData<User> getUserInfo(@PathVariable String id) throws IllegalAccessException {
            if("10".equals(id)) {
                throw new IllegalAccessException("查询数据有误,参数异常...");
            }
            User userInfo = nacosConsumerService.getUserInfo(id);
            if(userInfo == null) {
                throw new NullPointerException("查询数据有误, 空指针异常...");
            }
            ResultData resultData = ResultData.ok();
            resultData.setData(userInfo);
            return resultData;
        }
    
        public ResultData handlerFallback(@PathVariable String id, Throwable e) {
            ResultData resultData = ResultData.error();
            resultData.setMessage(e.getMessage());
            return resultData;
        }
    
    }

    通过测试,当出现空指针异常的时候,handlerFallback 方法就会对异常进行处理

     

     

    "10".equals(id)
  • 相关阅读:
    This counter can increment, decrement or skip ahead by an arbitrary amount
    LUT4/MUXF5/MUXF6 logic : Multiplexer 8:1
    synthesisable VHDL for a fixed ratio frequency divider
    Bucket Brigade FIFO SRL16E ( VHDL )
    srl16e fifo verilog
    DualPort Block RAM with Two Write Ports and Bytewide Write Enable in ReadFirst Mode
    Parametrilayze based on SRL16 shift register FIFO
    stm32 spi sdcard fatfs
    SPI bus master for System09 (2)
    SQLSERVER中的自旋锁
  • 原文地址:https://www.cnblogs.com/liuyangjava/p/15593604.html
Copyright © 2011-2022 走看看