preface

Hello everyone, I’m P, this is to share how to design a red envelope system, I hope it will be helpful to you. Mainly show the design of the red envelope system, red envelope algorithm is not the key, so there is no double means method and so on. The solution described below has been implemented, the code is on Github, welcome to discuss.

Demand analysis

Common red envelope system, by the user specified amount, the total number of red envelopes to complete the creation of red envelope, and then through a certain entrance to the target user, the user sees the red envelope, click the red envelope, random red envelope, finally, the user can check their own grabbed red envelope. The whole business process is not complicated, the difficulty is that the behavior of grabbing red envelopes may have high concurrency. Therefore, the optimization point of the system design is mainly concerned with the behavior of grabbing red envelopes.

Viewing red packets is too simple to cover in this article. Then there are only two types of system use cases: distribution and grab.

  1. Sending red envelopes: Users set the total amount and quantity of red envelopes
  2. Grab a red envelope: The user receives a random amount of money from the total red envelope

Nothing to say, I believe that everyone’s wechat red envelope is not less rob, a think all understand. It looks like a simple business, but there’s actually a little bit of trouble. First of all, grabbing red packets must ensure high availability, otherwise users will be angry. Secondly, it is necessary to ensure the consistency of system data can not be sent, otherwise the user who grabbed the red envelope can not receive money, the user will be very angry. Finally, the system can have high concurrency.

OK, direct detailed design after analysis. So there are simply two interfaces:

  1. Send a red envelope
  2. Grab a red envelope

Table structure design

Here is a direct construction sentence:

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 'Residual amount',
    `total` bigint(20)     NOT NULL DEFAULT '0' COMMENT 'Total number of red packets',
    `surplus_total` bigint(20)     NOT NULL DEFAULT '0' COMMENT 'Total amount of red envelope left',
    `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;
Copy the code

Red table:

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 'value',
    `status`     TINYINT(4) NOT NULL DEFAULT 0 COMMENT 'Red envelope state 1 available 2 unavailable',
    `version` bigint(20)     NOT NULL DEFAULT '0' COMMENT 'Version number'.PRIMARY KEY (`id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;
Copy the code

List:

CREATE TABLE `t_redpack_detail`
(
    `id`         bigint(20)     NOT NULL COMMENT 'primary key',
    `amount`     decimal(10.2) NOT NULL DEFAULT '0.00' COMMENT 'value',
    `user_id`    bigint(20)     NOT NULL DEFAULT '0' COMMENT 'User number',
    `redpack_id` bigint(20)     NOT NULL DEFAULT '0' COMMENT 'Red Envelope Number',
    `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;
Copy the code

The activity table is how many red packets you have sent and you need to maintain the remaining amount. The list is the details of the red envelopes that users grab. The red envelope table is the specific information of each red envelope. Why do we need three tables? In fact, it is ok if there is no red envelope. However, in our scheme of pre-allocating red packets, we need to use a table to record the information of red packets, so we have this table at the time of design.

OK, after analyzing the table structure, actually the scheme is almost seven, eight, eight. Take a look at the following solutions, from simple to complex.

Implementation based on distributed lock

The implementation based on distributed lock is the most simple and crude. The whole red envelope snatching interface uses activityId as the key to lock, ensuring that the same batch of red envelope snatching is executed in serial. The implementation of distributed locks is provided by the Spring-Integration-Redis project, and the core class is RedisLockRegistry. The lock is implemented using the Lua script of Redis with blocking local reentrant.

Implementation based on optimistic lock

The second way is to add optimistic locking version control to the red packet activity table. When multiple threads update the same activity table at the same time, only one clien will succeed. For other failed clients, set a maximum number of retry cycles. This scheme can achieve concurrent processing, but the conflict is very large. Since only one client will succeed at a time, the other clients need to retry, and even if retry, only one client can succeed at a time, thus TPS is very low. If the number of failed retries is less than the number of red packets issued, someone may not get the red packets, but there are still red packets left.

Pessimistic lock based implementation

Select * from T_redpack_activity where id = #{id} for update, note that pessimistic locks must be used in transactions. At this point, all the grabbing behaviors become serial. In this case, pessimistic locking is much more efficient than optimistic locking.

Pre-allocated red packets, based on the implementation of optimistic lock

As you can see, if we add the dimension of optimism lock to the red envelope details, the conflict is reduced again. Because the red envelope details were created after the user grabbed before, so now need to pre-allocate red envelope, that is, create red envelope activities that generate N red envelope, through the state to control available/unavailable. In this way, when multiple clients grab a red envelope, they get the details of all available red envelopes under the activity, and return one of them randomly and then update it. If the update succeeds, the user grabs the red envelope; if the update fails, a conflict occurs. In this way, conflict is reduced.

Implementation based on Redis queue

Similar to the previous scheme, however, the user will create a corresponding number of red packets when handing out red packets and add them to the Redis queue. When grabbing a red envelope will pop it up. The Redis queue fits our needs very well. There are no duplicate elements every time it pops up. Defect: Once the red envelope is ejected from the queue, the system crashes. After recovery, the details of the red envelope in the queue have been lost and need manual compensation.

Based on Redis queue, asynchronous library entry

This scheme does not operate the database after grabbing the red envelope, but saves the persistent information to Redis, and then returns success. By another thread UserRedpackPersistConsumer, pull the persistence information are put in storage. It should be noted that the problem of crash point still occurs if ordinary POP is used in the pull action at this time. Therefore, in consideration of availability, BRPOPLPUSH operation of Redis is used here. After ejected elements, they are added to another queue to ensure that the crash can be automatically recovered through the backup queue. The CrashRecoveryThread periodically pulls backup information from the DB to check whether the persistence succeeds. If so, the element is cleared; otherwise, the element is compensated and cleared. If an exception occurs during database operation, an error log redpack.persist. Log is recorded. This log is in a separate file and format for easy compensation (it is usually not triggered).

After the language

Of course, a robust system may take into account many aspects. If the red envelope itself is a particularly large amount of data to do more copies of the scheme. This article only demonstrates the advantages and disadvantages of each scheme for reference only. In addition, if you use Redis, you need to do high availability.