ShenYu实战、问题记录

概述

一款高性能的国产的Apache开源API网关,官方文档

在ShenYu v2.6.1, ShenYu注册中心只支持http类型,中间件注册类型已经被移除。 所以,请使用http注册类型来注册你的服务。不是微服务注册中心,它只是将元数据、选择器数据、规则数据注册到shenyu-admin,shenyu-admin将数据同步到shenyu-gateway。

插件

选择器和规则:一个插件有多个选择器,一个选择器对应多种规则。选择器相当于是对流量的一级筛选,规则就是最终的筛选。

包括:

  • HTTP Process:
    • ContextPath:上下文路径;
    • ModifyResponse:响应修改;
    • ParamMapping:
    • Redirect:
    • Request:
    • Rewrite:
  • Proxy:
    • Divide:
    • Dubbo:
    • gRPC:
    • Motan:
    • Mqtt:
    • Sofa:
    • Spring Cloud:
    • Tars:腾讯开源RPC框架;
    • Tcp:
    • WebSocket:
  • Fault-Tolerance:容错插件
    • Hystrix:
    • RateLimiter:
    • Resilience4j:
    • Sentinel:
  • Security:
    • Casdoor:
    • CryptorRequest:
    • CryptorResponse:
    • JWT:
    • OAuth2:
    • Sign:
    • Waf:
  • Observability:
    • Aliyun SLS:
    • Logging-ElasticSearch:
    • Logging-Huawei LTS:
    • Logging-Kafka:
    • Logging:
    • Logging-Pulsar:
    • Logging-RabbitMQ:
    • Logging-RocketMQ:
    • Logging-Tencent-Cls:
    • Metrics:
  • Common:目前仅有一个General-Context:
  • Cache:目前仅有一个Cache:
  • Mock:目前仅有一个Mock:
  • AI:
    • AI Prompt:
    • AI Proxy:
    • AI TokenLimiter:

参考插件中心

选择器和规则

PluginList->Proxy->Divide页面,新增Selector时,可快速配置:

同样地,对于Rules,也可快速配置:

自动配置

在Nacos做如下配置后:

yaml 复制代码
shenyu:
  register:
    registerType: http
    serverLists: http://${GATEWAY_ADMIN_ADDR:88.100.29.241:9095}
    props:
      username: ${GATEWAY_ADMIN_USERNAME:admin}
      password: ${GATEWAY_ADMIN_PASSWORD:123456}
  client:
    http:
      props:
        contextPath: ${SHENYU_CONTEXT_PATH:ems_admin_dev}
        host: ${POD_IP:8.130.15.145}
  discovery:
    enable: true
    protocol: http://
    type: nacos
    serverList: ${NACOS_ADDR:88.100.29.241:8848}
    registerPath: ${SHENYU_REGISTER_PATH:ems_admin_dev}
    props:
      nacosNameSpace: gateway
      groupName: DEFAULT_GROUP
      username: ${NACOS_USERNAME:nacos}
      password: ${NACOS_PASSWORD:nacos}

可自动创建Divide插件:

前文出现的,默认3次,默认3秒,估计在ShenYu源码里配置化的。

源码如下:

java 复制代码

自动配置原理:有待进一步调研。

Sa-Token

ShenYu可集成Sa-Token。

点击右侧的Edit:

配置Sa-Token使用的Redis地址。

注册

有些服务使用IP注册。

无法自动注册的服务,如Python服务,使用应用名:

如何实现??

问题

错误码

404

请求服务A接口正常:

请求服务B报错404:

解决方法:重启服务B。

408

某个接口请求报错,信息如下:

json 复制代码
{
    "code": 408,
    "message": "Request timeout, the maximum number of retry times has been exceeded"
}

排查此请求全链路涉及到的所有服务,检查retryCounttimeout配置

配置超时次数和时间:

记住上图的headerMaxSize和requestMaxSize,后面还会提到。

邮件发送4次

还是上面这个retryCount配置导致的问题,不过报错码并不是408,也没有直接且明确地告诉你:Request timeout, the maximum number of retry times has been exceeded

因此排查起来略微花费一点时间。

背景:postman请求接口,模拟定时任务执行,定时任务是判断收件人,抄送人,开启状态,查询MySQL数据库,获取Groovy脚本,解析Groovy脚本并执行,然后获取结果。脚本里写MySQL查询语句,获取查询结果后,作为附件发送出去。理论上只应该收到一封邮件,结果却几乎同一时间收到4次一模一样的邮件。

经过排查后,定位到ShenYu配置的超时次数是3次,超时时间为3秒;超时3次后,不再继续重试,即前前后后发起4次请求。

附录:

groovy 复制代码
package com.tesla.admin.service
import cn.hutool.core.date.DateUtil
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.JdbcTemplate

class SendMail {
	@Autowired
	JdbcTemplate jdbcTemplate
	//   {
	//        mainTitle: \'\', //主标题
	//        mainContent: \'\', //主内容
	//        subTitle: \'\',  //副标题
	//        listing: {     //如果邮件是个列表}
	//        table: {       //如果邮件是个表格
	//            name: \'\',
	//            columns: [
	//                {
	//                    title: \'\', //标题
	//                    width: \'\', //宽度
	//                    field: \'id\', //数据字段
	//                }
	//            ],
	//            data: [
	//                {
	//                    id: \'1\'
	//                }
	//            ]
	//        }
	//        detail: {
	//            header: \'\',
	//            body: \'\',
	//            footer: \'\'
	//        }
	//   }
	def run() {
	    DynamicDataSourceContextHolder.push("mysql1")
	    try {
	        def results = jdbcTemplate.queryForList("""
	                SELECT device_position
	                FROM ems.energy_consumption_analysis
	                WHERE acquisition_month = month(now()) - 1
	                GROUP BY device_position
	                ORDER BY abs(mom_rate) DESC;
	        """)
	        def tableDef = [
	            name   : \'异常详情\',
	            columns: [
	                [title: \'设备位置\', width: \'250\', field: \'device_position\']
	            ],
	            data   : results
	        ]
	        return [
	            mainTitle       : \'尊敬的用户,您好!\',
	            mainContent     : "附件是上月(${DateUtil.month(new Date())}月份)内所有用能异常情况汇总,请您下载查阅。",
	            table           : tableDef, // 附件使用
	            hideTableInBody : true, // 模板用以跳过渲染
	            detail          : [
	                footer: \'系统自动发送,请勿回复。\'
	            ],
	        ]
	    } catch (Exception e) {
	        log.error("groovy run error: ${e.message}")
	    } finally {
	        DynamicDataSourceContextHolder.poll()
	    }
	}
}

413

报错信息:Request entity too large 请求实体太大

出现此报错原因在于,ShenYu里配置过headerMaxSize和requestMaxSize,且该值过小。一般情况下,这两个值都是0,即没有限制。

解决方法:设置为0,或放大配置值。

-106

-107

通过网关请求某个服务时报错:

json 复制代码
{
    "code": -107,
    "message": "divide:Can not find selector, please check your configuration!"
}

可能的原因:

  • 该环境(dev、test或prod)没有发布过这个服务;
  • 写错服务名字:

-119

请确认接口经过网关,且satoken插件配置正常

解决方法:

无法修改注册IP

在修改注册IP时遇到告警,无法修改:

解决方法:删除整个服务的配置信息,然后重新添加配置+(自动或手动)注册IP。

无法删除注册IP

正常情况下的服务注册

遇到的问题如下,找不到删除按钮:

Pod异常,ShenYu没有自动剔除该异常Pod的注册信息;因此,页面上看到的IP肯定有问题;流量请求到不存在的IP。但是无法删除。

解决方法:只能手动删除整个配置,然后重新配置。

至于为什么ShenYu没有自动剔除异常Pod注册的IP,这才是根本问题。

OOM

几乎(所有需要经过ShenYu网关)全部接口超时异常:


如上图所示,一般是20秒左右定义为【超时】。

排除掉阿里云ALB、网络、k8s集群等可能原因之后,将问题定位在ShenYu网关上。

生产环境,gateway pod设置为3副本:

逐个查看pod日志,发现其中只有一个pod有异常,且时间停留在17点01分:

具体的报错日志:

复制代码
2025-07-02 10:08:13 [shenyu-netty-epoll-5] [traceId= spanId= sampled=] INFO  com.tesla.gateway.plugin.SatokenPlugin - CheckAndRemoveKeySet: conversation_id:44b81c59-3a26-480c-bb66-ba44a6ee2692, step: false
Exception in thread "AsyncAppender-Worker-ASYNC_FILE" Exception in thread "AsyncAppender-Worker-ASYNC_STDOUT" java.lang.OutOfMemoryError: Java heap space
    at java.base/java.util.Arrays.copyOf(Arrays.java:3745)
    at java.base/java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:172)
    at java.base/java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:538)
    at java.base/java.lang.StringBuilder.append(StringBuilder.java:178)
    at ch.qos.logback.core.pattern.FormattingConverter.write(FormattingConverter.java:39)
    at ch.qos.logback.core.pattern.CompositeConverter.convert(CompositeConverter.java:24)
    at ch.qos.logback.core.pattern.FormattingConverter.write(FormattingConverter.java:36)
    at ch.qos.logback.core.pattern.PatternLayoutBase.writeLoopOnConverters(PatternLayoutBase.java:115)
    at ch.qos.logback.classic.PatternLayout.doLayout(PatternLayout.java:165)
    at ch.qos.logback.classic.PatternLayout.doLayout(PatternLayout.java:39)
    at ch.qos.logback.core.encoder.LayoutWrappingEncoder.encode(LayoutWrappingEncoder.java:116)
    at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:230)
    at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:102)
    at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84)
    at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)
    at ch.qos.logback.core.AsyncAppenderBase$Worker.run(AsyncAppenderBase.java:290)
java.lang.OutOfMemoryError: Java heap space
    at java.base/java.util.Arrays.copyOf(Arrays.java:3745)
    at java.base/java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:172)
    at java.base/java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:538)
    at java.base/java.lang.StringBuilder.append(StringBuilder.java:178)
    at ch.qos.logback.core.pattern.FormattingConverter.write(FormattingConverter.java:39)
    at ch.qos.logback.core.pattern.CompositeConverter.convert(CompositeConverter.java:24)
    at ch.qos.logback.core.pattern.FormattingConverter.write(FormattingConverter.java:36)
    at ch.qos.logback.core.pattern.PatternLayoutBase.writeLoopOnConverters(PatternLayoutBase.java:115)
    at ch.qos.logback.classic.PatternLayout.doLayout(PatternLayout.java:165)
    at ch.qos.logback.classic.PatternLayout.doLayout(PatternLayout.java:39)
    at ch.qos.logback.core.encoder.LayoutWrappingEncoder.encode(LayoutWrappingEncoder.java:116)
    at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:230)
    at ch.qos.logback.core.rolling.RollingFileAppender.subAppend(RollingFileAppender.java:235)
    at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:102)
    at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84)
    at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)
    at ch.qos.logback.core.AsyncAppenderBase$Worker.run(AsyncAppenderBase.java:290)

出问题的pod只有一个,但现象是gateway作为一个服务,可以接收新的请求,但是全部接口都超时。

观察Grafana面板,10点多有一个内存使用率的【非常小】的跃升,哪怕是跃升之后,内存使用也不过900多MiB:

k8s yaml文件定义如下:

限额是3G,远远没有达到k8s内存异常触发OOM自动重启的条件。

重启有问题的一个pod,接口请求报错:
{"code":-106,"message":"Can not find url, please check your configuration!"}

意思是gateway pod状态虽然是Running,但是得等pod加载配置成功。

出现-106,无需在页面(对应于gateway-admin服务)上做任何配置化操作。。。

groovy 复制代码
bootRun {
    ignoreExitValue true
    jvmArgs('-Dspring.output.ansi.enabled=always',
            '-noverify',
            '-XX:TieredStopAtLevel=1',
            '-Xmx1024m')
    sourceResources sourceSets.main
}
yaml 复制代码
logging:
  level:
    root: info
    org.apache.shenyu.bonuspoint: info
    org.apache.shenyu.lottery: info
    org.apache.shenyu: debug
    org.apache.shenyu.plugin.sync.data.websocket.client.ShenyuWebsocketClient: INFO
    org.apache.shenyu.plugin.base.AbstractShenyuPlugin: WARN
    org.apache.shenyu.plugin.api.ShenyuPlugin: debug
yaml 复制代码
org.apache.shenyu.plugin.sync.data.websocket.client.ShenyuWebsocketClient: INFO
org.apache.shenyu.plugin.base.AbstractShenyuPlugin: WARN

参考/推荐阅读

相关推荐
Java后端的Ai之路1 天前
【Spring全家桶】-一文弄懂Spring Cloud Gateway
java·后端·spring cloud·gateway
研究司马懿5 天前
【云原生】Gateway API介绍
云原生·gateway
研究司马懿5 天前
【云原生】Gateway API路由、重定向、修饰符等关键操作
云原生·gateway
研究司马懿5 天前
【云原生】初识Gateway API
云原生·gateway
七夜zippoe6 天前
API网关设计模式实战 Spring Cloud Gateway路由过滤限流深度解析
java·设计模式·gateway·路由·api网关
汪碧康6 天前
一文讲解kubernetes的gateway Api的功能、架构、部署、管理及使用
云原生·容器·架构·kubernetes·gateway·kubelet·xkube
大佐不会说日语~6 天前
Docker Compose 部署 Spring Boot 应用 502 Bad Gateway 问题排查与解决
spring boot·docker·gateway·maven·故障排查
Dontla8 天前
Kubernetes流量管理双雄:Ingress与Gateway API解析(Nginx与Ingress与Gateway API的关系)
nginx·kubernetes·gateway
JavaLearnerZGQ8 天前
Gateway网关将登录用户信息传递给下游微服务(完整实现方案)
微服务·架构·gateway
Ares-Wang9 天前
网络》》BGP Border Gateway Protocol,边界网关协议
网络·gateway