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(); //采用自旋的方式
}