In the previous blog, the optimization of mysql solved the oversold problem, but it did not solve the problem that the same user has the chance to buy multiple times. This problem can be solved by adding locks. The solution idea is that when a request wants to buy, it needs to obtain a distributed lock first.
(a) use Redis to achieve distributed lock
Create RedisConfig under config to write the general configuration file of Redis:
public class RedisConfig { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean private RedisTemplate<String,Object> redisTemplate(){ RedisTemplate<String,Object> redisTemplate=new RedisTemplate<String, Object>(); redisTemplate.setConnectionFactory(redisConnectionFactory); / / set the redis key and the value of the default serialization rules 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); return stringRedisTemplate; }}Copy the code
For a primer on Redis, check out my previous Redis tutorial at blog.csdn.net/qq_41973594…
I will post a deeper understanding of Redis in a future blog post.
Once configured, you can then use Redis directly in your code, adding KillItemV3 to KillServiceImpl to implement distributed locking with redis atomic operations
@Autowired private StringRedisTemplate stringRedisTemplate; Public Boolean KillItemV3(Integer killId, Integer userId) throws Exception {// Implement distributed lock ValueOperations using Redis atomic operations valueOperations=stringRedisTemplate.opsForValue(); Final String key=new StringBuffer().appEnd (Killid).appEnd (userId).toString(); // Set redis value final String value= string.valueof (snowfla.nextid ()); / / attempts to acquire the lock a Boolean result. = valueOperations setIfAbsent (key, value); / / if access to the lock would like to see the back of the operation of the if (result) {stringRedisTemplate. Expire (key, 30, TimeUnit. SECONDS); / / judge whether the current user snapping up the goods from the if (itemKillSuccessMapper. CountByKillUserId (killId, userId) < = 0) {/ / get goods ItemKill for details itemKill=itemKillMapper.selectByidV2(killId); if (itemKill! =null&&itemKill.getCanKill()==1 && itemKill.getTotal()>0){ int res=itemKillMapper.updateKillItemV2(killId); if (res>0){ commonRecordKillSuccessInfo(itemKill,userId); return true; }}}else {system.out.println (" you have already bought this product "); } / / releases the lock the if (value equals (valueOperations. Get (key). The toString ())) {stringRedisTemplate. Delete (key); } } return false; }Copy the code
Compared to the previous version, this version requires a redis distributed lock to snap up items.
Add interface code to KillService:
Boolean KillItemV3(Integer killId,Integer userId) throws Exception;
Copy the code
Add redis distributed lock version to KillController:
/ / @ RequestMapping redis distributed lock version (value = prefix + "/ test/execute3", method = RequestMethod. POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseBody public BaseResponse testexecute3(@RequestBody @Validated KillDto killDto, BindingResult result, HttpSession httpSession){ if (result.hasErrors()||killDto.getKillid()<0){ return new BaseResponse(StatusCode.InvalidParam); } try { Boolean res=killService.KillItemV3(killDto.getKillid(),killDto.getUserid()); if (! Res){return new BaseResponse(statuscode.fail.getCode ()," The item has been bought or you have bought the item "); } } catch (Exception e) { e.printStackTrace(); } BaseResponse baseResponse=new BaseResponse(StatusCode.Success); Baseresponse.setdata (" snap up "); return baseResponse; }Copy the code
Run redis-server locally, redis installation package can go to the official website to download, or in my public account “Java fish” reply seconds kill system to obtain. Run redis server. –
Example Change the Http request path to /kill/test/execute3
When I ran JMeter, I found that there was no oversold and that each user actually only bought one item.
(2) Distributed lock implementation based on Redisson
One problem with implementing distributed locks using only Redis is that if the lock is not released, it will remain locked. The solution is to set a time for the lock, and the set time and set lock need to be atomic. Lua scripts can be used to ensure atomicity, but we are more likely to use redis-based third-party libraries for this project, using Redisson.
Create RedissonConfig in the config directory
@Configuration public class RedissonConfig { @Autowired private Environment environment; @Bean public RedissonClient redissonClient(){ Config config = new Config(); config.useSingleServer() .setAddress(environment.getProperty("redis.config.host")); // .setPassword(environment.getProperty("spring.redis.password")); RedissonClient client= Redisson.create(config); return client; }}Copy the code
The relevant configuration is placed in application.properties
# redis spring. Redis. Host = 127.0.0.1 spring. Redis. Port = 6379 spring. Redis. Password = Redis. Config. Host = redis: / / 127.0.0.1:6379Copy the code
Create KillItemV4 in killServiceImpl
@Autowired private RedissonClient redissonClient; @override public Boolean KillItemV4(Integer killId, Integer userId) throws Exception { Boolean result=false; final String key=new StringBuffer().append(killId).append(userId).toString(); RLock lock=redissonClient.getLock(key); Boolean cacheres=lock.tryLock(30,10, timeunit.seconds); // three parameters, wait time, lock expiration time, and TimeUnit. If (cacheres) {/ / judge whether the current user snapping up the goods from the if (itemKillSuccessMapper. CountByKillUserId (killId, userId) < = 0) {/ / get goods ItemKill for details itemKill=itemKillMapper.selectByidV2(killId); if (itemKill! =null&&itemKill.getCanKill()==1 && itemKill.getTotal()>0){ int res=itemKillMapper.updateKillItemV2(killId); if (res>0){ commonRecordKillSuccessInfo(itemKill,userId); result=true; }}}else {system.out.println (" you have already bought this product "); } lock.unlock(); } return result; }Copy the code
KillService = KillItemV4; KillService = KillItemV4;
Boolean KillItemV4(Integer killId,Integer userId) throws Exception;
Copy the code
Finally, add the following interface code to KillController:
/ / @ RequestMapping redission distributed lock version (value = prefix + "/ test/execute4", method = RequestMethod. POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseBody public BaseResponse testexecute4(@RequestBody @Validated KillDto killDto, BindingResult result, HttpSession httpSession){ if (result.hasErrors()||killDto.getKillid()<0){ return new BaseResponse(StatusCode.InvalidParam); } try { Boolean res=killService.KillItemV4(killDto.getKillid(),killDto.getUserid()); if (! Res){return new BaseResponse(statuscode.fail.getCode ()," The item has been bought or you have bought the item "); } } catch (Exception e) { e.printStackTrace(); } BaseResponse baseResponse=new BaseResponse(StatusCode.Success); Baseresponse.setdata (" snap up "); return baseResponse; }Copy the code
Run Redis and then use JMeter again to achieve the desired effect of not oversold and not oversold.
(3) Distributed lock based on ZooKeeper
I will follow up on ZooKeeper in a future blog post. Here, we mainly use the zooKeeper process mutex InterProcessMutex. Zookeeper uses the path to create temporary sequential nodes to achieve fair locking
Public InterProcessMutex(CuratorFramework Client, String Path){// Zookeeper uses path to create temporary sequential nodes, Fairness this lock (client, path, new StandardLockInternalsDriver ()); }Copy the code
The configuration file mainly configures the connection address and namespace of ZooKeeper.
@Configuration public class ZookeeperConfig { @Autowired private Environment environment; @Bean public CuratorFramework curatorFramework(){ CuratorFramework curatorFramework= CuratorFrameworkFactory.builder() .connectString(environment.getProperty("zk.host")) .namespace(environment.getProperty("zk.namespace" )) .retryPolicy(new RetryNTimes (5100)). The build (); curatorFramework.start(); return curatorFramework; }}Copy the code
Zookeeper configuration was added to the application.peoperties configuration file
# zookeeper zk. Host = 127.0.0.1:2181 zk. The namespace = the killCopy the code
Add ItemKillV5 version to KillService
Boolean KillItemV5(Integer killId,Integer userId) throws Exception;
Copy the code
Then add the code related to the Zookeeper distributed lock in KillServiceImpl. Press path+killId+userId+”-lock” to lock the Zookeeper distributed lock through InterProcessMutex. Each time the purchase code is invoked, acquire will attempt to obtain it. The timeout period is set to 10s
@Autowired private CuratorFramework curatorFramework; private final String path="/seckill/zookeeperlock/"; @override public Boolean KillItemV5(Integer killId, Integer userId) throws Exception{ Boolean result=false; InterProcessMutex mutex=new InterProcessMutex(curatorFramework,path+killId+userId+"-lock"); If (mutex.acquire(10L, timeunit.seconds)){// If (mutex.acquire(10L, timeunit.seconds)) (itemKillSuccessMapper countByKillUserId (killId, userId) < = 0) {/ / get goods ItemKill for details itemKill=itemKillMapper.selectByidV2(killId); if (itemKill! =null&&itemKill.getCanKill()==1 && itemKill.getTotal()>0){ int res=itemKillMapper.updateKillItemV2(killId); if (res>0){ commonRecordKillSuccessInfo(itemKill,userId); result=true; }}}else {system.out.println (" you have already bought this product "); } if (mutex! =null){ mutex.release(); } } return result; }Copy the code
Add zooKeeper-related shopping code to KillController:
/ / they are distributed lock version @ RequestMapping (value = prefix + "/ test/execute5", method = RequestMethod. POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseBody public BaseResponse testexecute5(@RequestBody @Validated KillDto killDto, BindingResult result, HttpSession httpSession){ if (result.hasErrors()||killDto.getKillid()<0){ return new BaseResponse(StatusCode.InvalidParam); } try { Boolean res=killService.KillItemV5(killDto.getKillid(),killDto.getUserid()); if (! Res){return new BaseResponse(statuscode.fail.getCode ()," The item has been bought or you have bought the item "); } } catch (Exception e) { e.printStackTrace(); } BaseResponse baseResponse=new BaseResponse(StatusCode.Success); Baseresponse.setdata (" snap up "); return baseResponse; }Copy the code
The Installation package of ZooKeeper can be downloaded from the official website or obtained by replying to the system in my official account “Java Fish Boy”. Run zkServer in the bin directory of Zookeeper to obtain Zookeeper running information:
Since the Redis information is already configured, you also need to start Redis and then start the system for stress testing with JMeter
The result of the test is that there is no oversold, and only one order per person is allowed.
(4) Summary
We solved the problem of multi-threading caused by the actual situation and business logic impassability through three kinds of distributed locks. So far, the second kill system is roughly completed. Of course, there can be more optimization in the future, such as adding shopping cart and other functional modules, the page can be beautified, here redis only used for the function of distributed lock, but also can buy up hot data into Redis. Rabbitmq can be used for flow limiting and peak clipping in addition to async.
Finally, put the code of the overall system: github.com/OliverLiy/S…
All the tools in this series of blogs are available in the public account “Java Fish” reply to the second kill system.