Spring優雅な統合Redisキャッシュ

11610 ワード

「明ちゃん、マルチシステムのセッション共有、どうやって処理しますか?」「Redisキャッシュだ!」「明ちゃん、簡単なメッセージキューを実現したいの?」「Redisキャッシュだ!」
「明ちゃん、分散ロックって何か案がありますか?」「Redisキャッシュだ!」「明ちゃん、会社のシステムはカタツムリのように応答して、どうしますか?」「Redisキャッシュだ!」
研究の精神に基づいて、私たちは明ちゃんの4番目の問題を分析します.
準備:Idea 2019.03/Gradle6.0.1/Maven3.6.3/JDK11.0.4/Lombok0.28/SpringBoot2.2.4RELEASE/mybatisPlus3.3.0/Soul2.1.2/
Dubbo2.7.5/Druid1.2.21/Zookeeper3.5.5/Mysql8.0.11/Vue2.5/Redis3.2
難易度:新米--戦士--老兵--マスター
ターゲット:Spring優雅にRedisを統合してデータベースキャッシュを行う手順:様々な問題に直面し、同時に時効性を維持するために、私はできるだけ最新のソフトウェアバージョンを使用します.ソースアドレス:https://github.com/xiexiaobiao/vehicle-shop-admin1結論:Redisキャッシュは金弾ではありません.システムDBに圧力がなければ、システム性能のボトルネックはDBにありません.キャッシュ層を強要することはお勧めしません.
ビジネスの複雑さを増大させる:同じキャッシュは、受注キャッシュなどのすべての関連メソッドによって上書きされる必要があります.受注データの更新に関連するメソッドがキャッシュロジック処理を行う限り.また,KVストレージでは,メソッドごとに返されるタイプが異なるため,複数のキャッシュプールが必要となるが,各メソッドのバックグラウンドのデータには関連があり,1つのメソッドが必要となることが多い.
関連する複数のキャッシュを処理して、メッシュ処理ロジックを形成します.
  • 同時問題がある:キャッシュはロック機構がなく、BスレッドはDB更新を行い、同時にAスレッドはデータを要求し、キャッシュ中に存在すればすぐに戻るが、Bスレッドはまだキャッシュに更新されていない.
    キャッシュとDBが一致しない;あるいはAスレッドBスレッドはDB更新されていますが、書き込みキャッシュの順序が逆転し、キャッシュとDBが一致しないこともありますので、官君がどう解決するか考えてみてください.
    3.メモリの消耗:小さいデータ量は直接すべてメモリに入ることができて、しかし大量のデータはすべて直接Redisに入ることができなくて、機械は耐えられません!DBデータインデックスのみをキャッシュし、
    「ブロンフィルタ」は無効な要求をブロックし、有効な要求はDBクエリーに行きます.
  • キャッシュ位置:キャッシュ注記の方法、実行タイミングはできるだけDBに近づき、先端から離れ、dao層を置くなど、官君を見てなぜか考えてみてください.

  • 適用シーン:1.DBがシステム性能のボトルネックであることを確認し、2.データ内容が安定し、低周波更新、高周波クエリー、例えば履歴受注データ;3.ホットスポットデータ、例えば新上場商品;
    2ステップ2.1の原理ここで私が言ったのは注釈モードで、4つの注釈があって、SpringCacheキャッシュの原理すなわち注釈+ブロックorg.springframework.cache.interceptor.CacheInterceptorは方法をブロック処理する:
    @Cacheable:クラスまたはメソッドにタグ付けできます.クラスにタグを付けると、クラスのすべてのメソッドの戻り値がキャッシュされます.メソッドを要求する場合は、キャッシュでkeyマッチングを行い、存在する場合はキャッシュデータを直接取り出して返します.主要パラメータ表:
    @CacheEveict:対応するデータをキャッシュから削除します.主要パラメータ表:
    @CachePut:メソッドはキャッシュ機能をサポートします.@Cacheableとは異なり、@CachePutマークアップを使用する方法では、実行前にキャッシュに以前に実行した結果が存在するかどうかをチェックすることはありません.
    このメソッドは、毎回実行され、指定したキャッシュに実行結果がキー値ペアとして格納されます.主要パラメータ表:
    @Caching:複数のCache注記を組み合わせて使用します.たとえば、ユーザーが新規に追加された場合、同時に他のキャッシュを削除し、ユーザー情報キャッシュ、すなわち以上の3つの注記のセットを更新します.
    2.2コード化プロジェクトには5つのマイクロサービスがあり、customerサービスモジュールを改造しただけです.
    依存を導入するgradleファイル:
    Redisプロファイル、resources/config/application-dev.ymlファイル:
    ファイル:com.biao.shop.customer.conf.RedisConf
    @Configurationbr/>@EnableCachingpublic class RedisConf {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
        return RedisCacheManager.create(redisConnectionFactory);
    }
    
    @Bean
    public CacheManager cacheManager() {
        // configure and return an implementation of Spring's CacheManager SPI
         SimpleCacheManager cacheManager = new SimpleCacheManager();
         cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
         return cacheManager;
    }
    
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory){
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        //   key     
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //   value     ,  Jackson 2,       JSON
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
                new Jackson2JsonRedisSerializer(Object.class);
        // json    ,   ,     json  hashmap
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(mapper);
        return redisTemplate;
    }

    }以上のコード解析:1.キャッシュマネージャCacheManagerを宣言すると、スナップショット(aspect)が作成され、Springキャッシュのスナップショットがトリガーされます.クラスまたはメソッドで使用する注記およびキャッシュのステータスに基づいて、キャッシュからデータを取得し、キャッシュにデータを追加するか、またはバッファから値を削除します.RedisTemplateはRedisコネクタであり、実際にはjedisクライアントである.
    ファイル:com.biao.shop.customer.impl.ShopClientServiceImpl
    @org.springframework.stereotype.Servicebr/>@Slf4jpublic class ShopClientServiceImpl extends ServiceImpl implements ShopClientService {
    private final Logger logger = LoggerFactory.getLogger(ShopClientServiceImpl.class);
    
    private ShopClientDao shopClientDao;
    
    @Autowired
    public ShopClientServiceImpl(ShopClientDao shopClientDao){
        this.shopClientDao = shopClientDao;
    }
    
    @Override
    public String getMaxClientUuId() {
        return shopClientDao.selectList(new LambdaQueryWrapper()
                .isNotNull(ShopClientEntity::getClientUuid).orderByDesc(ShopClientEntity::getClientUuid))
                .stream().limit(1).collect(Collectors.toList())
                .get(0).getClientUuid();
    }
    
    @Override
    @Caching(put = @CachePut(cacheNames = {"shopClient"},key = "#root.args[0].clientUuid"),
            evict = @CacheEvict(cacheNames = {"shopClientPage","shopClientPlateList","shopClientList"},allEntries = true))
    public int createClient(ShopClientEntity clientEntity) {
        clientEntity.setGenerateDate(LocalDateTime.now());
        return shopClientDao.insert(clientEntity);
    }
    
    /** */
    @Override
    @CacheEvict(cacheNames = {"shopClient","shopClientPage","shopClientPlateList","shopClientList"},allEntries = true)
    public int deleteBatchById(Collection ids) {
        logger.info("deleteBatchById   Redis  ");
        return shopClientDao.deleteBatchIds(ids);
    }
    
    @Override
    @CacheEvict(cacheNames = {"shopClient","shopClientPage","shopClientPlateList","shopClientList"},allEntries = true)
    public int deleteById(int id) {
        logger.info("deleteById   Redis  ");
        return shopClientDao.deleteById(id);
    }
    
    @Override
    @Caching(evict = {@CacheEvict(cacheNames = "shopClient",key = "#root.args[0]"),
            @CacheEvict(cacheNames = {"shopClientPage","shopClientPlateList","shopClientList"},allEntries = true)})
    public int deleteByUUid(String uuid) {
        logger.info("deleteByUUid   Redis  ");
        QueryWrapper qw = new QueryWrapper<>();
        qw.eq(true,"uuid",uuid);
        return shopClientDao.delete(qw);
    }
    
    @Override
    @Caching(put = @CachePut(cacheNames = "shopClient",key = "#root.args[0].clientUuid"),
            evict = @CacheEvict(cacheNames = {"shopClientPage","shopClientPlateList","shopClientList"},allEntries = true))
    public int updateClient(ShopClientEntity clientEntity) {
        logger.info("updateClient   Redis  ");
        clientEntity.setModifyDate(LocalDateTime.now());
        return shopClientDao.updateById(clientEntity);
    }
    
    @Override
    @CacheEvict(cacheNames = {"shopClient","shopClientPage","shopClientPlateList","shopClientList"},allEntries = true)
    public int addPoint(String uuid,int pointToAdd) {
        ShopClientEntity clientEntity =  this.queryByUuId(uuid);
        log.debug(clientEntity.toString());
        clientEntity.setPoint(Objects.isNull(clientEntity.getPoint()) ? 0 : clientEntity.getPoint() + pointToAdd);
        return shopClientDao.updateById(clientEntity);
    }
    
    @Override
    @Cacheable(cacheNames = "shopClient",key = "#root.args[0]")
    public ShopClientEntity queryByUuId(String uuid) {
        logger.info("queryByUuId    Redis  ");
        QueryWrapper qw = new QueryWrapper<>();
        qw.eq(true,"client_uuid",uuid);
        return shopClientDao.selectOne(qw);
    }
    
    @Override
    @Cacheable(cacheNames = "shopClientById",key = "#root.args[0]")
    public ShopClientEntity queryById(int id) {
        logger.info("queryById    Redis  ");
        return shopClientDao.selectById(id);
    }
    
    @Override
    @Cacheable(cacheNames = "shopClientPage")
    public PageInfo listClient(Integer current, Integer size, String clientUuid, String name,
                                                 String vehiclePlate, String phone) {
        logger.info("listClient    Redis  ");
        QueryWrapper qw = new QueryWrapper<>();
        Map map = new HashMap<>(4);
        map.put("client_uuid",clientUuid);
        map.put("vehicle_plate",vehiclePlate);
        map.put("phone",phone);
        // "name"     
        boolean valid = Objects.isNull(name);
        qw.allEq(true,map,false).like(!valid,"client_name",name);
        PageHelper.startPage(current,size);
        List clientEntities = shopClientDao.selectList(qw);
        return  PageInfo.of(clientEntities);
    }
    
    // java Stream
    @Override
    @Cacheable(cacheNames = "shopClientPlateList")
    public List listPlate() {
        logger.info("listPlate    Redis  ");
        List clientEntities =
                shopClientDao.selectList(new LambdaQueryWrapper().isNotNull(ShopClientEntity::getVehiclePlate));
        return clientEntities.stream().map(ShopClientEntity::getVehiclePlate).collect(Collectors.toList());
    }
    
    @Override
    @Cacheable(cacheNames = "shopClientList",key = "#root.args[0].toString()")
    public List listByClientDto(ClientQueryDTO clientQueryDTO) {
        logger.info("listByClientDto    Redis  ");
        QueryWrapper qw = new QueryWrapper<>();
        boolean phoneFlag = Objects.isNull(clientQueryDTO.getPhone());
        boolean clientNameFlag = Objects.isNull(clientQueryDTO.getClientName());
        boolean vehicleSeriesFlag = Objects.isNull(clientQueryDTO.getVehicleSeries());
        boolean vehiclePlateFlag = Objects.isNull(clientQueryDTO.getVehiclePlate());
        //  null          
        qw.eq(!phoneFlag,"phone",clientQueryDTO.getPhone())
                .like(!clientNameFlag,"client_name",clientQueryDTO.getClientName())
                .like(!vehicleSeriesFlag,"vehicle_plate",clientQueryDTO.getVehiclePlate())
                .like(!vehiclePlateFlag,"vehicle_series",clientQueryDTO.getVehicleSeries());
        return shopClientDao.selectList(qw);
    }

    }以上のコード解析:
  • メソッドの戻りタイプが異なるため、5つのキャッシュ2が確立する.SpEL式#rootを使用する.Args[0]取得方法第1パラメータ、#resultを用いて戻り対象を取得する
  • キー3を構築するために使用する.@Cacheableでは#resultを使用してオブジェクトをkey値として返すことができません.queryById(int id)メソッドのように、この注記はメソッドの実行前にNPEになります.
    キャッシュマッチングに入り、#resultはメソッド実行後に4を計算する.Caching注記は、deleteByUUId(String uuid)メソッドのような複数の注記を一度にセットして、ユーザーレコードを削除することができます.
    同時にshopClientを更新し、他のいくつかのキャッシュをクリアする必要があります.
    2.3テストはプロジェクト全体を実行し、起動順序:souladmin->soulbootstrap->zookeeper->authority->customer->stock->order->business->vueフロントエンド、
    バックエンド管理ページに入る:ページごとに顧客情報を閲覧し、それぞれページサインをクリックする:
    キャッシュshopClientPageには4つのデータがキャッシュされていることがわかります.key値はメソッドのパラメータの組み合わせです.また、ページチェックをクリックすると、システムのバックグラウンドにDB要求記録出力がなく、キャッシュが直接使用されていることを示します.
    お客様の情報を編集し、2つを自由に開きました.
    キャッシュshopClientByIdに2つのオブジェクトが追加されていることがわかります.編集をクリックすると、システムのバックグラウンドにDBクエリ記録出力がなく、キャッシュが直接使用されていることを示します.
    お客様を条件に問い合わせるには、次の手順に従います.
    キャッシュshopClientPageが1つ増加していることがわかります.key値が異なるため、1つのキャッシュデータとして独立し、複数回のクエリーを行うと、システムのバックグラウンドにDBクエリーSQL出力がなく、キャッシュが直接使用されていることを示します.
    新規顧客:
    shopClientPageキャッシュが空になり、同時に1つのshopClientキャッシュのオブジェクトが追加されます.つまり、複数のキャッシュプール操作が同時に行われます.
    質問:前に述べた2つの質問:
    1.マルチスレッド問題、DBトランザクションメカニズムに合わせて、キャッシュ遅延二重削除を行うことができ、毎回DB更新前に、キャッシュ中のオブジェクトを削除し、更新後、もう一度キャッシュ中のオブジェクトを削除する.
    2.キャッシュ方法の位置問題は、フロントエンドからバックエンドまでの「逆ピラミッドモデル」に従って、フロントエンドに近づくほど、キャッシュデータオブジェクトが他のビジネスロジックによって更新される可能性が高くなり、DBに近づくほど、DBの更新ごとにキャッシュロジックによって感知されることができるようになる.
    全文完了!