分布式微服务系统架构第148集:JavaPlus技术文档平台日更

发布于:2025-06-16 ⋅ 阅读:(17) ⋅ 点赞:(0)

加群联系作者vx:xiaoda0423

仓库地址:https://webvueblog.github.io/JavaPlusDoc/

https://1024bat.cn/

https://github.com/webVueBlog/fastapi_plus

https://webvueblog.github.io/JavaPlusDoc/

点击勘误issues,哪吒感谢大家的阅读

Redis中的key过期问题解决方案

Redis作为高性能的内存数据库,被广泛应用于缓存、会话管理、计数器等场景。在使用Redis过程中,key的过期问题是一个常见的挑战,本文将详细介绍Redis key过期的原理、常见问题及解决方案。

1. Redis key过期机制原理

1.1 过期策略

Redis采用两种策略来处理过期的key:

1.1.1 定期删除

Redis默认每隔100ms随机抽取一部分设置了过期时间的key进行检查,如果发现已过期则删除。这种策略是一种折中方案,避免了每次都扫描全部key带来的性能问题。

# redis.conf 配置
hz 10  # 默认每秒执行10次定期删除
1.1.2 惰性删除

当客户端尝试访问某个key时,Redis会检查该key是否已过期,如果过期则删除并返回空值。这种方式只有在访问key时才会触发过期检查,节省了CPU资源,但可能导致过期key长时间占用内存。

1.2 内存淘汰机制

当Redis内存使用达到上限时,会触发内存淘汰机制,根据配置的策略删除部分key:

  1. noeviction: 不删除任何key,新写入操作会报错

  2. allkeys-lru: 删除最近最少使用的key(常用)

  3. allkeys-random: 随机删除key

  4. volatile-lru: 在设置了过期时间的key中,删除最近最少使用的key

  5. volatile-random: 在设置了过期时间的key中,随机删除key

  6. volatile-ttl: 在设置了过期时间的key中,删除剩余寿命最短的key

  7. allkeys-lfu: 删除使用频率最少的key(Redis 4.0新增)

  8. volatile-lfu: 在设置了过期时间的key中,删除使用频率最少的key(Redis 4.0新增)

# redis.conf 配置
maxmemory 2gb  # 设置最大内存
maxmemory-policy allkeys-lru  # 设置淘汰策略

2. Redis key过期常见问题

2.1 缓存雪崩

问题: 大量key在同一时间点过期,导致大量请求直接访问数据库,可能使数据库瞬间崩溃。

场景示例: 系统在某个时间点批量设置了大量缓存,且过期时间相同,如电商系统在活动开始前预热商品数据,所有缓存设置为活动结束时间过期。

2.2 缓存击穿

问题: 某个热点key过期,导致大量并发请求直接访问数据库。

场景示例: 一个高访问量的商品详情页缓存突然过期,大量用户同时请求该商品信息。

2.3 缓存穿透

问题: 请求查询一个不存在的数据,导致请求直接落到数据库上。

场景示例: 恶意用户不断请求不存在的商品ID,每次请求都会查询数据库。

2.4 主从复制中的过期问题

问题: 在主从架构中,从节点不会主动过期key,只有当主节点过期一个key时,才会向从节点发送del命令。

场景示例: 如果主节点宕机,从节点提升为主节点,可能会出现已过期但未删除的key。

3. Redis key过期问题解决方案

3.1 缓存雪崩解决方案

3.1.1 过期时间添加随机值

为缓存设置过期时间时增加一个随机值,避免大量缓存同时过期。

// 设置过期时间为10-15分钟之间的随机值
long timeout = 10 + new Random().nextInt(5);
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MINUTES);
3.1.2 缓存预热

系统启动时或定时任务中提前加载热点数据到缓存。

@PostConstruct
public void preloadCache() {
    List<Product> hotProducts = productService.findHotProducts();
    for (Product product : hotProducts) {
        String key = "product:" + product.getId();
        redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 
            getRandomExpireTime(), TimeUnit.MINUTES);
    }
}
3.1.3 多级缓存

使用本地缓存+分布式缓存的多级缓存架构。

// 使用Caffeine作为本地缓存
private LoadingCache<String, Product> localCache = Caffeine.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .build(key -> getFromRedis(key));

private Product getFromRedis(String key) {
    String json = redisTemplate.opsForValue().get(key);
    return JSON.parseObject(json, Product.class);
}

3.2 缓存击穿解决方案

3.2.1 使用分布式锁

使用分布式锁确保同一时间只有一个请求去查询数据库和更新缓存。

public Product getProduct(Long id) {
    String key = "product:" + id;
    String json = redisTemplate.opsForValue().get(key);
    if (StringUtils.hasText(json)) {
        return JSON.parseObject(json, Product.class);
    }
    
    // 使用Redisson分布式锁
    RLock lock = redissonClient.getLock("lock:product:" + id);
    try {
        if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
            try {
                // 双重检查
                json = redisTemplate.opsForValue().get(key);
                if (StringUtils.hasText(json)) {
                    return JSON.parseObject(json, Product.class);
                }
                
                // 查询数据库
                Product product = productMapper.selectById(id);
                if (product != null) {
                    redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 
                        getRandomExpireTime(), TimeUnit.MINUTES);
                }
                return product;
            } finally {
                lock.unlock();
            }
        } else {
            // 获取锁失败,短暂休眠后重试获取缓存
            Thread.sleep(100);
            json = redisTemplate.opsForValue().get(key);
            if (StringUtils.hasText(json)) {
                return JSON.parseObject(json, Product.class);
            }
            return null;
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return null;
    }
}
3.2.2 热点数据永不过期

对于极热点数据,可以设置永不过期,而是通过后台异步更新缓存。

// 设置热点数据永不过期
redisTemplate.opsForValue().set("hotspot:product:" + id, value);

// 后台定时任务更新缓存
@Scheduled(fixedRate = 300000) // 每5分钟执行一次
public void refreshHotspotCache() {
    Set<String> keys = redisTemplate.keys("hotspot:product:*");
    for (String key : keys) {
        Long id = Long.valueOf(key.split(":")[2]);
        Product product = productMapper.selectById(id);
        if (product != null) {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(product));
        }
    }
}

3.3 缓存穿透解决方案

3.3.1 缓存空值

对于不存在的数据,也缓存一个空值,但过期时间较短。

public Product getProduct(Long id) {
    String key = "product:" + id;
    String json = redisTemplate.opsForValue().get(key);
    
    if (json != null) {
        if (json.isEmpty()) {
            // 空值缓存命中
            return null;
        }
        return JSON.parseObject(json, Product.class);
    }
    
    // 查询数据库
    Product product = productMapper.selectById(id);
    if (product == null) {
        // 缓存空值,过期时间短
        redisTemplate.opsForValue().set(key, "", 2, TimeUnit.MINUTES);
        return null;
    } else {
        redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 
            getRandomExpireTime(), TimeUnit.MINUTES);
        return product;
    }
}
3.3.2 布隆过滤器

使用布隆过滤器快速判断key是否存在,避免对不存在的数据进行查询。

// 初始化布隆过滤器
private BloomFilter<Long> bloomFilter = BloomFilter.create(
    Funnels.longFunnel(),
    10000000,  // 预计元素数量
    0.01       // 误判率
);

// 加载所有商品ID到布隆过滤器
@PostConstruct
public void initBloomFilter() {
    List<Long> allProductIds = productMapper.selectAllIds();
    for (Long id : allProductIds) {
        bloomFilter.put(id);
    }
}

public Product getProduct(Long id) {
    // 布隆过滤器判断
    if (!bloomFilter.mightContain(id)) {
        return null; // ID不存在,直接返回
    }
    
    // 继续查询缓存和数据库
    // ...
}

3.4 主从复制中的过期问题解决方案

3.4.1 合理配置主从参数

确保主节点及时过期key并同步到从节点。

# redis.conf 主节点配置
hz 20  # 提高定期删除频率
3.4.2 监控过期key情况

定期检查Redis中过期key的数量,及时发现异常。

# 监控过期key数量
redis-cli info stats | grep expired_keys

4. 最佳实践

4.1 统一的缓存访问模板

封装一个统一的缓存访问模板,集成各种解决方案。

public class CacheTemplate {
    
    private RedisTemplate<String, String> redisTemplate;
    private RedissonClient redissonClient;
    private BloomFilter<Long> bloomFilter;
    
    public <T> T queryWithCache(String keyPrefix, Long id, Class<T> clazz, Function<Long, T> dbFallback) {
        // 布隆过滤器判断
        if (bloomFilter != null && !bloomFilter.mightContain(id)) {
            return null;
        }
        
        String key = keyPrefix + id;
        String json = redisTemplate.opsForValue().get(key);
        
        // 缓存命中
        if (json != null) {
            if (json.isEmpty()) {
                return null; // 空值缓存
            }
            return JSON.parseObject(json, clazz);
        }
        
        // 分布式锁防击穿
        RLock lock = redissonClient.getLock("lock:" + key);
        try {
            if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
                try {
                    // 双重检查
                    json = redisTemplate.opsForValue().get(key);
                    if (json != null) {
                        return json.isEmpty() ? null : JSON.parseObject(json, clazz);
                    }
                    
                    // 查询数据库
                    T data = dbFallback.apply(id);
                    if (data == null) {
                        // 缓存空值
                        redisTemplate.opsForValue().set(key, "", 2, TimeUnit.MINUTES);
                    } else {
                        // 缓存数据,添加随机过期时间
                        long timeout = 10 + new Random().nextInt(5);
                        redisTemplate.opsForValue().set(key, JSON.toJSONString(data), 
                            timeout, TimeUnit.MINUTES);
                    }
                    return data;
                } finally {
                    lock.unlock();
                }
            } else {
                // 获取锁失败,短暂休眠后重试获取缓存
                Thread.sleep(100);
                json = redisTemplate.opsForValue().get(key);
                if (json != null) {
                    return json.isEmpty() ? null : JSON.parseObject(json, clazz);
                }
                return null;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        }
    }
}

4.2 定期更新策略

对于某些重要数据,可以采用定期更新策略,避免过期问题。

@Scheduled(fixedRate = 600000) // 每10分钟执行一次
public void refreshImportantCache() {
    List<Product> importantProducts = productService.findImportantProducts();
    for (Product product : importantProducts) {
        String key = "product:" + product.getId();
        redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 
            getRandomExpireTime(), TimeUnit.MINUTES);
    }
}

4.3 监控和告警

设置Redis监控和告警机制,及时发现过期相关问题。

@Scheduled(fixedRate = 300000) // 每5分钟执行一次
public void monitorRedisExpiration() {
    Long expiredKeys = redisTemplate.execute((RedisCallback<Long>) connection -> 
        connection.info().getProperty("expired_keys"));
    
    if (expiredKeys > THRESHOLD) {
        // 触发告警
        alertService.sendAlert("Redis过期key数量异常: " + expiredKeys);
    }
}

5. 总结

Redis key过期问题是使用Redis缓存系统时必须面对的挑战,通过理解Redis的过期机制原理,针对不同场景采用合适的解决方案,可以有效避免缓存雪崩、击穿和穿透等问题,提高系统的稳定性和性能。

关键解决方案包括:

  1. 为过期时间添加随机值,避免同时过期

  2. 使用分布式锁防止缓存击穿

  3. 缓存空值和使用布隆过滤器防止缓存穿透

  4. 采用多级缓存架构提高系统弹性

  5. 对热点数据进行特殊处理,如永不过期+后台更新

  6. 建立完善的监控和告警机制

通过这些方案的组合应用,可以构建一个健壮的Redis缓存系统,有效解决key过期带来的各种问题。

CPU使用率100%的异常排查

在生产环境中,CPU使用率飙升至100%是一种常见的性能问题,可能导致系统响应缓慢甚至服务不可用。本文将详细介绍如何排查和解决CPU使用率100%的问题。

1. 问题表现

当CPU使用率达到100%时,系统通常会出现以下症状:

  • 系统响应缓慢或无响应

  • 应用程序执行速度变慢

  • 请求处理时间增加

  • 任务队列积压

  • 服务超时或拒绝连接

2. 排查工具

2.1 Linux系统工具

top命令:实时显示系统中各个进程的资源占用情况

top

使用top命令后,可以按以下键进行排序:

  • 按 P 键:按CPU使用率排序(默认)

  • 按 M 键:按内存使用率排序

  • 按 T 键:按运行时间排序

htop命令:top的增强版,提供更友好的界面和更多功能

htop

ps命令:查看进程状态

# 查看CPU占用最高的前10个进程
ps aux | sort -k3nr | head -10

mpstat命令:查看多处理器统计信息

mpstat -P ALL 2 5  # 每2秒采样一次,共采样5次,显示所有CPU核心的统计信息

pidstat命令:监控进程的CPU使用情况

pidstat -u 2 5  # 每2秒采样一次,共采样5次
pidstat -p <PID> -u 2 5  # 监控特定进程
2.2 Java应用工具

jstack:生成Java线程转储

jstack <PID> > thread_dump.log

jstat:监控JVM的GC情况

jstat -gcutil <PID> 1000 10  # 每1秒采样一次,共采样10次

jmap:生成堆转储

jmap -dump:format=b,file=heap_dump.bin <PID>

Arthas:阿里开源的Java诊断工具

# 安装Arthas
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

# 使用thread命令查看线程情况
thread -n 3  # 显示CPU使用率最高的3个线程

3. 排查步骤

3.1 确认CPU使用率

首先使用top命令确认系统整体CPU使用率:

top

关注以下几个指标:

  • %us:用户空间占用CPU百分比

  • %sy:内核空间占用CPU百分比

  • %ni:用户进程空间内改变过优先级的进程占用CPU百分比

  • %id:空闲CPU百分比

  • %wa:等待输入输出的CPU时间百分比

3.2 定位高CPU进程

在top命令中,按P键按CPU使用率排序,找出CPU使用率最高的进程,记录其PID。

# 或者使用ps命令
ps aux | sort -k3nr | head -10
3.3 分析进程内的线程

找到高CPU进程后,进一步分析该进程内的线程情况:

# 查看进程内的线程CPU使用情况
top -Hp <PID>

记录CPU使用率高的线程ID,将线程ID转换为十六进制:

printf "%x\n" <线程ID>
3.4 生成线程转储

对于Java应用,使用jstack生成线程转储:

jstack <PID> > thread_dump.log

在thread_dump.log文件中搜索之前转换的十六进制线程ID,找到对应的线程栈信息。

grep -A 30 "0x<十六进制线程ID>" thread_dump.log
3.5 分析GC情况

如果怀疑是GC问题导致的高CPU,使用jstat查看GC情况:

jstat -gcutil <PID> 1000 10

关注以下指标:

  • S0S1EOM:各内存区域使用百分比

  • YGCYGCT:年轻代GC次数和时间

  • FGCFGCT:老年代GC次数和时间

  • GCT:总GC时间

如果频繁发生Full GC,可能是内存泄漏或内存配置不合理。

4. 常见原因及解决方案

4.1 代码问题

死循环或无限递归

  • 症状:线程栈显示同一方法反复出现

  • 解决方案:修复代码中的逻辑错误,添加适当的退出条件

算法效率低下

  • 症状:CPU密集型计算占用大量资源

  • 解决方案:优化算法,使用更高效的数据结构,考虑增加缓存

资源竞争

  • 症状:多个线程争用同一把锁,导致上下文切换频繁

  • 解决方案:减少锁粒度,使用并发容器,避免长时间持有锁

4.2 JVM问题

频繁GC

  • 症状:jstat显示GC活动频繁,GC线程占用大量CPU

  • 解决方案:调整JVM内存参数,增加堆内存,优化对象创建

JIT编译

  • 症状:启动初期CPU使用率高,CompilerThread占用资源

  • 解决方案:预热应用,使用AOT编译,调整JIT编译参数

4.3 系统问题

进程数过多

  • 症状:系统进程数量异常增多

  • 解决方案:检查是否有异常进程创建,限制进程数量

系统中断处理

  • 症状:系统CPU使用率高,但用户进程CPU使用率不高

  • 解决方案:检查硬件问题,更新驱动,调整系统参数

5. 实战案例

案例一:Java应用CPU飙升

现象:生产环境中一个Java应用CPU使用率突然飙升至100%,系统响应缓慢。

排查过程

  1. 使用top命令确认Java进程CPU使用率接近100%

  2. 使用top -Hp命令找到占用CPU最高的线程ID

  3. 将线程ID转换为十六进制:printf "%x\n" 12345

  4. 使用jstack生成线程转储并分析

  5. 发现问题线程在执行一个无限循环的操作

解决方案:修复代码中的无限循环问题,添加适当的退出条件和超时机制。

案例二:频繁GC导致CPU高负载

现象:应用运行一段时间后CPU使用率逐渐升高,响应变慢。

排查过程

  1. 使用jstat发现Full GC频繁发生

  2. 使用jmap生成堆转储并分析

  3. 发现某个集合对象不断增长,没有释放

解决方案:修复内存泄漏问题,确保临时对象能够被及时回收。

6. 预防措施

6.1 监控告警
  • 设置CPU使用率阈值告警,如连续5分钟超过80%触发告警

  • 监控GC频率和时间,设置合理的告警阈值

  • 监控线程数量,防止线程爆炸

6.2 性能测试
  • 在上线前进行充分的性能测试和压力测试

  • 模拟高并发场景,验证系统在极限情况下的表现

  • 进行长时间的稳定性测试,发现潜在的资源泄漏问题

6.3 代码审查
  • 重点关注循环、递归等可能导致CPU密集的代码

  • 检查资源释放是否完整

  • 避免使用低效算法处理大量数据

7. 总结

CPU使用率100%的问题排查是一个系统性工作,需要从操作系统、应用程序、JVM等多个层面进行分析。掌握相关工具和方法,可以帮助我们快速定位和解决问题,保障系统的稳定运行。

在实际工作中,建议建立标准的问题排查流程和工具集,提前做好监控和告警,做到早发现、早处理,避免问题扩大化。同时,持续优化代码质量和系统架构,从根本上减少高CPU问题的发生。

Nginx优化与防盗链

一、Nginx性能优化

1.1 基础优化

1.1.1 worker进程优化
# 设置worker进程数量,通常设置为CPU核心数
worker_processes auto;

# 绑定worker进程到指定CPU,避免进程切换带来的开销
worker_cpu_affinity auto;

# 每个worker进程可以打开的最大文件描述符数量
worker_rlimit_nofile 65535;
1.1.2 事件处理优化
events {
    # 使用epoll事件驱动模型,Linux系统下效率最高
    use epoll;

    # 每个worker进程的最大连接数
    worker_connections 10240;

    # 尽可能接受所有新连接
    multi_accept on;
}
1.1.3 HTTP基础优化
http {
    # 开启高效文件传输模式
    sendfile on;

    # 减少网络报文段的数量
    tcp_nopush on;

    # 提高网络包的传输效率
    tcp_nodelay on;

    # 设置客户端连接保持活动的超时时间
    keepalive_timeout 60;

    # 设置请求头的超时时间
    client_header_timeout 10;

    # 设置请求体的超时时间
    client_body_timeout 10;

    # 响应超时时间
    send_timeout 10;

    # 读取请求体的缓冲区大小
    client_body_buffer_size 128k;

    # 读取请求头的缓冲区大小
    client_header_buffer_size 32k;

    # 上传文件大小限制
    client_max_body_size 10m;
}

1.2 静态资源优化

1.2.1 Gzip压缩
http {
    # 开启gzip压缩
    gzip on;

    # 压缩的最小文件大小,小于这个值不压缩
    gzip_min_length 1k;

    # 压缩缓冲区大小
    gzip_buffers 4 16k;

    # 压缩HTTP版本
    gzip_http_version 1.1;

    # 压缩级别,1-9,级别越高压缩率越高,但CPU消耗也越大
    gzip_comp_level 6;

    # 需要压缩的MIME类型
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;

    # 是否在响应头中添加Vary: Accept-Encoding
    gzip_vary on;

    # IE6及以下禁用gzip
    gzip_disable "MSIE [1-6]\.";
}
1.2.2 静态资源缓存
server {
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        # 缓存时间设置
        expires 7d;

        # 添加缓存控制头
        add_header Cache-Control "public, max-age=604800";
    }

    # 针对不同类型文件设置不同的缓存策略
    location ~* \.(html|htm)$ {
        expires 1h;
        add_header Cache-Control "public, max-age=3600";
    }
}

1.3 负载均衡优化

1.3.1 高级负载均衡配置
upstream backend {
    # 使用IP哈希算法,确保同一客户端请求总是发送到同一服务器
    ip_hash;

    # 后端服务器列表
    server 192.168.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 weight=2 max_fails=3 fail_timeout=30s;

    # 备用服务器,只有当所有主服务器都不可用时才使用
    server 192.168.1.13:8080 backup;

    # 保持长连接的数量
    keepalive 32;
}

server {
    location / {
        proxy_pass http://backend;

        # 设置代理请求头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 超时设置
        proxy_connect_timeout 5s;
        proxy_send_timeout 10s;
        proxy_read_timeout 10s;

        # 启用HTTP/1.1
        proxy_http_version 1.1;

        # 设置连接为长连接
        proxy_set_header Connection "";
    }
}
1.3.2 健康检查
http {
    # 定义健康检查间隔和参数
    upstream backend {
        server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
        server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;

        # 被动健康检查:max_fails表示允许请求失败的次数,fail_timeout表示失败后暂停的时间
    }
}

二、Nginx防盗链配置

2.1 基于HTTP Referer的防盗链

server {
    # 图片防盗链
    location ~* \.(gif|jpg|jpeg|png|bmp|swf|webp)$ {
        # 允许的来源域名
        valid_referers none blocked server_names *.example.com example.* www.example.org/galleries/;

        # 如果referer不是上面指定的,则返回403
        if ($invalid_referer) {
            return 403;
        }

        # 或者返回一个默认的防盗链图片
        # if ($invalid_referer) {
        #     rewrite ^/ /images/forbidden.jpg break;
        # }

        root /path/to/your/files;
    }

    # 视频防盗链
    location ~* \.(mp4|avi|mkv|wmv|flv)$ {
        valid_referers none blocked server_names *.example.com example.*;
        if ($invalid_referer) {
            return 403;
        }
        root /path/to/your/videos;
    }
}

2.2 基于Cookie的防盗链

server {
    location ~* \.(gif|jpg|jpeg|png|bmp|swf)$ {
        # 检查cookie
        if ($http_cookie !~ "authorized=yes") {
            return 403;
        }
        root /path/to/your/files;
    }
}

2.3 基于签名的防盗链(安全哈希)

server {
    # 需要安装第三方模块:ngx_http_secure_link_module
    location /secure/ {
        # 验证链接的有效性
        secure_link $arg_md5,$arg_expires;
        secure_link_md5 "$secure_link_expires$uri$remote_addr secret_key";

        # 如果链接无效或过期
        if ($secure_link = "") {
            return 403;
        }

        # 如果链接已过期
        if ($secure_link = "0") {
            return 410; # Gone
        }

        # 正常处理请求
        root /path/to/your/secure/files;
    }
}

2.4 替换盗链图片

server {
    location ~* \.(gif|jpg|jpeg|png|bmp|swf)$ {
        valid_referers none blocked server_names *.example.com example.*;

        # 如果是盗链,则返回自定义的图片
        if ($invalid_referer) {
            # 可以是透明图片或水印图片
            rewrite ^/.*$ /images/watermark.png break;
        }

        root /path/to/your/files;
        expires 7d;
    }
}

三、实际应用案例

3.1 静态网站优化配置

server {
    listen 80;
    server_name www.example.com;
    root /var/www/html;
    index index.html;

    # 静态资源优化
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 7d;
        add_header Cache-Control "public, max-age=604800";

        # 防盗链配置
        valid_referers none blocked server_names *.example.com example.*;
        if ($invalid_referer) {
            return 403;
        }
    }

    # Gzip压缩
    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
    gzip_vary on;

    # 安全相关头信息
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Frame-Options SAMEORIGIN;
}

3.2 API服务器优化配置

server {
    listen 80;
    server_name api.example.com;

    # API请求限流
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

    location /api/ {
        # 应用限流
        limit_req zone=api_limit burst=20 nodelay;

        # 代理到后端服务
        proxy_pass http://backend_api;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 超时设置
        proxy_connect_timeout 5s;
        proxy_send_timeout 10s;
        proxy_read_timeout 10s;

        # CORS设置
        add_header 'Access-Control-Allow-Origin' 'https://www.example.com';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';

        # 预检请求处理
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
        }
    }
}

四、常见问题与解决方案

4.1 防盗链失效问题

  1. Referer头被伪造

  • 解决方案:结合IP限制、时间戳和签名机制增强防盗链安全性

  • 使用secure_link模块实现基于签名的防盗链

  • 移动端浏览器不发送Referer

    • 解决方案:配置valid_referers包含none选项,允许没有Referer的请求

  • CDN缓存导致防盗链失效

    • 解决方案:确保CDN配置与Nginx防盗链策略一致,或在CDN层实现防盗链

    4.2 性能优化问题

    1. 过度压缩导致CPU使用率高

    • 解决方案:调整gzip_comp_level到适当级别(通常4-6),或对大文件使用预压缩

  • 缓存策略不当

    • 解决方案:根据资源更新频率设置合理的缓存时间,使用版本号或哈希值处理更新

  • 连接数限制

    • 解决方案:调整worker_connections和系统的文件描述符限制

    五、最佳实践建议

    1. 定期更新Nginx版本,获取最新的安全补丁和性能改进

    2. 使用监控工具(如Prometheus + Grafana)监控Nginx性能指标

    3. 结合多种防盗链机制,不要仅依赖单一方法

    4. 针对不同类型的资源采用不同的优化策略

    5. 测试配置变更,使用nginx -t验证配置,并在生产环境应用前在测试环境验证

    6. 记录详细的访问日志,便于分析访问模式和潜在的盗链行为

    7. 定期审查防盗链规则,确保它们不会阻止合法访问

    8. 考虑使用CDN,分担源站压力并提供额外的防盗链保护


网站公告

今日签到

点亮在社区的每一天
去签到