微秒生成服务

最近做了一个聊天室,拉取历史消息时,排序采用微秒时间进行,得保证每条消息的微秒值不一样。
先用go写了一版:

package main

import (
    "encoding/json"
    "flag"
    "github.com/valyala/fasthttp"
    "log"
    "sync"
    "sync/atomic"
    "time"
)

var collection *Collection

func init() {
    collection = NewCollection()
}

func main() {
    addr := flag.String("addr", "0.0.0.0:7788", "listen host")
    flag.Parse()
    s := &fasthttp.Server{
        Handler: requestHandler,
        Logger:  NullLogger{},
    }
    log.Println("listen http" + "://" + *addr)
    err := s.ListenAndServe(*addr)
    if err != nil {
        log.Fatalln(err)
    }
}

func requestHandler(ctx *fasthttp.RequestCtx) {
    //业务key
    key := string(ctx.QueryArgs().Peek("key"))
    //需要得到的微秒个数
    incr := int64(ctx.QueryArgs().GetUintOrZero("incr"))
    var ret Ret
    if key == "" || incr == 0 {
        ret = Ret{Code: 1, Message: "invalid argument", Data: 0}
    } else {
        //客户端拿到data后循环incr次,每次自增1,即可展开得到所有的微秒
        ret = Ret{Code: 0, Message: "success", Data: collection.Get(key).Get(incr) - incr}
    }
    ctx.SetContentType("application/json; charset=UTF-8")
    _, _ = ctx.Write(ret.Encode())
}

// NullLogger 空日志
type NullLogger struct {
}

func (r NullLogger) Printf(string, ...any) {
}

// Ret 返回结构
type Ret struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Data    int64  `json:"data"`
}

func (r Ret) Encode() []byte {
    ret, _ := json.Marshal(r)
    return ret
}

// Micro 微秒对象
type Micro struct {
    incr *int64
}

func NewMicro() *Micro {
    var incr int64
    micro := &Micro{
        incr: &incr,
    }
    micro.Reset(time.Now())
    return micro
}

func (r *Micro) Get(num int64) int64 {
    return atomic.AddInt64(r.incr, num)
}

func (r *Micro) Reset(t time.Time) {
    atomic.StoreInt64(r.incr, t.Unix()*1000000)
}

// Collection 所有微秒服务的集合
type Collection struct {
    data  map[string]*Micro
    mutex *sync.RWMutex
}

func NewCollection() *Collection {
    c := &Collection{data: map[string]*Micro{}, mutex: &sync.RWMutex{}}
    //每隔1秒刷新集合中的所有变量的值
    go func(collection *Collection) {
        ticker := time.NewTicker(time.Second)
        defer ticker.Stop()
        for {
            t := <-ticker.C
            c.Reset(t)
        }
    }(c)
    return c
}

func (r *Collection) Get(key string) *Micro {
    r.mutex.RLock()
    micro, ok := r.data[key]
    r.mutex.RUnlock()
    if ok {
        return micro
    }
    r.mutex.Lock()
    micro, ok = r.data[key]
    if ok {
        r.mutex.Unlock()
        return micro
    }
    micro = NewMicro()
    r.data[key] = micro
    r.mutex.Unlock()
    return micro
}

func (r *Collection) Reset(t time.Time) {
    r.mutex.RLock()
    for _, v := range r.data {
        v.Reset(t)
    }
    r.mutex.RUnlock()
}

然后再用redis写了一版:

<?php

declare(strict_types=1);

namespace app\patch;

use RedisException;
use support\Log;

class RedisHelper
{
    /**
     * 单机redis服务器下,该脚本可以保证2065-01-24 13:19:59以内,秒并发1000000以内,分配不重复的微秒时间戳
     */
    protected const GET_MICRO_SECOND_SCRIPT = '
        local key = KEYS[1]
        local num = tonumber(ARGV[1])
        local currentTimestampStr = redis.call("TIME")[1]
        local data = redis.call("get",key)
        if data == false then
            data = currentTimestampStr .. "0"
        end
        local storeTimestampStr = string.sub(data, 0, 10)
        local microStr = string.sub(data, 11)
        if currentTimestampStr ~= storeTimestampStr then
            microStr = "0"
        end
        local ret = {}
        table.insert(ret, currentTimestampStr)
        table.insert(ret, microStr)
        redis.call("set",key,currentTimestampStr .. (tonumber(microStr) + num))
        return ret
    ';

    /**
     * 获取微秒时间戳,这个时间戳不是真实的,而是在秒级别自增的,该函数可以保证秒并发1000000下分配不重复的微秒时间戳
     * @param string $key
     * @param int $number
     * @return array
     * @throws RedisException
     */
    public static function getMicrosecond(string $key, int $number): array
    {
        $key = "microsecond:$key";
        $redis = redis();
        $redis->clearLastError();
        $data = $redis->eval(self::GET_MICRO_SECOND_SCRIPT, [$key, $number], 1);
        $err = $redis->getLastError();
        if (null !== $err) {
            Log::error(__METHOD__ . ' ' . $err);
            $redis->clearLastError();
            return [];
        }
        $ret = [];
        $data[0] = ((int)$data[0]) * 1000000;
        $data[1] = (int)$data[1];
        for ($i = 0; $i < $number; $i++) {
            $data[1] += 1;
            $ret[] = $data[0] + $data[1];
        }
        return $ret;
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
梦想星辰大海
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!