redissonを使用して、分散ロック、デッドロック、読み取り/書き込みロック、セマフォ、ロック、およびキャッシュ整合性ソリューションを解決する方法を実装します

1.Mavenの依存関係を導入します

<!--以后使用 redisson 作为分布锁,分布式对象等功能-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.0</version>
</dependency>

2.構成クラスを追加します

@Configuration
public class MyRedissonConfig {
    
    
    /**
     * 所有对Redisson的使用都是通过RedissonClient对象
     */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws IOException {
    
    
        //1 创建配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://81.68.112.20:6379");

        //2 根据Config创建出RedissonClient实例
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

3.Redisson-ロックテストとRedisson-ロックウォッチドッグの原則-Redissonはデッドロックをどのように解決しますか

    //压力测试
    //redisson锁测试
    @GetMapping("/hello")
    public String hello() {
    
    
        //1 获取一把锁 (只要锁名一样,就是同一把锁)
        RLock lock = redissonClient.getLock("my-lock");

        //2 加锁
//        lock.lock();//阻塞式等待 默认加的锁 都是30s
        //1) 锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s。不用担心担心业务时间长,锁自动过期被删掉
        //2) 加锁的业务只要运行完成,不会给当前锁续期,即使不手动解锁 锁也会在30s以后自动删除

        lock.lock(31, TimeUnit.SECONDS); //10秒自动解锁,自动解锁时间一定要大于业务的执行时间
        //1) 如果我们传递了锁的超时时间,就发送给redis执行脚本进行占锁,默认超时时间就是我们指定的时间
        //2) 如果我们没有指定锁的超时时间,就使用30*1000 【看门狗默认时间】
        //      只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗默认时间】每隔20秒自动续期,续成30s
        //      【看门狗时间】3,10s

        //最佳实战
        //1) 推荐lock.lock(30, TimeUnit.SECONDS); 省掉了续期操作。手动解锁
        try {
    
    
            System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
            Thread.sleep(30000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            System.out.println("解锁...." + Thread.currentThread().getId());
            //3 解锁  假设解锁代码没有运行,redisson会不会出现死锁
            lock.unlock();
        }
        return "hello";
    }

3.Reidsson-読み取り/書き込みロック

さらに面倒なことをせずに、コードに移動してください!

   /**
     * 保证一定能读取到最新数据,修改期间,写锁是一个排他锁(互斥锁,独享锁)读锁是一个共享锁
     * 写锁没释放读锁就必须等待
     * 读 + 读 相当于无锁,并发读,只会在 reids中记录好,所有当前的读锁,他们都会同时加锁成功
     * 写 + 读 等待写锁释放
     * 写 + 写 阻塞方式
     * 读 + 写 有读锁,写也需要等待
     * 只要有写的存在,都必须等待
     * @return String
     */
    @RequestMapping("/write")
    @ResponseBody
    public String writeValue() {
    
    

        RReadWriteLock lock = redission.getReadWriteLock("rw_lock");
        String s = "";
        RLock rLock = lock.writeLock();
        try {
    
    
            // 1、改数据加写锁,读数据加读锁
            rLock.lock();
            System.out.println("写锁加锁成功..." + Thread.currentThread().getId());
            s = UUID.randomUUID().toString();
            try {
    
     TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {
    
     e.printStackTrace(); }
            redisTemplate.opsForValue().set("writeValue",s);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            rLock.unlock();
            System.out.println("写锁释放..." + Thread.currentThread().getId());
        }
        return s;
    }

    @RequestMapping("/read")
    @ResponseBody
    public String readValue() {
    
    
        RReadWriteLock lock = redission.getReadWriteLock("rw_lock");
        RLock rLock = lock.readLock();
        String s = "";
        rLock.lock();
        try {
    
    
            System.out.println("读锁加锁成功..." + Thread.currentThread().getId());
            s = (String) redisTemplate.opsForValue().get("writeValue");
            try {
    
     TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {
    
     e.printStackTrace(); }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            rLock.unlock();
            System.out.println("读锁释放..." + Thread.currentThread().getId());
        }
        return s;
    }

4.Redisson-ロックアップテスト

/**
 * 放假锁门
 * 1班没人了
 * 5个班级走完,我们可以锁门了
 * @return
 */
@GetMapping("/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
    
    
    RCountDownLatch door = redission.getCountDownLatch("door");
    door.trySetCount(5);
    door.await();//等待5个班级都走完了,闭锁才完成
    return "放假了....";
}
@GetMapping("/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id) {
    
    
    RCountDownLatch door = redission.getCountDownLatch("door");
    door.countDown();// 每执行一次计数器减一
    return id + "班的人走完了.....";
}

JUCのCountDownLatchと一致

await()ロックが完了するのを待つ

countDown()カウンターをデクリメントした後、awaitは解放されます

5.Redisson-セマフォテスト

/**
 * 车库停车
 * 3车位
 * @return
 */
@GetMapping("/park")
@ResponseBody
public String park() throws InterruptedException {
    
    
    RSemaphore park = redission.getSemaphore("park");
    boolean b = park.tryAcquire();//获取一个信号,获取一个值,占用一个车位

    return "ok=" + b;
}

@GetMapping("/go")
@ResponseBody
public String go() {
    
    
    RSemaphore park = redission.getSemaphore("park");

    park.release(); //释放一个车位

    return "ok";
}

JUCのセマフォに似ています

6.Redisson-キャッシュ整合性の解決

キャッシュデータの整合性-二重書き込みモード
2つのスレッドの書き込みは最終的に1つのスレッドのみが正常に書き込み、後続の書き込みは以前に書き込まれたデータを上書きします。これにより、ダーティデータ
キャッシュデータの整合性が発生します-失敗モード
3つの接続

1つの接続がデータベースに書き込み、キャッシュを削除します

2番目の接続がデータベースへの書き込み中であり、書き込みが成功していない場合、ネットワーク接続は低速です。

3番目のリンクはデータを直接読み取り、読み取られたデータは最初の接続によって書き込まれたデータです。この時点で、2番目のリンクはデータを正常に書き込み、キャッシュを削除します。3番目のリンクはキャッシュの更新を開始し、次のことがわかります。更新された
キャッシュデータは2番目のキャッシュと一致しています。性的解決策
デュアルライトモードでも障害モードでも、キャッシュの一貫性が失われるという問題が発生します。つまり、複数の強度が同時に更新された場合、どうすればよいですか。 ?

1.ユーザー純度データ(注文データ、ユーザーデータ)の場合、同時実行の可能性は非常に低く、この問題を考慮する必要はほとんどありません。キャッシュされたデータと有効期限、読み取りのアクティブな更新は次の時点でトリガーされます。定期的な間隔
。2 メニュー、商品紹介、その他の基本データの場合は、運河サブスクリプション、binlogメソッドを使用することもできます。3
。キャッシュデータ+有効期限も、キャッシュ上のほとんどのビジネスの要件を満たすのに十分です
。並んだ順序に従って読み取りと書き込み、書き込みと書き込みを同時に行うためのロック、読み取りは関係ありません。読み取り/書き込みロックに適しています(ビジネスデータの中心とは関係なく、ダーティデータを許可することができます)一時的に無視されます)
要約:

キャッシュに入れることができるデータには、リアルタイムで高い整合性の要件があってはなりません。したがって、データをキャッシュするときに有効期限を追加して、毎日最新の値が取得されるようにします。
システムの複雑さを増すように過剰に設計しないでください。
リアルタイムで整合性の高いデータに遭遇した場合は、データベース、たとえそれが遅いとしても。

7.実際の分散ロックケース

/**
     * 使用分布式锁
     * 从数据库查询并封装分类数据
     * <p>
     * 缓存一致性问题
     * 缓存里面的数据如何和数据库里面的数据保持一致?
     * 1) 双写模式 数据库改完后,缓存也改
     * 2) 失效模式 数据库改完后,把缓存删掉
     * <p>
     * 缓存数据一致性-解决方案
     * 无论是双写模式还是失效模式,都会导致缓存的不一致问题,即多个实例同时更新会出事,怎么办?
     * 1、如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可
     * 2、如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
     * 3、缓存数据+过期时间也足够解决大部分业务对于缓存的要求。
     * 4、通过加效保证并发读写,写写的时候按顺序排好队,读读无所谓,所以适合使用读写锁,(业务不关心脏数据,允许临时脏数据可忽略);
     * 总结。
     * 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的,所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可,
     * 我们不应该过度设计,增加系统的复杂性
     * 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。
     * <p>
     * 我们系统的一致性解决方案:
     * 1、缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新
     * 2、读写敌据的时候,加上分布式的读写锁。
     * 经常写,经常读
     */
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock() {
    
    

        //1 锁的名字,锁的粒度,越细越快
        RLock lock = redisson.getLock("catalogJson-lock");
        //加锁
        lock.lock();
        Map<String, List<Catelog2Vo>> dataFromDB;
        try {
    
    
            //业务代码
            dataFromDB = getDataFromDB();
        }finally {
    
    
            lock.unlock();
        }
        return dataFromDB;
    }


    private Map<String, List<Catelog2Vo>> getDataFromDB() {
    
    
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if (!StringUtils.isEmpty(catalogJSON)) {
    
    
            //如果缓存不为null,直接可以返回
            Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
    
    
            });
            return result;
        }
        System.out.println("查询了数据库。。。。。。。");
        List<CategoryEntity> selectList = baseMapper.selectList(null);

        //1 查出所有1级分类
        List<CategoryEntity> level1Catrgorys = getParent_cid(selectList, 0L);

        //2 封装分类
        Map<String, List<Catelog2Vo>> parent_cid = level1Catrgorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
    
    
            //1 拿到每一个1级分类 查到这个1级分类的2级分类
            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
            //2 封装上面的结果
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
    
    
                catelog2Vos = categoryEntities.stream().map(l2 -> {
    
    
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                    //1 找当前二级分类的三级分类封装成vo
                    List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());
                    if (level3Catelog != null) {
    
    
                        List<Catelog2Vo.Catalog3Vo> collect = level3Catelog.stream().map(l3 -> {
    
    
                            //2 封装成指定格式
                            Catelog2Vo.Catalog3Vo catalog3Vo = new Catelog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(collect);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));

        //3 查到的数据库再放入缓存, 将对象转为json放在缓存中
        String jsonString = JSON.toJSONString(parent_cid);
        redisTemplate.opsForValue().set("catalogJSON", jsonString, 1, TimeUnit.DAYS);//1天过期
        return parent_cid;
    }

最初にキャッシュに値があるかどうかを照会し、分散ロックに値が入力されていない場合は、キャッシュに値があるかどうかを確認します。値がない場合は、照会された値がキャッシュに保存され、存在する場合は値の場合、直接返されます。ロックを解除する

おすすめ

転載: blog.csdn.net/u014496893/article/details/113854388