偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

SpringBoot 搶券活動(dòng):Redis 熱點(diǎn) Key 三大防護(hù)

數(shù)據(jù)庫 Redis
本文將從緩存擊穿、分片、異步化等角度,探討如何在項(xiàng)目中優(yōu)化?Redis?和數(shù)據(jù)庫的性能,以應(yīng)對(duì)搶券活動(dòng)中的熱點(diǎn)?Key?問題。

引言

在電商系統(tǒng)的搶券活動(dòng)中,經(jīng)常會(huì)出現(xiàn)某張熱門優(yōu)惠券被大量用戶同時(shí)訪問的情況,這就是典型的熱點(diǎn) Key 問題。這類問題會(huì)導(dǎo)致 Redis 負(fù)載過高,甚至可能引發(fā)緩存擊穿,大量請(qǐng)求直接打到數(shù)據(jù)庫,造成系統(tǒng)崩潰。

本文將從緩存擊穿、分片、異步化等角度,探討如何在項(xiàng)目中優(yōu)化 Redis 和數(shù)據(jù)庫的性能,以應(yīng)對(duì)搶券活動(dòng)中的熱點(diǎn) Key 問題。

熱點(diǎn) Key 問題分析

在搶券場(chǎng)景中,熱點(diǎn) Key 問題主要表現(xiàn)為:

  • 當(dāng)該熱點(diǎn) Key 在 Redis 中過期時(shí),大量請(qǐng)求會(huì)同時(shí)穿透到數(shù)據(jù)庫,造成緩存擊穿
  • 某張熱門優(yōu)惠券的訪問量遠(yuǎn)超其他優(yōu)惠券,導(dǎo)致 Redis 單節(jié)點(diǎn)負(fù)載過高
  • 數(shù)據(jù)庫瞬時(shí)承受巨大壓力,可能導(dǎo)致查詢超時(shí)甚至服務(wù)不可用

?

  • 緩存擊穿:是指當(dāng)某一key的緩存過期時(shí)大并發(fā)量的請(qǐng)求同時(shí)訪問此key,瞬間擊穿緩存服務(wù)器直接訪問數(shù)據(jù)庫,讓數(shù)據(jù)庫處于負(fù)載的情況。
  • 緩存穿透:是指緩存服務(wù)器中沒有緩存數(shù)據(jù),數(shù)據(jù)庫中也沒有符合條件的數(shù)據(jù),導(dǎo)致業(yè)務(wù)系統(tǒng)每次都繞過緩存服務(wù)器查詢下游的數(shù)據(jù)庫,緩存服務(wù)器完全失去了其應(yīng)有的作用。
  • 緩存雪崩:是指當(dāng)大量緩存同時(shí)過期或緩存服務(wù)宕機(jī),所有請(qǐng)求的都直接訪問數(shù)據(jù)庫,造成數(shù)據(jù)庫高負(fù)載,影響性能,甚至數(shù)據(jù)庫宕機(jī)。

緩存擊穿的解決方案

分布式鎖
// 使用Redisson實(shí)現(xiàn)分布式鎖防止緩存擊穿
@Service
public class CouponService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private CouponDao couponDao;
    
    public Coupon getCoupon(String couponId) {
        String key = "coupon:" + couponId;
        Coupon coupon = (Coupon) redisTemplate.opsForValue().get(key);
        
        if (coupon == null) {
            // 獲取分布式鎖
            RLock lock = redissonClient.getLock("lock:coupon:" + couponId);
            try {
                // 嘗試加鎖,最多等待100秒,鎖持有時(shí)間為10秒
                boolean isLocked = lock.tryLock(100, 10, TimeUnit.SECONDS);
                if (isLocked) {
                    try {
                        // 再次檢查Redis中是否有值
                        coupon = (Coupon) redisTemplate.opsForValue().get(key);
                        if (coupon == null) {
                            // 從數(shù)據(jù)庫中查詢
                            coupon = couponDao.getCouponById(couponId);
                            if (coupon != null) {
                                // 設(shè)置帶過期時(shí)間的緩存
                                redisTemplate.opsForValue().set(key, coupon, 30, TimeUnit.MINUTES);
                            }
                        }
                    } finally {
                        // 釋放鎖
                        lock.unlock();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return coupon;
    }
}

熱點(diǎn) Key 分片處理

當(dāng)單個(gè)熱點(diǎn) Key 的訪問量極高時(shí),可以采用分片策略將請(qǐng)求分散到多個(gè) Redis 節(jié)點(diǎn)上:

// 熱點(diǎn)Key分片處理實(shí)現(xiàn)
@Service
public class CouponService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private CouponDao couponDao;
    
    // 分片數(shù)量
    private static final int SHARD_COUNT = 16;
    
    // 獲取分片后的Key
    private String getShardedKey(String couponId, int shardIndex) {
        return"coupon:" + couponId + ":shard" + shardIndex;
    }
    
    // 初始化分片緩存
    public void initCouponShards(String couponId, int stock) {
        // 計(jì)算每個(gè)分片的庫存
        int stockPerShard = stock / SHARD_COUNT;
        int remaining = stock % SHARD_COUNT;
        
        for (int i = 0; i < SHARD_COUNT; i++) {
            int currentStock = stockPerShard + (i < remaining ? 1 : 0);
            String key = getShardedKey(couponId, i);
            redisTemplate.opsForValue().set(key, currentStock);
        }
    }
    
    // 扣減庫存(嘗試從隨機(jī)分片獲?。?    public boolean deductStock(String couponId) {
        // 隨機(jī)選擇一個(gè)分片
        int shardIndex = new Random().nextInt(SHARD_COUNT);
        String key = getShardedKey(couponId, shardIndex);
        
        // 使用Lua腳本原子性地扣減庫存
        String script = 
            "local stock = tonumber(redis.call('get', KEYS[1])) " +
            "if stock and stock > 0 then " +
            "  redis.call('decr', KEYS[1]) " +
            "  return 1 " +
            "else " +
            "  return 0 " +
            "end";
        
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
        
        Long result = redisTemplate.execute(redisScript, Collections.singletonList(key));
        return result != null && result == 1;
    }
}
根據(jù)分片負(fù)載動(dòng)態(tài)選擇
// 動(dòng)態(tài)分片選擇(根據(jù)剩余庫存)
public boolean deductStockByDynamicShard(String couponId) {
    // 獲取所有分片的庫存
    List<String> keys = new ArrayList<>();
    for (int i = 0; i < SHARD_COUNT; i++) {
        keys.add(getShardedKey(couponId, i));
    }
    
    // 使用MGET批量獲取所有分片庫存
    List<Object> results = redisTemplate.opsForValue().multiGet(keys);
    
    // 選擇庫存最多的分片
    int maxStockIndex = -1;
    int maxStock = 0;
    
    for (int i = 0; i < results.size(); i++) {
        if (results.get(i) != null) {
            int stock = Integer.parseInt(results.get(i).toString());
            if (stock > maxStock) {
                maxStock = stock;
                maxStockIndex = i;
            }
        }
    }
    
    if (maxStockIndex >= 0) {
        // 對(duì)選中的分片進(jìn)行扣減
        String key = getShardedKey(couponId, maxStockIndex);
        // 執(zhí)行Lua腳本扣減庫存...
    }
    
    returnfalse;
}

異步化處理

// 異步化處理搶券請(qǐng)求
@Service
public class CouponService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private CouponDao couponDao;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    // 搶券接口 - 快速返回,異步處理
    public boolean grabCoupon(String userId, String couponId) {
        // 先快速檢查Redis中是否有庫存
        String stockKey = "coupon:" + couponId + ":stock";
        Long stock = (Long) redisTemplate.opsForValue().get(stockKey);
        
        if (stock == null || stock <= 0) {
            returnfalse;
        }
        
        // 使用Lua腳本原子性地扣減庫存
        String script = 
            "local stock = tonumber(redis.call('get', KEYS[1])) " +
            "if stock and stock > 0 then " +
            "  redis.call('decr', KEYS[1]) " +
            "  return 1 " +
            "else " +
            "  return 0 " +
            "end";
        
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
        
        Long result = redisTemplate.execute(redisScript, Collections.singletonList(stockKey));
        
        if (result != null && result == 1) {
            // 庫存扣減成功,發(fā)送消息到MQ異步處理
            CouponGrabMessage message = new CouponGrabMessage(userId, couponId);
            rabbitTemplate.convertAndSend("coupon.exchange", "coupon.grab", message);
            returntrue;
        }
        
        returnfalse;
    }
    
    // 異步處理搶券結(jié)果
    @RabbitListener(queues = "coupon.grab.queue")
    public void handleCouponGrab(CouponGrabMessage message) {
        try {
            // 在數(shù)據(jù)庫中記錄用戶領(lǐng)取優(yōu)惠券的信息
            couponDao.recordUserCoupon(message.getUserId(), message.getCouponId());
            
            // 可以在這里添加其他業(yè)務(wù)邏輯,如發(fā)送通知等
        } catch (Exception e) {
            // 處理失敗,可以記錄日志或進(jìn)行補(bǔ)償操作
            log.error("Failed to handle coupon grab for user: {}, coupon: {}", 
                    message.getUserId(), message.getCouponId(), e);
            
            // 回滾Redis中的庫存(這里簡(jiǎn)化處理,實(shí)際中可能需要更復(fù)雜的補(bǔ)償機(jī)制)
            String stockKey = "coupon:" + message.getCouponId() + ":stock";
            redisTemplate.opsForValue().increment(stockKey);
        }
    }
}

其他優(yōu)化策略

本地緩存
// 使用Caffeine實(shí)現(xiàn)本地緩存
@Service
public class CouponService {
    
    // 本地緩存,最大容量100,過期時(shí)間5分鐘
    private LoadingCache<String, Coupon> localCache = Caffeine.newBuilder()
            .maximumSize(100)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build(this::loadCouponFromRedis);
    
    // 從Redis加載優(yōu)惠券信息
    private Coupon loadCouponFromRedis(String couponId) {
        String key = "coupon:" + couponId;
        return (Coupon) redisTemplate.opsForValue().get(key);
    }
    
    // 獲取優(yōu)惠券信息
    public Coupon getCoupon(String couponId) {
        try {
            return localCache.get(couponId);
        } catch (ExecutionException e) {
            // 處理異常,從其他地方獲取數(shù)據(jù)
            return loadCouponFromRedis(couponId);
        }
    }
}
限流
// 使用Sentinel實(shí)現(xiàn)熱點(diǎn)參數(shù)限流
@Service
public class CouponService {
    
    // 定義熱點(diǎn)參數(shù)限流規(guī)則
    static {
        initFlowRules();
    }
    
    private static void initFlowRules() {
        List<ParamFlowRule> rules = new ArrayList<>();
        ParamFlowRule rule = new ParamFlowRule();
        rule.setResource("getCoupon");
        rule.setParamIdx(0); // 第一個(gè)參數(shù)作為限流參數(shù)
        rule.setCount(1000); // 每秒允許的請(qǐng)求數(shù)
        
        // 針對(duì)特定值的限流設(shè)置
        ParamFlowItem item = new ParamFlowItem();
        item.setObject("hotCouponId1");
        item.setClassType(String.class.getName());
        item.setCount(500); // 針對(duì)熱點(diǎn)優(yōu)惠券ID的特殊限流
        rule.getParamFlowItemList().add(item);
        
        rules.add(rule);
        ParamFlowRuleManager.loadRules(rules);
    }
    
    // 帶限流的獲取優(yōu)惠券方法
    public Coupon getCoupon(String couponId) {
        Entry entry = null;
        try {
            // 資源名可使用方法名
            entry = SphU.entry("getCoupon", EntryType.IN, 1, couponId);
            
            // 業(yè)務(wù)邏輯
            return getCouponFromRedis(couponId);
        } catch (BlockException ex) {
            // 資源訪問阻止,被限流或降級(jí)
            // 進(jìn)行相應(yīng)的處理操作
            return getDefaultCoupon();
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
    }
}

實(shí)施建議

  • 對(duì)優(yōu)惠券系統(tǒng)進(jìn)行分層設(shè)計(jì),將熱點(diǎn)數(shù)據(jù)與普通數(shù)據(jù)分離處理
  • 監(jiān)控 Redis 的性能指標(biāo),及時(shí)發(fā)現(xiàn)和處理熱點(diǎn) Key
  • 提前對(duì)可能的熱點(diǎn) Key 進(jìn)行預(yù)判和預(yù)熱
  • 設(shè)計(jì)完善的降級(jí)和熔斷策略,保障系統(tǒng)在極端情況下的可用性
  • 定期進(jìn)行全鏈路壓測(cè),發(fā)現(xiàn)系統(tǒng)瓶頸并持續(xù)優(yōu)化

總結(jié)

在搶券活動(dòng)等高并發(fā)場(chǎng)景下,熱點(diǎn) Key 問題是 Redis 和數(shù)據(jù)庫面臨的主要挑戰(zhàn)之一。通過采用緩存擊穿預(yù)防、熱點(diǎn) Key 分片、異步化處理、本地緩存和限流等多種優(yōu)化策略,可以有效提升系統(tǒng)的性能和穩(wěn)定性。

在實(shí)際應(yīng)用中,應(yīng)根據(jù)具體業(yè)務(wù)場(chǎng)景選擇合適的優(yōu)化方案,并進(jìn)行充分的性能測(cè)試和壓力測(cè)試,確保系統(tǒng)在高并發(fā)情況下依然能夠穩(wěn)定運(yùn)行。

責(zé)任編輯:武曉燕 來源: 一安未來
相關(guān)推薦

2021-03-28 21:33:07

Redis熱點(diǎn)key

2025-05-28 03:10:00

2011-03-11 15:07:24

2020-02-11 16:10:44

Redis分布式鎖Java

2023-04-26 01:07:03

2019-01-09 09:35:41

搶票Python軟件

2019-10-30 16:54:08

golangredis數(shù)據(jù)庫

2018-02-23 17:16:03

態(tài)牛

2022-11-03 08:56:43

RediskeyBitmap

2018-07-13 05:31:13

2010-09-03 10:05:00

安全防護(hù)

2024-11-26 08:09:58

2021-06-21 18:23:39

戴爾

2014-05-13 09:05:09

2014-05-13 11:45:38

2025-02-10 09:22:40

2023-04-17 08:04:15

Redis性能內(nèi)存

2024-12-02 01:16:53

2024-11-21 16:47:55

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)