Commit 0cc583150940444d548e9f8597256ec30f4303be
1 parent
98b0736d
添加定时器, 检查心跳
Showing
19 changed files
with
377 additions
and
779 deletions
Show diff stats
cmd/game.go
src/common/common.go
1 | 1 | package common |
2 | 2 | |
3 | 3 | const ( |
4 | + //协程池 大小 | |
4 | 5 | WorkerPoolSize = 10 |
5 | 6 | MaxTaskPerWorker = 100 |
6 | - MaxPacketSize = 10 * 1024 * 1024 | |
7 | - MaxMsgChanLen = 20 | |
8 | -) | |
9 | 7 | |
10 | -const ( | |
8 | + //包头 | |
11 | 9 | HEADLEN = 16 |
12 | 10 | |
11 | + //jwt | |
13 | 12 | Pro2DTokenSignedString = "Pro2DSecret" |
13 | + | |
14 | + //定时器 | |
15 | + TickMS = 10 | |
16 | + WheelSize = 3600 | |
17 | + | |
18 | + //心跳 | |
19 | + HEART_TIMER_INTERVAL = 5 //s | |
20 | + HEART_TIMEOUT_COUNT_MAX = 20 //最大超时次数 | |
14 | 21 | ) | ... | ... |
src/components/kafkatimer/delayqueue.go deleted
... | ... | @@ -1,183 +0,0 @@ |
1 | -package kafkatimer | |
2 | - | |
3 | -import ( | |
4 | - "container/heap" | |
5 | - "sync" | |
6 | - "sync/atomic" | |
7 | - "time" | |
8 | -) | |
9 | - | |
10 | -type item struct { | |
11 | - Value interface{} | |
12 | - Priority int64 | |
13 | - Index int | |
14 | -} | |
15 | - | |
16 | -// this is a priority queue as implemented by a min heap | |
17 | -// ie. the 0th element is the *lowest* value | |
18 | -type priorityQueue []*item | |
19 | - | |
20 | -func newPriorityQueue(capacity int) priorityQueue { | |
21 | - return make(priorityQueue, 0, capacity) | |
22 | -} | |
23 | - | |
24 | -func (pq priorityQueue) Len() int { | |
25 | - return len(pq) | |
26 | -} | |
27 | - | |
28 | -func (pq priorityQueue) Less(i, j int) bool { | |
29 | - return pq[i].Priority < pq[j].Priority | |
30 | -} | |
31 | - | |
32 | -func (pq priorityQueue) Swap(i, j int) { | |
33 | - pq[i], pq[j] = pq[j], pq[i] | |
34 | - pq[i].Index = i | |
35 | - pq[j].Index = j | |
36 | -} | |
37 | - | |
38 | -func (pq *priorityQueue) Push(x interface{}) { | |
39 | - n := len(*pq) | |
40 | - c := cap(*pq) | |
41 | - if n+1 > c { | |
42 | - npq := make(priorityQueue, n, c*2) | |
43 | - copy(npq, *pq) | |
44 | - *pq = npq | |
45 | - } | |
46 | - *pq = (*pq)[0 : n+1] | |
47 | - item := x.(*item) | |
48 | - item.Index = n | |
49 | - (*pq)[n] = item | |
50 | -} | |
51 | - | |
52 | -func (pq *priorityQueue) Pop() interface{} { | |
53 | - n := len(*pq) | |
54 | - c := cap(*pq) | |
55 | - if n < (c/2) && c > 25 { | |
56 | - npq := make(priorityQueue, n, c/2) | |
57 | - copy(npq, *pq) | |
58 | - *pq = npq | |
59 | - } | |
60 | - item := (*pq)[n-1] | |
61 | - item.Index = -1 | |
62 | - *pq = (*pq)[0 : n-1] | |
63 | - return item | |
64 | -} | |
65 | - | |
66 | -func (pq *priorityQueue) PeekAndShift(max int64) (*item, int64) { | |
67 | - if pq.Len() == 0 { | |
68 | - return nil, 0 | |
69 | - } | |
70 | - | |
71 | - item := (*pq)[0] | |
72 | - if item.Priority > max { | |
73 | - return nil, item.Priority - max | |
74 | - } | |
75 | - heap.Remove(pq, 0) | |
76 | - | |
77 | - return item, 0 | |
78 | -} | |
79 | - | |
80 | -// The end of PriorityQueue implementation. | |
81 | - | |
82 | -// DelayQueue is an unbounded blocking queue of *Delayed* elements, in which | |
83 | -// an element can only be taken when its delay has expired. The head of the | |
84 | -// queue is the *Delayed* element whose delay expired furthest in the past. | |
85 | -type DelayQueue struct { | |
86 | - C chan interface{} | |
87 | - | |
88 | - mu sync.Mutex | |
89 | - pq priorityQueue | |
90 | - | |
91 | - // Similar to the sleeping state of runtime.timers. | |
92 | - sleeping int32 | |
93 | - wakeupC chan struct{} | |
94 | -} | |
95 | - | |
96 | -// New creates an instance of delayQueue with the specified size. | |
97 | -func New(size int) *DelayQueue { | |
98 | - return &DelayQueue{ | |
99 | - C: make(chan interface{}), | |
100 | - pq: newPriorityQueue(size), | |
101 | - wakeupC: make(chan struct{}), | |
102 | - } | |
103 | -} | |
104 | - | |
105 | -// Offer inserts the element into the current queue. | |
106 | -func (dq *DelayQueue) Offer(elem interface{}, expiration int64) { | |
107 | - item := &item{Value: elem, Priority: expiration} | |
108 | - | |
109 | - dq.mu.Lock() | |
110 | - heap.Push(&dq.pq, item) | |
111 | - index := item.Index | |
112 | - dq.mu.Unlock() | |
113 | - | |
114 | - if index == 0 { | |
115 | - // A new item with the earliest expiration is added. | |
116 | - if atomic.CompareAndSwapInt32(&dq.sleeping, 1, 0) { | |
117 | - dq.wakeupC <- struct{}{} | |
118 | - } | |
119 | - } | |
120 | -} | |
121 | - | |
122 | -// Poll starts an infinite loop, in which it continually waits for an element | |
123 | -// to expire and then send the expired element to the channel C. | |
124 | -func (dq *DelayQueue) Poll(exitC chan struct{}, nowF func() int64) { | |
125 | - for { | |
126 | - now := nowF() | |
127 | - | |
128 | - dq.mu.Lock() | |
129 | - item, delta := dq.pq.PeekAndShift(now) | |
130 | - if item == nil { | |
131 | - // No items left or at least one item is pending. | |
132 | - | |
133 | - // We must ensure the atomicity of the whole operation, which is | |
134 | - // composed of the above PeekAndShift and the following StoreInt32, | |
135 | - // to avoid possible race conditions between Offer and Poll. | |
136 | - atomic.StoreInt32(&dq.sleeping, 1) | |
137 | - } | |
138 | - dq.mu.Unlock() | |
139 | - | |
140 | - if item == nil { | |
141 | - if delta == 0 { | |
142 | - // No items left. | |
143 | - select { | |
144 | - case <-dq.wakeupC: | |
145 | - // Wait until a new item is added. | |
146 | - continue | |
147 | - case <-exitC: | |
148 | - goto exit | |
149 | - } | |
150 | - } else if delta > 0 { | |
151 | - // At least one item is pending. | |
152 | - select { | |
153 | - case <-dq.wakeupC: | |
154 | - // A new item with an "earlier" expiration than the current "earliest" one is added. | |
155 | - continue | |
156 | - case <-time.After(time.Duration(delta) * time.Millisecond): | |
157 | - // The current "earliest" item expires. | |
158 | - | |
159 | - // Reset the sleeping state since there's no need to receive from wakeupC. | |
160 | - if atomic.SwapInt32(&dq.sleeping, 0) == 0 { | |
161 | - // A caller of Offer() is being blocked on sending to wakeupC, | |
162 | - // drain wakeupC to unblock the caller. | |
163 | - <-dq.wakeupC | |
164 | - } | |
165 | - continue | |
166 | - case <-exitC: | |
167 | - goto exit | |
168 | - } | |
169 | - } | |
170 | - } | |
171 | - | |
172 | - select { | |
173 | - case dq.C <- item.Value: | |
174 | - // The expired element has been sent out successfully. | |
175 | - case <-exitC: | |
176 | - goto exit | |
177 | - } | |
178 | - } | |
179 | - | |
180 | -exit: | |
181 | - // Reset the states | |
182 | - atomic.StoreInt32(&dq.sleeping, 0) | |
183 | -} |
src/components/kafkatimer/delayqueue/delayqueue.go deleted
... | ... | @@ -1,186 +0,0 @@ |
1 | -package delayqueue | |
2 | - | |
3 | -import ( | |
4 | - "container/heap" | |
5 | - "sync" | |
6 | - "sync/atomic" | |
7 | - "time" | |
8 | -) | |
9 | - | |
10 | -// The start of PriorityQueue implementation. | |
11 | -// Borrowed from https://github.com/nsqio/nsq/blob/master/internal/pqueue/pqueue.go | |
12 | - | |
13 | -type item struct { | |
14 | - Value interface{} | |
15 | - Priority int64 | |
16 | - Index int | |
17 | -} | |
18 | - | |
19 | -// this is a priority queue as implemented by a min heap | |
20 | -// ie. the 0th element is the *lowest* value | |
21 | -type priorityQueue []*item | |
22 | - | |
23 | -func newPriorityQueue(capacity int) priorityQueue { | |
24 | - return make(priorityQueue, 0, capacity) | |
25 | -} | |
26 | - | |
27 | -func (pq priorityQueue) Len() int { | |
28 | - return len(pq) | |
29 | -} | |
30 | - | |
31 | -func (pq priorityQueue) Less(i, j int) bool { | |
32 | - return pq[i].Priority < pq[j].Priority | |
33 | -} | |
34 | - | |
35 | -func (pq priorityQueue) Swap(i, j int) { | |
36 | - pq[i], pq[j] = pq[j], pq[i] | |
37 | - pq[i].Index = i | |
38 | - pq[j].Index = j | |
39 | -} | |
40 | - | |
41 | -func (pq *priorityQueue) Push(x interface{}) { | |
42 | - n := len(*pq) | |
43 | - c := cap(*pq) | |
44 | - if n+1 > c { | |
45 | - npq := make(priorityQueue, n, c*2) | |
46 | - copy(npq, *pq) | |
47 | - *pq = npq | |
48 | - } | |
49 | - *pq = (*pq)[0 : n+1] | |
50 | - item := x.(*item) | |
51 | - item.Index = n | |
52 | - (*pq)[n] = item | |
53 | -} | |
54 | - | |
55 | -func (pq *priorityQueue) Pop() interface{} { | |
56 | - n := len(*pq) | |
57 | - c := cap(*pq) | |
58 | - if n < (c/2) && c > 25 { | |
59 | - npq := make(priorityQueue, n, c/2) | |
60 | - copy(npq, *pq) | |
61 | - *pq = npq | |
62 | - } | |
63 | - item := (*pq)[n-1] | |
64 | - item.Index = -1 | |
65 | - *pq = (*pq)[0 : n-1] | |
66 | - return item | |
67 | -} | |
68 | - | |
69 | -func (pq *priorityQueue) PeekAndShift(max int64) (*item, int64) { | |
70 | - if pq.Len() == 0 { | |
71 | - return nil, 0 | |
72 | - } | |
73 | - | |
74 | - item := (*pq)[0] | |
75 | - if item.Priority > max { | |
76 | - return nil, item.Priority - max | |
77 | - } | |
78 | - heap.Remove(pq, 0) | |
79 | - | |
80 | - return item, 0 | |
81 | -} | |
82 | - | |
83 | -// The end of PriorityQueue implementation. | |
84 | - | |
85 | -// DelayQueue is an unbounded blocking queue of *Delayed* elements, in which | |
86 | -// an element can only be taken when its delay has expired. The head of the | |
87 | -// queue is the *Delayed* element whose delay expired furthest in the past. | |
88 | -type DelayQueue struct { | |
89 | - C chan interface{} | |
90 | - | |
91 | - mu sync.Mutex | |
92 | - pq priorityQueue | |
93 | - | |
94 | - // Similar to the sleeping state of runtime.timers. | |
95 | - sleeping int32 | |
96 | - wakeupC chan struct{} | |
97 | -} | |
98 | - | |
99 | -// New creates an instance of delayQueue with the specified size. | |
100 | -func New(size int) *DelayQueue { | |
101 | - return &DelayQueue{ | |
102 | - C: make(chan interface{}), | |
103 | - pq: newPriorityQueue(size), | |
104 | - wakeupC: make(chan struct{}), | |
105 | - } | |
106 | -} | |
107 | - | |
108 | -// Offer inserts the element into the current queue. | |
109 | -func (dq *DelayQueue) Offer(elem interface{}, expiration int64) { | |
110 | - item := &item{Value: elem, Priority: expiration} | |
111 | - | |
112 | - dq.mu.Lock() | |
113 | - heap.Push(&dq.pq, item) | |
114 | - index := item.Index | |
115 | - dq.mu.Unlock() | |
116 | - | |
117 | - if index == 0 { | |
118 | - // A new item with the earliest expiration is added. | |
119 | - if atomic.CompareAndSwapInt32(&dq.sleeping, 1, 0) { | |
120 | - dq.wakeupC <- struct{}{} | |
121 | - } | |
122 | - } | |
123 | -} | |
124 | - | |
125 | -// Poll starts an infinite loop, in which it continually waits for an element | |
126 | -// to expire and then send the expired element to the channel C. | |
127 | -func (dq *DelayQueue) Poll(exitC chan struct{}, nowF func() int64) { | |
128 | - for { | |
129 | - now := nowF() | |
130 | - | |
131 | - dq.mu.Lock() | |
132 | - item, delta := dq.pq.PeekAndShift(now) | |
133 | - if item == nil { | |
134 | - // No items left or at least one item is pending. | |
135 | - | |
136 | - // We must ensure the atomicity of the whole operation, which is | |
137 | - // composed of the above PeekAndShift and the following StoreInt32, | |
138 | - // to avoid possible race conditions between Offer and Poll. | |
139 | - atomic.StoreInt32(&dq.sleeping, 1) | |
140 | - } | |
141 | - dq.mu.Unlock() | |
142 | - | |
143 | - if item == nil { | |
144 | - if delta == 0 { | |
145 | - // No items left. | |
146 | - select { | |
147 | - case <-dq.wakeupC: | |
148 | - // Wait until a new item is added. | |
149 | - continue | |
150 | - case <-exitC: | |
151 | - goto exit | |
152 | - } | |
153 | - } else if delta > 0 { | |
154 | - // At least one item is pending. | |
155 | - select { | |
156 | - case <-dq.wakeupC: | |
157 | - // A new item with an "earlier" expiration than the current "earliest" one is added. | |
158 | - continue | |
159 | - case <-time.After(time.Duration(delta) * time.Millisecond): | |
160 | - // The current "earliest" item expires. | |
161 | - | |
162 | - // Reset the sleeping state since there's no need to receive from wakeupC. | |
163 | - if atomic.SwapInt32(&dq.sleeping, 0) == 0 { | |
164 | - // A caller of Offer() is being blocked on sending to wakeupC, | |
165 | - // drain wakeupC to unblock the caller. | |
166 | - <-dq.wakeupC | |
167 | - } | |
168 | - continue | |
169 | - case <-exitC: | |
170 | - goto exit | |
171 | - } | |
172 | - } | |
173 | - } | |
174 | - | |
175 | - select { | |
176 | - case dq.C <- item.Value: | |
177 | - // The expired element has been sent out successfully. | |
178 | - case <-exitC: | |
179 | - goto exit | |
180 | - } | |
181 | - } | |
182 | - | |
183 | -exit: | |
184 | - // Reset the states | |
185 | - atomic.StoreInt32(&dq.sleeping, 0) | |
186 | -} |
src/components/kafkatimer/timingwheel.go deleted
... | ... | @@ -1,227 +0,0 @@ |
1 | -package kafkatimer | |
2 | - | |
3 | -import ( | |
4 | - "errors" | |
5 | - "sync/atomic" | |
6 | - "time" | |
7 | - "unsafe" | |
8 | - | |
9 | - "github.com/RussellLuo/timingwheel/delayqueue" | |
10 | -) | |
11 | -//时间轮 kafka | |
12 | - | |
13 | -// TimingWheel is an implementation of Hierarchical Timing Wheels. | |
14 | -type TimingWheel struct { | |
15 | - tick int64 // in milliseconds | |
16 | - wheelSize int64 | |
17 | - | |
18 | - interval int64 // in milliseconds | |
19 | - currentTime int64 // in milliseconds | |
20 | - buckets []*bucket | |
21 | - queue *delayqueue.DelayQueue | |
22 | - | |
23 | - // The higher-level overflow wheel. | |
24 | - // | |
25 | - // NOTE: This field may be updated and read concurrently, through Add(). | |
26 | - overflowWheel unsafe.Pointer // type: *TimingWheel | |
27 | - | |
28 | - exitC chan struct{} | |
29 | - waitGroup waitGroupWrapper | |
30 | -} | |
31 | - | |
32 | -// NewTimingWheel creates an instance of TimingWheel with the given tick and wheelSize. | |
33 | -func NewTimingWheel(tick time.Duration, wheelSize int64) *TimingWheel { | |
34 | - tickMs := int64(tick / time.Millisecond) | |
35 | - if tickMs <= 0 { | |
36 | - panic(errors.New("tick must be greater than or equal to 1ms")) | |
37 | - } | |
38 | - | |
39 | - startMs := timeToMs(time.Now().UTC()) | |
40 | - | |
41 | - return newTimingWheel( | |
42 | - tickMs, | |
43 | - wheelSize, | |
44 | - startMs, | |
45 | - delayqueue.New(int(wheelSize)), | |
46 | - ) | |
47 | -} | |
48 | - | |
49 | -// newTimingWheel is an internal helper function that really creates an instance of TimingWheel. | |
50 | -func newTimingWheel(tickMs int64, wheelSize int64, startMs int64, queue *delayqueue.DelayQueue) *TimingWheel { | |
51 | - buckets := make([]*bucket, wheelSize) | |
52 | - for i := range buckets { | |
53 | - buckets[i] = newBucket() | |
54 | - } | |
55 | - return &TimingWheel{ | |
56 | - tick: tickMs, | |
57 | - wheelSize: wheelSize, | |
58 | - currentTime: truncate(startMs, tickMs), | |
59 | - interval: tickMs * wheelSize, | |
60 | - buckets: buckets, | |
61 | - queue: queue, | |
62 | - exitC: make(chan struct{}), | |
63 | - } | |
64 | -} | |
65 | - | |
66 | -// add inserts the timer t into the current timing wheel. | |
67 | -func (tw *TimingWheel) add(t *Timer) bool { | |
68 | - currentTime := atomic.LoadInt64(&tw.currentTime) | |
69 | - if t.expiration < currentTime+tw.tick { | |
70 | - // Already expired | |
71 | - return false | |
72 | - } else if t.expiration < currentTime+tw.interval { | |
73 | - // Put it into its own bucket | |
74 | - virtualID := t.expiration / tw.tick | |
75 | - b := tw.buckets[virtualID%tw.wheelSize] | |
76 | - b.Add(t) | |
77 | - | |
78 | - // Set the bucket expiration time | |
79 | - if b.SetExpiration(virtualID * tw.tick) { | |
80 | - // The bucket needs to be enqueued since it was an expired bucket. | |
81 | - // We only need to enqueue the bucket when its expiration time has changed, | |
82 | - // i.e. the wheel has advanced and this bucket get reused with a new expiration. | |
83 | - // Any further calls to set the expiration within the same wheel cycle will | |
84 | - // pass in the same value and hence return false, thus the bucket with the | |
85 | - // same expiration will not be enqueued multiple times. | |
86 | - tw.queue.Offer(b, b.Expiration()) | |
87 | - } | |
88 | - | |
89 | - return true | |
90 | - } else { | |
91 | - // Out of the interval. Put it into the overflow wheel | |
92 | - overflowWheel := atomic.LoadPointer(&tw.overflowWheel) | |
93 | - if overflowWheel == nil { | |
94 | - atomic.CompareAndSwapPointer( | |
95 | - &tw.overflowWheel, | |
96 | - nil, | |
97 | - unsafe.Pointer(newTimingWheel( | |
98 | - tw.interval, | |
99 | - tw.wheelSize, | |
100 | - currentTime, | |
101 | - tw.queue, | |
102 | - )), | |
103 | - ) | |
104 | - overflowWheel = atomic.LoadPointer(&tw.overflowWheel) | |
105 | - } | |
106 | - return (*TimingWheel)(overflowWheel).add(t) | |
107 | - } | |
108 | -} | |
109 | - | |
110 | -// addOrRun inserts the timer t into the current timing wheel, or run the | |
111 | -// timer's task if it has already expired. | |
112 | -func (tw *TimingWheel) addOrRun(t *Timer) { | |
113 | - if !tw.add(t) { | |
114 | - // Already expired | |
115 | - | |
116 | - // Like the standard time.AfterFunc (https://golang.org/pkg/time/#AfterFunc), | |
117 | - // always execute the timer's task in its own goroutine. | |
118 | - go t.task() | |
119 | - } | |
120 | -} | |
121 | - | |
122 | -func (tw *TimingWheel) advanceClock(expiration int64) { | |
123 | - currentTime := atomic.LoadInt64(&tw.currentTime) | |
124 | - if expiration >= currentTime+tw.tick { | |
125 | - currentTime = truncate(expiration, tw.tick) | |
126 | - atomic.StoreInt64(&tw.currentTime, currentTime) | |
127 | - | |
128 | - // Try to advance the clock of the overflow wheel if present | |
129 | - overflowWheel := atomic.LoadPointer(&tw.overflowWheel) | |
130 | - if overflowWheel != nil { | |
131 | - (*TimingWheel)(overflowWheel).advanceClock(currentTime) | |
132 | - } | |
133 | - } | |
134 | -} | |
135 | - | |
136 | -// Start starts the current timing wheel. | |
137 | -func (tw *TimingWheel) Start() { | |
138 | - tw.waitGroup.Wrap(func() { | |
139 | - tw.queue.Poll(tw.exitC, func() int64 { | |
140 | - return timeToMs(time.Now().UTC()) | |
141 | - }) | |
142 | - }) | |
143 | - | |
144 | - tw.waitGroup.Wrap(func() { | |
145 | - for { | |
146 | - select { | |
147 | - case elem := <-tw.queue.C: | |
148 | - b := elem.(*bucket) | |
149 | - tw.advanceClock(b.Expiration()) | |
150 | - b.Flush(tw.addOrRun) | |
151 | - case <-tw.exitC: | |
152 | - return | |
153 | - } | |
154 | - } | |
155 | - }) | |
156 | -} | |
157 | - | |
158 | -// Stop stops the current timing wheel. | |
159 | -// | |
160 | -// If there is any timer's task being running in its own goroutine, Stop does | |
161 | -// not wait for the task to complete before returning. If the caller needs to | |
162 | -// know whether the task is completed, it must coordinate with the task explicitly. | |
163 | -func (tw *TimingWheel) Stop() { | |
164 | - close(tw.exitC) | |
165 | - tw.waitGroup.Wait() | |
166 | -} | |
167 | - | |
168 | -// AfterFunc waits for the duration to elapse and then calls f in its own goroutine. | |
169 | -// It returns a Timer that can be used to cancel the call using its Stop method. | |
170 | -func (tw *TimingWheel) AfterFunc(d time.Duration, f func()) *Timer { | |
171 | - t := &Timer{ | |
172 | - expiration: timeToMs(time.Now().UTC().Add(d)), | |
173 | - task: f, | |
174 | - } | |
175 | - tw.addOrRun(t) | |
176 | - return t | |
177 | -} | |
178 | - | |
179 | -// Scheduler determines the execution plan of a task. | |
180 | -type Scheduler interface { | |
181 | - // Next returns the next execution time after the given (previous) time. | |
182 | - // It will return a zero time if no next time is scheduled. | |
183 | - // | |
184 | - // All times must be UTC. | |
185 | - Next(time.Time) time.Time | |
186 | -} | |
187 | - | |
188 | -// ScheduleFunc calls f (in its own goroutine) according to the execution | |
189 | -// plan scheduled by s. It returns a Timer that can be used to cancel the | |
190 | -// call using its Stop method. | |
191 | -// | |
192 | -// If the caller want to terminate the execution plan halfway, it must | |
193 | -// stop the timer and ensure that the timer is stopped actually, since in | |
194 | -// the current implementation, there is a gap between the expiring and the | |
195 | -// restarting of the timer. The wait time for ensuring is short since the | |
196 | -// gap is very small. | |
197 | -// | |
198 | -// Internally, ScheduleFunc will ask the first execution time (by calling | |
199 | -// s.Next()) initially, and create a timer if the execution time is non-zero. | |
200 | -// Afterwards, it will ask the next execution time each time f is about to | |
201 | -// be executed, and f will be called at the next execution time if the time | |
202 | -// is non-zero. | |
203 | -func (tw *TimingWheel) ScheduleFunc(s Scheduler, f func()) (t *Timer) { | |
204 | - expiration := s.Next(time.Now().UTC()) | |
205 | - if expiration.IsZero() { | |
206 | - // No time is scheduled, return nil. | |
207 | - return | |
208 | - } | |
209 | - | |
210 | - t = &Timer{ | |
211 | - expiration: timeToMs(expiration), | |
212 | - task: func() { | |
213 | - // Schedule the task to execute at the next time if possible. | |
214 | - expiration := s.Next(msToTime(t.expiration)) | |
215 | - if !expiration.IsZero() { | |
216 | - t.expiration = timeToMs(expiration) | |
217 | - tw.addOrRun(t) | |
218 | - } | |
219 | - | |
220 | - // Actually execute the task. | |
221 | - f() | |
222 | - }, | |
223 | - } | |
224 | - tw.addOrRun(t) | |
225 | - | |
226 | - return | |
227 | -} |
src/components/kafkatimer/timingwheel_test.go deleted
... | ... | @@ -1,26 +0,0 @@ |
1 | -package kafkatimer | |
2 | - | |
3 | -import ( | |
4 | - "fmt" | |
5 | - "testing" | |
6 | - "time" | |
7 | -) | |
8 | - | |
9 | -func F() { | |
10 | - fmt.Println("I'm timer...") | |
11 | -} | |
12 | - | |
13 | - | |
14 | -func TestTimingWheel_Start(t *testing.T) { | |
15 | - tw := NewTimingWheel(time.Millisecond, 20) | |
16 | - tw.Start() | |
17 | - defer tw.Stop() | |
18 | - | |
19 | - exitC := make(chan time.Time, 1) | |
20 | - tw.AfterFunc(10 * time.Second, func() { | |
21 | - fmt.Println("The timer fires") | |
22 | - exitC <- time.Now().UTC() | |
23 | - }) | |
24 | - | |
25 | - fmt.Println(<-exitC) | |
26 | -} | |
27 | 0 | \ No newline at end of file |
src/components/kafkatimer/utils.go deleted
... | ... | @@ -1,38 +0,0 @@ |
1 | -package kafkatimer | |
2 | - | |
3 | -import ( | |
4 | - "sync" | |
5 | - "time" | |
6 | -) | |
7 | - | |
8 | -// truncate returns the result of rounding x toward zero to a multiple of m. | |
9 | -// If m <= 0, Truncate returns x unchanged. | |
10 | -func truncate(x, m int64) int64 { | |
11 | - if m <= 0 { | |
12 | - return x | |
13 | - } | |
14 | - return x - x%m | |
15 | -} | |
16 | - | |
17 | -// timeToMs returns an integer number, which represents t in milliseconds. | |
18 | -func timeToMs(t time.Time) int64 { | |
19 | - return t.UnixNano() / int64(time.Millisecond) | |
20 | -} | |
21 | - | |
22 | -// msToTime returns the UTC time corresponding to the given Unix time, | |
23 | -// t milliseconds since January 1, 1970 UTC. | |
24 | -func msToTime(t int64) time.Time { | |
25 | - return time.Unix(0, t*int64(time.Millisecond)).UTC() | |
26 | -} | |
27 | - | |
28 | -type waitGroupWrapper struct { | |
29 | - sync.WaitGroup | |
30 | -} | |
31 | - | |
32 | -func (w *waitGroupWrapper) Wrap(cb func()) { | |
33 | - w.Add(1) | |
34 | - go func() { | |
35 | - cb() | |
36 | - w.Done() | |
37 | - }() | |
38 | -} |
src/components/net/conn.go
... | ... | @@ -3,9 +3,12 @@ package net |
3 | 3 | import ( |
4 | 4 | "bufio" |
5 | 5 | "fmt" |
6 | + "math" | |
6 | 7 | "net" |
7 | 8 | "pro2d/src/common" |
8 | 9 | "pro2d/src/components/logger" |
10 | + "pro2d/src/utils" | |
11 | + "sync/atomic" | |
9 | 12 | ) |
10 | 13 | |
11 | 14 | type Head struct { |
... | ... | @@ -18,16 +21,20 @@ type Head struct { |
18 | 21 | |
19 | 22 | type Connection struct { |
20 | 23 | net.Conn |
21 | - Id int | |
22 | - Server *Server | |
24 | + Id int | |
25 | + Server *Server | |
23 | 26 | |
24 | - scanner *bufio.Scanner | |
25 | - writer *bufio.Writer | |
27 | + scanner *bufio.Scanner | |
28 | + writer *bufio.Writer | |
26 | 29 | |
27 | - WBuffer chan []byte | |
28 | - RBuffer chan *MsgPkg | |
30 | + WBuffer chan []byte | |
31 | + RBuffer chan *MsgPkg | |
29 | 32 | |
30 | - Quit chan *Connection | |
33 | + Quit chan *Connection | |
34 | + | |
35 | + nextCheckTime int64 //下一次检查的时间 | |
36 | + lastHeartCheckTime int64 //最后收消息时间 | |
37 | + heartTimeoutCount int //超时次数 | |
31 | 38 | } |
32 | 39 | |
33 | 40 | type MsgPkg struct { |
... | ... | @@ -42,11 +49,13 @@ func NewConn(id int, conn net.Conn, s *Server) *Connection { |
42 | 49 | Conn: conn, |
43 | 50 | Server: s, |
44 | 51 | |
45 | - scanner: bufio.NewScanner(conn), | |
46 | - writer: bufio.NewWriter(conn), | |
47 | - WBuffer: make(chan []byte), | |
48 | - RBuffer: make(chan *MsgPkg), | |
49 | - Quit: make(chan *Connection), | |
52 | + scanner: bufio.NewScanner(conn), | |
53 | + writer: bufio.NewWriter(conn), | |
54 | + WBuffer: make(chan []byte), | |
55 | + RBuffer: make(chan *MsgPkg), | |
56 | + Quit: make(chan *Connection), | |
57 | + lastHeartCheckTime: utils.Timex(), | |
58 | + heartTimeoutCount: 0, | |
50 | 59 | } |
51 | 60 | } |
52 | 61 | |
... | ... | @@ -63,7 +72,6 @@ func (c *Connection) write() { |
63 | 72 | logger.Error("write Flush fail err: " + err.Error()) |
64 | 73 | return |
65 | 74 | } |
66 | - logger.Debug("write :%s, n: %d", msg, n) | |
67 | 75 | } |
68 | 76 | } |
69 | 77 | |
... | ... | @@ -83,6 +91,8 @@ func (c *Connection) read() { |
83 | 91 | //将请求消息发送给任务队列 |
84 | 92 | c.Server.TaskQueue[workerID] <- req |
85 | 93 | |
94 | + atomic.StoreInt64(&c.lastHeartCheckTime, utils.Timex()) | |
95 | + | |
86 | 96 | //备注,可以在当前协程处理当条请求(如下, 实现很简单,已经删除),也可以丢到协程池里处理任务(如上),还未对比效果。 |
87 | 97 | //c.Server.OnRecv(req) |
88 | 98 | } |
... | ... | @@ -94,6 +104,41 @@ func (c *Connection) read() { |
94 | 104 | } |
95 | 105 | } |
96 | 106 | |
107 | + | |
108 | +func (c *Connection) checkHeartBeat(now int64) { | |
109 | + lastHeartCheckTime := atomic.LoadInt64(&c.lastHeartCheckTime) | |
110 | + logger.Debug("checkHeartBeat ID: %d, last: %d, now: %d", c.Id, lastHeartCheckTime, now) | |
111 | + if math.Abs(float64(lastHeartCheckTime - now)) > common.HEART_TIMER_INTERVAL { | |
112 | + c.heartTimeoutCount++ | |
113 | + if c.heartTimeoutCount >= common.HEART_TIMEOUT_COUNT_MAX { | |
114 | + c.Quiting() | |
115 | + return | |
116 | + } | |
117 | + logger.Debug("timeout count: %d", c.heartTimeoutCount) | |
118 | + }else { | |
119 | + c.heartTimeoutCount = 0 | |
120 | + } | |
121 | +} | |
122 | + | |
123 | +func (c *Connection) update() { | |
124 | + nextCheckTime := atomic.LoadInt64(&c.nextCheckTime) | |
125 | + now := utils.Timex() | |
126 | + if now >= nextCheckTime { | |
127 | + c.checkHeartBeat(now) | |
128 | + nextCheckTime = now + common.HEART_TIMER_INTERVAL | |
129 | + atomic.StoreInt64(&c.nextCheckTime, nextCheckTime) | |
130 | + } | |
131 | +} | |
132 | +// | |
133 | +//func (c *Connection) SetLastHeartCheckTime() { | |
134 | +// now := utils.Timex() | |
135 | +// lastHeartCheckTime := atomic.LoadInt64(&c.lastHeartCheckTime) | |
136 | +// if now - lastHeartCheckTime < common.HEART_TIMER_INTERVAL { | |
137 | +// logger.Debug("heart too quick") | |
138 | +// } | |
139 | +// atomic.StoreInt64(&c.lastHeartCheckTime, now) | |
140 | +//} | |
141 | + | |
97 | 142 | func (c *Connection) Start() { |
98 | 143 | go c.write() |
99 | 144 | go c.read() |
... | ... | @@ -109,6 +154,7 @@ func (c *Connection) Stop() { |
109 | 154 | } |
110 | 155 | |
111 | 156 | func (c *Connection) Quiting() { |
157 | + logger.Debug("ID: %d close", c.Id) | |
112 | 158 | c.Server.OnClose(c) |
113 | 159 | } |
114 | 160 | ... | ... |
src/components/net/server.go
... | ... | @@ -11,12 +11,15 @@ import ( |
11 | 11 | "pro2d/src/components/db" |
12 | 12 | "pro2d/src/components/etcd" |
13 | 13 | "pro2d/src/components/logger" |
14 | + "pro2d/src/components/timewheel" | |
14 | 15 | "pro2d/src/models" |
15 | 16 | "sync" |
17 | + "time" | |
16 | 18 | ) |
17 | 19 | |
18 | 20 | type ActionHandler func (msg *MsgPkg) (int32, proto.Message) |
19 | 21 | var ActionMap map[pb.ProtoCode]ActionHandler |
22 | +var TimeWheel *timewheel.TimeWheel | |
20 | 23 | |
21 | 24 | type Server struct { |
22 | 25 | SConf *conf.SConf |
... | ... | @@ -36,10 +39,6 @@ func NewServer(sConf *conf.SConf) *Server { |
36 | 39 | } |
37 | 40 | } |
38 | 41 | |
39 | -func (s *Server) StartTimer() { | |
40 | - | |
41 | -} | |
42 | - | |
43 | 42 | //StartWorkerPool 启动worker工作池 |
44 | 43 | func (s *Server) StartWorkerPool() { |
45 | 44 | //遍历需要启动worker的数量,依此启动 |
... | ... | @@ -96,6 +95,16 @@ func (s *Server) LoadPlugin() { |
96 | 95 | logger.Debug("load plugin success") |
97 | 96 | } |
98 | 97 | |
98 | +func (s *Server) handleTimeOut() { | |
99 | + s.Clients.Range(func(key, value interface{}) bool { | |
100 | + client := value.(*Connection) | |
101 | + client.update() | |
102 | + return true | |
103 | + }) | |
104 | + | |
105 | + TimeWheel.AfterFunc(1*time.Second, s.handleTimeOut) | |
106 | +} | |
107 | + | |
99 | 108 | func (s *Server)Start() error { |
100 | 109 | //mongo 初始化 |
101 | 110 | db.MongoDatabase = db.MongoClient.Database(conf.GlobalConf.GameConf.DBName) |
... | ... | @@ -110,10 +119,10 @@ func (s *Server)Start() error { |
110 | 119 | s.EtcdClient.PutWithLeasePrefix(conf.GlobalConf.GameConf.Name, conf.GlobalConf.GameConf.ID, fmt.Sprintf("%s:%d", conf.GlobalConf.GameConf.IP, conf.GlobalConf.GameConf.Port), 5) |
111 | 120 | |
112 | 121 | //初始化plugin |
113 | - _, err = plugin.Open(conf.GlobalConf.GameConf.PluginPath) | |
114 | - if err != nil { | |
115 | - return err | |
116 | - } | |
122 | + //_, err = plugin.Open(conf.GlobalConf.GameConf.PluginPath) | |
123 | + //if err != nil { | |
124 | + // return err | |
125 | + //} | |
117 | 126 | |
118 | 127 | port := fmt.Sprintf(":%d", s.SConf.Port) |
119 | 128 | l, err := net.Listen("tcp", port) |
... | ... | @@ -121,8 +130,15 @@ func (s *Server)Start() error { |
121 | 130 | return err |
122 | 131 | } |
123 | 132 | |
133 | + //启动协程池 | |
124 | 134 | s.StartWorkerPool() |
125 | 135 | |
136 | + //启动定时器 | |
137 | + TimeWheel = timewheel.NewTimeWheel(common.TickMS * time.Millisecond, common.WheelSize) | |
138 | + TimeWheel.Start() | |
139 | + s.handleTimeOut() | |
140 | + | |
141 | + //监听端口 | |
126 | 142 | logger.Debug("listen on %s\n", port) |
127 | 143 | id := 0 |
128 | 144 | for { |
... | ... | @@ -139,5 +155,5 @@ func (s *Server)Start() error { |
139 | 155 | } |
140 | 156 | |
141 | 157 | func (s *Server)Stop() { |
142 | -} | |
143 | - | |
158 | + TimeWheel.Stop() | |
159 | +} | |
144 | 160 | \ No newline at end of file | ... | ... |
src/components/skynettimer/timerwheel.go deleted
... | ... | @@ -1 +0,0 @@ |
1 | -package skynettimer |
src/components/kafkatimer/bucket.go renamed to src/components/timewheel/bucket.go
1 | -package kafkatimer | |
1 | +package timewheel | |
2 | 2 | |
3 | 3 | import ( |
4 | 4 | "container/list" |
... | ... | @@ -6,97 +6,52 @@ import ( |
6 | 6 | "sync/atomic" |
7 | 7 | "unsafe" |
8 | 8 | ) |
9 | - | |
10 | -// Timer represents a single event. When the Timer expires, the given | |
11 | -// task will be executed. | |
12 | -type Timer struct { | |
13 | - expiration int64 // in milliseconds | |
14 | - task func() | |
15 | - | |
16 | - // The bucket that holds the list to which this timer's element belongs. | |
17 | - // | |
18 | - // NOTE: This field may be updated and read concurrently, | |
19 | - // through Timer.Stop() and Bucket.Flush(). | |
20 | - b unsafe.Pointer // type: *bucket | |
21 | - | |
22 | - // The timer's element. | |
23 | - element *list.Element | |
24 | -} | |
25 | - | |
26 | -func (t *Timer) getBucket() *bucket { | |
27 | - return (*bucket)(atomic.LoadPointer(&t.b)) | |
28 | -} | |
29 | - | |
30 | -func (t *Timer) setBucket(b *bucket) { | |
31 | - atomic.StorePointer(&t.b, unsafe.Pointer(b)) | |
32 | -} | |
33 | - | |
34 | -// Stop prevents the Timer from firing. It returns true if the call | |
35 | -// stops the timer, false if the timer has already expired or been stopped. | |
36 | -// | |
37 | -// If the timer t has already expired and the t.task has been started in its own | |
38 | -// goroutine; Stop does not wait for t.task to complete before returning. If the caller | |
39 | -// needs to know whether t.task is completed, it must coordinate with t.task explicitly. | |
40 | -func (t *Timer) Stop() bool { | |
41 | - stopped := false | |
42 | - for b := t.getBucket(); b != nil; b = t.getBucket() { | |
43 | - // If b.Remove is called just after the timing wheel's goroutine has: | |
44 | - // 1. removed t from b (through b.Flush -> b.remove) | |
45 | - // 2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add) | |
46 | - // this may fail to remove t due to the change of t's bucket. | |
47 | - stopped = b.Remove(t) | |
48 | - | |
49 | - // Thus, here we re-get t's possibly new bucket (nil for case 1, or ab (non-nil) for case 2), | |
50 | - // and retry until the bucket becomes nil, which indicates that t has finally been removed. | |
51 | - } | |
52 | - return stopped | |
53 | -} | |
54 | - | |
55 | 9 | type bucket struct { |
56 | - // 64-bit atomic operations require 64-bit alignment, but 32-bit | |
57 | - // compilers do not ensure it. So we must keep the 64-bit field | |
58 | - // as the first field of the struct. | |
59 | - // | |
60 | - // For more explanations, see https://golang.org/pkg/sync/atomic/#pkg-note-BUG | |
61 | - // and https://go101.org/article/memory-layout.html. | |
10 | + //过期时间 | |
62 | 11 | expiration int64 |
63 | 12 | |
64 | - mu sync.Mutex | |
13 | + mu sync.Mutex | |
14 | + //相同过期时间的任务队列 | |
65 | 15 | timers *list.List |
66 | 16 | } |
67 | 17 | |
68 | -func newBucket() *bucket { | |
18 | +func newBucket() *bucket { | |
69 | 19 | return &bucket{ |
70 | - timers: list.New(), | |
71 | 20 | expiration: -1, |
21 | + mu: sync.Mutex{}, | |
22 | + timers: list.New(), | |
72 | 23 | } |
73 | 24 | } |
74 | 25 | |
75 | -func (b *bucket) Expiration() int64 { | |
76 | - return atomic.LoadInt64(&b.expiration) | |
77 | -} | |
78 | - | |
79 | -func (b *bucket) SetExpiration(expiration int64) bool { | |
80 | - return atomic.SwapInt64(&b.expiration, expiration) != expiration | |
26 | +func (b *bucket) SetExpiration(expiration int64) { | |
27 | + atomic.AddInt64(&b.expiration, expiration) | |
81 | 28 | } |
82 | 29 | |
83 | 30 | func (b *bucket) Add(t *Timer) { |
84 | 31 | b.mu.Lock() |
32 | + defer b.mu.Unlock() | |
85 | 33 | |
86 | 34 | e := b.timers.PushBack(t) |
87 | 35 | t.setBucket(b) |
88 | 36 | t.element = e |
37 | +} | |
38 | + | |
39 | +func (b *bucket) Flush(reinsert func(*Timer)) { | |
40 | + b.mu.Lock() | |
41 | + defer b.mu.Unlock() | |
42 | + | |
43 | + for e := b.timers.Front(); e != nil; { | |
44 | + next := e.Next() | |
45 | + t := e.Value.(*Timer) | |
46 | + b.remove(t) | |
89 | 47 | |
90 | - b.mu.Unlock() | |
48 | + reinsert(t) | |
49 | + e = next | |
50 | + } | |
91 | 51 | } |
92 | 52 | |
93 | 53 | func (b *bucket) remove(t *Timer) bool { |
94 | 54 | if t.getBucket() != b { |
95 | - // If remove is called from within t.Stop, and this happens just after the timing wheel's goroutine has: | |
96 | - // 1. removed t from b (through b.Flush -> b.remove) | |
97 | - // 2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add) | |
98 | - // then t.getBucket will return nil for case 1, or ab (non-nil) for case 2. | |
99 | - // In either case, the returned value does not equal to b. | |
100 | 55 | return false |
101 | 56 | } |
102 | 57 | b.timers.Remove(t.element) |
... | ... | @@ -108,26 +63,23 @@ func (b *bucket) remove(t *Timer) bool { |
108 | 63 | func (b *bucket) Remove(t *Timer) bool { |
109 | 64 | b.mu.Lock() |
110 | 65 | defer b.mu.Unlock() |
66 | + | |
111 | 67 | return b.remove(t) |
112 | 68 | } |
113 | 69 | |
114 | -func (b *bucket) Flush(reinsert func(*Timer)) { | |
115 | - b.mu.Lock() | |
116 | - defer b.mu.Unlock() | |
117 | - | |
118 | - for e := b.timers.Front(); e != nil; { | |
119 | - next := e.Next() | |
120 | - | |
121 | - t := e.Value.(*Timer) | |
122 | - b.remove(t) | |
123 | - // Note that this operation will either execute the timer's task, or | |
124 | - // insert the timer into another bucket belonging to a lower-level wheel. | |
125 | - // | |
126 | - // In either case, no further lock operation will happen to b.mu. | |
127 | - reinsert(t) | |
70 | +type Timer struct { | |
71 | + expiration int64 | |
72 | + //要被执行的任务 | |
73 | + task func() | |
128 | 74 | |
129 | - e = next | |
130 | - } | |
75 | + b unsafe.Pointer | |
76 | + element *list.Element | |
77 | +} | |
131 | 78 | |
132 | - b.SetExpiration(-1) | |
79 | +func (t *Timer) setBucket(b *bucket) { | |
80 | + atomic.StorePointer(&t.b, unsafe.Pointer(b)) | |
133 | 81 | } |
82 | + | |
83 | +func (t *Timer) getBucket() *bucket { | |
84 | + return (*bucket)(atomic.LoadPointer(&t.b)) | |
85 | +} | |
134 | 86 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,165 @@ |
1 | +package timewheel | |
2 | + | |
3 | +import ( | |
4 | + "pro2d/src/common" | |
5 | + "pro2d/src/components/workpool" | |
6 | + "sync/atomic" | |
7 | + "time" | |
8 | + "unsafe" | |
9 | +) | |
10 | + | |
11 | +type TimeWheel struct { | |
12 | + ticker *time.Ticker | |
13 | + tickMs int64 //一滴答的时间 1ms 可以自定义 我们这里选择使用1ms | |
14 | + wheelSize int64 | |
15 | + startMs int64 //开始时间 in millisecond | |
16 | + endMs int64 | |
17 | + wheelTime int64 //跑完一圈所需时间 | |
18 | + level int64 //层级 | |
19 | + | |
20 | + //时间刻度 列表 | |
21 | + bucket []*bucket | |
22 | + currentTime int64 //当前时间 in millisecond | |
23 | + prevflowWheel unsafe.Pointer // type: *TimingWheel | |
24 | + overflowWheel unsafe.Pointer // type: *TimingWheel | |
25 | + exitC chan struct{} | |
26 | + | |
27 | + WorkPool *workpool.WorkPool | |
28 | +} | |
29 | + | |
30 | +func NewTimeWheel(tick time.Duration, wheelSize int64) *TimeWheel { | |
31 | + //转化为毫秒 | |
32 | + tickMs := int64(tick / time.Millisecond) | |
33 | + //如果小于零 | |
34 | + if tickMs <=0 { | |
35 | + panic("tick must be greater than or equal to 1 ms") | |
36 | + } | |
37 | + | |
38 | + startMs := time.Now().UnixMilli() //ms | |
39 | + | |
40 | + workpool := workpool.NewWorkPool(common.WorkerPoolSize, common.MaxTaskPerWorker) | |
41 | + return newTimingWheel(tickMs, wheelSize, startMs, 0, nil, workpool) | |
42 | +} | |
43 | + | |
44 | +func newTimingWheel(tick, wheelSize int64, start, level int64, prev *TimeWheel, pool *workpool.WorkPool) *TimeWheel { | |
45 | + buckets := make([]*bucket, wheelSize) | |
46 | + for i := range buckets { | |
47 | + buckets[i] = newBucket() | |
48 | + } | |
49 | + | |
50 | + return &TimeWheel{ | |
51 | + tickMs: tick, | |
52 | + wheelSize: wheelSize, | |
53 | + startMs: start, | |
54 | + endMs: wheelSize * tick + start, | |
55 | + wheelTime: wheelSize * tick, | |
56 | + bucket: buckets, | |
57 | + currentTime: truncate(start, tick), | |
58 | + exitC: make(chan struct{}), | |
59 | + WorkPool: pool, | |
60 | + | |
61 | + prevflowWheel: unsafe.Pointer(prev), | |
62 | + level: level, | |
63 | + } | |
64 | +} | |
65 | + | |
66 | +func truncate(dst, m int64) int64 { | |
67 | + return dst - dst%m | |
68 | +} | |
69 | + | |
70 | +func (tw *TimeWheel) add(t *Timer) bool { | |
71 | + currentTime := atomic.LoadInt64(&tw.currentTime) | |
72 | + if t.expiration < currentTime + tw.tickMs { | |
73 | + return false | |
74 | + }else if t.expiration < currentTime + tw.wheelTime { | |
75 | + virtualID := t.expiration / tw.tickMs //需要多少滴答数 | |
76 | + b := tw.bucket[virtualID%tw.wheelSize] //pos = 所需滴答数 % wheelSize | |
77 | + b.Add(t) | |
78 | + | |
79 | + b.SetExpiration(virtualID * tw.tickMs) | |
80 | + }else { | |
81 | + overflowWheel := atomic.LoadPointer(&tw.overflowWheel) | |
82 | + if overflowWheel == nil { | |
83 | + level := atomic.LoadInt64(&tw.level) + 1 | |
84 | + atomic.CompareAndSwapPointer( | |
85 | + &tw.overflowWheel, | |
86 | + nil, | |
87 | + unsafe.Pointer(newTimingWheel(tw.wheelTime, tw.wheelSize, currentTime, level, tw , tw.WorkPool)), | |
88 | + ) | |
89 | + overflowWheel = atomic.LoadPointer(&tw.overflowWheel) | |
90 | + } | |
91 | + //递归添加到下一级定时器中 | |
92 | + (*TimeWheel)(overflowWheel).add(t) | |
93 | + } | |
94 | + | |
95 | + return true | |
96 | +} | |
97 | + | |
98 | +func (tw *TimeWheel) addOrRun(t *Timer) { | |
99 | + if !tw.add(t) { | |
100 | + workerID := t.expiration % tw.WorkPool.WorkerPoolSize | |
101 | + //将请求消息发送给任务队列 | |
102 | + tw.WorkPool.TaskQueue[workerID] <- t.task | |
103 | + } | |
104 | +} | |
105 | + | |
106 | +//拨动时钟 | |
107 | +func (tw *TimeWheel) advanceClock(expiration int64) { | |
108 | + level := atomic.LoadInt64(&tw.level) | |
109 | + currentTime := truncate(expiration, tw.tickMs) | |
110 | + atomic.StoreInt64(&tw.currentTime, currentTime) | |
111 | + | |
112 | + if level == 0 { | |
113 | + virtualID := expiration / tw.tickMs //需要多少滴答数 | |
114 | + b := tw.bucket[virtualID%tw.wheelSize] //pos = 所需滴答数 % wheelSize | |
115 | + b.Flush(tw.addOrRun) | |
116 | + } else { | |
117 | + prevflowWheel := atomic.LoadPointer(&tw.prevflowWheel) | |
118 | + if prevflowWheel != nil { | |
119 | + virtualID := expiration / tw.tickMs //需要多少滴答数 | |
120 | + b := tw.bucket[virtualID%tw.wheelSize] //pos = 所需滴答数 % wheelSize | |
121 | + b.Flush((*TimeWheel)(prevflowWheel).addOrRun) | |
122 | + } | |
123 | + } | |
124 | + | |
125 | + //如果基础的时钟指针转完了一圈,则递归拨动下一级时钟 | |
126 | + if currentTime >= tw.endMs { | |
127 | + atomic.StoreInt64(&tw.startMs, currentTime) | |
128 | + atomic.StoreInt64(&tw.endMs, currentTime + tw.wheelTime) | |
129 | + | |
130 | + overflowWheel := atomic.LoadPointer(&tw.overflowWheel) | |
131 | + if overflowWheel != nil { | |
132 | + (*TimeWheel)(overflowWheel).advanceClock(currentTime) | |
133 | + } | |
134 | + } | |
135 | +} | |
136 | + | |
137 | + | |
138 | +func (tw *TimeWheel) AfterFunc(d time.Duration, f func()) *Timer { | |
139 | + t := &Timer{ | |
140 | + expiration: time.Now().UTC().Add(d).UnixMilli(), | |
141 | + task: f, | |
142 | + } | |
143 | + tw.addOrRun(t) | |
144 | + return t | |
145 | +} | |
146 | + | |
147 | +func (tw *TimeWheel) Start() { | |
148 | + tw.ticker = time.NewTicker(time.Duration(tw.tickMs) * time.Millisecond) | |
149 | + tw.WorkPool.StartWorkerPool() | |
150 | + | |
151 | + go func() { | |
152 | + for { | |
153 | + select { | |
154 | + case t := <- tw.ticker.C: | |
155 | + tw.advanceClock(t.UnixMilli()) | |
156 | + case <- tw.exitC: | |
157 | + return | |
158 | + } | |
159 | + } | |
160 | + }() | |
161 | +} | |
162 | + | |
163 | +func (tw *TimeWheel) Stop() { | |
164 | + tw.exitC <- struct{}{} | |
165 | +} | |
0 | 166 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,30 @@ |
1 | +package timewheel | |
2 | + | |
3 | +import ( | |
4 | + "fmt" | |
5 | + "testing" | |
6 | + "time" | |
7 | +) | |
8 | +var tw *TimeWheel | |
9 | + | |
10 | +func Add() { | |
11 | + fmt.Println("ADD : 123456") | |
12 | + tw.AfterFunc(6*time.Second, Add) | |
13 | +} | |
14 | + | |
15 | +func Add1() { | |
16 | + fmt.Println("GET : 78901112") | |
17 | + tw.AfterFunc(9*time.Second, Add1) | |
18 | +} | |
19 | + | |
20 | +func TestTimeWheel_AfterFunc(t *testing.T) { | |
21 | + | |
22 | + tw = NewTimeWheel(time.Second, 5) | |
23 | + tw.Start() | |
24 | + defer tw.Stop() | |
25 | + | |
26 | + | |
27 | + Add() | |
28 | + Add1() | |
29 | + time.Sleep(time.Second * 200) | |
30 | +} | ... | ... |
... | ... | @@ -0,0 +1,41 @@ |
1 | +package workpool | |
2 | + | |
3 | +type Job func() | |
4 | + | |
5 | +type WorkPool struct { | |
6 | + WorkerPoolSize int64 | |
7 | + MaxTaskPerWorker int64 | |
8 | + TaskQueue []chan Job | |
9 | +} | |
10 | + | |
11 | +func NewWorkPool(poolSize, maxTaskSize int64) *WorkPool { | |
12 | + return &WorkPool{ | |
13 | + WorkerPoolSize: poolSize, | |
14 | + MaxTaskPerWorker: maxTaskSize, | |
15 | + TaskQueue: make([]chan Job, poolSize), | |
16 | + } | |
17 | +} | |
18 | + | |
19 | +//StartOneWorker 启动一个Worker工作流程 | |
20 | +func (wp *WorkPool) StartOneWorker(workerID int, taskQueue chan Job) { | |
21 | + //不断的等待队列中的消息 | |
22 | + for { | |
23 | + select { | |
24 | + //有消息则取出队列的Request,并执行绑定的业务方法 | |
25 | + case job := <-taskQueue: | |
26 | + _ = workerID | |
27 | + job() | |
28 | + } | |
29 | + } | |
30 | +} | |
31 | + | |
32 | +func (wp *WorkPool) StartWorkerPool() { | |
33 | + //遍历需要启动worker的数量,依此启动 | |
34 | + for i := 0; i < int(wp.WorkerPoolSize); i++ { | |
35 | + //一个worker被启动 | |
36 | + //给当前worker对应的任务队列开辟空间 | |
37 | + wp.TaskQueue[i] = make(chan Job, wp.MaxTaskPerWorker) | |
38 | + //启动当前Worker,阻塞的等待对应的任务队列是否有消息传递进来 | |
39 | + go wp.StartOneWorker(i, wp.TaskQueue[i]) | |
40 | + } | |
41 | +} | |
0 | 42 | \ No newline at end of file | ... | ... |
src/plugin/RolePlugin.go
src/plugin/protocode.go
src/utils/utils.go