当前位置:网站首页>Timing manager based on C #

Timing manager based on C #

2022-07-05 03:39:00 Dotnet cross platform

problem

We will encounter the following problems when using various systems :

  • 12306 Buy train tickets on if 15 If the payment is not completed within minutes, the order will be automatically cancelled .

  • Reserved seats in the conference venue , If 10 If the payment is not completed within minutes, the reservation will be automatically cancelled .

  • After the specified time , I need to perform a task .

I have done many systems before , It is often a specific task that is performed on a regular basis . And the appeal problem involves the timing task of sliding window time .

such as : I am in the morning 10 spot 20 I booked a train ticket separately , I need to 15 Pay in minutes , Otherwise, the order will be canceled . Hundreds of people may book other train tickets at the same time , I need to be in everyone's 15 Check when the minute deadline expires , If it has not been paid, the order will be cancelled automatically .

programme

After we figure out the problem to be solved , Let's think about the plan . Experienced programmers will immediately think of the following solutions :

  • Use the delayed delivery function of message queue , Send a delay after each order is successfully added 15 Minute delay message . Order status processor 15 Message received in minutes , Check payment status , If not, cancel the order .

  • Redis There are similar functions , The principle is roughly the same .

But I don't want to use the function of message queue , Because delayed message delivery is a technical implementation , I hope to use code to reflect this business implementation , So use pure code to deal with him .( I'm not trying to reinvent the wheel , Because this is a business requirement , There will be a need for change and expansion , So I decided to try to increase my experience )

Algorithm ideas :

Our demand timing is 15 Within minutes, , Assume no more than 1 Hours or days .( If exceeded 1 Hour , This design can be extended , This article will not be discussed for the time being )

We can consider dividing an hour into 3600 second , Every second represents a location to store all expired orders , When placing an order, according to the current time add 15 Minute intervals , We can get it 15 Minutes later , Add this order to the corresponding location .

Data structure selection :

We choose C# The latest concurrent dictionary provided in is used as the basic data structure ,Key The value is 3600 The value of each second in seconds , The content is a queue used to store all orders at that point in time .

39208b8b769b0a8bbcb1833b8f485304.png

public ConcurrentDictionary<int, ConcurrentQueue<IJob>> jobs = new ConcurrentDictionary<int, ConcurrentQueue<IJob>>();

We have thought about the data structure , In fact, most of the functions are completed , The design of the code is basically settled .

Code implementation ( I like experimenting with console applications )
  1. Set up a timing manager

    public class TimerManager
        {
            // The concurrent dictionary stores the tasks that need to be checked ( Here can be the order check task , Each task can contain an order Id)
            public ConcurrentDictionary<int, ConcurrentQueue<IJob>> jobs = 
            new ConcurrentDictionary<int, ConcurrentQueue<IJob>>();
    
            private Timer timer;
            public TimerManager()
            {
                // Every interval 1 Once per second . Synchronize with the current time .
                timer = new Timer(ProcessJobs, null, 0, 1000);
            }
       }
  2. Add a task to the dictionary

    /// <summary>
            ///  Add a task to the time dictionary 
            /// </summary>
            /// <param name="timeKey"> Calculated according to the delay time key value </param>
            /// <param name="duetime"> Millisecond unit </param>
            /// <exception cref="NotImplementedException"></exception>
            public void AddJob(IJob job, TimeSpan duetime)
            {
                var key = GetKey(duetime);
                ConcurrentQueue<IJob> queue = new ConcurrentQueue<IJob>();
                queue.Enqueue(job);
                jobs.AddOrUpdate(key, queue, (key, jobs) =>
                {
                    jobs.Enqueue(job);
                    return jobs;
                });
            }
  3. Calculate according to time Key Methods

    /// <summary>
            ///  Generate the current key value according to the delay time 
            /// </summary>
            /// <param name="duetime"></param>
            /// <returns></returns>
            private int GetKey(TimeSpan duetime)
            {
                var currentDateTime = DateTime.Now;
                
                // Due time 
                var targetDateTime = currentDateTime.Add(duetime);
    
                // Don't forget to convert minutes into seconds , Then add it to the delay time to get Key
                var key = targetDateTime.Minute * 60 + targetDateTime.Second;
                return key;
            }
  4. Add tasks to the dictionary

    /// <summary>
            ///  Add a task to the time dictionary 
            /// </summary>
            /// <param name="job"> Tasks to be performed </param>
            /// <param name="duetime"> How many intervals do you check </param>
            public void AddJob(IJob job, TimeSpan duetime)
            {
                var key = GetKey(duetime);
                ConcurrentQueue<IJob> queue = new ConcurrentQueue<IJob>();
                queue.Enqueue(job);
                
                // This is the method of concurrent dictionary , This is when Key If it doesn't exist, add a new value , When Key Existence exists in Key Add a new task to the queue of 
                jobs.AddOrUpdate(key, queue, (key, jobs) =>
                {
                    jobs.Enqueue(job);
                    return jobs;
                });
            }
  5. The method of processing tasks when the timer executes every second , Cycle to get tasks from the queue until all tasks are processed .

    private async void ProcessJobs(object state)
            {
                // Calculate according to the current time Key value 
                var key = DateTime.Now.Minute * 60 + DateTime.Now.Second;
                Console.WriteLine(key);
                
                // lookup Key Value the corresponding task queue and process .
                bool keyExists = jobs.TryGetValue(key, out var jobQueue);
                if (keyExists)
                {
                    IJob job;
                    while(jobQueue.TryDequeue(out job))
                    {
                        await job.Run();
                    }
                }
    
            }
  6. Design in code IJob and Job An implementation of , For ease of understanding , This job Not doing much . If you need to expand to check the order , You can record the order here Id, When creating a task, place an order ID Associated with the task , In this way, the timer can find the corresponding order when processing this task .

    public interface IJob
        {
            Task Run();
        }
        
            /// <summary>
        ///  Represents a job 
        /// </summary>
        public class Job : IJob
        {
            public Guid JobId { get; set; }
    
            public Job()
            {
                JobId = Guid.NewGuid();
            }
    
            public async Task Run()
            {
                Console.WriteLine(" Job Id: " + JobId.ToString() + " is running.");
                await Task.Delay(2000);
                Console.WriteLine(" Job Id:" + JobId.ToString() + " have completed.");
            }
        }
  7. The main program Programe Call the timing manager

    using TimerTest;
    
    Console.WriteLine("Hello, World!");
    
    TimerManager timerManager = new TimerManager();
    
    Job job1 = new Job();
    
    //  Add a task 1 Execute in minutes 
    timerManager.AddJob( job1, TimeSpan.FromMinutes(1));
    
    //  Adding another task in 2 Execute in minutes 
    Job job2 = new Job();
    timerManager.AddJob(job2, TimeSpan.FromMinutes(2));
    
    Console.ReadLine();
Execution results

You can see in the results that , Mission 1 stay 1014 Is processed on the key value of ,1014 The time corresponding to the key value of is 16:54 second , That is, when I run this program 1 Minutes later .

3fe2079650c6fa711da7ed9ceeadd848.png

Mission Add the time execution time
First mission ( timing 1 minute )15:5416:54
Second task ( timing 2 minute )15:5417:54

Mission 2 stay 1074 Is processed on the key value of ,1074 The corresponding time is 17:54 second perform . It can be seen from the above table that the program runs normally and the result is .

3e0201cf9a0149a3c179a485f5a4c84a.png

summary

This is a simple console program that verifies the implementation of the timing manager , We will 1 Hours are divided into 3600 second , Every second corresponds to one Key value , On this value, we store the tasks that need to be processed . When adding tasks , We also use the same algorithm to determine this Key value . When processing, divide by... According to the current time Key Value to process .

In this case , In the real world , We have 3600 individual Key Value can store all orders submitted by users every second , Time has not passed 1 I will deal with the corresponding task in seconds .

What can be improved later
  • We can add this class to ASP.NET MVC in , Use dependency injection for single instance lifecycle , Concurrent dictionaries and queues are thread safe , So you can use it safely .

  • We can expand Job Method , Add more information according to the business logic to facilitate processing . For example, processing orders ID, Or any other business ID.

  • The method of processing tasks can adopt multiple consumers to execute concurrently , Increase processing speed .

  • You can store task entities in the database , In order to deal with sudden downtime accidents, tasks can be rebuilt quickly .

  • Of course we can use Hangfire To easily realize this business .

    var jobId = BackgroundJob.Schedule(
        () => Console.WriteLine("Delayed!"),
        TimeSpan.FromDays(7)); // Just change this to minutes 

Finally, I wish .NET 20 Happy anniversary .

原网站

版权声明
本文为[Dotnet cross platform]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202140728263844.html