微秒生成服务
最近做了一个聊天室,拉取历史消息时,排序采用微秒时间进行,得保证每条消息的微秒值不一样。
先用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 协议》,转载必须注明作者和本文链接
推荐文章: