教程

Alibaba Sentinel 简介

1、概览 Sentinel,哨兵。顾名思义,它是微服务的强大守护者。它提供流量控制、并发限制、熔断和自适应系统保护等功能,以确保微服务的可靠性。它是一个开源组件,由阿里巴巴集团积极维护。此外,它还是 Spring Cloud Circuit Breaker 的官方组成部分。 本文将带你了解 Alibaba Sentinel 的核心功能,包括的流量控制、熔断器和自适应系统保护,以及它的注解支持和监控仪表盘。 2、功能 2.1、流量控制 Sentinel 控制随机传入请求的速度,以避免微服务超载。这可确保服务不会因流量激增而瘫痪。它支持各种流量整形策略。当每秒查询次数(QPS)过高时,这些策略会自动将流量调整为适当的形状。 这些流量整形策略包括: 直接拒绝模式(Direct Rejection Mode)- 当每秒请求数超过设定阈值时,它会自动拒绝后续的请求。 慢启动预热模式(Slow Start Warm-Up Mode) - 如果有突发的流量激增,此模式确保请求计数逐渐增加,直到达到上限为止。 2.2、熔断和降级 当一个服务同步调用另一个服务时,另一个服务有可能因某种原因宕机。在这种情况下,线程会被阻塞,因为它们一直在等待另一个服务的响应。这会导致资源耗尽,调用方服务也将无法处理更多请求。这就是所谓的级联效应,可能会导致整个微服务架构崩溃。 为了防止出现这种情况,引入了熔断器(Circuit Breaker)。它会立即阻止对其他服务的所有后续调用。在超时时间过后,一些请求会通过。如果请求成功,熔断器就会恢复正常流量。否则,超时时间将重新开始计时。 Sentinel 利用最大并发限制原理实现熔断。它通过限制并发线程的数量来减少不稳定资源的影响。 Sentinel 也会将不稳定的资源降级。当资源的响应时间过长时,对资源的所有调用都将在指定的时间窗口内被拒绝。这样可以防止调用变得非常缓慢,从而导致连锁效应。 2.3、自适应系统保护 Sentinel 会在系统负载过高时保护服务器。它使用 load1(系统负载)作为启动流量控制的指标。在以下情况下,请求将被阻止: 当前系统负载(load1) > 临界值(highestSystemLoad)。 当前并发请求(线程数) > 预计容量(最短响应时间 * 最大 QPS) 3、用法 3.1、添加 Maven 依赖 在 pom.xml 中添加 sentinel-core 依赖: <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.8.0</version> </dependency> 3.2、定义资源 使用 Sentinel API 在 try-catch 块中定义资源和相应的业务逻辑: try (Entry entry = SphU.

Spring Bean 的命名

1、概览 当有多个相同类型的实现时,需要对 Spring Bean 进行不同的命名。这是因为如果 Bean 没有唯一的名称,Spring 在注入 Bean 时会出现歧义。 通过控制 Bean 的命名,可以告诉 Spring 我们想将哪个 Bean 注入到目标对象中。 本文将带你了解 Spring Bean 命名策略,以及如何为同一类型的 Bean 赋予多个名称。 2、默认 Bean 命名策略 Spring 为创建 Bean 提供了多种注解,可以在不同的级别使用。例如,可以在 Bean 类上放置一些注解,在创建 Bean 的方法上放置另一些注解。 首先,来看看 Spring 的默认命名策略。当只指定注解而不指定任何值时,Spring 是如何命名 Bean 的? 2.1、类级注解 首先从在类级别使用的注解的默认命名策略开始。Spring 会使用类名为 Bean 命名,并将第一个字母转换为小写。 举个例子: @Service public class LoggingService { } 如上,Spring 为 LoggingService 类创建了一个 Bean,并使用 loggingService 名称进行了注册。 这种默认的命名策略适用于所有用于创建 Spring Bean 的类级别注解,例如 @Component、@Service 和 @Controller。 2.2、方法级注解 Spring 提供了 @Bean 和 @Qualifier 等注解,可用于创建 Bean 的方法。

在 Spring Boot 中优雅地实现读写分离

一、读写分离介绍 当使用Spring Boot开发数据库应用时,读写分离是一种常见的优化策略。读写分离将读操作和写操作分别分配给不同的数据库实例,以提高系统的吞吐量和性能。 读写分离实现主要是通过动态数据源功能实现的,动态数据源是一种通过在运行时动态切换数据库连接的机制。它允许应用程序根据不同的条件或配置选择不同的数据源,以实现更灵活和可扩展的数据库访问。 二、实现读写分离 - 基础 1、配置主数据库和从数据库的连接信息 # 主库配置 spring.datasource.master.jdbc-url=jdbc:mysql://ip:port/master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false spring.datasource.master.username=master spring.datasource.master.password=123456 spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver # 从库配置 spring.datasource.slave.jdbc-url=jdbc:mysql://ip:port/slave?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false spring.datasource.slave.username=slave spring.datasource.slave.password=123456 spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver 2、创建主数据库和从数据库的数据源配置类 通过不同的条件限制和配置文件前缀可以完成不同数据源的创建工作,不止是主从也可以是多个不同的数据库。 主库数据源配置: @Configuration @ConditionalOnProperty("spring.datasource.master.jdbc-url") public class MasterDataSourceConfiguration { @Bean("masterDataSource") @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } } 从库数据源配置: @Configuration @ConditionalOnProperty("spring.datasource.slave.jdbc-url") public class SlaveDataSourceConfiguration { @Bean("slaveDataSource") @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } } 3、创建主从数据源枚举 public enum DataSourceTypeEnum { /** * 主库 */ MASTER, /** * 从库 */ SLAVE, ; } 4、创建动态路由数据源 这儿做了一个开关,可以控制读写分离的开启和关闭工作,可以将操作全部切换到主库进行。然后根据上下文中的数据源类型来返回不同的数据源类型枚举。

在 Spring 中使用 Thymeleaf 显示错误信息

1、概览 本文将带你了解如何在 Spring 应用中使用 Thymeleaf 模板来渲染错误信息。 我们会通过一个简单的 Spring Boot 项目来进行演示,该项目是一个 “用户注册” 应用,需要验证客户端传递的各个字段,还要处理全局错误。 2、Spring Boot 应用示例 创建一个简单的 Spring Boot 用户注册应用,需要一个 Controller、一个 Repository 和一个 Entity。 2.1、Maven 依赖 添加所有需要的 Spring Boot Starter:Web Mvc、Hibernate Validation、 Thymeleaf 和 JPA。 此外,还需要一个 H2 内存数据库依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> <version>1.4.200</version> </dependency> 2.2、实体 User 实体如下: @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.

在集成测试中覆盖 Spring Bean

1、概览 在 Spring 集成测试中,我们可能想要覆盖应用的一些 Bean。通常,可以使用专门为测试定义的 Spring Bean 来实现。然而,在 Spring Context 中提供多个具有相同名称的 Bean,可能会遇到 BeanDefinitionOverrideException 异常。 本文将带你了解如何在 Spring Boot 应用中 Mock 或 Stub 集成测试的 Bean,同时避免 BeanDefinitionOverrideException。 2、在测试中使用 Mock 或 Stub 在深入了解细节之前,应该了解如何在测试中使用 Mock 或 Stub。这是一种强大的技术,可以确保应用不会出现错误。 也可以在 Spring 中采用这种方法。不过,只有在使用 Spring Boot 时才能直接模拟集成测试 Bean。 或者,也可以使用测试配置来 Stub 或 Mock bean。 3、Spring Boot 应用示例 创建一个简单的 Spring Boot 应用,包含了一个 Controller、Service 和一个 Configuration 类。 @RestController public class Endpoint { private final Service service; public Endpoint(Service service) { this.service = service; } @GetMapping("/hello") public String helloWorldEndpoint() { return service.

管理 Kafka 消费者组

1、简介 消费者组允许多个消费者从同一 Topic 消费数据,有助于创建更具可扩展性的 Kafka 应用。 本文将带你了解消费者组以及它们如何在其消费者之间重新平衡分区(Rebalance Partition)。 2、消费者组是什么? 消费者组是一组与一个或多个 Topic 相关联的唯一消费者。每个消费者可以从 0 个、1 个或多个分区中消费数据。此外,每个分区在给定时间内只能分配给一个消费者。分区分配会随着群成员的变化而变化。这就是所谓的组重新平衡(Group Rebalancing)。 消费者分组是 Kafka 应用的重要组成部分。它允许将类似的消费者分组,使他们可以并行地从一个分区 Topic 中消费数据。因此,它提高了 Kafka 应用的性能和可扩展性。 2.1、Group Coordinator 和 Group Leader 当实例化消费者组时,Kafka 也会创建 Group Coordinator(组协调器)。Group Coordinator 会定期接收来自消费者的请求,这些请求被称为 “心跳”。如果某个消费者停止发送心跳,Coordinator 就会认为该消费者要么已经离开了组,要么已经崩溃。这就是分区重新平衡(Partition Rebalance)的一个可能触发因素。 第一个向 Group Coordinator 提出加入 Group 请求的消费者成为 Group Leader。当因任何原因发生重新平衡(Rebalance)时,Leader 会从 Group Coordinator 处收到一份 Group 成员列表。然后,Leader 会使用 partition.assignment.strategy 配置中的自定义策略,在列表中的消费者之间重新分配分区。 2.2、提交的偏移量(Committed Offset) Kafka 使用提交的偏移量(Committed Offset)来跟踪从 Topic 读取的最后位置。提交的偏移量是消费者确认成功处理的 Topic 位置。换句话说,它是自身和其他消费者在后续轮次中读取事件的起始点。 Kafka 将所有分区提交的偏移量(committed offsets)存储在名为 __consumer_offsets 的内部 Topic 中。可以放心地信任它的信息,因为对于副本(Replicated) Broker 来说,Topic 是持久(durable)和容错的(fault-tolerant)。

Spring Boot + Open Telemetry 实现 Kafka 追踪

本文将带你了解如何使用 Spring Boot 和 Open Telemetry 为 Kafka 生产者和消费者配置追踪功能。我们会使用 Micrometer 库发送追踪信息,并使用Jaeger来存储和可视化这些数据。Spring Kafka内置了与 Micrometer 的集成,用于 KafkaTemplate 和监听容器。本文还会介绍何配置 Spring Kafka observability (可观察性),以在追踪中添加自定义标签(Tag)。 源码 你可以在 GitHub 上找到完整的源码。Clone 项目后,进入 kafka 目录,按照说明进行操作即可。 依赖 添加如下依赖,其中 Spring Boot Starter 和 Spring Kafka 用于发送或接收消息,Spring Boot Actuator 和 Micrometer Tracing Open Telemetry 桥接器用于自动生成与每条消息相关的追踪,最后是 opentelemetry-exporter-otlp,用于将追踪导出到应用外。 对于本文中的两个示例 Spring Boot 应用,依赖都是相同的。 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-otel</artifactId> </dependency> <dependency> <groupId>io.

Spring Boot 与 Gzip 压缩

响应压缩是 Web 应用一种常见的优化手段,通过压缩算法减小传输数据的体积,提高传输效率、节约带宽。客户端接收到数据后,使用相同的算法对数据进行解压从而获取到原始数据。 客户端和服务器需要通过 Header 来协商双方支持的压缩算法。 Accept-Encoding:请求头,告诉服务器客户端支持的压缩算法(多个使用逗号分割)。例如:Accept-Encoding: gzip, deflate。 Content-Encoding:响应头,告诉客户端当前 Payload 使用的编码方式(压缩算法)。例如:Content-Encoding: gzip。 常用的压缩算法如下: gzip deflate br JDK 提供了对 GZIP 压缩算法的实现:GZIPOutputStream 和 GZIPInputStream,我们可以用它们来实现 Gzip 压缩和解压缩。 使用 Gzip 压缩响应 在 Spring Boot 应用中创建一个 Controller,使用 GZIPOutputStream 把一张图片文件(20 KB)压缩后响应给客户端。 package cn.springdoc.demo.web.controller; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; import java.util.zip.GZIPOutputStream; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @RestController @RequestMapping("/demo") public class DemoController { @GetMapping public void file (HttpServletRequest request, HttpServletResponse response) throws IOException { // 20.

在 Spring 中使用 DeferredResult 实现长轮询

1、概览 长轮询(Long polling)通常用于在 B/S 架构的应用中保持客户端和服务器的连接,直到信息可用。通常在服务器必须调用下游服务以获取信息并等待结果时使用。 本文将带你了解如何在 Spring MVC 应用中使用 DeferredResult 实现长轮询,以及如何处理错误和超时。 2、使用 DeferredResult 进行长轮询 可以在 Spring MVC 中使用 DeferredResult 来异步处理入站 HTTP 请求。它允许释放 HTTP 工作线程来处理其他入站请求,并将工作转移到另一个工作线程。因此,它可以帮助处理需要较长计算或任意等待时间的请求,提高服务的可用性。 2.1、Publisher 使用 DeferredResult 创建一个 Publisher(发布者)应用。 首先,定义一个 Spring @RestController,它可以使用 DeferredResult,但不会将工作转移到另一个工作线程: @RestController @RequestMapping("/api") public class BakeryController { @GetMapping("/bake/{bakedGood}") public DeferredResult<String> publisher(@PathVariable String bakedGood, @RequestParam Integer bakeTime) { DeferredResult<String> output = new DeferredResult<>(); try { Thread.sleep(bakeTime); output.setResult(format("Bake for %s complete and order dispatched. Enjoy!", bakedGood)); } catch (Exception e) { // .

在 Spring Security 6 中实现动态权限管理

在 Spring Boot 3 之后,Spring Security 现在也升级到 Spring Security 6 了。 Spring Security 6 的用法跟之前比起来还是有很大差异,例如:动态权限定义的方式。 1、权限开发思路 先来说权限开发的思路,当设计好 RBAC 权限之后,具体到代码层面,有两种实现思路: 直接在接口/Service 层方法上添加权限注解,这样做的好处是实现简单,但是有一个问题就是权限硬编码,每一个方法需要什么权限都是代码中配置好的,后期如果想通过管理页面修改是不可能的,要修改某一个方法所需要的权限只能改代码。 将请求和权限的关系通过数据库来描述,每一个请求需要什么权限都在数据库中配置好,当请求到达的时候,动态查询,然后判断权限是否满足,这样做的好处是比较灵活,将来需要修改接口和权限之间的关系时,可以通过管理页面点击几下,问题就解决了,不用修改代码。 有人觉得第二种方案无法做到按钮级别的权限控制,这其实是一个误解。想要做到按钮级别的权限控制,只需要数据库中细化配置即可。 2、具体实践 2.1、旧方案回顾 在 vhr 项目中,通过重写两个类来和实现动态权限的。 第一个类是收集权限元数据的类: @Component public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { //... } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return true; } } 在 getAttributes 方法中,根据当前请求的 URL 地址(从参数 Object 中可提取出来)和根据权限表中的配置,分析出来当前请求需要哪些权限并返回。