Real time statistics of orders by time period (zset based on redis)

Keywords: Redis

1, Demand

On the C-side App, whether the user can select distribution within a certain time period requires real-time statistics of the number of orders in each period (every hour or every half an hour) in the background, and determines the distribution period that the user can select by considering the pressure requests of picking and packaging in the warehouse and distribution outside the warehouse.

2, Scheme

The zset (sorted set) data structure of redis is used for real-time statistical analysis.

The redis ordered collection zset, like the collection set, is also a collection of string type elements, and duplicate members are not allowed. The difference is that each element of zset is associated with a score (the score can be repeated). Redis uses the score to sort the members of the set from small to large.

Each warehouse uses a zset key to store every day, orderId as the member of zset, and the expected completion time stamp corresponding to the order as the score of zset;

When the user places an order, the orderId and the expected completion time stamp (zadd) corresponding to the order are recorded.

When the order is cancelled or the delivery is completed, the order (zrem) is deleted from the zset.

To query the number of orders within a time period, you only need to take the start and end timestamp of the time period as the score of zset, and use zcount to count the number of orders

3, zset test

127.0.0.1:6379> 
# 1. Add. If the value exists, it will be reordered. zadd
127.0.0.1:6379> zadd user  1 wuxiaolong   
(integer) 1
127.0.0.1:6379>  zadd user   10 sukailiang   8 yufeng   5 shilei   6 huangchao 
(integer) 4
127.0.0.1:6379> 
# 2. View the number of members of the zset set. zcard
127.0.0.1:6379> zcard user
(integer) 5
127.0.0.1:6379> 
# 3. View the members of the range specified by Zset, with scores for the output results. zrange
127.0.0.1:6379> zrange user 3 8
1) "yufeng"
2) "sukailiang"
127.0.0.1:6379> zrange user 3 8 withscores
1) "yufeng"
2) "8"
3) "sukailiang"
4) "10"
127.0.0.1:6379> zrange user 0 -1 withscores
 1) "wuxiaolong"
 2) "1"
 3) "shilei"
 4) "5"
 5) "huangchao"
 6) "6"
 7) "yufeng"
 8) "8"
 9) "sukailiang"
10) "10"
# 4. Get the subscript position of zset member. If the value does not exist, return null. zrank
127.0.0.1:6379> zrank user yufeng
(integer) 3
127.0.0.1:6379> zrank user wuxiaolong
(integer) 0
127.0.0.1:6379> zrank user nobody
(nil)
127.0.0.1:6379> 
# 5. Get the number of members between the scores specified in the zset set. zcount 
127.0.0.1:6379> zcount user 3 8
(integer) 3
127.0.0.1:6379> zcount user 5 8
(integer) 3
# 6. Delete one or more specified members. zrem
127.0.0.1:6379> zrem user yufeng
(integer) 1
127.0.0.1:6379> zrem user nobody
(integer) 0
127.0.0.1:6379> 
# 7. Get the score of the specified value. zscore
127.0.0.1:6379> zscore user wuxiaolong
"1"
127.0.0.1:6379> zscore user nobody
(nil)
127.0.0.1:6379> zscore user shilei
"5"
127.0.0.1:6379> 
# 8. Increase or decrease the score of the specified element. Negative values are minus and positive values are plus. zincrby
127.0.0.1:6379> 
127.0.0.1:6379> zincrby user 3 shilei
"8"
127.0.0.1:6379> zscore user shilei
"8"
127.0.0.1:6379> 

127.0.0.1:6379> zrange user 0 -1 withscores
1) "wuxiaolong"
2) "1"
3) "huangchao"
4) "6"
5) "shilei"
6) "8"
7) "sukailiang"
8) "10"
127.0.0.1:6379> 
127.0.0.1:6379> 
# 9. Get the value according to the range of the specified score. zrangebysocre
127.0.0.1:6379> zrangebyscore user 0 8
1) "wuxiaolong"
2) "huangchao"
3) "shilei"
127.0.0.1:6379> 
# 10. In reverse order, sort from top to bottom and output data in the specified range. zrevrange,zrevrangebyscore 
127.0.0.1:6379> zrevrange user 0 -1
1) "sukailiang"
2) "shilei"
3) "huangchao"
4) "wuxiaolong"
127.0.0.1:6379> zrevrange user 0 -1 withscores
1) "sukailiang"
2) "10"
3) "shilei"
4) "8"
5) "huangchao"
6) "6"
7) "wuxiaolong"
8) "1"
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> 
# 11. Delete data according to the coordinate and fraction range. zremrangebyscore,zremrangebyrank
127.0.0.1:6379> zrevrangebyscore user 8 1
1) "shilei"
2) "huangchao"
3) "wuxiaolong"
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> 
# 12.zset also has the operation of finding the intersection and union of two sets. zunionzstore,zinterstore 

4, JAVA testing

1.controller

package message.queue.engine.controller;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping(value = "/v1")
public class HelloController {

	@Autowired
	private ZSetOperations<String, Object> zSetOperations;

	@RequestMapping("/add")
	public Boolean add(@RequestParam("orderId") String orderId){

		// Time of performance
		Date deliveryDate = new Date();

		// Performance date
		String ymd = Utils.getYMD(deliveryDate);

		// One key a day
		String key = Const.STAT_PREFIX+ ymd;

		// If the order number is repeated, orderId will be overwritten
		Boolean result = zSetOperations.add(key, orderId, deliveryDate.getTime());

		// Store for one week
		if(result){
			Boolean expire = zSetOperations.getOperations().expire(key, Const.RDS_EXPIRE_DAY, TimeUnit.DAYS);
		}

		return result;
	}

	@RequestMapping("/del")
	public Long del(@RequestParam("orderId") String orderId){

		// Time of performance
		Date deliveryDate = new Date();

		// Performance date
		String ymd = Utils.getYMD(deliveryDate);

		// One key a day
		String key = Const.STAT_PREFIX+ ymd;

		// delete
		Long remove = zSetOperations.remove(key, orderId);

		return remove;
	}

	@RequestMapping("/stat")
	public Long stat(){

		// current time 
		Date now = new Date();
		// The length of the time period is divided into 30 minutes
		int segmentType = 30;

		// Performance date
		String ymd = Utils.getYMD(now);

		// One key a day
		String key = Const.STAT_PREFIX+ ymd;

		// Starting Today
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(now);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		Date todayStart = calendar.getTime();

		// Relative time period
		Long relativeTime = now.getTime() - todayStart.getTime();

		long[] segmentArr;
		if(segmentType ==30){
			segmentArr = Const.TIME_SEGMENT_30;
		}else {
			segmentArr = Const.TIME_SEGMENT_60;
		}

		Long start = null;
		Long end = null;
		for (int i=0; i< segmentArr.length-1; i++){
			if(relativeTime >= segmentArr[i] && relativeTime <= segmentArr[i+1]){
				start = segmentArr[i] + todayStart.getTime();
				end = segmentArr[i+1] + todayStart.getTime();
			}
		}

        // todo start=null

		// Count order quantity according to time period
		Long count = zSetOperations.count(key, start, end);

		System.out.println(start +"   " + end +"   " + count);

		return count;
	}
}

2.Constant

package or.message.queue.engine.constant;

public class Const {


    public static final String STAT_PREFIX = "eta:order:stat:zset:";

    public static final String ORDER_PREFIX = "eta:order:";

    public static final Integer RDS_EXPIRE_DAY = 7;

    public static final long[]  TIME_SEGMENT_30 = {
            0,
             1800000, 3600000,
             5400000, 7200000,
             9000000, 10800000,
            12600000, 14400000,
            16200000, 18000000, // 6 hours
            19800000, 21600000,
            23400000, 25200000,
            27000000, 28800000,
            30600000, 32400000,
            34200000, 36000000,  // 10 hours
            37800000, 39600000,
            41400000, 43200000,  // 12
            45000000, 46800000,
            48600000, 50400000,
            52200000, 54000000,
            55800000, 57600000,
            59400000, 61200000,
            63000000, 64800000, // 18
            66600000, 68400000,
            70200000, 72000000, // 20 hours
            73800000, 75600000,
            77400000, 79200000,
            81000000, 82800000,
            84600000, 86400000, // 24 hours
    };

    public static final long[]  TIME_SEGMENT_60 = {
            0,
            3600000,
            7200000,
            10800000,
            14400000,
            18000000, // 6 hours
            21600000,
            25200000,
            28800000,
            32400000,
            36000000,  // 10 hours
            39600000,
            43200000,  // 12
            46800000,
            50400000,
            54000000,
            57600000,
            61200000,
            64800000, // 18
            68400000,
            72000000, // 20 hours
            75600000,
            79200000,
            82800000,
            86400000, // 24 hours
    };
}

5, Extension of zset

  1. zset as delay queue

    zset will sort by score, if score represents the timestamp of the desired execution time. When it is inserted into the zset set at a certain time, it will be sorted according to the timestamp size, that is, before and after the execution time.

    Start an endless loop thread and continuously take the first key value. If the current timestamp is greater than or equal to the score of the key value, it will be taken out for consumption deletion, which can achieve the purpose of delaying execution

  2. Ranking List
    It is not easy to distinguish between real-time statistics if the list of "the most popular in one hour" is recorded in the database. We take the timestamp of the current hour as the key of zset, the post ID as the member, the number of hits, comments, etc. as the score, and update the score when the score changes. Use ZREVRANGE or ZRANGE to find the corresponding number of records.

Posted by jaimec on Fri, 29 Oct 2021 01:58:00 -0700