Skip to content

Commit 7b4acda

Browse files
committed
Make TimeUtil have multiple running states:
- IDLE: Direct invocation state for idle conditions. - RUNNING: Legacy mode for busy conditions. - REQUEST: Temporary state from IDLE to RUNNING. Through this design TimeUtil won't cost much in idle systems any more. Signed-off-by: Jason Joo <[email protected]>
1 parent 55a8294 commit 7b4acda

File tree

2 files changed

+290
-15
lines changed

2 files changed

+290
-15
lines changed

sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/TimeUtil.java

+179-15
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,202 @@
1515
*/
1616
package com.alibaba.csp.sentinel.util;
1717

18+
import java.util.List;
1819
import java.util.concurrent.TimeUnit;
1920

21+
import com.alibaba.csp.sentinel.log.RecordLog;
22+
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
23+
import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder;
24+
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
25+
import com.alibaba.csp.sentinel.util.function.Tuple2;
26+
2027
/**
2128
* Provides millisecond-level time of OS.
29+
* <p>
30+
* Here we should see that not all the time TimeUtil should
31+
* keep looping 1_000 times every second (Actually about 800/s due to some losses).
32+
* <pre>
33+
* * In idle conditions it just acts as System.currentTimeMillis();
34+
* * In busy conditions (significantly more than 1_000/s) it keeps loop to reduce costs.
35+
* </pre>
36+
* For detail design and proposals please goto
37+
* <a href="https://github.com/alibaba/Sentinel/issues/1702#issuecomment-692151160">https://github.com/alibaba/Sentinel/issues/1702</a>
2238
*
2339
* @author qinan.qn
40+
* @author jason
2441
*/
25-
public final class TimeUtil {
42+
public final class TimeUtil implements Runnable {
43+
private static final long CHECK_INTERVAL = 3000;
44+
private static final long HITS_LOWER_BOUNDARY = 800;
45+
private static final long HITS_UPPER_BOUNDARY = 1200;
46+
47+
public static enum STATE {
48+
IDLE, PREPARE, RUNNING;
49+
}
50+
private static class Statistic {
51+
private LongAdder writes = new LongAdder();
52+
private LongAdder reads = new LongAdder();
53+
public LongAdder getWrites() {
54+
return writes;
55+
}
56+
public LongAdder getReads() {
57+
return reads;
58+
}
59+
}
60+
private static TimeUtil INSTANCE;
2661

27-
private static volatile long currentTimeMillis;
62+
private volatile long currentTimeMillis;
63+
private volatile STATE state = STATE.IDLE;
64+
65+
private LeapArray<Statistic> statistics;
66+
67+
// thread private variables
68+
private long lastCheck = 0;
2869

2970
static {
30-
currentTimeMillis = System.currentTimeMillis();
31-
Thread daemon = new Thread(new Runnable() {
71+
INSTANCE = new TimeUtil();
72+
}
73+
74+
public TimeUtil() {
75+
this.statistics = new LeapArray<TimeUtil.Statistic>(3, 3000) {
76+
3277
@Override
33-
public void run() {
34-
while (true) {
35-
currentTimeMillis = System.currentTimeMillis();
36-
try {
37-
TimeUnit.MILLISECONDS.sleep(1);
38-
} catch (Throwable e) {
39-
40-
}
41-
}
78+
public Statistic newEmptyBucket(long timeMillis) {
79+
return new Statistic();
4280
}
43-
});
81+
82+
@Override
83+
protected WindowWrap<Statistic> resetWindowTo(WindowWrap<Statistic> windowWrap, long startTime) {
84+
Statistic val = windowWrap.value();
85+
val.getReads().reset();
86+
val.getWrites().reset();
87+
windowWrap.resetTo(startTime);
88+
return windowWrap;
89+
}
90+
};
91+
this.currentTimeMillis = System.currentTimeMillis();
92+
this.lastCheck = this.currentTimeMillis;
93+
Thread daemon = new Thread(this);
4494
daemon.setDaemon(true);
4595
daemon.setName("sentinel-time-tick-thread");
4696
daemon.start();
4797
}
98+
99+
@Override
100+
public void run() {
101+
while (true) {
102+
this.check();
103+
if (this.state == STATE.RUNNING) {
104+
this.currentTimeMillis = System.currentTimeMillis();
105+
this.statistics.currentWindow(this.currentTimeMillis).value().getWrites().increment();
106+
try {
107+
TimeUnit.MILLISECONDS.sleep(1);
108+
} catch (Throwable e) {
109+
}
110+
continue;
111+
}
112+
if (this.state == STATE.IDLE) {
113+
try {
114+
TimeUnit.MILLISECONDS.sleep(300);
115+
} catch (Throwable e) {
116+
}
117+
continue;
118+
}
119+
if (this.state == STATE.PREPARE) {
120+
RecordLog.info("TimeUtil switches to RUNNING");
121+
this.currentTimeMillis = System.currentTimeMillis();
122+
this.state = STATE.RUNNING;
123+
continue;
124+
}
125+
}
126+
}
127+
128+
/**
129+
* Current running state
130+
*
131+
* @return
132+
*/
133+
public STATE getState() {
134+
return state;
135+
}
136+
137+
/**
138+
* Current qps statistics (including reads and writes request)
139+
* excluding current working time window for accurate result.
140+
*
141+
* @param now
142+
* @return
143+
*/
144+
public Tuple2<Long, Long> currentQps(long now) {
145+
List<WindowWrap<Statistic>> list = this.statistics.listAll();
146+
long reads = 0;
147+
long writes = 0;
148+
int cnt = 0;
149+
for (WindowWrap<Statistic> windowWrap : list) {
150+
if (windowWrap.isTimeInWindow(now)) {
151+
continue;
152+
}
153+
cnt ++;
154+
reads += windowWrap.value().getReads().longValue();
155+
writes += windowWrap.value().getWrites().longValue();
156+
}
157+
if (cnt < 1) {
158+
return new Tuple2<Long, Long>(0L, 0L);
159+
}
160+
return new Tuple2<Long, Long>(reads / cnt, writes / cnt);
161+
}
162+
163+
/**
164+
* Check and operate the state if necessary.
165+
* ATTENTION: It's called in daemon thread.
166+
*/
167+
private void check() {
168+
long now = currentTime(true);
169+
// every period
170+
if (now - this.lastCheck < CHECK_INTERVAL) {
171+
return;
172+
}
173+
this.lastCheck = now;
174+
Tuple2<Long, Long> qps = currentQps(now);
175+
if (this.state == STATE.IDLE && qps.r1 > HITS_UPPER_BOUNDARY) {
176+
RecordLog.info("TimeUtil switches to PREPARE for better performance, reads={}/s, writes={}/s", qps.r1, qps.r2);
177+
this.state = STATE.PREPARE;
178+
} else if (this.state == STATE.RUNNING && qps.r1 < HITS_LOWER_BOUNDARY) {
179+
RecordLog.info("TimeUtil switches to IDLE due to not enough load, reads={}/s, writes={}/s", qps.r1, qps.r2);
180+
this.state = STATE.IDLE;
181+
}
182+
}
183+
184+
private long currentTime(boolean innerCall) {
185+
long now = this.currentTimeMillis;
186+
Statistic val = this.statistics.currentWindow(now).value();
187+
if (!innerCall) {
188+
val.getReads().increment();
189+
}
190+
if (this.state == STATE.IDLE || this.state == STATE.PREPARE) {
191+
now = System.currentTimeMillis();
192+
this.currentTimeMillis = now;
193+
if (!innerCall) {
194+
val.getWrites().increment();
195+
}
196+
}
197+
return now;
198+
}
199+
200+
/**
201+
* Current timestamp in milliseconds.
202+
*
203+
* @return
204+
*/
205+
public long getTime() {
206+
return this.currentTime(false);
207+
}
208+
209+
public static TimeUtil instance() {
210+
return INSTANCE;
211+
}
48212

49213
public static long currentTimeMillis() {
50-
return currentTimeMillis;
214+
return INSTANCE.getTime();
51215
}
52216
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 1999-2020 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.alibaba.csp.sentinel.util;
17+
18+
import static org.junit.Assert.assertEquals;
19+
import static org.junit.Assert.assertTrue;
20+
21+
import java.util.concurrent.atomic.AtomicBoolean;
22+
import java.util.concurrent.atomic.AtomicInteger;
23+
24+
import org.junit.Before;
25+
import org.junit.Test;
26+
27+
import com.alibaba.csp.sentinel.log.RecordLog;
28+
import com.alibaba.csp.sentinel.util.function.Tuple2;
29+
30+
/**
31+
* @author jason
32+
*
33+
*/
34+
public class TimeUtilTest {
35+
@Before
36+
public void initLogging() {
37+
System.setProperty("csp.sentinel.log.output.type", "console");
38+
}
39+
40+
private void waitFor(int step, int seconds) throws InterruptedException {
41+
for (int i = 0; i < seconds; i ++) {
42+
Tuple2<Long, Long> qps = TimeUtil.instance().currentQps(TimeUtil.currentTimeMillis());
43+
RecordLog.info("step {} qps: reads={}, writes={}", step, qps.r1, qps.r2);
44+
Thread.sleep(1000);
45+
}
46+
}
47+
48+
@Test
49+
public void test() throws InterruptedException {
50+
final AtomicInteger delayTime = new AtomicInteger(50);
51+
final AtomicBoolean shouldShutdown = new AtomicBoolean();
52+
for (int i = 0; i < 10; i ++) {
53+
new Thread(new Runnable() {
54+
55+
@Override
56+
public void run() {
57+
long last = 0;
58+
while (true) {
59+
if (shouldShutdown.get()) {
60+
break;
61+
}
62+
long now = TimeUtil.currentTimeMillis();
63+
int delay = delayTime.get();
64+
if (delay < 1) {
65+
if (last > now) {
66+
System.err.println("wrong value");
67+
}
68+
last = now;
69+
continue;
70+
}
71+
try {
72+
Thread.sleep(delay);
73+
} catch (InterruptedException e) {
74+
}
75+
if (last > now) {
76+
System.err.println("incorrect value");
77+
}
78+
last = now;
79+
}
80+
}
81+
}).start();
82+
}
83+
Tuple2<Long, Long> qps;
84+
waitFor(1, 4);
85+
// initial state
86+
assertEquals(TimeUtil.STATE.IDLE, TimeUtil.instance().getState());
87+
qps = TimeUtil.instance().currentQps(TimeUtil.currentTimeMillis());
88+
assertTrue(qps.r1 < 1000);
89+
assertTrue(qps.r2 < 1000);
90+
91+
// to RUNNING
92+
delayTime.set(0);
93+
// wait statistics to be stable
94+
waitFor(2, 8);
95+
qps = TimeUtil.instance().currentQps(TimeUtil.currentTimeMillis());
96+
assertEquals(TimeUtil.STATE.RUNNING, TimeUtil.instance().getState());
97+
assertTrue(qps.r1 > 1000);
98+
assertTrue(qps.r2 <= 1000);
99+
100+
// back
101+
delayTime.set(100);
102+
// wait statistics to be stable
103+
waitFor(3, 8);
104+
qps = TimeUtil.instance().currentQps(TimeUtil.currentTimeMillis());
105+
assertEquals(TimeUtil.STATE.IDLE, TimeUtil.instance().getState());
106+
assertTrue(qps.r1 < 1000);
107+
assertTrue(qps.r2 < 1000);
108+
shouldShutdown.set(true);
109+
}
110+
111+
}

0 commit comments

Comments
 (0)