I am participating in the “Spring Festival Creative Submission Contest”. For details, please see: Spring Festival Creative Submission Contest.

This article is mainly about many years ago IN a company I did the design of red envelope payment system, while digging gold year-end activities and share with you.

A red envelope classification

Product demand design is divided into two categories of red packets, personal red packets, group red packets. Group red packets are divided intoExclusive, even, group luckThree. Adapt to different scenarios. As shown below:

A red envelope to realize

Red envelope process:

1. The user enters the red envelope interface to initiate a request; 2. After receiving the request, the server will freeze the red envelope amount of the user (the premise is that the user has opened a balance account in advance). 3, whether the balance is sufficient (bottom of the teaching research), if enough to send red envelope success, and generate red envelope records, if not enough prompt error information. 4, push the final results to the user, if successful will push two messages, one is the sender to tell the user a successful red envelope, and push group/individual immediately receive. 5. If the red envelope is successfully sent, send an MQ message delayed for 1 day, and do an overdue non-refund treatment. Return the money from the frozen account to the user.

Procedures for getting red packets:

When the user receives the push of the red envelope, the user can receive the red envelope through the message. 1. Parameter state judgment, judging whether the red envelope is expired, whether the red envelope is finished, whether the red envelope is repeated and other logic; 2. Generate red envelope receiving records; 3. Generate red envelopes for account entry and account entry for recipients. Generate balance flow and increase balance flow. 4. Reduce the frozen balance of the giver and complete the red envelope collection process.

High concurrency of red packets

High concurrency design

There will certainly be concurrent problems with group lucky red envelopes, such as when receiving wechat group red envelopes. A group of 200 people come to get a 200 yuan red envelope at the same time, 10 people can get the red envelope, from the user’s initiation to the receipt of the basic within 1-2 seconds. How to ensure that both efficient receipt, and can ensure that the amount can be correct, and will not be wrong account. The solution we use is the “one fast one slow” solution.

1. For the scenario of group red packets, we will first calculate the amount of red packets in advance. Then store it in Redis Key:redpacket:amount_list:#{id}The stored structure is a List collection. 2. We make one every time we pick it uprpopOperation, get the amount of red envelope. Since this is a Redis operation, it is very efficient.3. To ensure the persistence and reliability of data. We will generate a fetch record to the mysql database for persistence. 4, then send a red envelope to get the successful message out, in the bookkeeping service for subscription, asynchronous entry.

The specific process is as follows:

Idempotent guarantee

In order to ensure that the process of receiving users orderly claim, and ensure that a user can only successfully claim once, if the second time to claim, we will remind that it has been claimed, can not be repeated. This is something we have to be very strict about in high concurrency scenarios. The key design of the lock is as follows: Redpacket :amount_list:#{id}_#{user_id} The advantage of this design is to ensure that only one valid request can enter the real processing logic in the current distributed system. Out security: 1, the increase in the receiving record user_id, redpacket_id as the only index. 2. Update the remaining amount of the red packet with optimistic lock (you can use @version of Tk. mapper).

Extensible design

In order to ensure extensible design, we adopted the strategy + template design model for low coupling design.

Lucky red envelope amount calculation

We use the median random algorithm (roughly the logic is to control a median value of the maximum amount, the minimum amount of the interval can not exceed the floating water line of the median), more random algorithm, we can see: why the Spring Festival is not always the best luck? After reading wechat grab red envelope algorithm you will understand!

Amount random code

public class RedPacketUtils {

	private static final Random random = new Random();


	/** * Data is randomly processed according to the total number of partitions and the limited range * the floating threshold of the sequence is 0.95 **@paramTotalMoney - Total amount divided *@paramSplitNum - Number of splits *@paramMin - Lower limit * for a single digit@paramMax - Maximum number of digits *@return- Returns a list of numbers that meet the requirement */
	public static List<BigDecimal> genRandomList(BigDecimal totalMoney, Integer splitNum, BigDecimal min, BigDecimal max) {
		totalMoney = totalMoney.multiply(new BigDecimal(100));
		min = min.multiply(new BigDecimal(100));
		max = max.multiply(new BigDecimal(100));
		List<Integer> li = genRandList(totalMoney.intValue(), splitNum, min.intValue(), max.intValue(), 0.95 f);
		List<BigDecimal> randomList = new CopyOnWriteArrayList<>();
		for (Integer v : li) {
			BigDecimal randomVlue = new BigDecimal(v).divide(new BigDecimal(100));
			randomList.add(randomVlue);
		}

		randomList = randomArrayList(randomList);
		return randomList;
	}

	/** * Data is processed randomly according to the total number of segments and the limited range **@paramTotal - Total number of partitions *@paramSplitNum - Number of splits *@paramMin - Lower limit * for a single digit@paramMax - Maximum number of digits *@paramThresh - Array float threshold [0.0, 1.0] */
	public static List<Integer> genRandList(int total, int splitNum, int min, int max, float thresh) {
		assert total >= splitNum * min && total <= splitNum * max : "Please verify the rationality of the red envelope parameter Settings";
		assert thresh >= 0.0 f && thresh <= 1.0 f;
		// Divide evenly
		int average = total / splitNum;
		List<Integer> list = new ArrayList<>(splitNum);
		int rest = total - average * splitNum;
		for (int i = 0; i < splitNum; i++) {
			if (i < rest) {
				list.add(average + 1);
			} else{ list.add(average); }}// If the floating threshold is 0, no data randomization is performed
		if (thresh == 0) {
			return list;
		}
		// Randomize the data according to the threshold
		int randOfRange = 0;
		int randRom = 0;
		int nextIndex = 0;
		int nextValue = 0;
		int surplus = 0;/ / spare
		int lack = 0;/ / the lack of
		for (int i = 0; i < splitNum - 1; i++) {
			nextIndex = i + 1;
			int itemThis = list.get(i);
			int itemNext = list.get(nextIndex);
			boolean isLt = itemThis < itemNext;
			int rangeThis = isLt ? max - itemThis : itemThis - min;
			int rangeNext = isLt ? itemNext - min : max - itemNext;
			int rangeFinal = (int) Math.ceil(thresh * (Math.min(rangeThis, rangeNext) + 100));
			randOfRange = random.nextInt(rangeFinal);
			randRom = isLt ? 1 : -1;
			int iValue = list.get(i) + randRom * randOfRange;
			nextValue = list.get(nextIndex) + randRom * randOfRange * -1;
			if (iValue > max) {
				surplus += (iValue - max);
				list.set(i, max);
			} else if (iValue < min) {
				list.set(i, min);
				lack += (min - iValue);
			} else {
				list.set(i, iValue);
			}
			list.set(nextIndex, nextValue);
		}
		if (nextValue > max) {
			surplus += (nextValue - max);
			list.set(nextIndex, max);
		}
		if (nextValue < min) {
			lack += (min - nextValue);
			list.set(nextIndex, min);
		}
		if (surplus - lack > 0) {// Pay less money to less than Max to Max
			for (int i = 0; i < list.size(); i++) {
				int value = list.get(i);
				if (value < max) {
					int tmp = max - value;
					if (surplus >= tmp) {
						surplus -= tmp;
						list.set(i, max);
					} else {
						list.set(i, value + surplus);
						returnlist; }}}}else if (lack - surplus > 0) {// More money is given to people who get more than min
			for (int i = 0; i < list.size(); i++) {
				int value = list.get(i);
				if (value > min) {
					int tmp = value - min;
					if (lack >= tmp) {
						lack -= tmp;
						list.set(i, min);
					} else {
						list.set(i, min + tmp - lack);
						returnlist; }}}}return list;
	}

	/** * scramble ArrayList */
	public static List<BigDecimal> randomArrayList(List<BigDecimal> sourceList) {
		if (sourceList == null || sourceList.isEmpty()) {
			return sourceList;
		}
		List<BigDecimal> randomList = new CopyOnWriteArrayList<>();
		do {
			int randomIndex = Math.abs(new Random().nextInt(sourceList.size()));
			randomList.add(sourceList.remove(randomIndex));
		} while (sourceList.size() > 0);

		return randomList;
	}


	public static void main(String[] args) {
		Long startTi = System.currentTimeMillis();
		List<BigDecimal> li = genRandomList(new BigDecimal(100000), 26000.new BigDecimal(2), new BigDecimal(30));
		li = randomArrayList(li);
		BigDecimal total = BigDecimal.ZERO;
		System.out.println("======total=========total:" + total);
		System.out.println("======size=========size:" + li.size());
		Long endTi = System.currentTimeMillis();
		System.out.println("= = = = = = time = = = = = = = = = :" + (endTi - startTi) / 1000 + "Seconds"); }}Copy the code

Reference documentation

  • www.jianshu.com/p/0174a026b…
  • Juejin. Cn/post / 705254…