Spring Cloud Gateway限流:Redis令牌桶防刷实战

电商大促当天,运营发现订单接口被脚本狂刷,服务器 CPU 飙到 95%,正常用户下单全被拖垮。限流放网关层做,还是放业务代码里做?本文用 Spring Cloud Gateway 4.0.7 + Redis 令牌桶,从原理到可运行代码,讲透网关限流怎么做、怎么防刷、怎么避坑。

摘要:接口被刷、系统被打垮,最该先做的是网关层限流。本文以 Spring Cloud Gateway(Spring Boot 4.0.7 + Spring Cloud 2025.1.2)为实战载体,讲清楚令牌桶、滑动窗口、固定窗口三种限流算法的区别,给出基于 Redis 的 RequestRateLimiter 官方限流完整配置、自定义 KeyResolver、自定义 Lua 滑动窗口限流的完整可运行代码,并总结 8 个真实踩坑点。关键词:Spring Cloud Gateway、网关限流、Redis 令牌桶、防刷、Lua 脚本。

一、这个问题到底是什么

接口被刷的本质,是请求量短时间内超过系统的处理能力,而系统缺少一道"先拦后放"的关卡。 限流就是给系统装一个水龙头:单位时间内只放固定数量的请求进去,多出来的直接拒绝,让后端永远工作在能力范围之内。

很多团队的第一反应是把限流写在业务代码里。订单接口里加个计数器、查一下 Redis 再放行,看起来简单,但有两个致命问题。

第一,业务代码只保护了自己这一个接口。一个订单服务有 20 个接口,你每个都写限流?写一遍不累,但限流逻辑散落在各个业务类里,规则不统一、改起来要动几十个文件,早晚出漏子。

第二,流量是在进业务代码之前就已经打到服务器上的。脚本刷的是网关入口,请求先经过网关、路由转发、到达业务层,这中间的带宽、连接数、线程资源全被消耗了。等业务代码里发现"哎呀超了",服务器资源已经被打掉一大半。

正确的做法是把限流放在网关层。Spring Cloud Gateway 是整个微服务流量的统一入口,所有请求都从这里过。在这里做限流,一份配置管所有下游服务,拦截发生在资源被大量消耗之前,这才是"防刷"而不是"事后补救"。

网关限流要解决三个具体问题:按什么维度限(每个用户?每个 IP?还是整个服务?)、用什么算法限(令牌桶?滑动窗口?)、限流结果怎么让客户端看懂(返回 429 还是业务码?)。 本文的实战会把这三点全部落地。

二、底层原理到底怎么回事

限流算法的本质就一句话:用一个"许可"机制,控制单位时间内的放行数量。 主流算法有三种:固定窗口、滑动窗口、令牌桶。先搞懂它们,再看 Spring Cloud Gateway 是怎么把它们落到 Redis 上的。

固定窗口:最简单,但有临界漏洞

固定窗口的思路是把时间切成固定长度的格子,比如每分钟一个格子,每个格子最多放 100 个请求。实现就两个 Redis 命令:INCR 计数,第一次进来时 EXPIRE 设置过期时间。

它的漏洞在窗口边界。假设每分钟限 100 次,用户在 10:00:59 秒发 100 个请求,又在 10:01:00 发 100 个请求------这两个请求群分别落在两个窗口里,每个都合法,但实际 2 秒内打进来 200 个请求,后端照样被冲垮。这就是著名的"临界突刺"问题。

滑动窗口:精确,但占内存

滑动窗口把时间看成一条连续流动的线,窗口随当前时刻向前滑动。实现上用 Redis 的 ZSET(有序集合):每个请求的时间戳作为 score 存进去,限流时先删掉窗口外的旧记录,再数窗口内还剩多少。

它的优点是精确,没有临界突刺;缺点是每个请求都要往 ZSET 里写一条记录,窗口内请求量越大,内存占用越高,还要定期清理过期成员。

令牌桶:平滑限速,还能容忍突发

令牌桶像一个匀速滴水的桶:一个定时器以固定速率(比如每秒 10 个)往桶里放令牌,桶有容量上限(比如 20 个),满了就溢出不增加。每个请求来的时候必须取走一个令牌,取到就放行,取不到就拒绝。

它的妙处在于"平滑 + 突发兼顾":平时匀速限流,但桶里攒下的令牌允许短时间的突发流量(比如瞬间来 20 个请求,桶里正好有 20 个令牌,全放行),突发过后又回到匀速。这正是网关限流最想要的特性------活动刚开始的流量尖峰能扛一下,但整体速率被锁死。

Spring Cloud Gateway 内置的 RequestRateLimiter 过滤器用的就是令牌桶,而且是基于 Redis Lua 脚本实现的,原子性有保证。

Gateway 限流的完整执行链路

Gateway 的限流请求从客户端发出后,要经过这样一条链:

复制代码
客户端请求
   ↓
Gateway 路由匹配(Predicate 判断走哪条路由)
   ↓
过滤器链(Filter Chain)按 order 依次执行
   ↓
RequestRateLimiter 过滤器(限流核心)
   ↓ 调用
KeyResolver(决定限流 key:按 IP?按用户?)
   ↓ 调用
RedisRateLimiter(执行 Lua 脚本,令牌桶判定)
   ↓
放行 → 转发到下游服务    或    拒绝 → 返回 429

其中三个组件各干一件事:

  • KeyResolver:决定"这一桶令牌算在谁头上"。同一个 key 的请求共享一个令牌桶。按 IP 限流,key 就是 IP;按用户限流,key 就是 userId。这个接口返回一个 Mono,字符串就是 Redis 里的 key。
  • RedisRateLimiter:真正执行限流算法的组件,内部用 Lua 脚本保证"取令牌"是原子操作,不会出现并发下两个请求同时拿到最后一个令牌的问题。
  • RequestRateLimiter:Gateway 的过滤器,把 KeyResolver 和 RedisRateLimiter 串起来,根据判定结果决定放行还是返回 HTTP 429。

为什么用 Lua 脚本? 因为"检查令牌数 + 扣减令牌"是两个操作,如果分开执行,两个并发请求可能同时读到"还剩 1 个令牌",然后都放行,限流失效。Lua 脚本在 Redis 里是单线程执行的,整个脚本一气呵成,不会被打断,这就是原子性。官方 RedisRateLimiter 用的 request_rate_limiter.lua 脚本就是这个思路:读当前令牌数、按速率补充、扣减、写回,全部在一个脚本里完成。

搞懂了这三层,再看配置就非常容易:配置里写的 replenishRate、burstCapacity,对应到令牌桶上就是"每秒放多少令牌"和"桶容量多大"。

三、实战:手把手写代码

这一节用 Spring Boot 4.0.7 + Spring Cloud 2025.1.2 搭一个完整的网关服务,实现两种限流:官方 RequestRateLimiter 令牌桶限流(按 IP),以及自定义 Lua 滑动窗口限流(按接口路径)。 所有文件都是完整的、复制即可运行的。

3.1 项目结构

复制代码
gateway-rate-limit/
├── pom.xml
├── src/main/resources/
│   ├── application.yml
│   └── sliding-window.lua
└── src/main/java/com/example/gateway/
    ├── GatewayApplication.java
    ├── config/RateLimitConfig.java
    └── filter/SlidingWindowRateLimitFilter.java

3.2 完整 pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.7</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>gateway-rate-limit</artifactId>
    <version>1.0.0</version>
    <name>gateway-rate-limit</name>
    <description>Spring Cloud Gateway 限流实战</description>

    <properties>
        <java.version>21</java.version>
        <spring-cloud.version>2025.1.2</spring-cloud.version>
    </properties>

    <dependencies>
        <!-- 网关核心:Gateway 5.0.2,由 Spring Cloud BOM 统一管理版本 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- 响应式 Redis 客户端:RequestRateLimiter 和自定义 Lua 限流都依赖它 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

这里要特别说明两点。第一,spring-cloud-starter-gatewayspring-cloud-dependencies 都没有写版本号,因为版本由 spring-cloud-dependencies BOM 统一管理,2025.1.2 这个版本对应 Gateway 5.0.2。第二,Spring Cloud 2025.1.2 只兼容 Spring Boot 4.0.7,parent 版本写成 4.0.7,不要自己升级到 4.1.x,否则依赖解析会报错。

3.3 完整 application.yml

yaml 复制代码
server:
  port: 8080

spring:
  application:
    name: gateway-service
  data:
    redis:
      host: localhost
      port: 6379
      timeout: 3s
  cloud:
    gateway:
      routes:
        # 订单服务路由:先过 RequestRateLimiter 令牌桶限流,再转发
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - name: RequestRateLimiter
              args:
                # 每秒补充 10 个令牌(平均每秒最多 10 个请求)
                redis-rate-limiter.replenishRate: 10
                # 桶容量 20:允许瞬间最多 20 个请求的突发
                redis-rate-limiter.burstCapacity: 20
                # 每个请求消耗 1 个令牌
                redis-rate-limiter.requestedTokens: 1
                # 用哪个 KeyResolver 决定限流 key,这里用按 IP 的
                key-resolver: "#{@ipKeyResolver}"

logging:
  level:
    org.springframework.cloud.gateway: INFO

这份配置的含义拆开讲:replenishRate: 10 是令牌补充速率,相当于水龙头每秒滴 10 滴水;burstCapacity: 20 是桶的容量,相当于桶最多存 20 滴水。请求过来必须取走 1 滴水,桶里没有就返回 429。注意 burstCapacity 必须大于等于 replenishRate,否则配置不合法。

3.4 完整启动类

java 复制代码
package com.example.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);
    }
}

3.5 完整 KeyResolver 配置类

java 复制代码
package com.example.gateway.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 限流 Key 解析器:决定令牌桶算在谁头上。
 * KeyResolver 接口只有一个方法:给定一次请求,返回这个请求的限流 key。
 */
@Configuration
public class RateLimitConfig {

    /**
     * 按客户端 IP 限流。
     * 同一个 IP 的所有请求共享一个令牌桶,可以防单个 IP 的脚本刷量。
     */
    @Bean
    public KeyResolver ipKeyResolver() {
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                String ip = exchange.getRequest().getRemoteAddress() == null
                        ? "unknown"
                        : exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
                return Mono.just("rate-limit:ip:" + ip);
            }
        };
    }

    /**
     * 按用户 ID 限流。
     * userId 从请求参数里取,适合已登录用户维度的限流。
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> {
            String userId = exchange.getRequest().getQueryParams().getFirst("userId");
            return Mono.just("rate-limit:user:" + (userId == null ? "anonymous" : userId));
        };
    }
}

这段代码在干什么:KeyResolver.resolve 方法接收一次请求,返回一个字符串 key。RedisRateLimiter 用这个 key 去 Redis 里读写令牌桶,所以 key 怎么设计,限流就按什么维度生效。两个 Bean 都定义了,application.yml 里用 #{@ipKeyResolver} 指定走 IP 那个;如果只定义一个 Bean,配置里甚至可以省略 key-resolver 这一行。

3.6 自定义 Lua 滑动窗口限流(完整 GlobalFilter)

官方 RequestRateLimiter 是令牌桶,适合平滑限速。如果你想精确统计"每 60 秒内最多 30 次"这种固定窗口语义,可以用滑动窗口。下面这个完整的全局过滤器,按接口路径限流,核心逻辑写在 Lua 脚本里保证原子性。

先写 Lua 脚本 sliding-window.lua,放在 src/main/resources/ 目录下:

lua 复制代码
-- 滑动窗口限流脚本
-- KEYS[1]  限流 key(这里按接口路径)
-- ARGV[1]  窗口大小(毫秒)
-- ARGV[2]  窗口内最大请求数
-- ARGV[3]  当前时间戳(毫秒)

local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

-- 1. 把窗口外(太旧)的请求记录删掉
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)

-- 2. 数一下窗口内还剩多少条记录
local count = redis.call('ZCARD', key)

-- 3. 没超限:记下本次请求,放行;超限:拒绝
if count < limit then
    -- score 存时间戳,member 加随机后缀防止同一毫秒重复
    redis.call('ZADD', key, now, now .. '-' .. math.random(1000000))
    redis.call('PEXPIRE', key, window)
    return 1
end
return 0

再写过滤器 SlidingWindowRateLimitFilter.java

java 复制代码
package com.example.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 滑动窗口限流全局过滤器:按接口路径,每 60 秒最多 30 次。
 * 注意这是 WebFlux 响应式写法,全程非阻塞。
 */
@Component
public class SlidingWindowRateLimitFilter implements GlobalFilter, Ordered {

    private static final long WINDOW_MS = 60_000L;
    private static final long LIMIT = 30L;

    private final ReactiveStringRedisTemplate redisTemplate;
    private final DefaultRedisScript<Long> slidingWindowScript;

    public SlidingWindowRateLimitFilter(ReactiveStringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.slidingWindowScript = new DefaultRedisScript<>();
        this.slidingWindowScript.setLocation(new ClassPathResource("sliding-window.lua"));
        this.slidingWindowScript.setResultType(Long.class);
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String key = "sliding:" + exchange.getRequest().getURI().getPath();
        return redisTemplate.execute(slidingWindowScript, List.of(key),
                        WINDOW_MS, LIMIT, System.currentTimeMillis())
                .flatMap(result -> {
                    if (Long.valueOf(1L).equals(result)) {
                        // 未超限,继续走过滤器链,转发给下游
                        return chain.filter(exchange);
                    }
                    // 超限:返回 429,并写入一段 JSON 响应体方便客户端识别
                    exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                    exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
                    byte[] body = "{\"code\":429,\"message\":\"请求太频繁,请稍后再试\"}"
                            .getBytes(StandardCharsets.UTF_8);
                    return exchange.getResponse().writeWith(
                            Mono.just(exchange.getResponse().bufferFactory().wrap(body)));
                });
    }

    /**
     * order 越小越先执行。-100 保证它在多数过滤器之前,
     * 也就是请求一进网关就先过限流,避免下游被拖垮。
     */
    @Override
    public int getOrder() {
        return -100;
    }
}

这段代码的几个关键点:redisTemplate.execute 把 Lua 脚本和参数发给 Redis 执行,返回 Mono<Long>,1 表示放行、0 表示拒绝;bufferFactory().wrap() 把 JSON 字符串转成响应体字节;getOrder() 返回 -100 让这个过滤器排在最前面,保证限流拦截发生在转发之前。这套过滤器和官方 RequestRateLimiter 会叠加生效,正好演示多级限流:官方那层按 IP 令牌桶限速,这层按路径滑动窗口限频。

3.7 启动与验证

  1. 本地启动一个 Redis(默认端口 6379)。
  2. mvn spring-boot:run 启动网关。
  3. 用 curl 连续快速请求,观察限流效果:
bash 复制代码
# 连续快速发 40 个请求,统计返回码分布
for i in $(seq 1 40); do
  curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/api/order/1
done

前 20 个请求(桶容量)能拿到 200 或 503(下游服务不存在时的网关错误码),超出后会出现 429。同时可以在 Redis 里查看限流 key:redis-cli keys 'rate-limit:*' 能看到按 IP 的令牌桶 key,keys 'sliding:*' 能看到按路径的滑动窗口 key。

四、踩坑经验和最佳实践

限流上线前,先把下面 8 个坑排掉,能省下半夜被叫起来的次数。

坑 1:忘了定义 KeyResolver,启动直接报错。 RequestRateLimiter 过滤器需要 key-resolver 指定用哪个 KeyResolver Bean,如果 Bean 不存在,启动时抛 NoSuchBeanDefinitionException。解决方案:要么配置里写 key-resolver: "#{@ipKeyResolver}",要么只定义一个 KeyResolver Bean 让 Spring 自动注入。

坑 2:burstCapacity 小于 replenishRate。 桶容量比每秒补充量还小,相当于水龙头比桶还大,配置校验直接失败。记住规则:burstCapacity >= replenishRate。经验值一般是补充速率的 1.5 到 2 倍,给突发流量留余量。

坑 3:在响应式网关里用了阻塞 Redis 客户端。 Gateway 是基于 WebFlux 的非阻塞架构,线程池很小。如果引入同步的 StringRedisTemplate(Lettuce 同步模式)在过滤器里调用,一个阻塞就把整个 event loop 线程卡住,高并发下网关直接雪崩。必须用 ReactiveStringRedisTemplate,就像本文示例里那样。

坑 4:限流 key 设计太粗,所有请求共用一个桶。 如果把 key 写死成 rate-limit:all,一个用户刷量,全站用户一起被限流,这是生产事故级别的配置错误。key 必须按业务维度区分:IP、userId、接口路径,或者组合。

坑 5:阈值拍脑袋定。 限流值定多少,不是感觉出来的。先用压测工具(如 JMeter、wrk)测出下游服务的真实 QPS 上限,再乘 0.7 的保险系数作为网关限流值。比如订单服务压测极限 200 QPS,网关就限 140,留出波动空间。

坑 6:网关集群部署,Redis 却是单点。 多个网关实例共享 Redis 令牌桶才能全局限流(这正是用 Redis 做限流存储的意义),但 Redis 自己挂了,所有请求都会卡在限流环节。生产环境给 Redis 配哨兵或集群,并且设置连接超时(timeout: 3s),Redis 故障时限流快速失败而不是无限等待。

坑 7:默认 429 响应没有业务可读的响应体。 客户端收到 429 只知道"被限了",不知道是哪个维度的限流、多久能恢复。要么在自定义过滤器中写 JSON 响应体(本文示例已演示),要么约定 429 + 响应头 Retry-After 告诉客户端等待秒数。

坑 8:Lua 脚本路径写错,启动时找不到脚本。 DefaultRedisScriptsetLocation(new ClassPathResource("xxx.lua")) 路径必须和 resources 目录下的实际文件名完全一致,大小写敏感。脚本加载失败通常启动时就能看到异常,别拖到线上才暴露。

最佳实践一句话总结:限流阈值压测定、key 按业务维度分、全部用响应式客户端、Redis 要高可用、429 响应体要可读、上线前用脚本模拟刷量验证效果。

五、性能对比和技术选型

三种限流方案里,令牌桶最均衡,滑动窗口最精确,固定窗口最省钱但最不靠谱。 实测结论(本地 2C4G 环境,单机网关,Redis 同机部署):

方案 实现成本 单请求 Redis 命令数 精确度 典型场景
固定窗口(INCR+EXPIRE) 最低 1-2 有临界突刺 内部接口粗略限流
滑动窗口(ZSET) 3-4 精确无突刺 秒杀、活动精确限频
令牌桶(RequestRateLimiter) 低(内置) 1(Lua 原子) 平滑+容忍突发 网关入口默认首选

选型建议分三档:

  • 网关统一限流:直接用 RequestRateLimiter。 官方组件、Lua 原子性、配置即用,覆盖 90% 的场景。本文主方案就是它。
  • 需要精确窗口语义:用自定义 Lua 滑动窗口。 比如"每 60 秒最多 30 次"这类强语义需求,令牌桶表达不了"精确到秒的频次上限",滑动窗口更合适。
  • 单机小应用:别杀鸡用牛刀。 单实例、低并发,用 Guava 或 Bucket4j 的内存限流即可,省一次 Redis 往返。一旦多实例部署,立刻切回 Redis 方案。

网关限流和 Nginx 限流怎么分工? Nginx 的 limit_req 在 L7 最前端,挡最粗暴的流量(比如单 IP 大流量攻击);Spring Cloud Gateway 限流在业务路由层,能拿到业务上下文(userId、路径、请求参数),做精细的业务维度限流。两者叠加不冲突,Nginx 挡粗的,网关挡细的。

六、总结

网关限流是微服务防刷的第一道防线,核心就三件事:选对算法、设计好 key、用好 Redis 的原子性。 展开来说:

第一,算法上,Spring Cloud Gateway 内置的 RequestRateLimiter 用令牌桶,平滑限速又能容忍突发,是网关入口的默认首选;需要精确的固定时间窗语义时,用 Lua 实现滑动窗口。第二,key 的设计决定了限流粒度,按 IP 防脚本、按 userId 防单个用户、按路径防单个接口,实际项目经常组合使用。第三,所有"检查+扣减"逻辑必须放在 Lua 脚本里原子执行,否则并发下限流形同虚设。

从架构视角看,限流放网关层而不是业务代码层,是因为流量在进入业务之前就该被拦住,一份配置管所有下游,这才是防刷的正确姿势。配合 Nginx 粗粒度限流、Redis 高可用部署、压测得出的阈值,这套组合能扛住绝大多数刷量场景。

最后提醒一句:限流是"保命"手段,不是"增长"手段。正常用户的合理流量被误伤,说明阈值或 key 设计有问题,上线后要持续观察限流命中率和误杀率,动态调整。技术方案对了,剩下的就是运维的细心活。

相关推荐
java_logo3 小时前
Docker 部署 FalkorDB:轻松搭建属性图数据库与知识图谱平台
数据库·docker·知识图谱·falkordb·轩辕镜像·falkordb部署教程·falkordb部署文档
小白说大模型3 小时前
Spring AI 框架中集成 MCP 的完整指南:从服务端到客户端的全流程实践
大数据·数据库·人工智能·安全·spring·chatgpt·开源
计算机魔术师4 小时前
第二届世界人形机器人运动会开幕:2056 台机器人齐聚“冰丝带“,666 支队伍竞技 51 赛项
数据库·人工智能·机器人
@insist1234 小时前
系统集成项目管理工程师-配置管理角色与活动
数据库·软考·系统集成项目管理工程师·软考中项·软件水平考试
nvd114 小时前
LiteLLM 与 Redis 响应缓存机制:从精确哈希匹配到模型底层 KV-Cache 边界
redis·缓存·哈希算法
巨大八爪鱼4 小时前
XP系统运行IoTDB 2.0.10数据库服务器
数据库·iotdb·xp
卓怡学长5 小时前
w181springboot机场乘客服务系统
java·数据库·spring boot·spring·intellij-idea
阮胜昌5 小时前
MySQL 9.7.0 LTS 现已发布:扩展了社区功能,并为企业级应用增加了动态数据脱敏功能
数据库·mysql
晓子文集5 小时前
Tushare接口文档:每日涨跌停价格(stk_limit)
大数据·数据库·金融数据·量化投资