- First, a simple Lua script code is written to obtain the value value corresponding to the lock, check whether it is equal to requestId, and delete the lock (unlock) if it is equal;
- Then, pass the Lua code to jedis eval() method, and assign the parameter KEYS[1] to lockKey and ARGV[1] to requestId. The eval () method is to hand over the Lua code to the Redis server for execution.
1.3 case (scenario in which multiple families receive rewards)
The key code is released here. The detailed runnable code can be downloaded from the address at the end of the text.
1.3. 1 Preparation
This case simulates that multiple people in the family can receive a reward, but only one person can receive it successfully and can't receive it again (the demand of the reward module has been made before)
- family_reward_record table
CREATE TABLE `family_reward_record` ( `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT 'Primary key id', `family_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'Trade name', `reward_type` int(10) NOT NULL DEFAULT '1' COMMENT 'Commodity inventory quantity', `state` int(1) NOT NULL DEFAULT '0' COMMENT 'Commodity status', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Warehousing time', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=270 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Family receive reward form (only one person in the family can receive it successfully, and can't receive it again)';
- application.yml
spring: datasource: url: jdbc:mysql://47.98.178.84:3306/dev username: dev password: password driver-class-name: com.mysql.jdbc.Driver redis: host: 47.98.178.84 port: 6379 password: password timeout: 2000 # mybatis mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: cn.van.mybatis.demo.entity
1.3. 2 core implementation
- Jedis stand-alone configuration class - redisconfig java
@Configuration public class RedisConfig extends CachingConfigurerSupport { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.timeout}") private int timeout; @Bean public JedisPool redisPoolFactory() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); if (StringUtils.isEmpty(password)) { return new JedisPool(jedisPoolConfig, host, port, timeout); } return new JedisPool(jedisPoolConfig, host, port, timeout, password); } @Bean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, Visibility.ANY); objectMapper.enableDefaultTyping(DefaultTyping.NON_FINAL); Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); jsonRedisSerializer.setObjectMapper(objectMapper); redisTemplate.setDefaultSerializer(jsonRedisSerializer); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
- Distributed lock tool class - redisdistributedlock java
@Component public class RedisDistributedLock { /** * Successfully obtained lock token */ private static final String LOCK_SUCCESS = "OK"; /** * Successfully unlocked sign */ private static final Long RELEASE_SUCCESS = 1L; @Autowired private JedisPool jedisPool; /** * redis Data store expiration time */ final int expireTime = 500; /** * Attempt to acquire distributed lock * @param lockKey lock * @param lockValue Request ID * @return Is it successful */ public boolean tryLock(String lockKey, String lockValue) { Jedis jedis = null; try{ jedis = jedisPool.getResource(); String result = jedis.set(lockKey, lockValue, "NX", "PX", expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } } finally { if(jedis != null){ jedis.close(); } } return false; } /** * Release distributed lock * @param lockKey lock * @param lockValue Request ID * @return Release successful */ public boolean unLock(String lockKey, String lockValue) { Jedis jedis = null; try { jedis = jedisPool.getResource(); String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue)); if (RELEASE_SUCCESS.equals(result)) { return true; } } finally { if(jedis != null){ jedis.close(); } } return false; } }
- When unlocked: families with {familyId = 1} receive rewards at the same time
@Override public HttpResult receiveAward() { Long familyId = 1L; Map<String, Object> params = new HashMap<String, Object>(16); params.put("familyId", familyId); params.put("rewardType", 1); int count = familyRewardRecordMapper.selectCountByFamilyIdAndRewardType(params); if (count == 0) { FamilyRewardRecordDO recordDO = new FamilyRewardRecordDO(familyId,1,0,LocalDateTime.now()); int num = familyRewardRecordMapper.insert(recordDO); if (num == 1) { return HttpResult.success(); } return HttpResult.failure(-1, "Record insertion failed"); } return HttpResult.success("The record already exists"); }
- Implementation of locking: families with {familyId = 2} receive rewards at the same time
@Override public HttpResult receiveAwardLock() { Long familyId = 2L; Map<String, Object> params = new HashMap<String, Object>(16); params.put("familyId", familyId); params.put("rewardType", 1); int count = familyRewardRecordMapper.selectCountByFamilyIdAndRewardType(params); if (count == 0) { // If there is no record, create a collection record FamilyRewardRecordDO recordDO = new FamilyRewardRecordDO(familyId,1,0,LocalDateTime.now()); // Key of distributed lock (familyid + rewardtype) String lockKey = recordDO.getFamilyId() + "_" + recordDO.getRewardType(); // Value of distributed lock (unique value) String lockValue = createUUID(); boolean lockStatus = redisLock.tryLock(lockKey, lockValue); // Lock occupied if (!lockStatus) { log.info("The lock is occupied"); return HttpResult.failure(-1,"fail"); } // No matter how many requests are locked, only one request can get the lock for insertion log.info("Got the lock, the current moment:{}",System.currentTimeMillis()); int num = familyRewardRecordMapper.insert(recordDO); if (num != 1) { log.info("Data insertion failed!"); return HttpResult.failure(-1, "Data insertion failed!"); } log.info("Data insertion succeeded! Ready to unlock..."); boolean unLockState = redisLock.unLock(lockKey,lockValue); if (!unLockState) { log.info("Unlocking failed!"); return HttpResult.failure(-1, "Unlocking failed!"); } log.info("Unlock succeeded!"); return HttpResult.success(); } log.info("The record already exists"); return HttpResult.success("The record already exists"); } private String createUUID() { UUID uuid = UUID.randomUUID(); String str = uuid.toString().replace("-", "_"); return str; }
1.3. 3 test
I use the JMeter tool for testing. The conditions of locking and unlocking are set to five concurrent requests.
1.3. 3.1 unlocked
/** * Family members receive rewards (unlocked) * @return */ @PostMapping("/receiveAward") public HttpResult receiveAward() { return redisLockService.receiveAward(); }
- Request method: POST
- Request address: http://localhost:8080/redisLock/receiveAward
- Return result: five records are inserted
1.3. 3.2 locking
/** * Family members receive rewards (locked) * @return */ @PostMapping("/receiveAwardLock") public HttpResult receiveAwardLock() { return redisLockService.receiveAwardLock(); }
- Request method: POST
- Request address: http://localhost:8080/redisLock/receiveAwardLock
- Return result: only one record was inserted
Through comparison, it shows that the distributed lock works.
1.4 summary
I used this locking method at home. It looks OK. In fact, there will be problems in Redis cluster, such as:
Client a obtains the lock on the master node of Redis, but the locked key has not been synchronized to the slave node. The master fails and failover occurs. A slave node is upgraded to the master node. Client B can also obtain the lock of the same key, but client a has also obtained the lock, which leads to multiple clients getting the lock.
For this reason, Redis author antirez proposed a more advanced implementation method of distributed lock: Redlock based on the distributed environment.
2, Redlock implementation
2.1 principle
The Redlock algorithm proposed by antirez is roughly as follows:
In the Redis distributed environment, we assume that there are n Redis masters. These nodes are completely independent of each other, and there is no master-slave replication or other cluster coordination mechanism. We ensure that locks will be obtained and released on N instances using the same method as in Redis single instance. Now let's assume that there are five Redis master nodes, and we need to run these Redis instances on five servers to ensure that they will not all go down at the same time.
2.1. 1 lock
In order to get the lock, the client should perform the following operations (locking steps of RedLock algorithm):
- Get the current Unix time, in milliseconds;
last
After reading the above knowledge points, if you feel that the foundation of Java is not solid enough, or you don't brush enough questions and your knowledge is not comprehensive
Xiaobian has specially customized a set of < < analysis collection of interview questions for senior positions of large JAVA front-line factories: JAVA foundation - intermediate - Advanced interview + SSM framework + distributed + performance tuning + microservice + concurrent programming + Network + design pattern + data structure and algorithm >
Don't panic if you don't have enough knowledge! There is also a complete set of < Java core advanced manual >, which can instantly check and fill gaps
All of them are collected and sorted out one by one. They are made by hand - collected and collected*** [my study notes] ***, friends in need can take it themselves
There are also pure hand-painted outlines of major knowledge systems for combing: xmind hand-painted maps of Java foundation building, MySQL, Redis, concurrent programming, Spring, distributed high-performance architecture knowledge, micro service architecture knowledge, open source framework knowledge points, etc~
Don't panic if you don't have enough knowledge! There is also a complete set of < Java core advanced manual >, which can instantly check and fill gaps
[external chain picture transferring... (img-PktrwbAt-1628611860117)]
All of them are collected and sorted out one by one. They are made by hand - collected and collected*** [my study notes] ***, friends in need can take it themselves
There are also pure hand-painted outlines of major knowledge systems for combing: xmind hand-painted maps of Java foundation building, MySQL, Redis, concurrent programming, Spring, distributed high-performance architecture knowledge, micro service architecture knowledge, open source framework knowledge points, etc~
[external chain picture transferring... (img-cQfgVpG5-1628611860119)]