From 054bef4a8b55e689f82f059ef4f2a074a4484321 Mon Sep 17 00:00:00 2001 From: byteblogs168 <598092184@qq.com> Date: Sat, 14 Jan 2023 20:48:22 +0800 Subject: [PATCH] =?UTF-8?q?0.0.4.1=20=E6=BB=91=E5=8A=A8=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=20=E6=9B=BF=E6=8D=A2=E4=B8=BA=20=E7=8E=AF=E5=BD=A2=E6=BB=91?= =?UTF-8?q?=E5=8A=A8=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/ExampleApplicationTests.java | 24 +- .../ExistsTransactionalRetryServiceTest.java | 2 +- .../client/core/report/ReportHandler.java | 40 +- .../core/strategy/LocalRetryStrategies.java | 2 +- .../client/core/window/RetryLeapArray.java | 69 +++ .../XRetryClientCoreApplicationTests.java | 7 +- .../x/retry/common/core/window/LeapArray.java | 406 ++++++++++++++++++ .../retry/common/core/window/WindowWrap.java | 102 +++++ 8 files changed, 625 insertions(+), 27 deletions(-) create mode 100644 x-retry-client-core/src/main/java/com/x/retry/client/core/window/RetryLeapArray.java create mode 100755 x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/window/LeapArray.java create mode 100755 x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/window/WindowWrap.java diff --git a/example/src/test/java/com/example/ExampleApplicationTests.java b/example/src/test/java/com/example/ExampleApplicationTests.java index 559e44eac..4aca3d020 100644 --- a/example/src/test/java/com/example/ExampleApplicationTests.java +++ b/example/src/test/java/com/example/ExampleApplicationTests.java @@ -1,16 +1,21 @@ package com.example; -//import com.example.client.DemoClient; import com.example.client.DemoClient; import com.example.mapper.SchoolMapper; import com.example.po.School; +import com.x.retry.client.core.report.ReportHandler; +import com.x.retry.client.core.window.RetryLeapArray; import com.x.retry.common.core.model.Result; +import com.x.retry.server.model.dto.RetryTaskDTO; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.web.client.RestTemplate; +import java.util.concurrent.ConcurrentLinkedQueue; + @SpringBootTest @Slf4j public class ExampleApplicationTests { @@ -21,6 +26,8 @@ public class ExampleApplicationTests { @Autowired private RestTemplate restTemplate; + private RetryLeapArray retryLeapArray = ReportHandler.slidingWindow; + @Autowired(required = false) private DemoClient demoClient; @@ -44,6 +51,21 @@ public class ExampleApplicationTests { System.out.println(template); } + @SneakyThrows + @Test + public void windows() { + + for (int i = 0; i < 100000; i++) { + int finalI = i; + new Thread(() -> { + RetryTaskDTO retryTaskDTO = new RetryTaskDTO(); + retryTaskDTO.setBizId(finalI + ""); + ConcurrentLinkedQueue value = retryLeapArray.currentWindow().value(); + value.add(retryTaskDTO); + }).start(); + } + Thread.sleep(5 * 1000); + } } diff --git a/example/src/test/java/com/example/ExistsTransactionalRetryServiceTest.java b/example/src/test/java/com/example/ExistsTransactionalRetryServiceTest.java index ce762c5c1..ebca44a3f 100644 --- a/example/src/test/java/com/example/ExistsTransactionalRetryServiceTest.java +++ b/example/src/test/java/com/example/ExistsTransactionalRetryServiceTest.java @@ -89,7 +89,7 @@ public class ExistsTransactionalRetryServiceTest { .thenReturn(new Result(0, "5")) ; try { - for (int i = 0; i < 100; i++) { + for (int i = 0; i < 1000; i++) { threadPoolExecutor.execute(() -> testExistsTransactionalRetryService.testSimpleInsert(UUID.randomUUID().toString())); } } catch (Exception e) { diff --git a/x-retry-client-core/src/main/java/com/x/retry/client/core/report/ReportHandler.java b/x-retry-client-core/src/main/java/com/x/retry/client/core/report/ReportHandler.java index 5522411a4..84da8df2e 100644 --- a/x-retry-client-core/src/main/java/com/x/retry/client/core/report/ReportHandler.java +++ b/x-retry-client-core/src/main/java/com/x/retry/client/core/report/ReportHandler.java @@ -9,18 +9,21 @@ import com.x.retry.client.core.exception.XRetryClientException; import com.x.retry.client.core.intercepter.RetrySiteSnapshot; import com.x.retry.client.core.retryer.RetryerInfo; import com.x.retry.client.core.spel.SPELParamFunction; +import com.x.retry.client.core.window.RetryLeapArray; import com.x.retry.common.core.log.LogUtils; -import com.x.retry.common.core.window.Listener; -import com.x.retry.common.core.window.SlidingWindow; +import com.x.retry.common.core.window.WindowWrap; import com.x.retry.server.model.dto.RetryTaskDTO; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.springframework.util.ReflectionUtils; import java.lang.reflect.Method; -import java.time.temporal.ChronoUnit; -import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.function.Function; /** @@ -28,13 +31,20 @@ import java.util.function.Function; * @date : 2022-03-08 09:24 */ @Component +@Slf4j public class ReportHandler implements Lifecycle { @Autowired @Qualifier("XRetryJacksonSerializer") private RetryArgSerializer retryArgSerializer; - private static SlidingWindow slidingWindow; + public static final int SAMPLE_COUNT = 10; + + public static final int INTERVAL_IN_MS = 1000; + + private static ScheduledExecutorService dispatchService = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "DispatchService")); + + public static RetryLeapArray slidingWindow = new RetryLeapArray(SAMPLE_COUNT, INTERVAL_IN_MS, new ReportListener()); /** * 异步上报到服务端 @@ -50,7 +60,7 @@ public class ReportHandler implements Lifecycle { Method executorMethod = retryerInfo.getExecutorMethod(); RetryTaskDTO retryTaskDTO = new RetryTaskDTO(); - String bizId = null; + String bizId; try { Class bizIdGenerate = retryerInfo.getBizIdGenerate(); BizIdGenerate generate = bizIdGenerate.newInstance(); @@ -73,31 +83,21 @@ public class ReportHandler implements Lifecycle { Function spelParamFunction = new SPELParamFunction(bizNoSpel, executorMethod); retryTaskDTO.setBizNo(spelParamFunction.apply(args)); - slidingWindow.add(retryTaskDTO); + slidingWindow.currentWindow().value().add(retryTaskDTO); return true; } @Override public void start() { + dispatchService.scheduleAtFixedRate(() -> { + slidingWindow.currentWindow(); + }, INTERVAL_IN_MS, INTERVAL_IN_MS / SAMPLE_COUNT, TimeUnit.MILLISECONDS); - Listener reportListener = new ReportListener(); - - slidingWindow = SlidingWindow - .Builder - .newBuilder() - .withTotalThreshold(50) - .withDuration(5, ChronoUnit.SECONDS) - .withListener(reportListener) - .build(); - slidingWindow.start(); } @Override public void close() { - if (Objects.nonNull(slidingWindow)) { - slidingWindow.end(); - } } } diff --git a/x-retry-client-core/src/main/java/com/x/retry/client/core/strategy/LocalRetryStrategies.java b/x-retry-client-core/src/main/java/com/x/retry/client/core/strategy/LocalRetryStrategies.java index 07007a254..ea0e36626 100644 --- a/x-retry-client-core/src/main/java/com/x/retry/client/core/strategy/LocalRetryStrategies.java +++ b/x-retry-client-core/src/main/java/com/x/retry/client/core/strategy/LocalRetryStrategies.java @@ -97,7 +97,7 @@ public class LocalRetryStrategies extends AbstractRetryStrategies { log.info("内存重试完成且异常未被解决 scene:[{}]", retryerInfo.getScene()); // 上报服务端异常 if (RetryType.LOCAL_REMOTE.name().equals(retryerInfo.getRetryType().name())){ - // TODO上报 + // 上报 log.debug("上报 scene:[{}]", retryerInfo.getScene()); reportHandler.report(retryerInfo.getScene(), retryerInfo.getExecutorClassName(), params); } diff --git a/x-retry-client-core/src/main/java/com/x/retry/client/core/window/RetryLeapArray.java b/x-retry-client-core/src/main/java/com/x/retry/client/core/window/RetryLeapArray.java new file mode 100644 index 000000000..accc8a31b --- /dev/null +++ b/x-retry-client-core/src/main/java/com/x/retry/client/core/window/RetryLeapArray.java @@ -0,0 +1,69 @@ +package com.x.retry.client.core.window; + +import com.x.retry.common.core.util.JsonUtil; +import com.x.retry.common.core.window.LeapArray; +import com.x.retry.common.core.window.Listener; +import com.x.retry.common.core.window.WindowWrap; +import com.x.retry.server.model.dto.RetryTaskDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * @author: www.byteblogs.com + * @date : 2022-05-28 15:07 + */ +@Slf4j +public class RetryLeapArray extends LeapArray> { + + protected List> listenerList; + + /** + * The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}. + * + * @param sampleCount bucket count of the sliding window + * @param intervalInMs the total time interval of this {@link LeapArray} in milliseconds + */ + public RetryLeapArray(int sampleCount, int intervalInMs) { + super(sampleCount, intervalInMs); + } + + public RetryLeapArray(int sampleCount, int intervalInMs, List> listenerList) { + super(sampleCount, intervalInMs); + this.listenerList = listenerList; + } + + public RetryLeapArray(int sampleCount, int intervalInMs, Listener listener) { + this(sampleCount, intervalInMs, Collections.singletonList(listener)); + } + + @Override + public ConcurrentLinkedQueue newEmptyBucket(long timeMillis) { + return new ConcurrentLinkedQueue<>(); + } + + @Override + protected WindowWrap> resetWindowTo(WindowWrap> windowWrap, long startTime) { + + ConcurrentLinkedQueue deepCopy = new ConcurrentLinkedQueue<>(windowWrap.value()); + try { + + if (!CollectionUtils.isEmpty(deepCopy)) { + for (Listener listener : listenerList) { + listener.handler(new ArrayList<>(deepCopy)); + } + } + } catch (Exception e) { + log.error("滑动窗口监听器处理失败 data:[{}]", JsonUtil.toJsonString(windowWrap.value()), e); + } + + windowWrap.value().removeAll(deepCopy); + windowWrap.resetTo(startTime); + + return windowWrap; + } +} diff --git a/x-retry-client-core/src/test/java/com/x/retry/common/client/core/xretryclientcore/XRetryClientCoreApplicationTests.java b/x-retry-client-core/src/test/java/com/x/retry/common/client/core/xretryclientcore/XRetryClientCoreApplicationTests.java index 0b127c2fd..c9f9a5b6f 100644 --- a/x-retry-client-core/src/test/java/com/x/retry/common/client/core/xretryclientcore/XRetryClientCoreApplicationTests.java +++ b/x-retry-client-core/src/test/java/com/x/retry/common/client/core/xretryclientcore/XRetryClientCoreApplicationTests.java @@ -1,13 +1,12 @@ package com.x.retry.common.client.core.xretryclientcore; +import com.x.retry.client.core.window.RetryLeapArray; +import com.x.retry.server.model.dto.RetryTaskDTO; +import lombok.SneakyThrows; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class XRetryClientCoreApplicationTests { - @Test - void contextLoads() { - } - } diff --git a/x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/window/LeapArray.java b/x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/window/LeapArray.java new file mode 100755 index 000000000..372136cb5 --- /dev/null +++ b/x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/window/LeapArray.java @@ -0,0 +1,406 @@ +package com.x.retry.common.core.window; + +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; + +/** + *

+ * Basic data structure for statistic metrics in Sentinel. + *

+ *

+ * Leap array use sliding window algorithm to count data. Each bucket cover {@code windowLengthInMs} time span, + * and the total time span is {@link #intervalInMs}, so the total bucket amount is: + * {@code sampleCount = intervalInMs / windowLengthInMs}. + *

+ * + * @param type of statistic data + * @author jialiang.linjl + * @author Eric Zhao + * @author Carpenter Lee + */ +@Slf4j +public abstract class LeapArray { + + protected int windowLengthInMs; // 窗口长度 + protected int sampleCount; // 窗口数 + protected int intervalInMs; // 总时间间隔 + private double intervalInSecond; + + protected final AtomicReferenceArray> array; + + /** + * The conditional (predicate) update lock is used only when current bucket is deprecated. + */ + private final ReentrantLock updateLock = new ReentrantLock(); + + /** + * The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}. + * + * @param sampleCount bucket count of the sliding window + * @param intervalInMs the total time interval of this {@link LeapArray} in milliseconds + */ + public LeapArray(int sampleCount, int intervalInMs) { + this.windowLengthInMs = intervalInMs / sampleCount; + this.intervalInMs = intervalInMs; + this.intervalInSecond = intervalInMs / 1000.0; + this.sampleCount = sampleCount; + + this.array = new AtomicReferenceArray<>(sampleCount); + } + + /** + * Get the bucket at current timestamp. + * + * @return the bucket at current timestamp + */ + public WindowWrap currentWindow() { + return currentWindow(System.currentTimeMillis()); + } + + /** + * Create a new statistic value for bucket. + * + * @param timeMillis current time in milliseconds + * @return the new empty bucket + */ + public abstract T newEmptyBucket(long timeMillis); + + /** + * Reset given bucket to provided start time and reset the value. + * + * @param startTime the start time of the bucket in milliseconds + * @param windowWrap current bucket + * @return new clean bucket at given start time + */ + protected abstract WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime); + + public int calculateTimeIdx(/*@Valid*/ long timeMillis) { + long timeId = timeMillis / windowLengthInMs; + // Calculate current index so we can map the timestamp to the leap array. + return (int) (timeId % array.length()); + } + + protected long calculateWindowStart(/*@Valid*/ long timeMillis) { + return timeMillis - timeMillis % windowLengthInMs; + } + + /** + * Get bucket item at provided timestamp. + * + * @param timeMillis a valid timestamp in milliseconds + * @return current bucket item at provided timestamp if the time is valid; null if time is invalid + */ + public WindowWrap currentWindow(long timeMillis) { + if (timeMillis < 0) { + return null; + } + + // 计算出窗口在哪个 元素位置处 + int idx = calculateTimeIdx(timeMillis); + + // 计算窗口的开始时间 + long windowStart = calculateWindowStart(timeMillis); + + /* + * Get bucket item at given time from the array. + * + * (1) Bucket is absent, then just create a new bucket and CAS update to circular array. + * (2) Bucket is up-to-date, then just return the bucket. + * (3) Bucket is deprecated, then reset current bucket and clean all deprecated buckets. + */ + while (true) { + WindowWrap old = array.get(idx); + if (old == null) { + /* + * B0 B1 B2 NULL B4 + * ||_______|_______|_______|_______|_______||___ + * 200 400 600 800 1000 1200 timestamp + * ^ + * time=888 + * bucket is empty, so create new and update + * + * If the old bucket is absent, then we create a new bucket at {@code windowStart}, + * then try to update circular array via a CAS operation. Only one thread can + * succeed to update, while other threads yield its time slice. + */ + WindowWrap window = new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); + + if (array.compareAndSet(idx, null, window)) { + // Successfully updated, return the created bucket. + return window; + } else { + // Contention failed, the thread will yield its time slice to wait for bucket available. + Thread.yield(); + } + + } else if (windowStart == old.windowStart()) { + /* + * B0 B1 B2 B3 B4 + * ||_______|_______|_______|_______|_______||___ + * 200 400 600 800 1000 1200 timestamp + * ^ + * time=888 + * startTime of Bucket 3: 800, so it's up-to-date + * + * If current {@code windowStart} is equal to the start timestamp of old bucket, + * that means the time is within the bucket, so directly return the bucket. + */ + return old; + } else if (windowStart > old.windowStart()) { + /* + * (old) + * B0 B1 B2 NULL B4 + * |_______||_______|_______|_______|_______|_______||___ + * ... 1200 1400 1600 1800 2000 2200 timestamp + * ^ + * time=1676 + * startTime of Bucket 2: 400, deprecated, should be reset + * + * If the start timestamp of old bucket is behind provided time, that means + * the bucket is deprecated. We have to reset the bucket to current {@code windowStart}. + * Note that the reset and clean-up operations are hard to be atomic, + * so we need a update lock to guarantee the correctness of bucket update. + * + * The update lock is conditional (tiny scope) and will take effect only when + * bucket is deprecated, so in most cases it won't lead to performance loss. + */ + if (updateLock.tryLock()) { + try { + // Successfully get the update lock, now we reset the bucket. + return resetWindowTo(old, windowStart); + } finally { + updateLock.unlock(); + } + } else { + // Contention failed, the thread will yield its time slice to wait for bucket available. + Thread.yield(); + } + } else if (windowStart < old.windowStart()) { + // Should not go through here, as the provided time is already behind. + return new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); + } + } + + } + + /** + * Get the previous bucket item before provided timestamp. + * + * @param timeMillis a valid timestamp in milliseconds + * @return the previous bucket item before provided timestamp + */ + public WindowWrap getPreviousWindow(long timeMillis) { + if (timeMillis < 0) { + return null; + } + int idx = calculateTimeIdx(timeMillis - windowLengthInMs); + timeMillis = timeMillis - windowLengthInMs; + WindowWrap wrap = array.get(idx); + + if (wrap == null || isWindowDeprecated(wrap)) { + return null; + } + + if (wrap.windowStart() + windowLengthInMs < (timeMillis)) { + return null; + } + + return wrap; + } + + /** + * Get the previous bucket item for current timestamp. + * + * @return the previous bucket item for current timestamp + */ + public WindowWrap getPreviousWindow() { + return getPreviousWindow(System.currentTimeMillis()); + } + + /** + * Get statistic value from bucket for provided timestamp. + * + * @param timeMillis a valid timestamp in milliseconds + * @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null + */ + public T getWindowValue(long timeMillis) { + if (timeMillis < 0) { + return null; + } + int idx = calculateTimeIdx(timeMillis); + + WindowWrap bucket = array.get(idx); + + if (bucket == null || !bucket.isTimeInWindow(timeMillis)) { + return null; + } + + return bucket.value(); + } + + /** + * Check if a bucket is deprecated, which means that the bucket + * has been behind for at least an entire window time span. + * + * @param windowWrap a non-null bucket + * @return true if the bucket is deprecated; otherwise false + */ + public boolean isWindowDeprecated(/*@NonNull*/ WindowWrap windowWrap) { + return isWindowDeprecated(System.currentTimeMillis(), windowWrap); + } + + public boolean isWindowDeprecated(long time, WindowWrap windowWrap) { + return time - windowWrap.windowStart() > intervalInMs; + } + + /** + * Get valid bucket list for entire sliding window. + * The list will only contain "valid" buckets. + * + * @return valid bucket list for entire sliding window. + */ + public List> list() { + return list(System.currentTimeMillis()); + } + + public List> list(long validTime) { + int size = array.length(); + List> result = new ArrayList>(size); + + for (int i = 0; i < size; i++) { + WindowWrap windowWrap = array.get(i); + if (windowWrap == null || isWindowDeprecated(validTime, windowWrap)) { + continue; + } + result.add(windowWrap); + } + + return result; + } + + /** + * Get all buckets for entire sliding window including deprecated buckets. + * + * @return all buckets for entire sliding window + */ + public List> listAll() { + int size = array.length(); + List> result = new ArrayList>(size); + + for (int i = 0; i < size; i++) { + WindowWrap windowWrap = array.get(i); + if (windowWrap == null) { + continue; + } + result.add(windowWrap); + } + + return result; + } + + /** + * Get aggregated value list for entire sliding window. + * The list will only contain value from "valid" buckets. + * + * @return aggregated value list for entire sliding window + */ + public List values() { + return values(System.currentTimeMillis()); + } + + public List values(long timeMillis) { + if (timeMillis < 0) { + return new ArrayList(); + } + int size = array.length(); + List result = new ArrayList(size); + + for (int i = 0; i < size; i++) { + WindowWrap windowWrap = array.get(i); + if (windowWrap == null || isWindowDeprecated(timeMillis, windowWrap)) { + continue; + } + result.add(windowWrap.value()); + } + return result; + } + + /** + * Get the valid "head" bucket of the sliding window for provided timestamp. + * Package-private for test. + * + * @param timeMillis a valid timestamp in milliseconds + * @return the "head" bucket if it exists and is valid; otherwise null + */ + WindowWrap getValidHead(long timeMillis) { + // Calculate index for expected head time. + int idx = calculateTimeIdx(timeMillis + windowLengthInMs); + + WindowWrap wrap = array.get(idx); + if (wrap == null || isWindowDeprecated(wrap)) { + return null; + } + + return wrap; + } + + /** + * Get the valid "head" bucket of the sliding window at current timestamp. + * + * @return the "head" bucket if it exists and is valid; otherwise null + */ + public WindowWrap getValidHead() { + return getValidHead(System.currentTimeMillis()); + } + + /** + * Get sample count (total amount of buckets). + * + * @return sample count + */ + public int getSampleCount() { + return sampleCount; + } + + /** + * Get total interval length of the sliding window in milliseconds. + * + * @return interval in second + */ + public int getIntervalInMs() { + return intervalInMs; + } + + /** + * Get total interval length of the sliding window. + * + * @return interval in second + */ + public double getIntervalInSecond() { + return intervalInSecond; + } + + public void debug(long time) { + StringBuilder sb = new StringBuilder(); + List> lists = list(time); + sb.append("Thread_").append(Thread.currentThread().getId()).append("_"); + for (WindowWrap window : lists) { + sb.append(window.windowStart()).append(":").append(window.value().toString()); + } + System.out.println(sb.toString()); + } + + public long currentWaiting() { + // TODO: default method. Should remove this later. + return 0; + } + + public void addWaiting(long time, int acquireCount) { + // Do nothing by default. + throw new UnsupportedOperationException(); + } +} diff --git a/x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/window/WindowWrap.java b/x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/window/WindowWrap.java new file mode 100755 index 000000000..b6aba36d5 --- /dev/null +++ b/x-retry-common/x-retry-common-core/src/main/java/com/x/retry/common/core/window/WindowWrap.java @@ -0,0 +1,102 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.x.retry.common.core.window; + +/** + * Wrapper entity class for a period of time window. + * + * @param data type + * @author jialiang.linjl + * @author Eric Zhao + */ +public class WindowWrap { + + /** + * Time length of a single window bucket in milliseconds. + * 窗口的长度 + */ + private final long windowLengthInMs; + + /** + * Start timestamp of the window in milliseconds. + * 窗口的开始时间 + */ + private long windowStart; + + /** + * Statistic data. + * 需要的统计的数据 + */ + private T value; + + /** + * @param windowLengthInMs a single window bucket's time length in milliseconds. + * @param windowStart the start timestamp of the window + * @param value statistic data + */ + public WindowWrap(long windowLengthInMs, long windowStart, T value) { + this.windowLengthInMs = windowLengthInMs; + this.windowStart = windowStart; + this.value = value; + } + + public long windowLength() { + return windowLengthInMs; + } + + public long windowStart() { + return windowStart; + } + + public T value() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + /** + * Reset start timestamp of current bucket to provided time. + * + * @param startTime valid start timestamp + * @return bucket after reset + */ + public WindowWrap resetTo(long startTime) { + this.windowStart = startTime; + return this; + } + + /** + * Check whether given timestamp is in current bucket. + * + * @param timeMillis valid timestamp in ms + * @return true if the given time is in current bucket, otherwise false + * @since 1.5.0 + */ + public boolean isTimeInWindow(long timeMillis) { + return windowStart <= timeMillis && timeMillis < windowStart + windowLengthInMs; + } + + @Override + public String toString() { + return "WindowWrap{" + + "windowLengthInMs=" + windowLengthInMs + + ", windowStart=" + windowStart + + ", value=" + value + + '}'; + } +}