Abstract:

This blog is the fourteenth article of “Java seckill system combat series”. This article will optimize the core business logic of seckill system with the help of the “single thread” feature of cache middleware Redis and its atomic operation, and thoroughly solve the problems of “oversold inventory” and “repeated seckill”.

Content:

For cache middleware Redis, believe that your friend or more or less has heard of, even in actual combat, in this paper, we will based on Redis SpringBoot integration middleware, and based on the outstanding characteristics of “single thread” and atomic operations to achieve a “distributed lock”, and then control the high concurrency case multithreaded access to Shared resources “, Finally solve the problem of “concurrency safety”, i.e., “oversold inventory” or “duplicate kill”!

(1) According to the convention, first we need to add the third party dependencies of Redis, as shown below:

<! -- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> < version > 1.3.5. RELEASE < / version > < / dependency >Copy the code

Then add the Host, port Post, and link key Password of the Redis service to the application.properties configuration file, as shown below:

RedisTemplate and StringRedisTemplate operate components, and specify their corresponding Key and Value serialization strategies:

@configuration public class RedisConfig {@autoWired private RedisConnectionFactory redisConnectionFactory; @Bean public RedisTemplate<String,Object>redisTemplate(){ RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); / / TODO: specify the Key and the Value of the serialization strategy redisTemplate. SetKeySerializer (new StringRedisSerializer ()); redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer());return redisTemplate;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(){
        StringRedisTemplate stringRedisTemplate=new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        returnstringRedisTemplate; }}Copy the code

(3) At this point, it can be said that we are fully prepared, and then we can use it! In order to distinguish the previous seckill logic method, we open a new seckill logic method killItemV3, and use Redis atomic operation SETNX and EXPIRE methods to achieve a “distributed lock”, and then control the access of high concurrency multithreading to shared resources, the complete source code is as follows:

- Redis distributed lock @override public BooleankillItemV3(Integer killId, Integer userId) throws Exception {
    Boolean result=false;

    if (itemKillSuccessMapper.countByKillUserId(killId,userId) <= 0){//TODO: use Redis atomic operation to implement distributed lock - on shared operation - resource control ValueOperations valueOperations=stringRedisTemplate.opsForValue(); final String key=new StringBuffer().append(killId).append(userId).append("-RedisLock").toString();
        final String value=RandomUtil.generateOrderCode();
        Boolean cacheRes=valueOperations.setIfAbsent(key,value); 
        if (cacheRes){
            stringRedisTemplate.expire(key,30, TimeUnit.SECONDS);

            try {
                ItemKill itemKill=itemKillMapper.selectByIdV2(killId);
                if(itemKill! =null && 1==itemKill.getCanKill() && itemKill.getTotal()>0){ int res=itemKillMapper.updateKillItemV2(killId);
                    if (res>0){
                        commonRecordKillSuccessInfo(itemKill,userId);

                        result=true;
                    }
                }
            }catch (Exception e){
                throw new Exception("Not on date, out of time or sold out!");
            }finally {
                if(value.equals(valueOperations.get(key).toString())){ stringRedisTemplate.delete(key); }}}}else{
        throw new Exception("Redis- you've already snapped it up!");
    }
    return result;
}Copy the code

In the above code, we mainly realize the function of “distributed lock” comprehensively through the following operations, including

(1) valueOperations. SetIfAbsent (key, value); If the current Key does not exist in the cache, the value will be set successfully. If the current Key already exists in the cache, the value will be set successfully. Through this feature, we can combine “the one-to-one correspondence between KillId and UserId ~ that is, one person can only grab one commodity” together as Key!

(2) set the Key, then need to be released at some point, namely stringRedisTemplate. Expire (Key, 30, TimeUnit. SECONDS); Actions can assist!

(3) However, the real “release lock” operation is the following code to achieve, that is, to determine whether the current lock to be released is the first to obtain the lock, if so, to release! This is a good way to avoid the problem of accidentally deleting locks!

if (value.equals(valueOperations.get(key).toString())){
    stringRedisTemplate.delete(key);
}Copy the code

At this point, based on the Redis atomic operations to achieve distributed lock, and high concurrency control multithreaded access to Shared resources, so as to solve seconds kill scenarios “oversold inventory”, “repeat” kill, using JMeter to test, under the pressure of user list with goods “can be seconds kill total number” is the same as the one chapter, Total =6 books, 10 users in total.

Click on the Start button of JMeter and look at the output of the console and the database tables ITEM_kill and ITEM_KILL_SUCCESS. You can see that the second kill record is very satisfactory:



That is, goods with 6 copies in stock ~ books are obtained by exactly 6 out of 10 users! This kind of result is in fact for us, for the user must be a happy ending!

Although the actor was happy with his ending, the director realized that there were still some flaws in the play! If a Redis node is down after executing the atomic operation SetNX of Redis and before executing Expire, it is likely to enter the “Key lock” situation permanently. That is, after restarting, the previous Key will not be released, so the Key will remain in the cache forever. The corresponding user will not be able to kill the goods in seconds! This is indeed a hidden danger!

Since there is a hidden danger, then we have to find a way to solve it! Don’t worry, let’s move on to the next chapter!

Supplement:

1, at present, the overall construction of the second kill system and code combat has been completed, the complete source code database address can be downloaded here: gitee.com/steadyjack/… Remember Fork and Star!!

2. Finally, don’t forget to pay attention to Debug’s wechat official account: