1. 程式人生 > 實用技巧 >Python爬蟲實戰講解:某東商品評論資訊採集流程分析

Python爬蟲實戰講解:某東商品評論資訊採集流程分析

部署在一個tomcat下

  1. 模擬一個減庫存的程式碼 這段程式碼請求少的話是沒有問題的,但是在高併發的場景下就會出現問題
    @RequestMapping("redisTest")
    public String redisTest() {
        String key = "count";
        //獲取庫存數量
        int count = Integer.parseInt(stringRedisTemplate.opsForValue().get(key));
        //有庫存減少
        if (count > 0) {
            count = count - 1;
            stringRedisTemplate.opsForValue().set(key, String.valueOf(count));
            System.out.println("扣除成功,剩餘數量:" + count);
        } else {
            System.out.println("扣除失敗");
        }
        return "end";
    }
    
  2. 當前redis count的值為500
  3. 使用jmemter 200個執行緒同時訪問

  4. 結果,可以看到有些庫存並沒有減掉
  5. 解決方案,加個方法鎖(必須獲取到鎖才能進入,否則阻塞)
        @RequestMapping("redisTest")
    public synchronized String redisTest() {
        System.out.println(this);
    
        String key = "count";
        //獲取庫存數量
        int count = Integer.parseInt(stringRedisTemplate.opsForValue().get(key));
        //有庫存減少
        if (count > 0) {
            count = count - 1;
            stringRedisTemplate.opsForValue().set(key, String.valueOf(count));
            System.out.println("扣除成功,剩餘數量:" + count);
        } else {
            System.out.println("扣除失敗");
        }
        return "end";
    }
    

叢集

  1. 以上是一個tomcat,如果有兩個tomcat使用nginx代理做負載均衡指向8080和8081埠
  2. 本機啟動兩個客戶端,啟動jmemter測試,可以看到會有重複的剩餘數量

  3. 此時我們就需要使用到redis的分散式鎖來解決,這種也沒有完美的解決方案
/**
 * 分散式鎖
 *    1.  stringRedisTemplate.opsForValue().setIfAbsent("key", "value");if (!flag){return "";}finally { stringRedisTemplate.delete("key");}   如果加鎖後運維部署把程式kill了,其他執行緒永遠進不來
 *   2. stringRedisTemplate.opsForValue().setIfAbsent("key", "value", 10, TimeUnit.SECONDS);finally { stringRedisTemplate.delete("key");}
 *   在超高併發時程式執行時間不一致,第一個執行緒15秒 10秒後鎖自動解除程式還未執行完,第二個執行緒進來獲得鎖後第一個執行緒執行完把鎖刪除了  依次類推,釋放的永遠不是自己的鎖
 *   3. 動態設定鎖的方法String lockValue = UUID.randomUUID().toString();Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lock, lockValue, 10, TimeUnit.SECONDS);if (!flag) { return ""; }
 *   if (lockValue.equals(stringRedisTemplate.opsForValue().get(lock))) { stringRedisTemplate.delete("key"); }, 這裡還是會出現鎖失效的可能
 *   4. 續命   獲取鎖後開一個定時器進行在鎖自動釋放前獲取當前自己鎖是否被釋放 如果沒有證明程式碼還沒執行完,進行續命,若執行完刪除當前定時器
 */

redisson

redis客戶端,內部實現了很多對分散式鎖的支援

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.13.2</version>
</dependency>	
@Bean
public Redisson redisson() {
   Config config = new Config();
   config.useSingleServer().setAddress("redis://192.168.150.110").setDatabase(0);
    return ((Redisson) Redisson.create(config));
 }
    String lockKey = "key";
    RLock redissonLock = redisson.getLock(lockKey);
    try {
     	//內部使用續命的方式 使用lua指令碼保證原子性
        redissonLock.lock();
        String countKey = "count";
        //獲取庫存數量
        int count = Integer.parseInt(stringRedisTemplate.opsForValue().get(countKey));
        //有庫存減少
        if (count > 0) {
            count = count - 1;
            stringRedisTemplate.opsForValue().set(countKey, String.valueOf(count));
            System.out.println("扣除成功,剩餘數量:" + count);
        } else {
            System.out.println("扣除失敗");
        }
    } finally {
        redissonLock.unlock();
    }
    return "end";