Let's start by describing what problems would be caused to our cache in the case of high concurrency?
1. Cache Breakdown: In the case of high concurrency, when a hot key fails, a large number of requests will be made to access our data, such as when a commodity is killed in seconds. 2. Cache avalanche: refers to a time when a large number of key s expire, which can be resolved by setting different expiration times. 3. Cache Penetration: A malicious attack that exploits the feature that the cache does not go to query the database can be done by setting a key as the key when we query the database and find that there is no data in the database, but with an empty string or null value and a short expiration time.
Resolve Cache Penetration: This requires configuring distributed lock resolution for cache breakdown
if (pmsSkuInfo != null) { /*4.mysql Return the results you want to query to redis*/ System.out.println("ip by:" + ip + "Users:" + Thread.currentThread().getName() + "mysql Return data results to redis slow //"In memory"; jedis.set("sku:" + skuId + ":info", JSON.toJSONString(pmsSkuInfo)); } else { /*The sku does not exist in the database*/ /*To prevent cache penetration (large number of requests accessing database mysql), string null values or empty values to redis , And set 3 minutes for automatic destruction of null values or empty strings*/ jedis.setex("sku:" + skuId + ":info", 60 * 3, JSON.toJSONString("")); }
This article focuses on the solution for cache breakdown (implementation of Redis-based distributed locks).
Redis's distributed lock has three issues to solve: A. Setting locks and setting lock timeouts must be atomic.Through the setex command. B. Solve the problem of mistakenly deleting a lock: use value (set a unique token) to determine if it is your own lock. C. Determining and deleting locks by determining whether they are owned by you are two separate operations, not atomicity (you can use a lua script).
@Override public PmsSkuInfo getSkuById(String skuId, String ip) { System.out.println("ip by:" + ip + "Users:" + Thread.currentThread().getName() + "Request for access to commodity details"); PmsSkuInfo pmsSkuInfo = new PmsSkuInfo(); /*1.Connection Cache*/ Jedis jedis = redisUtil.getJedis(); /*2.Query Cache*/ String skuKey = "sku:" + skuId + ":info"; String skuJson = jedis.get(skuKey); /*StringUtils.isEmpty(skuJson)*/ if (skuJson != null && !skuJson.equals("")) { /*Converts json to a java object class, but skuJson cannot be empty*/ pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class); System.out.println("ip by:" + ip + "Users:" + Thread.currentThread().getName() + "from redis Get item details in the cache"); } else { /*3.If not in the cache, query mysql*/ System.out.println("ip by:" + ip + "Users:" + Thread.currentThread().getName() + "Cache distributed locks were not requested in the cache:" + "sku:" + skuId + ":lock"); /** * Cache penetration issues: (1). nx distributed locks based on redis */ /*Set Distributed Lock Return Value to OK*/ String token = UUID.randomUUID().toString(); String OK = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10 * 1000); if (OK != null && !OK.equals("") && OK.equals("OK")) { /*Successful setup with access to database within 10 seconds of expiration*/ System.out.println("ip by:" + ip + "Users:" + Thread.currentThread().getName() + "Successfully acquired the distributed lock, with access to the database within 10 seconds of expiration" + "sku:" + skuId + ":lock"); pmsSkuInfo = getSkuByIdFromDb(skuId); /*Test code:*/ /*try { Thread.sleep(5*1000); } catch (InterruptedException e) { e.printStackTrace(); }*/ if (pmsSkuInfo != null) { /*4.mysql Return the results you want to query to redis*/ System.out.println("ip by:" + ip + "Users:" + Thread.currentThread().getName() + "mysql Return data results to redis In Cache"); jedis.set("sku:" + skuId + ":info", JSON.toJSONString(pmsSkuInfo)); } else { /*The sku does not exist in the database*/ /*To prevent cache penetration (a large number of requests accessing database mysql), null values or empty strings are given to redis, and null values or empty strings are set for automatic destruction for 3 minutes*/ jedis.setex("sku:" + skuId + ":info", 60 * 3, JSON.toJSONString("")); } /*Release the MySQL lock in your hand after accessing MySQL*/ /*Question 1: To avoid that users who get locks still do not get data from the database after the expiration time, the locks have been automatically destroyed (expiration time has expired) Then the next user gets the lock and starts accessing the database, but when the last user gets the lock, he will come back to perform Delete Lock. jedis.del("sku:" + skuId + ":lock");Command, at which point the deleted lock is the lock of the user currently being accessed, causing the current user to fail to access. So when the current user gets the lock, the last user comes back to do the Delete Lock jedis.del("sku:" + skuId + ":lock"); when commanding The last user should compare the newToken at this point with the random token that he or she got the lock assignment. The same user can delete the lock Verify that you deleted your lock "sku:" + skuId + ": info" =token k, V structure */ String newToken = jedis.get("sku:" + skuId + ":lock"); if (newToken != null && !newToken.equals("") && newToken.equals(token)) { /*Question 2: When the code just got here, my own token expired, and then deleted the next user's token in if. What can I do? You can use a lua script to delete the key while querying it to prevent high concurrency from happening unexpectedly! String newToken = jedis.get("sku:" + skuId + ":info"); if(newToken!=null&&!newToken.equals("")&&newToken.equals(token)){ jedis.del("sku:" + skuId + ":lock"); }Change to the following code: lua script, delete the key while querying it! String script = "if redis.call('get' , KEYS[1]==ARGV[1] then return redis.call('del',KEYS[1])) else return o end"; jedis.eval(script,Collections.singletonList("lock"),Conllections.singletonList(token)); */ jedis.del("sku:" + skuId + ":lock"); System.out.println("ip by:" + ip + "Users:" + Thread.currentThread().getName() + "In Access MySQL After that, put the mysql Lock released" + "sku:" + skuId + ":lock"); } } else { /*Setup failed, spin (the thread tried to access the getSkuById method again after a few seconds of sleep)*/ System.out.println("ip by:" + ip + "Users:" + Thread.currentThread().getName() + "Didn't get the distributed lock, spin (the thread retries access after a few seconds of sleep) getSkuById Method)"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } /*return No new threads will be generated, this is spin.If no return is added, a new getSkuById() "orphan" thread will be generated.*/ return getSkuById(skuId, ip); } } jedis.close();/*Be sure to close jidis in the last step*/ return pmsSkuInfo; }