Distributed cache (common problems and solutions)

1) The local cache faces problems

When there are multiple services, the cache of each service can only be used for this service, so that each service has to query the database once, and when the data is updated, only the cache data of a single service will be updated, which will cause data inconsistency Problem
Insert picture description here
All services go to the same redis to get data, you can avoid this problem
Insert picture description here

2) Distributed lock

When the distributed project needs to be locked under high concurrency, but the local lock can only lock the current service, at this time, a distributed lock is required
Insert picture description here

3) The evolution of distributed locks

Fundamental

We can go to a place at the same time to "occupy the pit", and if we do, we will execute logic. Otherwise, you must wait until the lock is released. "Zhankeng" can go to redis, you can go to the database, you can go to any place that everyone can access. Waiting for a way that can spin.

Stage one

Insert picture description here

	public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
    
    
        //阶段一
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
        //获取到锁,执行业务
        if (lock) {
    
    
            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            //删除锁,如果在此之前报错或宕机会造成死锁
            stringRedisTemplate.delete("lock");
            return categoriesDb;
        }else {
    
    
            //没获取到锁,等待100ms重试
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();
        }
    }

public Map<String, List<Catalog2Vo>> getCategoryMap() {
    
    
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String catalogJson = ops.get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)) {
    
    
            System.out.println("缓存不命中,准备查询数据库。。。");
            Map<String, List<Catalog2Vo>> categoriesDb= getCategoriesDb();
            String toJSONString = JSON.toJSONString(categoriesDb);
            ops.set("catalogJson", toJSONString);
            return categoriesDb;
        }
        System.out.println("缓存命中。。。。");
        Map<String, List<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
    
    });
        return listMap;
    }

problem:

1. Setnx is occupied, the business code is abnormal or the program crashes during the page process. No delete lock logic is executed, which causes a deadlock

Solution: Set the automatic expiration of the lock, even if it is not deleted, it will be deleted automatically

Stage two

Insert picture description here

  public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
    
    
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
        if (lock) {
    
    
            //设置过期时间
            stringRedisTemplate.expire("lock", 30, TimeUnit.SECONDS);
            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            stringRedisTemplate.delete("lock");
            return categoriesDb;
        }else {
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();
        }
    }

problem:

1. Setnx is set up, and it is about to set the expiration time, and it is down. It's deadlocked again.

solve:

Setting the expiration time and placeholder must be atomic. redis supports the use of setnx ex command

Stage three

Insert picture description here

public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
    
    
    //加锁的同时设置过期时间,二者是原子性操作
    Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111",5, TimeUnit.SECONDS);
    if (lock) {
    
    
        Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
        //模拟超长的业务执行时间
        try {
    
    
            Thread.sleep(6000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        stringRedisTemplate.delete("lock");
        return categoriesDb;
    }else {
    
    
        try {
    
    
            Thread.sleep(100);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return getCatalogJsonDbWithRedisLock();
    }
}

problem:

1. Delete the lock directly? ? ?
If the lock itself expires due to a long business time, we directly delete it, which may delete the lock held by others.
Solution: When
the lock is occupied, the value is specified as uuid, and each person matches his own lock before deleting it.

Stage four

Insert picture description here

 public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
    
    
        String uuid = UUID.randomUUID().toString();
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
     	//为当前锁设置唯一的uuid,只有当uuid相同时才会进行删除锁的操作
        Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
        if (lock) {
    
    
            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            String lockValue = ops.get("lock");
            if (lockValue.equals(uuid)) {
    
    
                try {
    
    
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                stringRedisTemplate.delete("lock");
            }
            return categoriesDb;
        }else {
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();
        }
    }

problem:

1. If it is judged to be the current value, when the lock is about to be deleted, the lock has expired and someone else has set the new value. Then we delete someone else’s lock

solve:

Delete locks must guarantee atomicity. Use redis+Lua script to complete

Stage Five-Final Form

Insert picture description here

 public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
    
    
        String uuid = UUID.randomUUID().toString();
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        Boolean lock = ops.setIfAbsent("lock", uuid,5, TimeUnit.SECONDS);
        if (lock) {
    
    
            Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
            String lockValue = ops.get("lock");
            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                    "    return redis.call(\"del\",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end";
            stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), lockValue);
            return categoriesDb;
        }else {
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            return getCatalogJsonDbWithRedisLock();
        }
    }

Ensure the atomicity of locking [occupation + expiration time] and deleting lock [judgment + deletion]. The harder thing, the automatic renewal of the lock

Guess you like

Origin blog.csdn.net/songyinyi/article/details/113756432