Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to use SpringBoot to realize second kill system

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)05/31 Report--

Today, the editor will share with you the relevant knowledge points about how to use SpringBoot to achieve the second kill system. The content is detailed and the logic is clear. I believe most people still know too much about this knowledge, so share this article for your reference. I hope you can get something after reading this article. Let's take a look at it.

Generally speaking, the second kill system does not have many functions, such as:

Make a plan to kill in seconds. What time does it start on a certain day, what goods are sold, how many are going to be sold, and how long they last.

Show a list of second kill plans. It usually shows the same day, some at 8 o'clock and some at 10:00.

Product details page.

Place an order for purchase.

Wait

The main purpose of this article is to use the code to achieve the function of preventing goods from being oversold, so functions such as making a second kill plan and displaying goods do not focus on writing.

There are e-commerce products are mainly SPU (for example, iPhone 12 iPhone 11 is two SPU) and SKU (for example, iPhone 1264g white, iPhone 12128G black is two SKU) processing, display is SPU, purchase buckle inventory is SKU, this article for convenience, directly use product to replace.

There are also some prerequisites for placing an order, such as going through a risk control system to confirm whether you are a scalper, marketing system, whether there are related coupons, virtual currency, and so on.

Issuing an order to complete also need to go to the library management, logistics, as well as points, and so on, this article does not involve.

This article does not cover the database, everything operates on Redis, but I still want to talk about the consistency of the database and cached data.

If the concurrency of our system is not high and the database can hold up, we can directly operate the database. In order to prevent overselling, we can use:

Pessimistic lock

Select * from SKU table where sku_id=1 for update

Or optimistic lock

Update SKU set stock=stock-1 where sku_id=1 and update_version= previous version number

If the concurrency is higher, for example, the concurrency of commodity details pages is generally the highest. In order to reduce the pressure on the database, caches such as Redis are used. In order to ensure the consistency between the database and Redis, the "modified and deleted" scheme is mostly used.

However, in this scheme, in the case of higher concurrency, such as C10K, C10M, etc., in the moment of modifying the database and deleting Redis content, a large number of concurrent queries will be transmitted to the database, resulting in an exception.

In this case, the SPU details interface must not be connected to the database.

The steps should be:

The B-side management system operates the database (this concurrency will not be high).

After the data is stored in the database, a message is sent to MQ.

After receiving the Topic of the subscribed MQ, the relevant processor takes the information from the database and puts it into the Redis.

The relevant service interface only fetches data from Redis.

In the actual project, it is recommended to combine the second-kill product-related interfaces on the ToC side into a microservice, product-server. The selling interface is combined into a microservice, order-server.

Second kill plan entity class

Omit get/set

Public class SecKillPlanEntity implements Serializable {private static final long serialVersionUID = 88667978039607461L; / * id * / private Long id; / * * Commodity id * / private Long productId; / * Commodity name * / private String productName; / * * Price Unit: sub * / private Long price / * underlined price unit: * / private Long linePrice; / * * inventory * / private Long stock; / * * A user buys only one item marked 0 No 1 is * / private int buyOneFlag; / * Plan status 0 has not been submitted, 1 has been submitted * / private int planStatus / * start time * / private Date startTime; / * * end time * / private Date endTime; / * creation time * / private Date createTime;}

Description:

As mentioned earlier, the second kill goods should be displayed is SPU, the sale of buckle inventory is SKU, this article for convenience, only use product to replace.

There are two ways for users to buy second-kill products:

A user is only allowed to buy one item.

A user can buy multiple items multiple times.

So this class uses buyOneFlag as the logo.

PlanStatus represents whether this second kill is really carried out. 0 does not show to the C end, does not carry on the sale; 1 displays to the C side, carries on the sale.

Add a second kill plan & query the second kill plan @ RestControllerpublic class ProductController {@ Resource private RedisTemplate redisTemplate; / / randomly generate a second kill plan to Redis @ GetMapping ("/ addSecKillPlan") @ ResponseBody public DefaultResult addSecKillPlan (@ RequestParam ("saledate") String saleDate) {DateTimeFormatter dtf = DateTimeFormatter.ofPattern ("yyyy-MM-dd HH:mm:ss"); Random rand = new Random (); Gson gson = new Gson () List list = Lists.newArrayList (); for (int I = 0; I

< 10; i++) { long productId = rand.nextInt(100) + 1; long price = rand.nextInt(100) + 1; long stock = rand.nextInt(100) + 1; String saleStartTime = " 10:00:00"; String saleEndTime = " 12:00:00"; int buyOneFlag = 0; if (i >

4) {saleStartTime = "14:00:00"; saleEndTime = "16:00:00"; buyOneFlag = 1;} SecKillPlanEntity entity = new SecKillPlanEntity (); entity.setId (I + 1L); entity.setProductId (productId); entity.setProductName ("Commodity" + productId); entity.setBuyOneFlag (buyOneFlag) Entity.setLinePrice (999999L); entity.setPlanStatus (1); entity.setPrice (price * 100); entity.setStock (stock); entity.setEndTime (Date. From (LocalDateTime.parse (saleDate + saleEndTime, dtf) .atZone (ZoneId.systemDefault ()). ToInstant () Entity.setStartTime (Date.from (LocalDateTime.parse (saleDate + saleStartTime, dtf) .atZone (ZoneId.systemDefault ()) .toInstant ()); entity.setCreateTime (new Date ()); / / Redis ValueOperations setProduct = redisTemplate.opsForValue (); setProduct.set ("product_" + productId, gson.toJson (entity)) / / write to inventory if (buyOneFlag = = 1) {/ / A user buys only one item / / purchase user Set redisTemplate.opsForSet () .add ("product_buyers_" + productId, ""); / / inventory for (int j = 0; j < stock) ) {redisTemplate.opsForList () .leftPush ("product_one_stock_" + productId, "1");}} else {/ / users can buy multiple redisTemplate.opsForValue () .set ("product_stock_" + productId, stock + ");} list.add (entity) System.out.println (gson.toJson (entity));} redisTemplate.opsForValue (). Set ("seckill_plan_" + saleDate, gson.toJson (list)); return DefaultResult.success (list);} @ GetMapping ("/ findSecKillPlanByDate") @ ResponseBody public DefaultResult findSecKillPlanByDate (@ RequestParam ("saledate") String saleDate) {Gson gson = new Gson () String planJson = redisTemplate.opsForValue () .get ("seckill_plan_" + saleDate); List list = gson.fromJson (planJson, new TypeToken () {} .getType ()) / / set new inventory for (SecKillPlanEntity entity: list) {if (entity.getBuyOneFlag () = = 1) {long newStock = redisTemplate.opsForList () .size ("product_one_stock_" + entity.getProductId ()); entity.setStock (newStock) } else {long newStock = Long .parseLong (redisTemplate.opsForValue (). Get ("product_stock_" + entity.getProductId ()); entity.setStock (newStock);}} return DefaultResult.success (list);}}

Description:

AddSecKillPlan is to randomly generate 10 sales plans, some selling only one piece, and some selling multiple pieces. And press the relevant data into the Redis.

The seckill_plan_ date, which represents all the second kill plans for a day, is shown in the list.

Product_ merchandise ID, which represents a commodity information, which is used on the details page.

Product_one_stock_ merchandise ID, which represents the number of inventory of only one item. The value is List, and you can push as many "1s" into the inventory as you have.

ID, a product_buyers_ product, represents a buyer who sells only one item. Users who have already bought it are not allowed to buy it again.

Product_stock_ merchandise ID, which represents the number of inventory of multiple items that can be sold. The value is the number of inventory.

FindSecKillPlanByDate, show off the sale plan of a certain day. The inventory is taken from the two KEY related to the inventory.

LUA script

Only one buyone.lua:

-- merchandise inventory Key product_one_stock_XXXlocal stockKey = KEYS [1]-- merchandise purchase user record Key product_buyers_XXXlocal buyersKey = KEYS [2]-- user IDlocal uid = KEYS [3]-- verify whether the user has purchased local result=redis.call ("sadd", buyersKey, uid) if (tonumber (result) = = 1) then-- No purchase, local stock=redis.call ("lpop", stockKey)-- except nil and false All other values are true (including 0) if (stock) then-- return 1 else with inventory-- return-1 endelse without inventory-- return-3end has been purchased

Multiple buymore.lua items are available for sale:

-- Keylocal key = KEYS [1]-- number of purchases local val = ARGV [1]-- existing total inventory local stock = redis.call ("GET", key) if (tonumber (stock) = 0) then-- not oversold after deducting the number of purchases, return to current inventory return decrstock else-- oversold, add the deduction back to redis.call ("INCRBY", key, val) return-2 endend

Description:

1. Only one item is sold. First, the buyer's ID is entered into the product_buyers_ product ID with the command "sadd". If 1 is returned, it means that the user has not purchased it before, otherwise-3 has been purchased.

If you lpop the value from the ID of product_one_stock_ goods, if there is still stock, it will return 1, which will be in stock, otherwise it will be nil and there will be no inventory.

[reference documentation]

2. Multiple pieces can be sold. As I said before, I will no longer describe it.

Put the two lua files in the resources directory of the Spring Boot project.

Sales interface @ RestControllerpublic class OrderController {@ Resource private RedisTemplate redisTemplate; @ GetMapping ("/ addOrder") @ ResponseBody public DefaultResult addOrder (@ RequestParam ("uid") long userId, @ RequestParam ("pid") long productId, @ RequestParam ("quantity") int quantity) {Gson gson = new Gson (); String productJson = redisTemplate.opsForValue (). Get ("product_" + productId); SecKillPlanEntity entity = gson.fromJson (productJson, SecKillPlanEntity.class) / / TODO to verify whether the sales plan has been submitted, whether the time of sale is long code = 0; if (entity.getBuyOneFlag () = = 1) {/ / users only buy one code = this.buyOne ("product_one_stock_" + productId, "product_buyers_" + productId, userId) } else {/ / users buy multiple code = this.buyMore ("product_stock_" + productId, quantity);} DefaultResult result = DefaultResult.success (null); / / ENUM should be used to handle error codes. This article saves if (code < 0) {result.setCode (code). If (code = =-1) {result.setMsg ("No inventory");} else if (code = =-2) {result.setMsg ("insufficient inventory");} else if (code = =-3) {result.setMsg ("already purchased");}} return result } private Long buyOne (String stockKey, String buysKey, long userId) {DefaultRedisScript defaultRedisScript = new DefaultRedisScript (); defaultRedisScript.setResultType (Long.class); defaultRedisScript.setScriptSource (new ResourceScriptSource (new ClassPathResource ("buyone.lua"); / / "{pre}:" List keys = Lists.newArrayList (stockKey, buysKey, userId + "); Long result = redisTemplate.execute (defaultRedisScript, keys,"); return result } private Long buyMore (String stockKey, int quantity) {DefaultRedisScript defaultRedisScript = new DefaultRedisScript (); defaultRedisScript.setResultType (Long.class); defaultRedisScript.setScriptSource (new ResourceScriptSource (new ClassPathResource ("buymore.lua"); List keys = Lists.newArrayList (stockKey); Long result = redisTemplate.execute (defaultRedisScript, keys, quantity+ ""); return result;}}

Description:

1, mainly look at buyOne, buyMore two private methods, which is written about how to use RedisTemplate to execute lua scripts.

In addition, I see some information saying that if you use a Redis cluster, you will report an error. Because I do not have a Redis cluster environment, I cannot test it. If you have an environment, you can try it.

2. AddOrder has some code that is very low in order to save time, for example, some checks are not added, the error code should use ENUM and so on.

Test case:

A user buys only one item, which is successful.

A user purchases and sells only one item 1, which fails.

N users buy only one item 1, which is not enough in stock.

A user can buy more than one item 2, which is successful.

A user can buy more than one item for sale. 2, the inventory is insufficient.

These are all the contents of the article "how to use SpringBoot to achieve a second kill system". Thank you for reading! I believe you will gain a lot after reading this article. The editor will update different knowledge for you every day. If you want to learn more knowledge, please pay attention to the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report