It's about to grab red envelopes for the Chinese New Year. How to design a red envelope grabbing system

preface

Hello, I'm little P. this article is to share how to design a red envelope grabbing system. I hope it will be helpful to you. It mainly shows the design of the red envelope system. The red envelope algorithm is not the focus, so there is no implementation of double mean method. The scheme described below has been implemented. The code is in github. Welcome to discuss.

requirement analysis

In the common red envelope system, the user specifies the amount and total number of red envelopes to complete the creation of red envelopes, and then sends the red envelopes to the target user through an entrance. After seeing the red envelopes, the user clicks the red envelopes and obtains the red envelopes randomly. Finally, the user can view the red envelopes he has grabbed. The whole business process is not complex. The difficulty is that the behavior of grabbing red envelopes may have high concurrency. Therefore, the optimization of system design mainly focuses on the behavior of grabbing red envelopes.

Because viewing red envelopes is too simple, it is not discussed in this article. Then there are only two kinds of system use cases: sending and grabbing.

  1. Send red envelopes: users set the total amount and quantity of red envelopes
  2. Grab red envelopes: users get a certain amount randomly from the total red envelopes

There's nothing to say. I believe everyone's wechat red envelopes have been robbed. I understand it when I think about it. It seems that the business is very simple, but there is still a little trouble. First of all, to grab red envelopes, you must ensure high availability, otherwise users will be very angry. Secondly, we must ensure the consistency of system data, and do not over send, otherwise the users who grab the red envelope will not receive money, and the users will be very angry. Finally, the system may have high concurrency.

OK, the detailed design will be carried out directly after the analysis. So there are only two interfaces:

  1. Hand out red envelopes
  2. Grab a red envelope

Table structure design

The table creation statement is directly given here:

Red envelope activity table:

CREATE TABLE `t_redpack_activity`
(
    `id`         bigint(20)     NOT NULL COMMENT 'Primary key',
    `total_amount`     decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT 'Total amount',
    `surplus_amount`     decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT 'Remaining amount',
    `total` bigint(20)     NOT NULL DEFAULT '0' COMMENT 'Total number of red envelopes',
    `surplus_total` bigint(20)     NOT NULL DEFAULT '0' COMMENT 'Total remaining red envelopes',
    `user_id`    bigint(20)     NOT NULL DEFAULT '0' COMMENT 'User number',
    `version` bigint(20)     NOT NULL DEFAULT '0' COMMENT 'Version number',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

Red envelope form:

CREATE TABLE `t_redpack`
(
    `id`         bigint(20)     NOT NULL COMMENT 'Primary key',
    `activity_id`         bigint(20)     NOT NULL DEFAULT 0 COMMENT 'Red envelope activity ID',
    `amount`     decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT 'amount of money',
    `status`     TINYINT(4) NOT NULL DEFAULT 0 COMMENT 'Red envelope status 1 available 2 unavailable',
    `version` bigint(20)     NOT NULL DEFAULT '0' COMMENT 'Version number',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

Schedule:

CREATE TABLE `t_redpack_detail`
(
    `id`         bigint(20)     NOT NULL COMMENT 'Primary key',
    `amount`     decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT 'amount of money',
    `user_id`    bigint(20)     NOT NULL DEFAULT '0' COMMENT 'User number',
    `redpack_id` bigint(20)     NOT NULL DEFAULT '0' COMMENT 'Red envelope No',
    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time',
    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update time',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;

The activity table is how many red envelopes you sent and the remaining amount needs to be maintained. The details list is the red envelope details snatched by the user. The red envelope table is each specific red envelope information. Why three tables? In fact, if there is no red envelope table, it is OK. However, our scheme needs to use a table to record the information of red packets in advance, so this table is available during design.

OK, after analyzing the table structure, the scheme is almost 7788. Please continue to look at the following scheme, from simple to complex.

Implementation based on distributed lock

The implementation based on distributed lock is the simplest and rudimentary. The whole red envelope grabbing interface uses activityId as the key to lock, so as to ensure that the same batch of red envelope grabbing behavior is executed serially. The implementation of distributed locks is provided by the spring integration Redis project, and the core class is RedisLockRegistry. The lock is implemented through the lua script of Redis, and the blocking local reentry is realized.

Implementation based on optimistic lock

The second way is to add optimistic lock version control to the red envelope activity table. When multiple threads update the same activity table at the same time, only one clien will succeed. For other failed clients, cycle retry and set a maximum number of cycles. This scheme can realize the processing in the case of concurrency, but there is a great conflict. Because only one person will succeed at a time, other clients need to retry. Even if they retry, only one person can succeed at a time, so the TPS is very low. When the number of failed retries set is less than the number of red packets issued, it may lead to that someone does not grab the red packet at last, and there are actually remaining red packets.

Implementation based on pessimistic lock

Since the red envelope activity table adds a lot of optimistic lock conflicts, you can consider using pessimistic locks: select * from t_redpack_activity where id = #{id} for update. Note that pessimistic locks can only be used in transactions. At this point, all red envelope grabbing behavior becomes serial. In this case, the efficiency of pessimistic lock is much higher than that of optimistic lock.

Pre allocation of red packets, based on the implementation of optimistic lock

It can be seen that if we add the dimension of the concept of music lock to the red envelope details, the conflict will be reduced. Because the red envelope details were created after the user grabbed them, it is now necessary to pre allocate red envelopes, that is, N red envelopes are generated when creating red envelope activities, and the availability / unavailability is controlled by status. In this way, when multiple client s grab red packets, they will obtain all available red packet details under the activity, return one of them randomly, and then update it. If the update is successful, it means that the user has grabbed the red packet, and if it fails, it means that there is a conflict, so they can try again circularly. In this way, the conflict is reduced.

Implementation of Redis based queue

Similar to the previous scheme, however, when users issue red envelopes, a corresponding number of red envelopes will be created and added to the Redis queue. It will pop up when grabbing the red envelope. The Redis queue meets our requirements very well. There will be no duplicate elements in each pop-up, and they will be destroyed as soon as they are used up. Defect: once the red packet is ejected from the queue during red packet grabbing, the system crashes. After recovery, the red packet details in the queue have been lost and need to be compensated manually.

Asynchronous receipt based on Redis queue

This scheme does not operate the database after grabbing the red envelope, but saves the persistent information to Redis, and then returns success. Through another thread
UserRedpackPersistConsumer, pull the persistent information for warehousing. It should be noted that the pull action at this time will still have the problem of crash point if using ordinary pop. Therefore, considering the availability, Redis's BRPOPLPUSH operation is used here. After the element is popped, it is added to another queue for backup to ensure that it can be automatically recovered through the backup queue after it crashes. The crash recovery thread, CrashRecoveryThread, periodically pulls the backup information to the DB to verify whether the persistence is successful. If it is successful, clear this element. Otherwise, compensate and clear this element. If an exception occurs during the operation of the database, the error log Redpack persist. Log, which uses a separate file and format to facilitate compensation (generally not triggered).

Post language

Of course, a robust system may have to consider all aspects. If the red envelope itself is a large amount of data, it also needs to make a multi copy scheme. This paper just demonstrates the advantages and disadvantages of various schemes for reference only. In addition, if Redis is adopted, high availability is required.

Keywords: Java Programmer architecture

Added by Iank1968 on Sun, 23 Jan 2022 13:27:09 +0200