cache invalidation problem

1. Cache penetration

Cache penetration refers to querying a data that must not exist . Since the cache is a miss, the database will be queried, but the database does not have such a record. We did not write the null of this query into the cache, which will lead to this non-existent data. Every time data is requested, it must be queried at the storage layer, which loses the meaning of caching.
When the traffic is heavy , the DB may hang up. If someone uses a key that does not exist to frequently attack our application, this is a vulnerability.
solve:
Cache empty results and set a short expiration time.

2. Cache Avalanche

Cache avalanche means that when we set up the cache, we adopt the same expiration time, causing the cache to fail at a certain moment at the same time, all requests are forwarded to the DB, and the instantaneous pressure on the DB is too heavy to avalanche.
solve:
Add a random value based on the original expiration time , such as 1-5 minutes random, so that the repetition rate of each cache expiration time will be reduced, and it will be difficult to trigger collective invalidation events.

3. Cache breakdown

For some keys with an expiration time set , if these keys may be accessed with high concurrency at some point in time, it is a very " hot " data.
At this time, a problem needs to be considered: if the key just expires before a large number of requests come in at the same time, then all data queries for this key will fall to the db , which is called cache breakdown.
solve:
Locking, a large number of concurrency only let one check, others wait, release the lock after checking, other people get the lock, check the cache first, there will be data, no need to go to the db
cache
db
Local locks: synchronized, JUC (Lock), in distributed cases, if you want to lock all distributed services, you must use distributed locks

local lock 

 The local lock process is as follows:

       // 本地锁
        synchronized (this) {
            // 查询数据库前先再查一次缓存
            ......
            System.out.println("查询了数据库");
            // 查询数据库的业务
            ......

            // 在放开锁之前放入redis,因为存放进redis也是一次网络交互,如果放在锁外面也需要时间(此时可能被别的线程乘虚而入又查询数据库)
            ......
        }

 The local lock flow chart is as follows:

Distributed lock - use redis to store key

The lock is the lock, and the key is stored in redis every time --- use setnu (atomic operation), if the storage is successful, then the lock is obtained

stage one

question:
1. Setnx occupies a place, the business code is abnormal or the program crashes during the page process. The deletion lock logic is not executed, which causes a deadlock
solve:
Set the automatic expiration of the lock, even if it is not deleted, it will be deleted automatically

stage two

question:
1. After setnx is set, it is about to set the expiration time, and the system crashes. Deadlocked again.
solve:
Setting expiration and placeholders must be atomic. Redis supports the use of the setnx ex command (set the expiration time while occupying the place)

stage three

question:
1. Delete the lock directly? ? ?
If the lock itself expires due to a long business time, we delete it directly, and it is possible to delete the lock that others are holding.
solve:
When occupying the lock, the value is specified as uuid , and each person matches their own lock to delete

stage four

question:
1. If it happens to be the current value, when the lock is about to be deleted, the lock has expired, and someone else has already set it to a new value. Then what we delete is someone else's lock
solve:
Deletion locks must guarantee atomicity. Use redis+Lua script to complete
Final version:
1. The lock stored by each thread must have its own identification (uuid, etc.)
2. When occupying the lock, the storage and setting time need to be atomic (EX NX)
3. When deleting a lock, judge whether it is your own lock and the deletion operation needs to be atomic (LUA script, script succeeds or fails together)
        // TODO 本地锁:synchronized,JUC(Lock),在分布式情况下,想要锁住所有的分布式服务,必须使用分布式锁

        // 分布式锁 redis存lock Key来实现
        // 先去redis占坑
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {
            Map<String, List<Catelog2Vo>> result ;
            //占坑成功
            try {
                result = getDataFromDb();
            } finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                // 删除key
                Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
                        Arrays.asList("lock"), uuid);
            }
            return result;
        } else {
            // 加锁失败  重试
            // 这里也可休眠一段时间
            return getCatelogJsonFromDBWithRedisLock(); //采用自旋的方式
        }

Guess you like

Origin blog.csdn.net/m0_62946761/article/details/132136201