栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

基于redis分布式锁解决缓存击穿【模拟】

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

基于redis分布式锁解决缓存击穿【模拟】

缓存击穿
redis中某一个热点key过期,大量的请求压到数据库上,可以使用分布式锁来解决

redis提供的分布式锁功能
用jedis进行举例,对应的方法为

public Long setnx(String key, String value);//当key不存在时设置,成功返回1(相当于获取锁),失败0
public Long expire(String key, long seconds);//设置key的过期时间
public Long del(String key);//删除key,相当于释放锁

解决缓存击穿的思路:
1.线程请求redis,如果有数据直接返回。
2.redis中没有数据,通过查询的key和表名拼接一个新的key作为锁,存储到redis中(setnx成功返回1,失败返回0)。
3.获取到锁的线程(返回值为1)放行到数据库查询,并且设置一个锁的过期时间(expire),其他线程继续请求redis(轮询一定次数抛异常)。
4.获取到锁的线程从数据库的查询的结果写入redis,并且释放锁(del)。

用一个类来模拟数据库:

class Database {
    //模拟数据被请求的次数
    public static int count = 0;
}

定义一些前置信息:

//redis连接信息
private static final String REDIS_HOST = "192.168.1.9";
private static final Integer REDIS_PORT = 6379;
//模拟热点数据的key
private static final String USER_ID = "40d665c8-7652-468e-8f0f-95666cdc304c";
//数据库表名称
private static final String TABLE_NAME = "tb_user";

定义模拟查询业务的方法:

	//模拟查询业务
    private void selectData() {
        Jedis jedis = getJedis();//从jedisPool中获取jedis对象
        
        //从redis中查询数据
        String result = jedis.get(USER_ID);

        if (!(result == null || "".equals(result.trim()))) {//缓存有数据,直接返回
            System.out.println("直接从缓存中查询出user数据:" + result);
            return;
        }

        //缓存中没有数据 从数据库查
        //分布式锁的key设置为 ‘表名.key’
        String lockKey = TABLE_NAME + "@" + USER_ID;

        //加锁
        Long lock = jedis.setnx(lockKey, "lock");

        if (lock == 1) {//获取锁成功,设置锁超时30s
            jedis.expire(lockKey, 30L);
            System.out.println("获取到了锁...");

            //从数据库中查询数据
            Database.count++;

            //数据库的数据设置到redis中
            jedis.setex(USER_ID,300L, "从数据库中查询到的user对象" + Database.count);
            System.out.println("数据缓存完毕...");

            //添加完成,释放锁
            jedis.del(lockKey);
            System.out.println("释放了锁...");
            return;
        }

        //未成功获取到锁,重试查询redis 5次
        int count = 5;
        while (count > 0) {
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            String value = jedis.get(USER_ID);

            if (value == null || "".equals(value.trim())) {
                count--;
                continue;
            }
            //如果查询到了数据 return
            System.out.println(Thread.currentThread().getName() + "从redis中获取到了数据==>" + value);
            return;

        }
        //重试5次仍然没有从redis中查询到数据
        throw new RuntimeException("重试5次没有查询到数据,抛出到异常处理器进行处理");
    }

开启100个线程测试:

	@Test
    //模拟多线程查询
    public void concurrentSelect() {

        for (int i = 0; i < 100; i++) {
            new Thread(this::selectData).start();
        }

        while (true){}//主线程不能结束
    }

控制台输出的结果如下:

获取到了锁...
数据缓存完毕...
直接从缓存中查询出user数据:从数据库中查询到的user对象1
直接从缓存中查询出user数据:从数据库中查询到的user对象1
释放了锁...
直接从缓存中查询出user数据:从数据库中查询到的user对象1
直接从缓存中查询出user数据:从数据库中查询到的user对象1
...
Thread-57从redis中获取到了数据==>从数据库中查询到的user对象1
Thread-94从redis中获取到了数据==>从数据库中查询到的user对象1
Thread-46从redis中获取到了数据==>从数据库中查询到的user对象1
Thread-81从redis中获取到了数据==>从数据库中查询到的user对象1
Thread-71从redis中获取到了数据==>从数据库中查询到的user对象1

从控制台的结果可以看出,无论多少个线程请求该热点数据的key,也只有一个会压到数据库中,避免了缓存击穿的问题。设置到redis中key的存活时间不宜过长,不然没办法保证最终一致性;也不宜过短,key频繁过期而使用分布式锁会带来不小的性能开销。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/704165.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号