mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-18 22:54:35 +08:00
optimize ttl by time wheel
This commit is contained in:
@@ -32,8 +32,8 @@ const (
|
|||||||
|
|
||||||
CreatedStatus = 0
|
CreatedStatus = 0
|
||||||
PreparedStatus = 1
|
PreparedStatus = 1
|
||||||
CommitedStatus = 2
|
CommittedStatus = 2
|
||||||
RollbackedStatus = 3
|
RolledBackStatus = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTransaction(cluster *Cluster, c redis.Connection, id string, args [][]byte, keys []string) *Transaction {
|
func NewTransaction(cluster *Cluster, c redis.Connection, id string, args [][]byte, keys []string) *Transaction {
|
||||||
@@ -89,10 +89,10 @@ func (tx *Transaction) rollback() error {
|
|||||||
tx.cluster.db.Remove(key)
|
tx.cluster.db.Remove(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tx.status != CommitedStatus {
|
if tx.status != CommittedStatus {
|
||||||
tx.cluster.db.UnLocks(tx.keys...)
|
tx.cluster.db.UnLocks(tx.keys...)
|
||||||
}
|
}
|
||||||
tx.status = RollbackedStatus
|
tx.status = RolledBackStatus
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ func Commit(cluster *Cluster, c redis.Connection, args [][]byte) redis.Reply {
|
|||||||
// finish transaction
|
// finish transaction
|
||||||
defer func() {
|
defer func() {
|
||||||
cluster.db.UnLocks(tx.keys...)
|
cluster.db.UnLocks(tx.keys...)
|
||||||
tx.status = CommitedStatus
|
tx.status = CommittedStatus
|
||||||
//cluster.transactions.Remove(tx.id) // cannot remove, may rollback after commit
|
//cluster.transactions.Remove(tx.id) // cannot remove, may rollback after commit
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
39
src/db/db.go
39
src/db/db.go
@@ -4,10 +4,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/HDT3213/godis/src/config"
|
"github.com/HDT3213/godis/src/config"
|
||||||
"github.com/HDT3213/godis/src/datastruct/dict"
|
"github.com/HDT3213/godis/src/datastruct/dict"
|
||||||
List "github.com/HDT3213/godis/src/datastruct/list"
|
|
||||||
"github.com/HDT3213/godis/src/datastruct/lock"
|
"github.com/HDT3213/godis/src/datastruct/lock"
|
||||||
"github.com/HDT3213/godis/src/interface/redis"
|
"github.com/HDT3213/godis/src/interface/redis"
|
||||||
"github.com/HDT3213/godis/src/lib/logger"
|
"github.com/HDT3213/godis/src/lib/logger"
|
||||||
|
"github.com/HDT3213/godis/src/lib/timewheel"
|
||||||
"github.com/HDT3213/godis/src/pubsub"
|
"github.com/HDT3213/godis/src/pubsub"
|
||||||
"github.com/HDT3213/godis/src/redis/reply"
|
"github.com/HDT3213/godis/src/redis/reply"
|
||||||
"os"
|
"os"
|
||||||
@@ -239,14 +239,26 @@ func (db *DB) RUnLocks(keys ...string) {
|
|||||||
|
|
||||||
/* ---- TTL Functions ---- */
|
/* ---- TTL Functions ---- */
|
||||||
|
|
||||||
|
func genExpireTask(key string) string {
|
||||||
|
return "expire:" + key
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) Expire(key string, expireTime time.Time) {
|
func (db *DB) Expire(key string, expireTime time.Time) {
|
||||||
db.stopWorld.Wait()
|
db.stopWorld.Wait()
|
||||||
db.TTLMap.Put(key, expireTime)
|
db.TTLMap.Put(key, expireTime)
|
||||||
|
taskKey := genExpireTask(key)
|
||||||
|
timewheel.At(expireTime, taskKey, func() {
|
||||||
|
logger.Info("expire " + key)
|
||||||
|
db.TTLMap.Remove(key)
|
||||||
|
db.Data.Remove(key)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) Persist(key string) {
|
func (db *DB) Persist(key string) {
|
||||||
db.stopWorld.Wait()
|
db.stopWorld.Wait()
|
||||||
db.TTLMap.Remove(key)
|
db.TTLMap.Remove(key)
|
||||||
|
taskKey := genExpireTask(key)
|
||||||
|
timewheel.Cancel(taskKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) IsExpired(key string) bool {
|
func (db *DB) IsExpired(key string) bool {
|
||||||
@@ -262,32 +274,7 @@ func (db *DB) IsExpired(key string) bool {
|
|||||||
return expired
|
return expired
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) CleanExpired() {
|
|
||||||
now := time.Now()
|
|
||||||
toRemove := &List.LinkedList{}
|
|
||||||
db.TTLMap.ForEach(func(key string, val interface{}) bool {
|
|
||||||
expireTime, _ := val.(time.Time)
|
|
||||||
if now.After(expireTime) {
|
|
||||||
// expired
|
|
||||||
db.Data.Remove(key)
|
|
||||||
toRemove.Add(key)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
toRemove.ForEach(func(i int, val interface{}) bool {
|
|
||||||
key, _ := val.(string)
|
|
||||||
db.TTLMap.Remove(key)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) TimerTask() {
|
func (db *DB) TimerTask() {
|
||||||
ticker := time.NewTicker(db.interval)
|
|
||||||
go func() {
|
|
||||||
for range ticker.C {
|
|
||||||
db.CleanExpired()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Subscribe Functions ---- */
|
/* ---- Subscribe Functions ---- */
|
||||||
|
@@ -289,7 +289,7 @@ func Persist(db *DB, args [][]byte) redis.Reply {
|
|||||||
return reply.MakeIntReply(0)
|
return reply.MakeIntReply(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
db.TTLMap.Remove(key)
|
db.Persist(key)
|
||||||
db.AddAof(makeAofCmd("persist", args))
|
db.AddAof(makeAofCmd("persist", args))
|
||||||
return reply.MakeIntReply(1)
|
return reply.MakeIntReply(1)
|
||||||
}
|
}
|
||||||
|
@@ -87,10 +87,7 @@ func TestRenameNx(t *testing.T) {
|
|||||||
newKey := key + RandString(2)
|
newKey := key + RandString(2)
|
||||||
Set(testDB, toArgs(key, value, "ex", "1000"))
|
Set(testDB, toArgs(key, value, "ex", "1000"))
|
||||||
result := RenameNx(testDB, toArgs(key, newKey))
|
result := RenameNx(testDB, toArgs(key, newKey))
|
||||||
if _, ok := result.(*reply.OkReply); !ok {
|
asserts.AssertIntReply(t, result, 1)
|
||||||
t.Error("expect ok")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result = Exists(testDB, toArgs(key))
|
result = Exists(testDB, toArgs(key))
|
||||||
asserts.AssertIntReply(t, result, 0)
|
asserts.AssertIntReply(t, result, 0)
|
||||||
result = Exists(testDB, toArgs(newKey))
|
result = Exists(testDB, toArgs(newKey))
|
||||||
|
21
src/lib/timewheel/delay.go
Normal file
21
src/lib/timewheel/delay.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package timewheel
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
var tw = New(time.Second, 3600)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tw.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delay(duration time.Duration, key string, job func()) {
|
||||||
|
tw.AddTimer(duration, key, job)
|
||||||
|
}
|
||||||
|
|
||||||
|
func At(at time.Time, key string, job func()) {
|
||||||
|
tw.AddTimer(at.Sub(time.Now()), key, job)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Cancel(key string) {
|
||||||
|
tw.RemoveTimer(key)
|
||||||
|
}
|
165
src/lib/timewheel/timewheel.go
Normal file
165
src/lib/timewheel/timewheel.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package timewheel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"github.com/HDT3213/godis/src/lib/logger"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeWheel struct {
|
||||||
|
interval time.Duration
|
||||||
|
ticker *time.Ticker
|
||||||
|
slots []*list.List
|
||||||
|
|
||||||
|
timer map[string]int
|
||||||
|
currentPos int
|
||||||
|
slotNum int
|
||||||
|
addTaskChannel chan Task
|
||||||
|
removeTaskChannel chan string
|
||||||
|
stopChannel chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Task struct {
|
||||||
|
delay time.Duration
|
||||||
|
circle int
|
||||||
|
key string
|
||||||
|
job func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(interval time.Duration, slotNum int) *TimeWheel {
|
||||||
|
if interval <= 0 || slotNum <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tw := &TimeWheel{
|
||||||
|
interval: interval,
|
||||||
|
slots: make([]*list.List, slotNum),
|
||||||
|
timer: make(map[string]int),
|
||||||
|
currentPos: 0,
|
||||||
|
slotNum: slotNum,
|
||||||
|
addTaskChannel: make(chan Task),
|
||||||
|
removeTaskChannel: make(chan string),
|
||||||
|
stopChannel: make(chan bool),
|
||||||
|
}
|
||||||
|
tw.initSlots()
|
||||||
|
|
||||||
|
return tw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) initSlots() {
|
||||||
|
for i := 0; i < tw.slotNum; i++ {
|
||||||
|
tw.slots[i] = list.New()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) Start() {
|
||||||
|
tw.ticker = time.NewTicker(tw.interval)
|
||||||
|
go tw.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) Stop() {
|
||||||
|
tw.stopChannel <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) AddTimer(delay time.Duration, key string, job func()) {
|
||||||
|
if delay < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tw.addTaskChannel <- Task{delay: delay, key: key, job: job}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) RemoveTimer(key string) {
|
||||||
|
if key == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tw.removeTaskChannel <- key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) start() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tw.ticker.C:
|
||||||
|
tw.tickHandler()
|
||||||
|
case task := <-tw.addTaskChannel:
|
||||||
|
tw.addTask(&task)
|
||||||
|
case key := <-tw.removeTaskChannel:
|
||||||
|
tw.removeTask(key)
|
||||||
|
case <-tw.stopChannel:
|
||||||
|
tw.ticker.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) tickHandler() {
|
||||||
|
l := tw.slots[tw.currentPos]
|
||||||
|
tw.scanAndRunTask(l)
|
||||||
|
if tw.currentPos == tw.slotNum-1 {
|
||||||
|
tw.currentPos = 0
|
||||||
|
} else {
|
||||||
|
tw.currentPos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) scanAndRunTask(l *list.List) {
|
||||||
|
for e := l.Front(); e != nil; {
|
||||||
|
task := e.Value.(*Task)
|
||||||
|
if task.circle > 0 {
|
||||||
|
task.circle--
|
||||||
|
e = e.Next()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
job := task.job
|
||||||
|
job()
|
||||||
|
}()
|
||||||
|
next := e.Next()
|
||||||
|
l.Remove(e)
|
||||||
|
if task.key != "" {
|
||||||
|
delete(tw.timer, task.key)
|
||||||
|
}
|
||||||
|
e = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) addTask(task *Task) {
|
||||||
|
pos, circle := tw.getPositionAndCircle(task.delay)
|
||||||
|
task.circle = circle
|
||||||
|
|
||||||
|
tw.slots[pos].PushBack(task)
|
||||||
|
|
||||||
|
if task.key != "" {
|
||||||
|
tw.timer[task.key] = pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) getPositionAndCircle(d time.Duration) (pos int, circle int) {
|
||||||
|
delaySeconds := int(d.Seconds())
|
||||||
|
intervalSeconds := int(tw.interval.Seconds())
|
||||||
|
circle = int(delaySeconds / intervalSeconds / tw.slotNum)
|
||||||
|
pos = int(tw.currentPos+delaySeconds/intervalSeconds) % tw.slotNum
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *TimeWheel) removeTask(key string) {
|
||||||
|
position, ok := tw.timer[key]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l := tw.slots[position]
|
||||||
|
for e := l.Front(); e != nil; {
|
||||||
|
task := e.Value.(*Task)
|
||||||
|
if task.key == key {
|
||||||
|
delete(tw.timer, task.key)
|
||||||
|
l.Remove(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
e = e.Next()
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user