【Java项目技术亮点】全链路分层限流:从网关到数据库的多层防护体系

秒杀活动开始,瞬间涌入10万QPS,系统直接被打挂------这就是没有限流的后果。本文将揭秘大厂如何通过网关层、应用层、Redis层、数据库层的多层限流架构,让系统在面对突发流量时稳如泰山。

文章目录


一、场景引入:一次秒杀活动的系统崩溃

1.1 真实案例

某电商平台举办秒杀活动,流量瞬间暴涨:

复制代码
时间线:
T+0秒:秒杀开始,用户涌入
T+1秒:QPS从1000飙升到50000
T+2秒:Nginx连接数打满,部分请求502
T+3秒:网关层CPU飙到90%,响应变慢
T+5秒:应用层线程池耗尽,请求堆积
T+10秒:Redis连接数打满,缓存击穿
T+15秒:数据库连接池耗尽,大量超时
T+20秒:系统全面崩溃,服务重启
T+30秒:用户投诉刷屏,活动被迫中止

后果:
- 直接经济损失:秒杀商品被超卖
- 品牌信誉受损
- 技术团队通宵救火
- 后续活动不敢再办

问题根源:没有任何限流措施,突发流量直接穿透到数据库,导致系统雪崩。

1.2 为什么需要分层限流?

复制代码
单层限流的问题:

┌─────────────────────────────────────────────────────────────┐
│                                                              │
│   如果只在网关层限流:                                        │
│   ├── 网关挡住了大部分流量                                    │
│   └── 但恶意用户直接绕过网关访问应用层                        │
│                                                              │
│   如果只在应用层限流:                                        │
│   ├── 应用层挡住了业务流量                                    │
│   └── 但Redis层和数据库层仍然可能被击穿                       │
│                                                              │
│   如果只在数据库层限流:                                      │
│   ├── 数据库保住了                                            │
│   └── 但前面的服务已经过载,用户体验极差                      │
│                                                              │
│   结论:每层都需要限流,形成多层防护!                        │
│                                                              │
└─────────────────────────────────────────────────────────────┘

二、解决方案:全链路分层限流架构

2.1 四层限流架构

复制代码
全链路分层限流:

┌─────────────────────────────────────────────────────────────────────┐
│                        第一层:网关层(Nginx)                       │
│   - IP维度限流(防止单IP刷接口)                                   │
│   - 接口维度限流(防止单接口被刷)                                 │
│   - 静态资源CDN缓存                                               │
│   过滤掉:80%的无效请求                                           │
├─────────────────────────────────────────────────────────────────────┤
│                        第二层:应用层(Spring Cloud Gateway)        │
│   - 用户维度限流(令牌桶算法)                                     │
│   - API维度限流(滑动窗口)                                        │
│   - 黑名单/白名单                                                 │
│   过滤掉:15%的异常请求                                           │
├─────────────────────────────────────────────────────────────────────┤
│                        第三层:Redis层                              │
│   - 分布式令牌桶(Lua脚本保证原子性)                              │
│   - 热点Key限流                                                   │
│   - 库存预扣(防止超卖)                                          │
│   过滤掉:4%的重复请求                                            │
├─────────────────────────────────────────────────────────────────────┤
│                        第四层:数据库层                             │
│   - 连接池最大连接数限制                                          │
│   - 慢查询监控与熔断                                              │
│   - 数据库级限流(MySQL max_connections)                         │
│   最终到达:1%的有效请求                                          │
└─────────────────────────────────────────────────────────────────────┘

2.2 各层限流算法选择

层级 限流算法 维度 工具/实现
网关层 固定窗口/漏桶 IP、接口 Nginx limit_req
应用层 令牌桶 用户、API Guava RateLimiter / Sentinel
Redis层 分布式令牌桶 用户、接口 Redis + Lua
数据库层 连接池限制 连接数 HikariCP max_connections

三、实战代码:四层限流实现

3.1 第一层:Nginx网关限流

nginx 复制代码
# nginx.conf

# 1. IP维度限流:单IP每秒最多10个请求
limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;

# 2. 接口维度限流:秒杀接口每秒最多1000个请求
limit_req_zone $uri zone=api_limit:10m rate=1000r/s;

server {
    listen 80;
    server_name api.example.com;
    
    # 静态资源CDN缓存
    location ~* \.(jpg|png|css|js)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
    
    # 秒杀接口:双重限流
    location /api/seckill {
        # IP限流
        limit_req zone=ip_limit burst=20 nodelay;
        # 接口限流
        limit_req zone=api_limit burst=200 nodelay;
        
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    
    # 普通接口:IP限流
    location /api/ {
        limit_req zone=ip_limit burst=50 nodelay;
        
        proxy_pass http://backend;
    }
}

3.2 第二层:Spring Cloud Gateway限流

java 复制代码
/**
 * Gateway限流配置
 */
@Configuration
public class GatewayRateLimitConfig {
    
    /**
     * 用户维度限流:每秒最多5个请求
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getHeaders().getFirst("X-User-Id")
        );
    }
    
    /**
     * IP维度限流
     */
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
        );
    }
}

/**
 * 限流规则配置
 */
@Configuration
public class RateLimiterConfig {
    
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            // 秒杀接口:用户维度,每秒2个请求
            .route("seckill_route", r -> r.path("/api/seckill/**")
                .filters(f -> f.requestRateLimiter(config -> {
                    config.setRateLimiter(redisRateLimiter());
                    config.setKeyResolver(userKeyResolver());
                    config.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                }))
                .uri("lb://seckill-service"))
            
            // 普通接口:IP维度,每秒10个请求
            .route("api_route", r -> r.path("/api/**")
                .filters(f -> f.requestRateLimiter(config -> {
                    config.setRateLimiter(redisRateLimiter());
                    config.setKeyResolver(ipKeyResolver());
                }))
                .uri("lb://api-service"))
            
            .build();
    }
    
    @Bean
    public RedisRateLimiter redisRateLimiter() {
        return new RedisRateLimiter(2, 4); // replenishRate, burstCapacity
    }
}

3.3 第三层:Redis分布式限流(Lua脚本)

java 复制代码
/**
 * Redis分布式限流器
 * 使用Lua脚本保证原子性
 */
@Component
@Slf4j
public class RedisRateLimiter {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 令牌桶限流Lua脚本
     */
    private static final String TOKEN_BUCKET_SCRIPT = 
        "local key = KEYS[1]\n" +
        "local rate = tonumber(ARGV[1])\n" +
        "local capacity = tonumber(ARGV[2])\n" +
        "local now = tonumber(ARGV[3])\n" +
        "local requested = tonumber(ARGV[4])\n" +
        "\n" +
        "local fill_time = capacity / rate\n" +
        "local ttl = math.floor(fill_time * 2)\n" +
        "\n" +
        "local last_tokens = redis.call('get', key)\n" +
        "if last_tokens == false then\n" +
        "    last_tokens = capacity\n" +
        "end\n" +
        "\n" +
        "local last_updated = redis.call('get', key .. ':last_updated')\n" +
        "if last_updated == false then\n" +
        "    last_updated = 0\n" +
        "end\n" +
        "\n" +
        "local delta = math.max(0, now - tonumber(last_updated))\n" +
        "local filled_tokens = math.min(capacity, tonumber(last_tokens) + (delta * rate))\n" +
        "local allowed = filled_tokens >= requested\n" +
        "local new_tokens = filled_tokens\n" +
        "if allowed then\n" +
        "    new_tokens = filled_tokens - requested\n" +
        "end\n" +
        "\n" +
        "redis.call('setex', key, ttl, new_tokens)\n" +
        "redis.call('setex', key .. ':last_updated', ttl, now)\n" +
        "\n" +
        "return allowed\n";
    
    private RedisScript<Boolean> tokenBucketScript;
    
    @PostConstruct
    public void init() {
        tokenBucketScript = new RedisScript<Boolean>() {
            @Override
            public String getSha1() {
                return redisTemplate.execute(
                    (RedisCallback<String>) connection -> 
                        connection.scriptLoad(TOKEN_BUCKET_SCRIPT.getBytes())
                );
            }
            
            @Override
            public Class<Boolean> getResultType() {
                return Boolean.class;
            }
            
            @Override
            public String getScriptAsString() {
                return TOKEN_BUCKET_SCRIPT;
            }
        };
    }
    
    /**
     * 尝试获取令牌
     * 
     * @param key 限流key
     * @param rate 令牌产生速率(每秒)
     * @param capacity 令牌桶容量
     * @return 是否允许通过
     */
    public boolean tryAcquire(String key, double rate, long capacity) {
        long now = System.currentTimeMillis() / 1000;
        
        try {
            Boolean allowed = redisTemplate.execute(
                tokenBucketScript,
                Collections.singletonList(key),
                String.valueOf(rate),
                String.valueOf(capacity),
                String.valueOf(now),
                "1"
            );
            
            return Boolean.TRUE.equals(allowed);
            
        } catch (Exception e) {
            log.error("❌ Redis限流异常: key={}", key, e);
            // 限流器故障时,默认允许通过(降级策略)
            return true;
        }
    }
    
    /**
     * 滑动窗口限流
     */
    public boolean slidingWindow(String key, long windowSize, long limit) {
        long now = System.currentTimeMillis();
        long windowStart = now - windowSize;
        
        String luaScript = 
            "redis.call('zremrangeByScore', KEYS[1], 0, ARGV[1])\n" +
            "local current = redis.call('zcard', KEYS[1])\n" +
            "if tonumber(current) < tonumber(ARGV[2]) then\n" +
            "    redis.call('zadd', KEYS[1], ARGV[3], ARGV[4])\n" +
            "    redis.call('pexpire', KEYS[1], ARGV[5])\n" +
            "    return 1\n" +
            "else\n" +
            "    return 0\n" +
            "end\n";
        
        RedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        
        Long result = redisTemplate.execute(
            script,
            Collections.singletonList(key),
            String.valueOf(windowStart),
            String.valueOf(limit),
            String.valueOf(now),
            UUID.randomUUID().toString(),
            String.valueOf(windowSize)
        );
        
        return result != null && result == 1;
    }
}

3.4 应用层限流(Sentinel)

java 复制代码
/**
 * Sentinel限流配置
 */
@Configuration
public class SentinelConfig {
    
    @PostConstruct
    public void init() {
        // 秒杀接口:每秒最多1000个请求
        FlowRule seckillRule = new FlowRule();
        seckillRule.setResource("seckill");
        seckillRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        seckillRule.setCount(1000);
        seckillRule.setLimitApp("default");
        seckillRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
        seckillRule.setWarmUpPeriodSec(10);
        
        // 下单接口:每秒最多500个请求
        FlowRule orderRule = new FlowRule();
        orderRule.setResource("createOrder");
        orderRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        orderRule.setCount(500);
        
        // 用户维度:每秒最多10个请求
        ParamFlowRule userRule = new ParamFlowRule();
        userRule.setResource("api");
        userRule.setParamIdx(0); // 第一个参数是userId
        userRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        userRule.setCount(10);
        
        FlowRuleManager.loadRules(Arrays.asList(seckillRule, orderRule));
        ParamFlowRuleManager.loadRules(Collections.singletonList(userRule));
    }
}

/**
 * 业务层使用Sentinel限流
 */
@Service
@Slf4j
public class SeckillService {
    
    @Autowired
    private RedisRateLimiter redisRateLimiter;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 秒杀下单(多层限流)
     */
    public SeckillResult doSeckill(Long userId, Long skuId) {
        // 第一层:用户维度限流(Redis分布式令牌桶)
        String userKey = "seckill:limit:user:" + userId;
        if (!redisRateLimiter.tryAcquire(userKey, 1, 5)) {
            log.warn("⛔ 用户限流: userId={}", userId);
            return SeckillResult.fail("操作太频繁,请稍后再试");
        }
        
        // 第二层:商品维度限流
        String skuKey = "seckill:limit:sku:" + skuId;
        if (!redisRateLimiter.tryAcquire(skuKey, 100, 1000)) {
            log.warn("⛔ 商品限流: skuId={}", skuId);
            return SeckillResult.fail("该商品太火爆了,请稍后再试");
        }
        
        // 第三层:Sentinel限流
        if (!SphU.entry("seckill")) {
            return SeckillResult.fail("系统繁忙,请稍后再试");
        }
        
        try {
            // 库存预扣(Lua脚本保证原子性)
            boolean stockDeducted = deductStock(skuId);
            if (!stockDeducted) {
                return SeckillResult.fail("库存不足");
            }
            
            // 异步下单(MQ削峰)
            asyncCreateOrder(userId, skuId);
            
            return SeckillResult.success("秒杀成功");
            
        } finally {
            SphU.exit();
        }
    }
    
    /**
     * 库存预扣(Redis Lua脚本)
     */
    private boolean deductStock(Long skuId) {
        String stockKey = "seckill:stock:" + skuId;
        
        String luaScript = 
            "if tonumber(redis.call('get', KEYS[1])) > 0 then\n" +
            "    return redis.call('decr', KEYS[1])\n" +
            "else\n" +
            "    return -1\n" +
            "end\n";
        
        RedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        
        Long result = redisTemplate.execute(
            script,
            Collections.singletonList(stockKey)
        );
        
        return result != null && result >= 0;
    }
    
    /**
     * 异步创建订单(MQ削峰)
     */
    private void asyncCreateOrder(Long userId, Long skuId) {
        // 发送MQ消息,异步处理下单逻辑
        // 这样即使MQ堆积,也不会影响秒杀接口的响应速度
    }
}

3.5 第四层:数据库连接池限流

java 复制代码
/**
 * 数据库连接池配置(HikariCP)
 */
@Configuration
public class DataSourceConfig {
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public HikariConfig hikariConfig() {
        HikariConfig config = new HikariConfig();
        
        // 连接池大小 = (core_count * 2) + effective_spindle_count
        // 4核8线程的服务器,连接池大小约 8 * 2 + 1 = 17
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        
        // 连接超时:如果连接池已满,等待多久后抛出异常
        config.setConnectionTimeout(3000); // 3秒
        
        // 空闲连接超时
        config.setIdleTimeout(600000); // 10分钟
        
        // 连接最大生命周期
        config.setMaxLifetime(1800000); // 30分钟
        
        // 连接测试
        config.setConnectionTestQuery("SELECT 1");
        
        return config;
    }
    
    @Bean
    public DataSource dataSource(HikariConfig hikariConfig) {
        return new HikariDataSource(hikariConfig);
    }
}

四、高级进阶:熔断与降级

4.1 Sentinel熔断配置

java 复制代码
/**
 * Sentinel熔断降级配置
 */
@Configuration
public class DegradeConfig {
    
    @PostConstruct
    public void init() {
        // 慢调用比例熔断
        DegradeRule slowCallRule = new DegradeRule();
        slowCallRule.setResource("createOrder");
        slowCallRule.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType());
        slowCallRule.setCount(0.5); // 慢调用比例阈值50%
        slowCallRule.setTimeWindow(10); // 熔断时长10秒
        slowCallRule.setSlowRatioThreshold(0.5); // 慢调用比例
        slowCallRule.setStatIntervalMs(1000); // 统计时长1秒
        
        // 异常比例熔断
        DegradeRule errorRule = new DegradeRule();
        errorRule.setResource("createOrder");
        errorRule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());
        errorRule.setCount(0.5); // 异常比例阈值50%
        errorRule.setTimeWindow(30); // 熔断时长30秒
        
        DegradeRuleManager.loadRules(Arrays.asList(slowCallRule, errorRule));
    }
}

4.2 降级策略

java 复制代码
/**
 * 降级处理
 */
@Service
@Slf4j
public class DegradeService {
    
    /**
     * 秒杀接口降级:返回"已售罄"页面
     */
    @SentinelResource(
        value = "seckill",
        blockHandler = "seckillBlockHandler",
        fallback = "seckillFallback"
    )
    public SeckillResult seckill(Long userId, Long skuId) {
        // 正常业务逻辑
        return doSeckill(userId, skuId);
    }
    
    /**
     * 限流降级
     */
    public SeckillResult seckillBlockHandler(Long userId, Long skuId, BlockException ex) {
        log.warn("⛔ 秒杀被限流: userId={}, skuId={}", userId, skuId);
        return SeckillResult.fail("活动太火爆了,请稍后再试");
    }
    
    /**
     * 异常降级
     */
    public SeckillResult seckillFallback(Long userId, Long skuId, Throwable ex) {
        log.error("❌ 秒杀异常: userId={}, skuId={}", userId, skuId, ex);
        return SeckillResult.fail("系统繁忙,请稍后再试");
    }
}

五、预判问题与解答

Q1:四层限流的阈值怎么设置?

A

复制代码
阈值设置原则:

1. 网关层:
   - IP限流:根据正常用户行为设置(如每秒10次)
   - 接口限流:根据接口容量设置(如秒杀接口每秒1000次)

2. 应用层:
   - 用户限流:根据业务场景(如每秒5次)
   - API限流:根据压测结果(如每秒500次)

3. Redis层:
   - 根据Redis集群QPS容量设置
   - 单节点Redis约5万QPS

4. 数据库层:
   - 根据数据库连接池大小设置
   - 连接池大小 = (CPU核数 * 2) + 1

调优方法:
- 先设置宽松阈值
- 根据监控数据逐步收紧
- 预留20%的缓冲空间

Q2:限流和熔断有什么区别?

A

特性 限流 熔断
目的 防止流量过大 防止故障扩散
触发条件 QPS超过阈值 错误率/慢调用率超过阈值
行为 拒绝部分请求 暂时停止调用
恢复 请求减少后自动恢复 超时后自动恢复
关系 预防性措施 补救性措施
复制代码
最佳实践:限流 + 熔断配合使用
- 限流防止流量过大
- 熔断防止故障扩散
- 降级提供兜底方案

Q3:分布式限流和单机限流怎么选?

A

复制代码
选择原则:

1. 单机限流(Guava RateLimiter):
   - 适用:单体应用、负载均衡已分配流量
   - 优点:性能好,无网络开销
   - 缺点:无法全局控制

2. 分布式限流(Redis + Lua):
   - 适用:微服务、需要全局控制
   - 优点:全局一致,可动态调整
   - 缺点:有网络开销,依赖Redis

3. 组合使用:
   - 网关层:分布式限流(全局控制)
   - 应用层:单机限流(保护单实例)

Q4:限流器故障怎么办?

A

复制代码
故障处理策略:

1. 降级策略:
   - 限流器故障时,默认允许通过
   - 记录日志,发送告警

2. 本地缓存:
   - 限流规则本地缓存
   - Redis不可用时,使用本地规则

3. 熔断机制:
   - 限流器响应超时,触发熔断
   - 熔断期间,使用本地限流

4. 监控告警:
   - 限流器故障率监控
   - 故障时立即通知运维

Q5:怎么防止恶意用户绕过限流?

A

复制代码
防护措施:

1. 多层限流:
   - IP限流(防止单IP刷接口)
   - 用户限流(防止单用户刷接口)
   - 设备限流(防止多账号同一设备)

2. 验证码:
   - 高频请求触发验证码
   - 图形验证码、滑块验证码

3. 行为分析:
   - 分析用户行为模式
   - 异常行为加入黑名单

4. WAF防护:
   - Web应用防火墙
   - 识别和拦截恶意请求

六、面试高频考点

考点1:常见的限流算法有哪些?

参考答案

复制代码
常见限流算法:

1. 计数器(固定窗口):
   - 实现简单
   - 边界问题(窗口切换时可能突发2倍流量)

2. 滑动窗口:
   - 解决固定窗口的边界问题
   - 实现复杂,需要记录每个请求的时间

3. 令牌桶(推荐):
   - 平滑限流,允许突发流量
   - 适合大多数场景

4. 漏桶:
   - 严格限流,不允许突发
   - 适合需要严格控制的场景

考点2:Redis Lua脚本限流的优势?

参考答案

复制代码
优势:

1. 原子性:
   - Lua脚本在Redis中执行是原子的
   - 不会出现竞态条件

2. 性能:
   - 减少网络往返
   - 一次请求完成判断和更新

3. 一致性:
   - 分布式环境下,所有节点看到的状态一致

4. 灵活性:
   - 可以实现复杂的限流逻辑
   - 如令牌桶、滑动窗口等

考点3:Sentinel和Hystrix的区别?

参考答案

特性 Sentinel Hystrix
限流 原生支持 需配合其他工具
熔断 支持 支持
控制台 有(可视化配置)
热点参数限流 支持 不支持
系统自适应限流 支持 不支持
活跃度 活跃(阿里维护) 停止维护

考点4:全链路限流的最佳实践?

参考答案

复制代码
最佳实践:

1. 分层防护:
   - 每层都有限流,形成纵深防御
   - 越靠近用户,限流越粗粒度
   - 越靠近数据库,限流越细粒度

2. 动态调整:
   - 限流阈值通过配置中心动态调整
   - 根据监控数据自动优化

3. 降级策略:
   - 限流时返回友好的提示
   - 熔断时提供兜底方案

4. 监控告警:
   - 实时监控限流触发情况
   - 异常时及时告警

七、总结与最佳实践

7.1 核心要点回顾

复制代码
全链路分层限流核心流程:

┌─────────────────────────────────────────────────────────────┐
│  1. 网关层(Nginx)                                         │
│     ├── IP限流:防止单IP刷接口                              │
│     ├── 接口限流:防止单接口过载                            │
│     └── 静态资源缓存:CDN加速                               │
│                                                              │
│  2. 应用层(Gateway + Sentinel)                            │
│     ├── 用户维度限流:令牌桶算法                            │
│     ├── API维度限流:滑动窗口                               │
│     └── 熔断降级:防止故障扩散                              │
│                                                              │
│  3. Redis层                                                 │
│     ├── 分布式令牌桶:Lua脚本保证原子性                     │
│     ├── 热点Key限流:防止缓存击穿                           │
│     └── 库存预扣:防止超卖                                  │
│                                                              │
│  4. 数据库层                                                │
│     ├── 连接池限流:防止连接耗尽                            │
│     ├── 慢查询监控:及时发现性能问题                        │
│     └── SQL优化:从根本上减少数据库压力                     │
└─────────────────────────────────────────────────────────────┘

7.2 性能提升数据

某电商平台秒杀活动实测数据:

指标 优化前(无限流) 优化后(四层限流) 提升
系统可用性 60%(频繁崩溃) 99.9% 显著提升
峰值QPS 3000(系统崩溃) 50000 16倍↑
平均响应时间 5s+ 50ms 99%↓
超卖率 5% 0% 100%↓
用户体验 极差 良好 显著提升

八、参考与拓展


互动讨论:你们公司是怎么做限流的?有没有遇到过被流量打挂的经历?欢迎在评论区分享!

如果本文对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,持续获取更多Java后端技术干货!

相关推荐
우리帅杰1 小时前
【AI测试】Python AI大模型介绍
开发语言·人工智能·python·ai编程
The Sheep 20231 小时前
C#多线程学习
开发语言·学习·c#
Shadow(⊙o⊙)1 小时前
QT常用控件1.0,enabled() geometry() QIcon的.qrc文件导入
开发语言·c++·qt
geovindu1 小时前
python: Generators Pattern
开发语言·python·设计模式·生成器模式
wuminyu1 小时前
Java锁膨胀机制之偏向锁到轻量级锁源码剖析
java·linux·c语言·jvm·c++
xhtdj1 小时前
技术采用曲线回望二十年
运维·数据库·人工智能·clickhouse·动态规划
没有不重的名么1 小时前
spyder使用教程
开发语言·python
阿正的梦工坊1 小时前
【Rust】06-函数、控制流与模块组织
开发语言·算法·rust
码不停蹄的玄黓1 小时前
SpringBoot 实现拦截器
java·spring boot·后端