Windows/C# system-level sequential number generato

2019-07-14 14:13发布

问题:

Is there a managed system-level sequential number generator? DateTime.Now.Ticks won't do because the operations I'm doing sometimes occur more than once per tick.


Requirement Clarifications:

  • Process agnostic - there's really only one process that would be accessing this.
  • Performance is critical! This is used for logging impressions on an adserver, which can reach 1k/sec

It would need to be one of the following:

  • A 4-byte sequential number that resets every tick
  • A 12-byte sequential number - essentially adding 4 bytes of granularity to a DateTime

回答1:

None that are intended for this, but you could use System.Diagnostics.PerformanceCounter. You could also use the Registry but you'd need to serialize read/write access accross the processes.

System.Diagnostics.PerformanceCounter pc 
    = new System.Diagnostics.PerformanceCounter("SeqCounter", "SeqInstance");
long myVal=pc.Increment();

Edit

The more I think about this, I think this could be a nice solution. Increment will increase the counter by 1 through an atomic operation which should work accross all processes on a system.

Edit

Based on your edits, I would not recommend using a performance counter. A performance counter was a way to cordinate accross multiple processes. I am not sure how the internal implemention is coded.

Why can't you just use a static variable and increment it? Your going to have to lock something if you want this to be threadsafe.

System.Threading.Interlocked.Increment

FYI: If you use the long version on a 32 bit system it won't nessecarilly be thread safe.


Editing to show the implemention I used (DS):

public static class Int32Sequencer
{
    private static Int32 lastSequence = Int32.MinValue;
    private static Object lockObject = new Object();
    public static Int32 GetNextSequence()
    {
        lock (lockObject)
        {
            unchecked { lastSequence++; }
            return lastSequence;
        }
    }
}


回答2:

A Guid is as close as you're going to get, but those are "unique" and not necessarily sequential. If you really want sequential across multiple processes at the system level, you will probably have to roll your own.

EDIT:

Ok, so from your new requirements, I'm going to assume:

  1. Only one process needs to do the work
  2. You are appending to a database

So here is what I would recommend:

  1. At the process start-up, query the DB for the last (greatest) value (0 if none exist).
  2. Use a simple long and increment for each DB row. You are going to want to insert in batches due to your high data rate.

That should do it. Keep it simple. This has no locks, a slight (negligible) hit at start-up, and sequential numbers in your DB. This algorithm is also process-agnotic as long you only have one process running it.



回答3:

I think the closest thing is a guid, which as I'm sure you're aware is at best only partially sequential.



回答4:

There is an article over here that gives some details for sql server : Sequential GUID's in SQL Server This technique is used to minimize page splits due to the randomness of GUID's. Maybe this link will give you some hints or ideas.



回答5:

I think we need to know a bit more about what you are looking for. Can you clarify your question a bit. In particular does the service ...

  • Need to work across all processes, one process, or a particular user?
  • Must the numbers be unique or just sequential?

Based on your question there appear to be a couple of different items you may be looking for.

Need a sequential group of numbers across all processes on the system

AFAIK, No such service exist. One should be fairly easy to write but getting it to work across all processes is tricky.

Need a unique sequential group of numbers across all processes on the system

Slight variation on the first question. Such a service does not exist because it would be impossible to implement. There is no way to guarantee a unique sequential number using the built-in data types simply because the value will eventually overflow and leave you with a duplicate number.

Need a method of getting unique values in the system

As several other users have mentioned the best choice is a System.Guid instance. You can create a new one using Guid.NewGuid(). For almost all purposes they can be considered unique but are not sequential.



回答6:

I like the database option simply for safety. Make sure you install a monster SQL server with allot of bandwidth between your servers with enough memory though. A system similar to this was implemented at the first company I ever worked for (Before I even became a programmer) and it was very dodgy. You may battle to scale this.

Another option would be to implement a singleton function into your code... provided only one application domain will be calling it. MAY be a bit faster than doing a database trip. If however you are going to be logging this stuff to the database anyway.... What about combining the two... Run a singleton for speed and then write to the database when resources permit.

Again, if the sequential requirement is not that strong, a Guid would be your best bet.



回答7:

There's no way to get a single sequential series without locking. It doesn't matter what mechanism you're using to assign the next value - a performance counter, a static variable, whatever - when two threads both need the next value at the same time, one has to wait on the other.

The first thing I'd do is write a test program that spawned a large number of threads which each repeatedly called a locking increment function such as the one Daniel Schaffer posted. That will let you find the threshold where your application starts to thrash - where it's spending more time waiting on Monitor.Enter than doing anything else.

If that turns out to be a problem - and I'd bet that if the volumes you're talking about are real, it will - then you should make each thread maintain its own sequential counter, which you can do by marking the counter field with the ThreadStaticAttribute. You can then generate unique identifiers off of a combination of the thread's ID and the counter.

This approach won't work if you're not using a thread pool (since the counter dies when the thread it belongs to does). And you probably also want to make the application's startup count part of the compound ID, so that you don't have to write the thread counters to durable storage. (If you didn't do this, when you restarted the server the threads would start generating counters at zero again, and if your application created a thread with the same ID as an earlier instance, you'd get duplicate identifiers.)

This is obviously not trivial to write (or, more importantly, test), so I'd definitely recommend proving that it's necessary first.