惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 意犹未尽

VsCode AI生态开源项目 掌握Redis集群通信,解决数据存取难题 jvm堆外内存-direct buffer spring-boot-actuator-Health原理 服务治理 - 意犹未尽 概念、架构、协议格式到裸协议实现,彻底搞懂 MCP 的本质 maven-antrun-plugin插件 Spring AI-MCP源码整理 java响应式编程基础 elasticseach-分页搜索 sentinel增加ip来源限流后占用服务高内存问题分析 spring-boot-actuator - 意犹未尽 elasticseach-head插件安装及使用 设计模式之美学习-代码命名规范 ES-Client-api-easy es sentinel-ProcessorSlot sentinel-SPI初始化时机 记录一次内存泄漏排查 设计思路之系统做深能力的思维方式
压测实践案例之网关
意犹未尽 · 2025-03-26 · via 博客园 - 意犹未尽

业务服务提供的压测代码

/**
 * @Author liqiang
 * @Description 网关测试
 * @Date 2025/03/26/14:32
 */
@RestController
@RequestMapping("/c/gateway/testGateway")
public class TestGateWayQueryApi {

    @PostMapping("/1.0/testGateway")
    @ApiOperation(value = "测试gateway", notes = "测试gateway")
    public ResponseBase<String> testGateway(Long sleep) {
        if (Objects.nonNull(sleep) && sleep > 0) {
            try {
                Thread.sleep(sleep);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return ResponseBase.success(String.valueOf(System.currentTimeMillis()));
    }


}

压测如何判断是依赖服务瓶颈

背景:优化网关代码,通过压测针对异常指标判断瓶颈

1.当发现响应曲线上升。

2.简单方式使用另外一个接口轮询服务在冒尖的时候判断是否是服务异常

第一组

 第二组

第三组

第四组

以上图可以看出来是业务服务瓶颈。

如果非业务服务瓶颈就需要关注网关CPU JVM 内存等信息,以及网关的IO模型,我的网关使用spring-cloud-gateway

整条链路采用响应式的,如依赖的redis使用ReactorReditTemplate。针对JWTtoken解析采用弹性线程池。避免阻塞netty的 event loop工作线程。

观察服务器指标

1.top pid  然后输入1 查看各个cpu利用情况

2.jvm内存占用和回收情况

jstat -gcutil 10755

关于网关和业务服务压测

网关是采用NIO模型,网关性能瓶颈主要在CPU,如果没有读取body的情况内存占用也及小。我们生产网关jvm设置2G

业务服务可以通过压测接口1万 2万QPS就能达到性能瓶颈点。

但是网关如果没有明显的代码缺陷在NIO IO模型下,理论上支持10万QPS以上,集群部署加Nginx可以支持水平扩容。

针对网关应该换个思维跟业务服务区分开来,网关压到他的瓶颈还是不好压,网关如果没有明显代码缺陷,压不出来。压测网关我觉得换个思维,我觉得能够满足现有业务,同时有几倍的容错空间就行了

网关压测结果

部分服务出现异常网关是否能够正常提供能力

压测准备

1.阻塞线程组压测线程组准备

2. 混合压测非阻塞

压测结果分析

响应时间分布

TPS分布

梯度压测分布

网关优化方案

优化项(减少高CPU占用和IO占用阻塞 work EventLoop工作线程)

1.将网关的大量的redis操作改为响应式的ReactorRedisTemplate

  if (!stringRedisTemplate.opsForSet().isMember(Const.EFFECTIVE_LOGIN_USER_ID, user.getUserId())) {
            return getVoidMono(serverWebExchange, new TokenForbiddenResponse("User Token Forbidden or Expired!"));
        }

修改后

    private Mono<TokenContext> validateUser(TokenContext tokenContext) {
        JWTInfo jwtInfo = tokenContext.getJwtInfo();
        StopWatch stopWatch = null;
        if (log.isDebugEnabled()) {
            stopWatch = new StopWatch();
            stopWatch.start("EFFECTIVE_LOGIN_USER_ID");
        }
        StopWatch finalStopWatch = stopWatch;
        return reactiveRedisTemplate.opsForSet()
            .isMember(Const.EFFECTIVE_LOGIN_USER_ID, jwtInfo.getUserId())
            .flatMap(isMember -> {
                if (log.isDebugEnabled()) {
                    finalStopWatch.stop();
                    log.debug("ReactorAccessFilter EFFECTIVE_LOGIN_USER_ID ={}",
                        finalStopWatch.getLastTaskTimeMillis());
                }
                if (!isMember) {
                    // 返回错误信号,由上层统一处理
                    return Mono.error(
                        new YxtRuntimeException(ResponseCodeType.BIZ_EXCEPTION, "User Token Forbidden or Expired!"));
                }
                return Mono.just(tokenContext);
            });
    }

2.关于JWT解析改为弹性线程池

public class UserAuthUtil {
    public UserAuthUtil(KeyConfiguration keyConfiguration){
        this.keyConfiguration=keyConfiguration;
    }

    private KeyConfiguration keyConfiguration;

    public JWTInfo getInfoFromToken(String token) throws Exception {
        try {
            return JWTHelper.getInfoFromToken(token, keyConfiguration.getUserPubKey());
        }catch (ExpiredJwtException ex){
            throw new UserTokenException("User token expired!");
        }catch (SignatureException ex){
            throw new UserTokenException("User token signature error!");
        }catch (IllegalArgumentException ex){
            throw new UserTokenException("User token is null or empty!");
        }
    }
}

修改后

    // 解析 JWT 并缓存到 Redis
    private Mono<JWTInfo> parseAndCacheJwt(String token) {
        return Mono.fromCallable(() -> userAuthUtil.getInfoFromToken(token))  // 阻塞操作封装
            .subscribeOn(Schedulers.boundedElastic()); // 切换到弹性线程池
    }

优化方案二

充分利用cpu资源,将默认的Work Event Loop改为cpu核数*2

/**
 * @Author liqiang
 * @Description ReactorNetty
 * @Date 2025/03/27/16:48
 */
@Configuration
public class ReactorNettyConfig {

    /**
     * @return
     * @See reactor.netty.tcp.TcpResources#create
     */
    @Bean
    public ReactorResourceFactory reactorClientResourceFactory() {
        /**
         * 定义 Netty 的Selector 线程数,负责监听网络事件(如连接建立、数据到达)
         * 默认值:-1(由 Netty 自动分配,通常为 CPU 核心数)。
         * 若为 CPU 密集型应用,设置为 1 可减少上下文切换;若为高并发 I/O 密集型应用,保留默认值或按需调整。
         */
        System.setProperty("reactor.netty.ioSelectCount", "1");
        /**
         * 定义 Netty 的I/O 工作线程数,负责处理网络读写、编解码等任务。
         * :通常设置为 CPU 核心数 * 2(需与容器 CPU 资源限制对齐)。
         */
        System.setProperty("reactor.netty.ioWorkerCount",
            String.valueOf(Runtime.getRuntime().availableProcessors() * 2));
        return new ReactorResourceFactory();
    }
}

总结经验记录一些小插曲

上线预发后无IO接口也有几百ms延迟

排查方案

1.进转发服务 执行curl看是否有延迟

2.网关ping转发服务是否正常

3.网关pod直调转发服务是否正常

4.在网关pod直接调用网关是否正常

ps:最终发现通过域名转发到下游有延迟(上游涉及 waf),PS:如果压测需要走内网压,走外网会有稳定100+ms延迟

本地压测资源 导致吞吐量上不去

关注本地客户端的带宽、cpu、内存等信息,避免因为本地导致压测不上去,建议公司多台服务器一起混合压(ps也要考虑带宽)。