一、Sentinel快速入门
Sentinel官网地址:https://sentinelguard.io/zh-cn/index.html
Sentinel项目地址:https://github.com/alibaba/Sentinel
Sentinel是阿里巴巴开源的一款微服务流量治理组件,主要以流量为切入点,从流量限流、熔断降级、系统负载保护等多个维度来帮助开发者保障微服务的稳定性。(对标产品:Springcloud Hystrix 豪猪哥)
Sentinel分为两个部分:
- 核心库(Java客户端):该jar包不依赖任何框架,能够运行于 Java 8 及以上版本,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard):相当于是Sentinel框架的管理中心,可以通过图形化界面对Sentinel进行配置、监控(可以对服务的流量、请求、响应等指标进行监控)等操作,从而更好地保障应用的稳定性和可靠性。
两款熔断框架对比:
功能 Sentinel Hystrix(豪猪哥) 线程隔离 信号量隔离 线程池隔离/信号量隔离 熔断策略 基于慢调用比例或异常比例 基于异常比例 限流 基于QPS,支持流量整形 有限的支持 Fallback 支持 支持 控制台 开箱即用,可配置规则、查看秒级监控、机器发现等 不完善 配置方式 基于控制台,重启后失效 基于注解或配置文件,永久生效 1. 启动sentinel-dashboard控制台
通过可视化更方便操作Sentinel。
1、下载sentinel-dashboard的jar包:https://github.com/alibaba/Sentinel/releases/tag/1.8.6
2、使用命令行运行jar包的方式启动控制台:(或者构建镜像丢到容器中启动)
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar
参数介绍:
-
-Dserver.port=8090:指定Sentinel控制台程序的端口为8090。
-
-Dcsp.sentinel.dashboard.server=localhost:8090:Sentinel控制台的访问地址,客户端会自动向该地址发送心跳包。
-
-Dproject.name=sentinel-dashboard:指定Sentinel控制台程序显示的名称。
-
文档地址:https://github.com/alibaba/Sentinel/wiki/%E5%90%AF%E5%8A%A8%E9%85%8D%E7%BD%AE%E9%A1%B9
3、访问sentinel的控制台:http://localhost:8090/,默认账号/密码:都是sentinel。
登录成功后,便可查看控制台内部信息,默认会监控sentinel-dashboard服务本身:
2. 构建微服务环境
数据库准备:
CREATE DATABASE cloud_demo DEFAULT CHARACTER SET utf8mb4; use cloud_demo; DROP TABLE IF EXISTS `tb_user`; CREATE TABLE `tb_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人', `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `username`(`username`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; INSERT INTO `tb_user` VALUES (1, '潘掌柜', '黑龙江省牡丹江市'); INSERT INTO `tb_user` VALUES (2, '文二狗', '陕西省西安市'); INSERT INTO `tb_user` VALUES (3, '华沉鱼', '湖北省十堰市'); INSERT INTO `tb_user` VALUES (4, '张必沉', '天津市'); INSERT INTO `tb_user` VALUES (5, '郑爽爽', '辽宁省沈阳市大东区'); INSERT INTO `tb_user` VALUES (6, 'kunkun', '山东省青岛市'); DROP TABLE IF EXISTS `tb_order`; CREATE TABLE `tb_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单id', `user_id` bigint(20) NOT NULL COMMENT '用户id', `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名称', `price` bigint(20) NOT NULL COMMENT '商品价格', `num` int(10) NULL DEFAULT 0 COMMENT '商品数量', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `username`(`name`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; INSERT INTO `tb_order` VALUES (101, 1, 'Apple 苹果 iPhone 12 ', 699900, 1); INSERT INTO `tb_order` VALUES (102, 2, '雅迪 yadea 新国标电动车', 209900, 1); INSERT INTO `tb_order` VALUES (103, 3, '骆驼(CAMEL)休闲运动鞋女', 43900, 1); INSERT INTO `tb_order` VALUES (104, 4, '小米10 双模5G 骁龙865', 359900, 1); INSERT INTO `tb_order` VALUES (105, 5, 'OPPO Reno3 Pro 双模5G 视频双防抖', 299900, 1); INSERT INTO `tb_order` VALUES (106, 6, '美的(Midea) 新能效 冷静星II ', 544900, 1); INSERT INTO `tb_order` VALUES (107, 2, '西昊/SIHOO 人体工学电脑椅子', 79900, 1); INSERT INTO `tb_order` VALUES (108, 3, '梵班(FAMDBANN)休闲男鞋', 31900, 1);
2.1 创建父工程
springboot版本2.7.12,springcloud版本2021.0.3。
创建maven工程,删除src目录,并向pom文件中引入一些依赖:
4.0.0 cn.z3inc sentinel-demo pom 1.0 org.springframework.boot spring-boot-starter-parent 2.7.12 8 8 UTF-8 1.18.20 2021.0.3 2021.0.4.0 3.4.3 5.8.11 8.0.23 org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import mysql mysql-connector-java ${mysql.version} com.baomidou mybatis-plus-boot-starter ${mybatis-plus.version} cn.hutool hutool-all ${hutool.version} org.projectlombok lombok ${org.projectlombok.version} org.springframework.boot spring-boot-starter-test test org.apache.maven.plugins maven-surefire-plugin true 2.2 创建用户微服务
1、创建用户模块
2、引入依赖:
4.0.0 cn.z3inc sentinel-demo 1.0 user-service 8 8 UTF-8 com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-test org.springframework.boot spring-boot-starter-web mysql mysql-connector-java com.baomidou mybatis-plus-boot-starter org.projectlombok lombok cn.hutool hutool-all org.springframework.boot spring-boot-maven-plugin 3、编写application.yml文件配置:
server: port: 9001 spring: application: name: user-service #服务名称 cloud: nacos: # nacos注册中心配置 discovery: server-addr: 127.0.0.1:8848 #nacos服务器地址 # 数据库配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/cloud_demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai username: root password: 123456 # mp配置 mybatis-plus: mmapper-locations: classpath:mapper/*.xml #mapper配置文件存放路径 type-aliases-package: cn.z3inc.user.pojo # 类型别名(实体类所在包) configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #配置标准sql输出 map-underscore-to-camel-case: true #开启驼峰映射 #日志级别 logging: level: cn.z3inc: debug pattern: dateformat: MM-dd HH:mm:ss:SSS # 格式化输出日期
4、编写启动类:
@SpringBootApplication @MapperScan("cn.z3inc.user.mapper") public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }
5、编写业务:
实体类:
package cn.z3inc.user.pojo; import lombok.Data; @Data public class User { private Long id; private String username; private String address; }
mapper接口:
package cn.z3inc.user.mapper; import cn.z3inc.user.pojo.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper extends BaseMapper
{ } service接口:
package cn.z3inc.user.service; import cn.z3inc.user.pojo.User; import com.baomidou.mybatisplus.extension.service.IService; public interface UserService extends IService
{ } package cn.z3inc.user.service.impl; import cn.z3inc.user.mapper.UserMapper; import cn.z3inc.user.pojo.User; import cn.z3inc.user.service.UserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; @Service public class UserServiceImpl extends ServiceImpl
implements UserService { } controller:
package cn.z3inc.user.controller; import cn.z3inc.user.pojo.User; import cn.z3inc.user.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; /** * 根据ID获取用户信息 * * @param id 用户ID * @return */ @GetMapping("/{id}") public User findById(@PathVariable("id") Long id) { return userService.getById(id); } /** * 修改用户信息 * * @param user 用户信息 */ @PutMapping("/update") public void updateUser(@RequestBody User user) { userService.updateById(user); } /** * 根据ID删除用户 * * @param id * @return */ @DeleteMapping("/{id}") public void deleteUser(@PathVariable("id") Long id) { userService.removeById(id); } /** * 新增用户 * @param user */ @PostMapping("/save") public void saveUser(@RequestBody User user) { userService.save(user); } }
6、启动nacos、用户微服务测试
.\startup.cmd -m standalone
2.3 创建订单微服务
1、创建订单模块
2、引入依赖:
4.0.0 cn.z3inc sentinel-demo 1.0 order-service 8 8 UTF-8 org.springframework.cloud spring-cloud-starter-openfeign org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-test org.springframework.boot spring-boot-starter-web mysql mysql-connector-java com.baomidou mybatis-plus-boot-starter org.projectlombok lombok cn.hutool hutool-all org.springframework.boot spring-boot-maven-plugin 3、编写application.yml配置:
server: port: 9001 spring: application: name: user-service #服务名称 cloud: nacos: # nacos注册中心配置 discovery: server-addr: 127.0.0.1:8848 #nacos服务器地址 # 数据库配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/cloud_demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai username: root password: 123456 # mp配置 mybatis-plus: mmapper-locations: classpath:mapper/*.xml #mapper配置文件存放路径 type-aliases-package: cn.z3inc.order.pojo # 类型别名(实体类所在包) configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #配置标准sql输出 map-underscore-to-camel-case: true #开启驼峰映射 global-config: db-config: id-type: auto # 主键策略 table-prefix: tb_ # 表名前缀配置 #日志级别 logging: level: cn.z3inc: debug pattern: dateformat: MM-dd HH:mm:ss:SSS # 格式化输出日期
4、编写启动类:
package cn.z3inc.order; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @EnableFeignClients(basePackages = "cn.z3inc.order.feign") //开启feign远程调用 @SpringBootApplication @MapperScan("cn.z3inc.order.mapper") public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
5、编写业务:
实体类:
//用户实体 @Data public class User { private Long id; private String username; private String address; } // 订单实体 @Data public class Order { private Long id; private String name; private Long price; private Integer num; private Long userId; @TableField(exist = false) // 排除数据表中不存在的字段 private User user; }
mapper接口:
package cn.z3inc.order.mapper; import cn.z3inc.order.pojo.Order; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @Mapper public interface OrderMapper extends BaseMapper
{ } service接口:
package cn.z3inc.order.service; import cn.z3inc.order.pojo.Order; import com.baomidou.mybatisplus.extension.service.IService; public interface OrderService extends IService
{ //根据id查订单 Order findById(Long id); } feign接口:
package cn.z3inc.order.feign; import cn.z3inc.order.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; /** * 用户服务Feign接口 */ @FeignClient("user-service")//指定要调用的服务名称 public interface UserClient { /* 主要是基于SpringMVC的注解来声明远程调用的信息,比如: - 服务名称:userservice - 请求方式:GET - 请求路径:/user/{id} - 请求参数:Long id - 返回值类型:User */ // 定义远程调用方法 // 通过id查用户 @GetMapping("/user/{id}") //调用对应controller的方法路径 User findById(@PathVariable("id") Long id);// @PathVariable注解一定要指定参数名称,否则会报错 }
service接口实现类:
@Service public class OrderServiceImpl extends ServiceImpl
implements OrderService { @Autowired private UserClient userClient; @Override public Order findById(Long id) { Order order = super.getById(id); User user = userClient.findById(order.getUserId());//微服务远程调用 order.setUser(user); return order; } } controller:
package cn.z3inc.order.controller; import cn.z3inc.order.pojo.Order; import cn.z3inc.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @GetMapping("/{orderId}") public Order findById(@PathVariable("orderId") Long orderId) { return orderService.findById(orderId); } }
6、测试:
2.4 搭建网关微服务
1、创建网关模块
2、引入依赖:
org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-loadbalancer com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery 3、修改application.yml配置:
server: port: 10010 # 网关的服务端口 spring: application: name: gateway # 服务名称 cloud: nacos: server-addr: localhost:8848 # nacos服务地址 # 网关的配置 gateway: # 跨域配置 globalcors: cors-configurations: '[/**]': allowed-origin-patterns: "*" allowed-headers: "*" allow-credentials: true allowed-methods: - GET - POST - DELETE - PUT - OPTION # 网关路由配置 routes: - id: user-service # 路由id uri: lb://user-service # 路由地址 predicates: # 路由断言(匹配规则) - Path=/user/** - id: order-service uri: lb://order-service predicates: - Path=/order/**
4、编写启动类:
package cn.z3inc.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
5、测试:
3. 整合Sentinel
向订单微服中引入sentinel依赖,并连接sentinel-dashboard控制台进行监控。
1、引入sentinel依赖:
com.alibaba.cloud spring-cloud-starter-alibaba-sentinel 2、修改订单服务的application.yml文件,添加sentinel控制台配置:
spring: cloud: sentinel: transport: dashboard: localhost:8090 #控制台地址 http-method-specify: true #开启请求方式前缀
3、重启订单微服务
4、访问订单微服务接口:http://localhost:9002/order/102
5、访问sentinel-dashboard控制台,查看订单服务请求资源:http://localhost:8090
簇点链路:就是项目内的调用链路。链路中被Sentinel监控的每个接口就是一个资源。默认情况下Sentinel会监控SpringMVC的每一个Endpoint(http接口)。限流、熔断等都是针对簇点链路中的资源设置的,而资源名默认就是接口的请求路径。
二、请求限流
请求限流:限制访问接口的请求的并发量,避免服务因流量激增出现故障。
也是在sentinel控制台中配置,点击指定簇点链路后面的流控按钮,便可做限流处理:
使用 JMeter做压测:
(1)为测试计划创建线程组
(2)为线程组添加http取样器:
(3)为http取样器添加监听报告:
(4)为http取样器添加查看结果树:
(5)执行测试
最后控制台查看监控结果:
可以看出这个接口,通过的QPS为6,拒绝的QPS为4,符合我们的预期。
三、线程隔离
限流可以降低服务器压力,尽量减少因并发流量引起的服务故障的概率,但并不能完全避免服务故障。一旦某个服务出现故障,我们必须隔离对这个服务的调用,避免发生雪崩。
线程隔离:也叫做舱壁模式,模拟船舱隔板的防水原理。通过限定每个业务使用的线程数量,将故障业务隔离,避免故障扩散。(好处:即使服务崩了损失一部分线程,也不会影响到整个 tomcat 的资源)
1. OpenFeign整合Sentinel
对订单服务的FeignClient接口做线程隔离。
修改订单服务的application.yml文件,配置开启Feign的sentinel功能:
feign: sentinel: enabled: true # 开启feign对sentinel的支持
重启订单服务,可以看到查询用户的FeignClient自动变成了一个簇点资源:
2. 配置线程隔离
给查询用户feign接口的簇点资源配置并发线程数:(最多使用5个线程)
执行JMeter测试脚步,每秒发送100个请求:
测试结果如下:
四、服务熔断
服务熔断:由 断路器 统计请求的异常比例或慢调用比例,如果超出阈值则会 熔断 该业务,则拦截该接口的请求。熔断期间,所有请求快速失败,全都走fallback降级逻辑,避免影响到当前服务。(Hystrix豪猪哥的做法:如果feign接口调不通,那就走feign接口实现类的逻辑,避免服务雪崩)
1. 编写降级逻辑
跟豪猪哥的用法类似。
触发限流或熔断后的请求不一定要直接报错,也可以返回一些默认数据或者友好提示,用户体验会更好。
通常为FeignClient编写失败后的降级逻辑有两种方式:
- 方式一:FallbackClass,无法对远程调用的异常做处理。
- 方式二:FallbackFactory,可以对远程调用的异常做处理(推荐使用)。
1、为订单服务的UserClient定义降级处理类,实现FallbackFactory接口。
package cn.z3inc.order.fallback; import cn.z3inc.order.feign.UserClient; import cn.z3inc.order.pojo.User; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.openfeign.FallbackFactory; /** * UserClient降级处理类 * */ @Slf4j @Configuration public class UserClientFallback implements FallbackFactory
{ // 定义userclient降级逻辑 @Override public UserClient create(Throwable cause) { return new UserClient(){ @Override public User findById(Long id) { log.error("远程调用UserClient#findById方法出现异常,参数:{}", id, cause); return new User(); } }; } } 2、创建一个配置类,把UserClientFallback注册为一个Bean:
package cn.z3inc.order.config; import cn.z3inc.order.fallback.UserClientFallback; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; @Slf4j public class DefaultFeignConfig { @Bean public UserClientFallback userClientFallback() { return new UserClientFallback(); } }
3、在UserClient中配置刚才创建的降级处理类:
/** * 用户服务Feign接口 */ @FeignClient(value = "user-service", //指定要调用的服务名称 configuration = DefaultFeignConfig.class, // 指定Feign的配置类 fallbackFactory = UserClientFallback.class // 指定回退工厂类 ) public interface UserClient {
4、重启订单服务,将用户服务停掉&测试:
2. 服务熔断
当远程调用的服务挂掉后,直接走降级逻辑,避免影响到当前服务。也就是将feign接口进行熔断。当远程调用服务恢复正常后,再允许调用该接口。这其实就是断路器的工作模式了。
Sentinel中的断路器不仅可以统计某个接口的慢请求比例,还可以统计异常请求比例。当这些比例超出阈值时,就会熔断该接口,即拦截访问该接口的一切请求,降级处理;当该接口恢复正常时,再放行对于该接口的请求。
断路器的工作状态切换有一个状态机来控制:
状态机包括三个状态:
- closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
- open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态持续一段时间后会进入half-open状态
- half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
-
还没有评论,来说两句吧...