【手撕系列】Java限流算法详解上篇:从原理到实现,一文搞定!(建议收藏)

🔥 本文是限流算法系列的上篇,将带你深入理解固定窗口和滑动窗口限流算法。从原理到代码实现,全方位掌握这两种限流技术。

📚博主匠心之作,强推专栏

在这里插入图片描述

一、为什么需要限流?

在开发高并发系统时,限流是一个绕不开的话题。还记得我在一个电商项目中遇到的真实案例:某次大促活动,系统瞬间涌入大量用户,短短几分钟内数据库连接池就被打满,眼看着系统就要崩溃…

如果当时系统有合适的限流机制,就不会出现这样的问题。那么,究竟为什么需要限流呢?

1.1 限流的意义

  1. 保护系统稳定性

    就像我们上面说的电商大促场景,如果不对流量进行限制,瞬时的高并发可能会导致:

    • 服务器CPU飙升
    • 内存溢出
    • 数据库连接耗尽
    • 线程池打满

    合理的限流可以让系统在承受范围内稳定运行。

  2. 控制资源利用

    在实际项目中,我们的系统资源都是有限的:

    • 数据库最大连接数
    • 线程池大小
    • 网络带宽
    • 第三方服务调用配额

    通过限流,我们可以让资源使用保持在合理范围内。

  3. 保护核心业务

    拿常见的电商系统来说,我们通常会对不同业务设置不同的限流策略:

    • 下单支付等核心接口设置较高的限制
    • 商品评论等非核心接口设置较低的限制

    这样在流量高峰期,核心业务依然能正常运行。

  4. 防止恶意攻击

    限流还能有效防范:

    • 爬虫大量抓取
    • 恶意刷单
    • 暴力破解密码
    • DDOS攻击

1.2 常见的限流场景

  1. 接口访问控制

    比如用户登录接口,限制单个IP每分钟最多调用100次,避免暴力破解。

  2. 秒杀场景

    在秒杀系统中,我们通常会:

    • 限制单个用户的抢购频率
    • 控制整体流量的进入速率
    • 对不同商品设置不同的限流规则
  3. 用户操作频率

    常见的例子:

    • 发帖、评论限制每分钟不超过5次
    • 短信验证码每天最多发送10条
    • 支付密码错误次数限制
  4. 第三方服务调用

    很多第三方服务都有调用限制:

    • 短信服务:每秒最多100条
    • 支付接口:每秒最多1000次
    • 地图服务:每天最多100万次

    这时就需要在代码层面做好限流,避免超限导致服务不可用。

二、计数器限流算法详解

2.1 固定窗口计数器

算法原理

固定窗口计数器的思想其实很简单,以我之前做过的用户注册限流为例:我们限制同一个手机号一分钟内只能发送一次验证码。具体是这样实现的:

  1. 时间窗口

    我们把时间分成一个个的窗口,比如每分钟一个窗口。假设用户在12:00:00发送了一次验证码,那这次请求会落在12:00这个窗口内。

  2. 计数规则

    每个窗口都有一个计数器。当请求到来时:

    • 先判断请求所属的时间窗口
    • 将该窗口的计数器+1
    • 判断是否超过限制
  3. 窗口重置

    当时间进入下一个窗口时,计数器会自动清零。比如12:00的窗口结束后,进入12:01时,计数器就会重置。

  4. 代码实现

先来看看最基础的固定窗口限流器实现:

public class FixedWindowRateLimiter {
   
   
    // 窗口大小(毫秒)
    private final long windowSize;
    // 窗口内允许的最大请求数
    private final int maxRequests;
    
    // 使用Map来存储每个窗口的计数,key是窗口的起始时间戳
    private final Map<Long, AtomicInteger> windowCountMap = new ConcurrentHashMap<>();
    
    public FixedWindowRateLimiter(int maxRequests, long windowSize) {
   
   
        this.maxRequests = maxRequests;
        this.windowSize = windowSize;
    }
    
    public boolean tryAcquire() {
   
   
        long now = System.currentTimeMillis();
        // 计算当前时间所属的窗口起始时间
        long windowStart = now - (now % windowSize);
        
        // 获取当前窗口的计数器,如果不存在则创建
        AtomicInteger counter = windowCountMap.computeIfAbsent(windowStart, 
            k -> new AtomicInteger(0));
        
        // 如果计数未达到阈值,计数加1
        if (counter.get() < maxRequests) {
   
   
            if (counter.incrementAndGet() <= maxRequests) {
   
   
                // 清理过期的窗口
                removeExpiredWindows(windowStart);
                return true;
            }
            // 超过阈值,计数减1
            counter.decrementAndGet();
        }
        
        return false;
    }
    
    private void removeExpiredWindows(long currentWindowStart) {
   
   
        // 删除一个窗口之前的所有计数
        windowCountMap.entrySet().removeIf(entry -> 
            entry.getKey() < currentWindowStart - windowSize);
    }
}

使用示例:

// 创建限流器:每分钟最多100个请求
FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(100, 60 * 1000);

// 在业务代码中使用
public void handleRequest() {
   
   
    if (!limiter.tryAcquire()) {
   
   
        throw new RuntimeException("请求太频繁,请稍后再试");
    }
    // 处理业务逻辑
}
代码分析与优缺点

看完具体实现,我们来分析下这个固定窗口限流器的优缺点。

优点:

在《Transformer!!从每一模块原理讲解到代码实现【超详细!】》中,对Transformer各模块原理及代码实现有详细阐述。 ### 位置编码 位置编码用于为输入序列中的每个位置提供位置信息,因为Transformer本身不具备捕捉序列位置信息的能力。其原理是通过特定的公式生成不同频率的正弦和余弦波来表示位置。以下是可能的代码解释示例: ```python import torch import torch.nn as nn import math class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=5000): super(PositionalEncoding, self).__init__() pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) self.register_buffer('pe', pe.unsqueeze(0)) def forward(self, x): x = x + self.pe[:, :x.size(1)] return x ``` ### 多头注意力 多头注意力机制允许模型在不同的表示子空间中并行地关注输入序列的不同部分。它通过多个注意力头对输入进行处理,然后将结果拼接并线性变换。代码示例如下: ```python import torch import torch.nn as nn class MultiHeadAttention(nn.Module): def __init__(self, num_heads, d_model): super(MultiHeadAttention, self).__init__() assert d_model % num_heads == 0 self.d_k = d_model // num_heads self.num_heads = num_heads self.W_q = nn.Linear(d_model, d_model) self.W_k = nn.Linear(d_model, d_model) self.W_v = nn.Linear(d_model, d_model) self.W_o = nn.Linear(d_model, d_model) def forward(self, Q, K, V, mask=None): batch_size = Q.size(0) Q = self.W_q(Q).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) K = self.W_k(K).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) V = self.W_v(V).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) if mask is not None: scores = scores.masked_fill(mask == 0, -1e9) attention = torch.softmax(scores, dim=-1) out = torch.matmul(attention, V) out = out.transpose(1, 2).contiguous().view(batch_size, -1, self.num_heads * self.d_k) return self.W_o(out) ``` ### 前馈神经网络(FeedForward)和层归一化(NormLayer) - **FeedForward 模块**:前馈神经网络通常由两个线性层和一个激活函数组成,用于对多头注意力的输出进行进一步的非线性变换。代码解析如下: ```python import torch import torch.nn as nn class FeedForward(nn.Module): def __init__(self, d_model, d_ff): super(FeedForward, self).__init__() self.fc1 = nn.Linear(d_model, d_ff) self.fc2 = nn.Linear(d_ff, d_model) self.relu = nn.ReLU() def forward(self, x): return self.fc2(self.relu(self.fc1(x))) ``` - **NormLayer 模块**:层归一化用于对输入的每个样本进行归一化处理,有助于稳定模型的训练。代码解析如下: ```python import torch import torch.nn as nn class NormLayer(nn.Module): def __init__(self, d_model, eps=1e-6): super(NormLayer, self).__init__() self.gamma = nn.Parameter(torch.ones(d_model)) self.beta = nn.Parameter(torch.zeros(d_model)) self.eps = eps def forward(self, x): mean = x.mean(dim=-1, keepdim=True) std = x.std(dim=-1, keepdim=True) return self.gamma * (x - mean) / (std + self.eps) + self.beta ``` ### Encoder - **Encoder 类**:编码器由多个 EncoderLayer 堆叠而成,用于对输入序列进行编码。 ```python import torch import torch.nn as nn class Encoder(nn.Module): def __init__(self, num_layers, d_model, num_heads, d_ff): super(Encoder, self).__init__() self.layers = nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff) for _ in range(num_layers)]) def forward(self, x, mask): for layer in self.layers: x = layer(x, mask) return x ``` - **EncoderLayer 类**:每个 EncoderLayer 包含多头注意力和前馈神经网络两个子层,并且在每个子层后都进行层归一化。 ```python import torch import torch.nn as nn class EncoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff): super(EncoderLayer, self).__init__() self.self_attn = MultiHeadAttention(num_heads, d_model) self.feed_forward = FeedForward(d_model, d_ff) self.norm1 = NormLayer(d_model) self.norm2 = NormLayer(d_model) def forward(self, x, mask): attn_output = self.self_attn(x, x, x, mask) x = self.norm1(x + attn_output) ff_output = self.feed_forward(x) x = self.norm2(x + ff_output) return x ``` - **前向传播过程**:输入序列首先经过位置编码,然后依次通过多个 EncoderLayer 进行处理。 ### Decoder - **Decoder 类**:解码器同样由多个 DecoderLayer 堆叠而成,用于根据编码器的输出生成目标序列。 ```python import torch import torch.nn as nn class Decoder(nn.Module): def __init__(self, num_layers, d_model, num_heads, d_ff): super(Decoder, self).__init__() self.layers = nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff) for _ in range(num_layers)]) def forward(self, x, enc_output, src_mask, tgt_mask): for layer in self.layers: x = layer(x, enc_output, src_mask, tgt_mask) return x ``` - **DecoderLayer 类**:每个 DecoderLayer 包含自注意力、编码器 - 解码器注意力和前馈神经网络三个子层,并且在每个子层后都进行层归一化。 ```python import torch import torch.nn as nn class DecoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff): super(DecoderLayer, self).__init__() self.self_attn = MultiHeadAttention(num_heads, d_model) self.cross_attn = MultiHeadAttention(num_heads, d_model) self.feed_forward = FeedForward(d_model, d_ff) self.norm1 = NormLayer(d_model) self.norm2 = NormLayer(d_model) self.norm3 = NormLayer(d_model) def forward(self, x, enc_output, src_mask, tgt_mask): attn_output1 = self.self_attn(x, x, x, tgt_mask) x = self.norm1(x + attn_output1) attn_output2 = self.cross_attn(x, enc_output, enc_output, src_mask) x = self.norm2(x + attn_output2) ff_output = self.feed_forward(x) x = self.norm3(x + ff_output) return x ``` - **前向传播过程**:目标序列首先经过位置编码,然后依次通过多个 DecoderLayer 进行处理,同时利用编码器的输出进行跨注意力计算。 ### Transformer整体框架 ```python import torch import torch.nn as nn class Transformer(nn.Module): def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff): super(Transformer, self).__init__() self.encoder = Encoder(num_layers, d_model, num_heads, d_ff) self.decoder = Decoder(num_layers, d_model, num_heads, d_ff) self.src_embedding = nn.Embedding(src_vocab_size, d_model) self.tgt_embedding = nn.Embedding(tgt_vocab_size, d_model) self.positional_encoding = PositionalEncoding(d_model) self.fc = nn.Linear(d_model, tgt_vocab_size) def forward(self, src, tgt, src_mask, tgt_mask): src_embedded = self.positional_encoding(self.src_embedding(src)) tgt_embedded = self.positional_encoding(self.tgt_embedding(tgt)) enc_output = self.encoder(src_embedded, src_mask) dec_output = self.decoder(tgt_embedded, enc_output, src_mask, tgt_mask) output = self.fc(dec_output) return output ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值