• [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)
  • 相关阅读:
    【Day1】1.了解Python
    fastadmin 隐藏操作栏按钮
    fastadmin中上传配置
    第 2 讲高等数学—两个重要的极限定理(万门大学)
    第 1 讲高等数学—元素和极限(万门大学)
    人工智能如何改变我们的未来生活
    fastadmin 中的日期时间,日期时间范围范围插件和key-value插件
    fastadmin 金额 字段类型及html验证
    51nod 1051 最大子矩阵和(DP)
    codforces 1C Ancient Berland Circus(几何)
  • 原文地址:https://www.cnblogs.com/liuyangjava/p/15593604.html
Copyright © 2020-2023  润新知