forked from all_user/stat_center
Initial commit
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
var StatRds *Manager
|
||||
|
||||
type Manager struct {
|
||||
client *redis.Client
|
||||
options *redis.Options
|
||||
}
|
||||
|
||||
// Redis 配置项
|
||||
type Options = redis.Options
|
||||
|
||||
func NewManagerWithUrl(url string, fn func(opt *Options)) (*Manager, error) {
|
||||
opts, err := redis.ParseURL(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts == nil {
|
||||
err = errors.New(fmt.Sprintf("NewManagerWithUrl failed url %s", url))
|
||||
return nil, err
|
||||
}
|
||||
if fn != nil {
|
||||
fn(opts)
|
||||
}
|
||||
return &Manager{options: opts}, nil
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
AdvertiseClickDayHashTemplate = "stat-center.advertise.day.click.%s" // 广告点击
|
||||
AdvertiseClickDayHashIdHourSep = "@"
|
||||
IpLimiterKeyTemplate = "stat-center.%s:%d.ip.limiter.key#%s"
|
||||
IpLimiterDayRecordKey = "stat-center.ip.limiter.day.record.key"
|
||||
DownloadCheckBotMsgHashKeyTmpl = "stat-center.download.check.bot.msg.hash.key.%s" // 下载提示消息
|
||||
ServerStartTimeHashKeyTemplate = "stat-center.server.start_time.hash.key.%d"
|
||||
ServerKeepAliveHashKeyTemplate = "stat-center.server.keepalive.hash.key.%d"
|
||||
ServerCloseFlagHashKeyTemplate = "stat-center.server.close.flag.hash.key.%d"
|
||||
)
|
||||
|
||||
func GetServerStartTimeHashKey(index int) string {
|
||||
return fmt.Sprintf(ServerStartTimeHashKeyTemplate, index)
|
||||
}
|
||||
|
||||
func GetServerKeepAliveHashKey(index int) string {
|
||||
return fmt.Sprintf(ServerKeepAliveHashKeyTemplate, index)
|
||||
}
|
||||
|
||||
func GetServerCloseFlagHashKey(index int) string {
|
||||
return fmt.Sprintf(ServerCloseFlagHashKeyTemplate, index)
|
||||
}
|
||||
|
||||
func GetDownloadCheckBotMsgHashKey() string {
|
||||
return fmt.Sprintf(DownloadCheckBotMsgHashKeyTmpl, time.Now().Format("2006-02-01-15"))
|
||||
}
|
||||
|
||||
func GetIpLimitDayKey(serverIp string, serverPort int) string {
|
||||
ts := time.Now().Local().Format("2006-02-01")
|
||||
return fmt.Sprintf(IpLimiterKeyTemplate, serverIp, serverPort, ts)
|
||||
}
|
||||
|
||||
func GetIpLimitSpecifiedDayKey(serverIp string, serverPort int, day time.Time) string {
|
||||
ts := day.Local().Format("2006-02-01")
|
||||
return fmt.Sprintf(IpLimiterKeyTemplate, serverIp, serverPort, ts)
|
||||
}
|
||||
|
||||
func GetAdvertiseClickDayHash(now time.Time) *Hash {
|
||||
t := now.Local().Format("2006-02-01")
|
||||
return NewHash(StatRds.Client(), fmt.Sprintf(AdvertiseClickDayHashTemplate, t))
|
||||
}
|
||||
|
||||
func GetAdvertiseClickHourName(appId int32, adsId int64, hour int) string {
|
||||
return fmt.Sprintf("%d%s%d%s%d", appId, AdvertiseClickDayHashIdHourSep, adsId, AdvertiseClickDayHashIdHourSep, hour)
|
||||
}
|
||||
|
||||
const (
|
||||
NameSource = "name:source" //姓名池
|
||||
NameSourceExpire = 2 * 24 * time.Hour
|
||||
|
||||
AppClickDaySetTemplate = "nh.app.day.click.%s" // app点击
|
||||
AppClickDaySetIdHourSep = "@"
|
||||
)
|
||||
|
||||
func GetAppClickDaySet(now time.Time) *Hash {
|
||||
t := now.Local().Format("2006-02-01")
|
||||
return NewHash(StatRds.Client(), fmt.Sprintf(AppClickDaySetTemplate, t))
|
||||
}
|
||||
|
||||
func GetAppClickHourName(appId string, hour int) string {
|
||||
return fmt.Sprintf("%s%s%d", appId, AppClickDaySetIdHourSep, hour)
|
||||
}
|
||||
|
||||
func GetAppClickHourNameByAppId(appId int64, parentAppId int64, hour int) string {
|
||||
return fmt.Sprintf("%s_%s%s%d", appId, parentAppId, AppClickDaySetIdHourSep, hour)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
//key: {name:value, name:value, name:value}
|
||||
|
||||
type Hash struct {
|
||||
key string
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
func NewHash(client *redis.Client, key string) *Hash {
|
||||
return &Hash{
|
||||
key: key,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Hash) Key() string {
|
||||
return s.key
|
||||
}
|
||||
|
||||
// Expire 设置集合过期时间
|
||||
func (s *Hash) Expire(expiration time.Duration) (bool, error) {
|
||||
return s.client.Expire(s.key, expiration).Result()
|
||||
}
|
||||
|
||||
// Delete 删除一个或多个 Name
|
||||
func (s *Hash) Delete(names ...string) (int64, error) {
|
||||
return s.client.HDel(s.key, names...).Result()
|
||||
}
|
||||
|
||||
// Exists 检测Name是否在哈希表中
|
||||
func (s *Hash) Exists(name string) (bool, error) {
|
||||
return s.client.HExists(s.key, name).Result()
|
||||
}
|
||||
|
||||
// NameValues 获取所有哈希表数据
|
||||
func (s *Hash) NameValues() (map[string]string, error) {
|
||||
return s.client.HGetAll(s.key).Result()
|
||||
}
|
||||
|
||||
// Names 获取所有哈希表的 Name
|
||||
func (s *Hash) Names() ([]string, error) {
|
||||
return s.client.HKeys(s.key).Result()
|
||||
}
|
||||
|
||||
// Values 获取所有哈希表的 Total
|
||||
func (s *Hash) Values() ([]string, error) {
|
||||
return s.client.HVals(s.key).Result()
|
||||
}
|
||||
|
||||
// SetNX 当name不存在时才向哈希表中设置一对Name:Total
|
||||
func (s *Hash) SetNX(name string, value interface{}) (bool, error) {
|
||||
return s.client.HSetNX(s.key, name, value).Result()
|
||||
}
|
||||
|
||||
// Set 向哈希表中设置一对Name:Total
|
||||
func (s *Hash) Set(name string, value interface{}) (bool, error) {
|
||||
return s.client.HSet(s.key, name, value).Result()
|
||||
}
|
||||
|
||||
// Get 获取哈希表中Name对应的Value
|
||||
func (s *Hash) Get(name string) (string, error) {
|
||||
return s.client.HGet(s.key, name).Result()
|
||||
}
|
||||
|
||||
// IncInt 返回累加之后的新值
|
||||
func (s *Hash) IncInt(name string, value int64) (int64, error) {
|
||||
return s.client.HIncrBy(s.key, name, value).Result()
|
||||
}
|
||||
|
||||
// IncFloat 返回累加之后的新值
|
||||
func (s *Hash) IncFloat(name string, value float64) (float64, error) {
|
||||
return s.client.HIncrByFloat(s.key, name, value).Result()
|
||||
}
|
||||
|
||||
// Len 返回哈希表的大小
|
||||
func (s *Hash) Len() (int64, error) {
|
||||
return s.client.HLen(s.key).Result()
|
||||
}
|
||||
|
||||
// Scan 扫描哈希表数据
|
||||
func (s *Hash) Scan(match string, piece int64, callback func(name, value string)) error {
|
||||
proc := func(l []string) {
|
||||
for i := 0; i+1 < len(l); i += 2 {
|
||||
callback(l[i], l[i+1])
|
||||
}
|
||||
}
|
||||
lst, cur, err := s.client.HScan(s.key, 0, match, piece).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proc(lst)
|
||||
for cur > 0 {
|
||||
lst, cur, err = s.client.HScan(s.key, cur, match, piece).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proc(lst)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
//go:embed limit.lua
|
||||
var lua string
|
||||
|
||||
const (
|
||||
SUCCESS = "success"
|
||||
FAILED = "failed"
|
||||
)
|
||||
|
||||
type Bucket struct {
|
||||
Name string `json:"na"`
|
||||
Capacity int64 `json:"ca"`
|
||||
Rate int64 `json:"ra"`
|
||||
Size int64 `json:"si"`
|
||||
GenerateAt int64 `json:"ge_at"`
|
||||
CreatedAt int64 `json:"cr_at"`
|
||||
UpdatedAt int64 `json:"up_at"`
|
||||
PassedCount int64 `json:"pa_co"`
|
||||
RejectCount int64 `json:"re_co"`
|
||||
PassedHours map[int64]int64 `json:"pa_ho"`
|
||||
RejectHours map[int64]int64 `json:"re_ho"`
|
||||
}
|
||||
|
||||
type BucketList []*Bucket
|
||||
|
||||
func (a BucketList) Len() int { return len(a) }
|
||||
func (a BucketList) Less(i, j int) bool {
|
||||
return a[i].PassedCount > a[j].PassedCount
|
||||
}
|
||||
func (a BucketList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type TokenBucket struct {
|
||||
keyGen func() string
|
||||
capacity int64
|
||||
rate int64
|
||||
script *redis.Script
|
||||
client *redis.Client
|
||||
expire time.Duration
|
||||
lock sync.RWMutex
|
||||
curKey string
|
||||
}
|
||||
|
||||
func NewTokenBucket(client *redis.Client, keyGen func() string, capacity, rate int64, expire time.Duration) *TokenBucket {
|
||||
return &TokenBucket{
|
||||
expire: expire,
|
||||
capacity: capacity,
|
||||
rate: rate,
|
||||
client: client,
|
||||
script: redis.NewScript(lua),
|
||||
keyGen: keyGen,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TokenBucket) TakeTokens(name string, count int64) error {
|
||||
key := s.keyGen()
|
||||
now := time.Now().Local()
|
||||
cmd := []string{
|
||||
key, //key
|
||||
name, //name
|
||||
fmt.Sprintf("%d", s.capacity),
|
||||
fmt.Sprintf("%d", s.rate),
|
||||
fmt.Sprintf("%d", count),
|
||||
fmt.Sprintf("%d", now.UnixMilli()),
|
||||
fmt.Sprintf("%d", now.Hour()),
|
||||
}
|
||||
ret, err := s.script.Run(s.client, cmd).String()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ret != SUCCESS {
|
||||
return errors.New("sorry, I can't work anymore~")
|
||||
}
|
||||
var same = false
|
||||
if s.lock.TryRLock() {
|
||||
same = key == s.curKey
|
||||
s.lock.RUnlock()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
if !same {
|
||||
if s.lock.TryLock() {
|
||||
if ok, err := s.client.Expire(key, s.expire).Result(); err == nil && ok {
|
||||
s.curKey = key
|
||||
}
|
||||
s.lock.Unlock()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TokenBucket) Traverse(callback func(key string, item *Bucket)) error {
|
||||
return NewHash(s.client, s.keyGen()).Scan("", 1000, func(name, value string) {
|
||||
bucket := Bucket{}
|
||||
if err := json.Unmarshal([]byte(value), &bucket); err == nil {
|
||||
callback(name, &bucket)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
local CAPACITY = "ca"
|
||||
local RATE = "ra"
|
||||
local SIZE = "si"
|
||||
local GENERATE_AT = "ge_at"
|
||||
local CREATED_AT = "cr_at"
|
||||
local UPDATED_AT = "up_at"
|
||||
local PASSED_COUNT = "pa_co"
|
||||
local REJECT_COUNT = "re_co"
|
||||
local PASSED_HOURS = "pa_ho"
|
||||
local REJECT_HOURS = "re_ho"
|
||||
local SUCCESS = "success"
|
||||
local FAILED = "failed"
|
||||
|
||||
local sKey = tostring(KEYS[1] or "")
|
||||
local sName = tostring(KEYS[2] or "")
|
||||
local iCapacity = tonumber(KEYS[3] or 0)
|
||||
local iRate = tonumber(KEYS[4] or 0)
|
||||
local iCount = tonumber(KEYS[5] or 0)
|
||||
local iNow = tonumber(KEYS[6] or 0)
|
||||
local sHour = tostring(KEYS[7] or "")
|
||||
|
||||
if sKey == "" or sName == "" or sHour == "" then
|
||||
return FAILED .. " param error1"
|
||||
end
|
||||
if iCapacity <= 0 or iRate <= 0 or iCount <= 0 or iNow <= 0 then
|
||||
return FAILED .. "param error2"
|
||||
end
|
||||
local bucket = {}
|
||||
local data = redis.pcall("HGET", sKey, sName)
|
||||
local init = false
|
||||
if data == nil or data == false or data == "" then
|
||||
init = true
|
||||
else
|
||||
bucket = cjson.decode(data)
|
||||
if bucket == nil then
|
||||
init = true
|
||||
end
|
||||
end
|
||||
if init then
|
||||
bucket[CAPACITY] = iCapacity
|
||||
bucket[RATE] = iRate
|
||||
bucket[SIZE] = iCapacity
|
||||
bucket[GENERATE_AT] = iNow
|
||||
bucket[CREATED_AT] = iNow
|
||||
bucket[UPDATED_AT] = iNow
|
||||
bucket[PASSED_COUNT] = 0
|
||||
bucket[REJECT_COUNT] = 0
|
||||
bucket[PASSED_HOURS] = {}
|
||||
bucket[REJECT_HOURS] = {}
|
||||
else
|
||||
bucket[CAPACITY] = iCapacity
|
||||
bucket[RATE] = iRate
|
||||
end
|
||||
local bk_size = tonumber(bucket[SIZE] or 0)
|
||||
local bk_capacity = tonumber(bucket[CAPACITY] or 0)
|
||||
if bk_size < bk_capacity then
|
||||
local bk_last = tonumber(bucket[GENERATE_AT] or 0)
|
||||
local dura = iNow - bk_last
|
||||
local incr = math.floor(dura * iRate / 1000)
|
||||
if incr >= 1 then
|
||||
bk_size = math.min(bk_size + incr, bk_capacity)
|
||||
bucket[SIZE] = bk_size
|
||||
bucket[GENERATE_AT] = iNow
|
||||
end
|
||||
end
|
||||
local message = FAILED
|
||||
if iCount <= bk_size then
|
||||
bucket[SIZE] = bk_size - iCount
|
||||
bucket[PASSED_COUNT] = tonumber(bucket[PASSED_COUNT] or 0) + 1
|
||||
local hours = bucket[PASSED_HOURS]
|
||||
if hours == nil then
|
||||
hours = {}
|
||||
end
|
||||
hours[sHour] = tonumber(hours[sHour] or 0) + 1
|
||||
bucket[PASSED_HOURS] = hours
|
||||
message = SUCCESS
|
||||
else
|
||||
bucket[REJECT_COUNT] = tonumber(bucket[REJECT_COUNT] or 0) + 1
|
||||
local hours = bucket[REJECT_HOURS]
|
||||
if hours == nil then
|
||||
hours = {}
|
||||
end
|
||||
hours[sHour] = tonumber(hours[sHour] or 0) + 1
|
||||
bucket[REJECT_HOURS] = hours
|
||||
message = FAILED
|
||||
end
|
||||
bucket[UPDATED_AT] = iNow
|
||||
data = cjson.encode(bucket)
|
||||
redis.pcall("HSET", sKey, sName, data)
|
||||
return message
|
||||
@@ -0,0 +1,28 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestF2(t *testing.T) {
|
||||
ts := time.Now().Local().Format("2006-02-01-15")
|
||||
t.Log(ts)
|
||||
}
|
||||
|
||||
func TestScan(t *testing.T) {
|
||||
mgr, err := NewManagerWithUrl("redis://127.0.0.1:6379/1", nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
set := NewHash(mgr.Client(), "set-test-key")
|
||||
for i := 0; i < 1000; i++ {
|
||||
set.Set(fmt.Sprintf("name:%d", i), fmt.Sprintf("value:%d", i))
|
||||
}
|
||||
|
||||
err = set.Scan("", 20, func(name, value string) {
|
||||
t.Logf("N: %s, V:%s\n", name, value)
|
||||
})
|
||||
t.Log(err)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (m *Manager) Client() *redis.Client {
|
||||
if m.client == nil {
|
||||
m.initConnect()
|
||||
}
|
||||
return m.client
|
||||
}
|
||||
|
||||
func (m *Manager) Close() {
|
||||
if m.client != nil {
|
||||
m.client.Close()
|
||||
m.client = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) initConnect() error {
|
||||
return m.Connect(m.options)
|
||||
}
|
||||
|
||||
func (m *Manager) Connect(opts *Options) error {
|
||||
c := redis.NewClient(opts)
|
||||
if err := c.Ping().Err(); err != nil {
|
||||
log.Panicf("[error] ping redis err %+v\n", err)
|
||||
return err
|
||||
}
|
||||
m.client = c
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
goredis "github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
func (m *Manager) Get(key string) (*string, error) {
|
||||
if key == "" {
|
||||
return nil, errors.New("key is empty")
|
||||
}
|
||||
data, err := m.Client().Get(key).Result()
|
||||
if err != nil {
|
||||
if err == goredis.Nil {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
} else {
|
||||
return &data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Set(key string, value interface{}, expire time.Duration) error {
|
||||
if err := m.Client().Set(key, value, expire).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Del(keys ...string) error {
|
||||
if err := m.Client().Del(keys...).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetBytes(key string) ([]byte, error) {
|
||||
cmd := m.Client().Get(key)
|
||||
if err := cmd.Err(); err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return cmd.Bytes()
|
||||
}
|
||||
|
||||
func (m *Manager) SAdd(key string, members ...interface{}) error {
|
||||
if err := m.Client().SAdd(key, members...).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Exists(keys ...string) (bool, error) {
|
||||
cmd := m.Client().Exists(keys...)
|
||||
if err := cmd.Err(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return cmd.Val() > 0, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Expire(key string, expire time.Duration) error {
|
||||
if err := m.Client().Expire(key, expire).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Keys(pattern string) ([]string, error) {
|
||||
list := m.Client().Keys(pattern)
|
||||
if err := list.Err(); err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return list.Val(), nil
|
||||
|
||||
}
|
||||
|
||||
func (m *Manager) Incr(pattern string) (int64, error) {
|
||||
val, err := m.Client().Incr(pattern).Result()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
//获取key的剩余生存时间
|
||||
func (m *Manager) TTL(pattern string) time.Duration {
|
||||
durCmd := m.Client().TTL(pattern)
|
||||
if durCmd != nil {
|
||||
return durCmd.Val()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user