SpringBootの使用Redisによる分散ロック(秒殺システム)


一、Redis分布式錠概念編
Redisの公式推奨のRedissonをそのままredisの分散錠として採用することを提案します。
1.1、なぜ分散錠を使うのですか?
    私達はアプリケーションを開発する時、ある共有変数にマルチスレッド同期を行う必要がある場合、私達が習ったJavaマルチスレッドの18種類の武芸を使って処理してもいいです。完璧に実行できます。Bugはありません。
    これは単一マシンアプリケーションです。つまり、すべての要求は現在のサーバーのJVM内部に割り当てられます。そしてオペレーティングシステムのスレッドにマッピングして処理します。この共有変数はこのJVM内部のメモリ空間だけです。
    その後、業務が発展して、クラスタを作る必要があります。一つのアプリケーションはいくつかのマシンに配備してから負荷の均衡を取る必要があります。

    上の図では、変数AはJVM 1、JVM 2、JVM 3の三つのJVMメモリに存在しています(この変数Aは主に一つのクラスのメンバー変数です。状態のあるオブジェクトです。例えば、UserControllerコントローラの中の整形タイプのメンバー変数)。制御を加えないと、変数Aは同時にJVMにメモリを割り当てられます。三つの要求が送られてきましたが、この変数の操作は明らかに間違っています。同時に送られてこなくても、三つの要求はそれぞれ三つの異なるJVMメモリ領域のデータを操作します。変数Aの間に共有が存在しなくて、視認性もありません。処理の結果も違います。
    もし私達の業務にこのシーンが存在すれば、私達はこの問題を解決する方法が必要です。
    一つの方法または属性が高併発の場合の同じ時間は同じスレッドでしか実行できないことを保証するために、従来の単体アプリケーションの単一機配置の場合、関連するAPI(RentrantrantLockやSynchronizedなど)をJava同時処理して相互反発制御を行うことができる。単一マシン環境では、Javaには多くの同時処理関連のAPIが提供されている。しかし、事業の発展の必要に応じて、オリジナルの単一のマシンによって配備されたシステムが分散クラスタシステムに進化した後、分散システムのマルチスレッド、マルチプロセスによって、異なるマシンに分散されるため、これは元の単一マシンの配備の場合の同時制御ロックポリシーを無効にし、単純なJava APIは分散式ロックの能力を提供できない。この問題を解決するためには、共有リソースのアクセスを制御するためにJVMの相互反発メカニズムが必要です。
1.2、分布式錠はどのような条件を備えていますか?
    分散式のロックの3つの実現方法を分析する前に、まず分散式のロックにはどのような条件があるべきかを調べてください。
    1、分散システムの環境下で、一つの方法は同じ時間に一つのマシンのスレッドによってのみ実行されます。
    2、高利用可能な取得錠と解除錠。
    3、高性能の取得ロックと解除ロック。
    4、再入力可能特性を備えている。
    5、ロックが効かないメカニズムを備え、デッドロックを防止する。
    6、非ブロッキング機能を備えています。つまり、ロックを取得していないと直接にロックを取得できません。
1.3、分散式錠の3つの実現方式
    現在はほとんどの大手サイトやアプリケーションが分散的に展開されており、分散的なシーンでのデータ整合性の問題はずっと重要な話題です。分布式のCAP理論は「どの分散システムも同時に整合性、利用可能性、パーティションフォーメーション性を満たしていないので、最大2つの項目を同時に満たすしかない」と教えてくれます。だから、多くのシステムは設計の初めにこの三つのものを取捨選択します。インターネットの分野のほとんどのシーンでは、システムの高利用性と引き換えに強い一致を犠牲にする必要があります。システムはしばしば「最終的な一致」を保証する必要があります。この最終時間はユーザーが納得できる範囲内であれば大丈夫です。
    多くの場面において、私たちはデータの最終的な一致を保証するために、分散式事務、分散式錠など多くの技術案をサポートする必要があります。ある時は、同じ時間に同じスレッドでしか実行できない方法を保証する必要があります。
    1、データベースに基づいて分散式のロックを実現する。
    2、キャッシュ(Redisなど)に基づいて分散錠を実現する。
    3、Zookeeperに基づいて分散式のロックを実現する;
    この3つの案がありますが、業務によっては自分の状況に合わせて選択します。彼らの間にベストがないのはもっと適しているだけです。
二、Redis分布式錠実戦編
2.1、導入依存

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
2.2、配置Redis配置情報

spring
    redis:
      port: 6379
      host: 127.0.0.1
      password:
      database: 0
2.3、RedisConfig属性を配置し、FastJSONを使ってあなたの対象を序列化するなら、前に書いた文章を見てもいいです。

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate initRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws Exception {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
2.4、RedisLockツール類を書く

/**
* @Description //    Redis       
*              Redis          ,         Redisson        
* @Date $ $
* @Author huangwb
**/
 
@Component
public class RedisLockCommon {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
 
    /**
     * Redis     
     *
     * @param key
     * @param value
     * @return
     */
    public Boolean tryLock(String key, String value) {
        if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {
            return true;
        }
        String currentValue = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(currentValue) && Long.valueOf(currentValue) < System.currentTimeMillis()) {
            //                                               
            String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);
            if (StringUtils.isNotEmpty(oldValue) && oldValue.equals(currentValue)) {
                return true;
            }
        }
        return false;
    }
 
 
    /**
     * Redis     
     *
     * @param key
     * @param value
     */
    public void unlock(String key, String value) {
        String currentValue = stringRedisTemplate.opsForValue().get(key);
        try {
            if (StringUtils.isNotEmpty(currentValue) && currentValue.equals(value)) {
                stringRedisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
        }
    }
}
2.5、在庫を減らす操作

@Override
    public boolean decrementProductStore(Long productId, Integer productQuantity) {
        String key = "dec_store_lock_" + productId;
        long time = System.currentTimeMillis();
        try {
            //      
            if (!redisLock.tryLock(key, String.valueOf(time))) {
                return false;
            }
            ProductInfo productInfo = productInfoMapper.selectByPrimaryKey(productId);
            //      
            if (productInfo.getProductStock() == 0) {
                return false;
            }
            //     
            productInfo.setProductStock(productInfo.getProductStock() - 1);
            productInfoMapper.updateByPrimaryKey(productInfo);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            //  
            redisLock.unlock(key, String.valueOf(time));
        }
        return true;
 
    }
2.6、テストインターフェース

@GetMapping("test")
public String createOrderTest() {
    if (!productInfoService.decrementProductStore(1L, 1)) {
        return "    ";
    }
    OrderMaster orderMaster = new OrderMaster();
    //   
    orderMaster.setOrderStatus(0);
    //   
    orderMaster.setPayStatus(0);
    orderMaster.setBuyerName("  ");
    orderMaster.setBuyerAddress("    ");
    orderMaster.setBuyerPhone("18692794847");
    orderMaster.setOrderAmount(BigDecimal.ZERO);
    orderMaster.setCreateTime(DateUtils.getCurrentDate());
    orderMaster.setOrderId(UUID.randomUUID().toString().replaceAll("-", ""));
    orderMasterService.insert(orderMaster);
}
2.7、アプリabを使ってhttpインターフェーステストができます。
    具体的な文章はこの文章を見てもいいです。
三、結び
ここで、SpringBootに関する記事を紹介します。SprigBoot Redisを使って布式のロックを実現します。これに関連して、Spring Boot Redisの布式ロックの内容は以前の文章を検索してください。また、下記の関連記事をご覧ください。これからもよろしくお願いします。
    文章の概観編はhttps://www.jb51.net/article/177250.htmから来ています。