T getRemoved() {
94 | return (T) removed;
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/com/github/phantomthief/failover/Failover.java:
--------------------------------------------------------------------------------
1 | package com.github.phantomthief.failover;
2 |
3 | import static com.github.phantomthief.failover.util.RandomListUtils.getRandom;
4 | import static java.util.stream.Collectors.toList;
5 |
6 | import java.util.Collection;
7 | import java.util.List;
8 | import java.util.Set;
9 |
10 | import javax.annotation.Nullable;
11 |
12 | import com.github.phantomthief.failover.util.FailoverUtils;
13 | import com.github.phantomthief.util.ThrowableConsumer;
14 | import com.github.phantomthief.util.ThrowableFunction;
15 |
16 | /**
17 | * Failover接口,有多个实现。
18 | * 这是原来的接口,方法比较多,子类要实现这些方法负担也比较大,重构后抽取了SimpleFailover接口,
19 | * 现在建议使用SimpleFailover,更简单,也能实现绝大部分功能。
20 | *
21 | *
22 | * 请先阅读README.md,可以到这里在线阅读。
23 | *
24 | *
25 | * @author w.vela
26 | */
27 | public interface Failover extends SimpleFailover {
28 |
29 | /**
30 | * 获取所有资源的列表,包括不可用资源。
31 | * @return 所有资源
32 | */
33 | List getAll();
34 |
35 | /**
36 | * 返回可用的资源列表。
37 | * @return 可用资源列表
38 | * @see #getOneAvailable()
39 | * @see #getAvailable(int)
40 | */
41 | List getAvailable();
42 |
43 | /**
44 | * 返回可用资源列表,但是排除掉参数指定的资源
45 | * @param exclusions 需要从结果中排除的资源
46 | * @return 满足条件的资源列表
47 | */
48 | default List getAvailableExclude(Collection exclusions) {
49 | return getAvailable().stream().filter(e -> !exclusions.contains(e)).collect(toList());
50 | }
51 |
52 | /**
53 | * 获取不健康(当前权重=最小权重)的资源。
54 | * @return 资源列表
55 | */
56 | Set getFailed();
57 |
58 | /**
59 | * 获取一个可使用的资源,如果所有的资源都down了,可能会返回null,注意判空。
60 | *
61 | * @return 一个用于执行调用的资源
62 | */
63 | @Nullable
64 | @Override
65 | default T getOneAvailable() {
66 | return getRandom(getAvailable());
67 | }
68 |
69 | /**
70 | * 当调用失败后,使用方想要换一个资源重试,可使用本方法重新获取另一个资源,注意判空。
71 | *
72 | * @param exclusions 需要排除的资源列表,不会出现在返回的结果中,通常是之前调用失败的资源
73 | * @return 一个用于执行调用的资源
74 | */
75 | @Nullable
76 | @Override
77 | default T getOneAvailableExclude(Collection exclusions) {
78 | return getRandom(getAvailableExclude(exclusions));
79 | }
80 |
81 | /**
82 | * 获取可用的资源列表,但是限制只返回n个。
83 | * @param n 返回值的限制数
84 | * @return 资源列表
85 | */
86 | default List getAvailable(int n) {
87 | return getRandom(getAvailable(), n);
88 | }
89 |
90 | /**
91 | * 选定一个资源执行业务操作,失败了就再选另一个,直到成功或者所有的资源都试过一遍。
92 | * 通常来说,建议不要用这个方法,因为它的默认实现行为不固定,但是现在也不敢改了,使用者自己来重试更好控制一些。
93 | * @param func 业务操作回调,有返回值
94 | * @return 业务回调的返回值
95 | * @throws X 最后一次失败的业务异常,有时候还会丢出NoAvailableResourceException
96 | * @see FailoverUtils#supplyWithRetry
97 | */
98 | default E supplyWithRetry(ThrowableFunction func) throws X {
99 | return FailoverUtils.supplyWithRetry(getAll().size(), 0, this, func);
100 | }
101 |
102 | /**
103 | * 选定一个资源执行业务操作,失败了就再选另一个,直到成功或者所有的资源都试过一遍。
104 | * 通常来说,建议不要用这个方法,因为它的默认实现行为不固定,但是现在也不敢改了,使用者自己来重试更好控制一些。
105 | * @param func 业务操作回调,没有返回值
106 | * @throws X 最后一次失败的业务异常,有时候还会丢出NoAvailableResourceException
107 | * @see FailoverUtils#runWithRetry
108 | */
109 | default void runWithRetry(ThrowableConsumer func) throws X {
110 | FailoverUtils.runWithRetry(getAll().size(), 0, this, func);
111 | }
112 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/phantomthief/failover/util/AliasMethod.java:
--------------------------------------------------------------------------------
1 | package com.github.phantomthief.failover.util;
2 |
3 | import static java.util.Objects.requireNonNull;
4 |
5 | import java.util.ArrayDeque;
6 | import java.util.ArrayList;
7 | import java.util.Deque;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.Map.Entry;
11 | import java.util.concurrent.ThreadLocalRandom;
12 |
13 | import javax.annotation.Nonnull;
14 |
15 | /**
16 | * http://www.keithschwarz.com/darts-dice-coins/
17 | *
18 | * @author w.vela
19 | * Created on 2020-04-07.
20 | */
21 | public class AliasMethod {
22 |
23 | private final Object[] values;
24 | private final int[] alias;
25 | private final double[] probability;
26 |
27 | public AliasMethod(@Nonnull Map weightMap) {
28 | requireNonNull(weightMap);
29 | if (weightMap.isEmpty()) {
30 | throw new IllegalArgumentException("weightMap is empty");
31 | }
32 | List probabilities = new ArrayList<>(weightMap.size());
33 | List valueList = new ArrayList<>(weightMap.size());
34 | double sum = 0;
35 | for (Entry entry : weightMap.entrySet()) {
36 | double weight = entry.getValue().doubleValue();
37 | if (weight > 0) {
38 | sum += weight;
39 | valueList.add(entry.getKey());
40 | }
41 | }
42 | for (Entry entry : weightMap.entrySet()) {
43 | double weight = entry.getValue().doubleValue();
44 | if (weight > 0) {
45 | probabilities.add(weight / sum);
46 | }
47 | }
48 | if (sum <= 0) {
49 | throw new IllegalArgumentException("invalid weight map:" + weightMap);
50 | }
51 | values = valueList.toArray(new Object[0]);
52 |
53 | int size = probabilities.size();
54 | probability = new double[size];
55 | alias = new int[size];
56 |
57 | double average = 1.0 / size;
58 |
59 | Deque small = new ArrayDeque<>();
60 | Deque large = new ArrayDeque<>();
61 |
62 | for (int i = 0; i < size; ++i) {
63 | if (probabilities.get(i) >= average) {
64 | large.add(i);
65 | } else {
66 | small.add(i);
67 | }
68 | }
69 |
70 | while (!small.isEmpty() && !large.isEmpty()) {
71 | int less = small.removeLast();
72 | int more = large.removeLast();
73 |
74 | probability[less] = probabilities.get(less) * size;
75 | alias[less] = more;
76 |
77 | probabilities.set(more, probabilities.get(more) + probabilities.get(less) - average);
78 |
79 | if (probabilities.get(more) >= average) {
80 | large.add(more);
81 | } else {
82 | small.add(more);
83 | }
84 | }
85 |
86 | while (!small.isEmpty()) {
87 | probability[small.removeLast()] = 1.0;
88 | }
89 | while (!large.isEmpty()) {
90 | probability[large.removeLast()] = 1.0;
91 | }
92 | }
93 |
94 | @SuppressWarnings("unchecked")
95 | public T get() {
96 | ThreadLocalRandom r = ThreadLocalRandom.current();
97 | int column = r.nextInt(probability.length);
98 | boolean coinToss = r.nextDouble() < probability[column];
99 | int index = coinToss ? column : alias[column];
100 | return (T) values[index];
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/test/java/com/github/phantomthief/failover/impl/WeightFailoverTestMissingNode.java:
--------------------------------------------------------------------------------
1 | package com.github.phantomthief.failover.impl;
2 |
3 | import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
4 | import static java.util.Collections.emptyList;
5 | import static java.util.concurrent.TimeUnit.SECONDS;
6 | import static org.junit.jupiter.api.Assertions.assertFalse;
7 | import static org.junit.jupiter.api.Assertions.assertTrue;
8 |
9 | import org.junit.jupiter.api.Test;
10 |
11 | /**
12 | * @author w.vela
13 | * Created on 2017-12-18.
14 | */
15 | class WeightFailoverTestMissingNode {
16 |
17 | @Test
18 | void testMissingEnable() {
19 | WeightFailover failover = WeightFailover. newGenericBuilder()
20 | .checker(s -> {
21 | System.err.println("check:" + s);
22 | return s.equals("test1");
23 | }, 1)
24 | .checkDuration(1, SECONDS)
25 | .autoAddOnMissing(10)
26 | .build(emptyList());
27 | assertTrue(failover.getAll().isEmpty());
28 | assertTrue(failover.getAvailable().isEmpty());
29 | assertTrue(failover.getFailed().isEmpty());
30 |
31 | failover.success("test");
32 |
33 | assertTrue(failover.getAll().contains("test"));
34 | assertTrue(failover.getAvailable().contains("test"));
35 | assertFalse(failover.getFailed().contains("test"));
36 |
37 | failover.fail("test2");
38 | assertTrue(failover.getAll().contains("test2"));
39 | assertTrue(failover.getAvailable().contains("test2"));
40 | assertFalse(failover.getFailed().contains("test2"));
41 |
42 | failover.down("test1");
43 | assertTrue(failover.getAll().contains("test1"));
44 | assertFalse(failover.getAvailable().contains("test1"));
45 | assertTrue(failover.getFailed().contains("test1"));
46 |
47 | sleepUninterruptibly(2, SECONDS);
48 |
49 | assertTrue(failover.getAll().contains("test1"));
50 | assertTrue(failover.getAvailable().contains("test1"));
51 | assertFalse(failover.getFailed().contains("test1"));
52 | }
53 |
54 | @Test
55 | void testNormal() {
56 | WeightFailover failover = WeightFailover. newGenericBuilder()
57 | .checker(s -> {
58 | System.err.println("check:" + s);
59 | return s.equals("test1");
60 | }, 1)
61 | .checkDuration(1, SECONDS)
62 | .build(emptyList());
63 | assertTrue(failover.getAll().isEmpty());
64 | assertTrue(failover.getAvailable().isEmpty());
65 | assertTrue(failover.getFailed().isEmpty());
66 |
67 | failover.success("test");
68 |
69 | assertFalse(failover.getAll().contains("test"));
70 | assertFalse(failover.getAvailable().contains("test"));
71 | assertFalse(failover.getFailed().contains("test"));
72 |
73 | failover.fail("test2");
74 | assertFalse(failover.getAll().contains("test2"));
75 | assertFalse(failover.getAvailable().contains("test2"));
76 | assertFalse(failover.getFailed().contains("test2"));
77 |
78 | failover.down("test1");
79 | assertFalse(failover.getAll().contains("test1"));
80 | assertFalse(failover.getAvailable().contains("test1"));
81 | assertFalse(failover.getFailed().contains("test1"));
82 |
83 | sleepUninterruptibly(2, SECONDS);
84 |
85 | assertFalse(failover.getAll().contains("test1"));
86 | assertFalse(failover.getAvailable().contains("test1"));
87 | assertFalse(failover.getFailed().contains("test1"));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/test/java/com/github/phantomthief/failover/util/SharedResourceTest.java:
--------------------------------------------------------------------------------
1 | package com.github.phantomthief.failover.util;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertFalse;
4 | import static org.junit.jupiter.api.Assertions.assertNotNull;
5 | import static org.junit.jupiter.api.Assertions.assertNull;
6 | import static org.junit.jupiter.api.Assertions.assertSame;
7 | import static org.junit.jupiter.api.Assertions.assertThrows;
8 | import static org.junit.jupiter.api.Assertions.assertTrue;
9 | import static org.junit.jupiter.api.Assertions.fail;
10 |
11 | import org.junit.jupiter.api.Test;
12 |
13 | import com.github.phantomthief.failover.util.SharedResource.UnregisterFailedException;
14 |
15 | /**
16 | * @author w.vela
17 | * Created on 16/2/19.
18 | */
19 | class SharedResourceTest {
20 |
21 | private static SharedResource resources = new SharedResource<>();
22 |
23 | @Test
24 | void test() {
25 | MockResource mock = resources.register("1", MockResource::new);
26 | assertSame(mock, resources.get("1"));
27 | assertSame(mock, resources.register("1", MockResource::new));
28 | resources.register("2", MockResource::new);
29 |
30 | MockResource mockResource1 = resources.get("1");
31 | MockResource mockResource2 = resources.get("1");
32 | assertNotNull(mockResource1);
33 | assertSame(mockResource1, mockResource2);
34 | assertFalse(mockResource1.isShutdown());
35 |
36 | resources.unregister("1", MockResource::close);
37 | mockResource1 = resources.get("1");
38 | assertNotNull(mockResource1);
39 | assertFalse(mockResource1.isShutdown());
40 |
41 | resources.unregister("1", MockResource::close);
42 | assertTrue(mockResource1.isShutdown());
43 | mockResource1 = resources.get("1");
44 | assertNull(mockResource1);
45 | }
46 |
47 | @Test
48 | void testUnpairUnregister() {
49 | resources.register("3", MockResource::new);
50 | MockResource mock = resources.get("3");
51 | assertSame(mock, resources.unregister("3", MockResource::close));
52 | assertThrows(IllegalStateException.class,
53 | () -> resources.unregister("3", MockResource::close));
54 | }
55 |
56 | @Test
57 | void testCleanupFailed() {
58 | resources.register("4", MockResource::new);
59 | MockResource mock = resources.get("4");
60 | UnregisterFailedException e = assertThrows(UnregisterFailedException.class,
61 | () -> resources.unregister("4", it -> {
62 | throw new IllegalArgumentException();
63 | }));
64 | assertSame(mock, e.getRemoved());
65 | assertSame(IllegalArgumentException.class, e.getCause().getClass());
66 | }
67 |
68 | @Test
69 | void testRegFail() {
70 | assertThrows(RuntimeException.class, () -> resources.register("5", (s) -> {
71 | throw new RuntimeException();
72 | }));
73 | MockResource mockResource = resources.get("5");
74 | assertNull(mockResource);
75 | assertThrows(IllegalStateException.class,
76 | () -> resources.unregister("5", MockResource::close));
77 | }
78 |
79 | private static class MockResource {
80 |
81 | private String name;
82 | private boolean shutdown = false;
83 |
84 | MockResource(String name) {
85 | this.name = name;
86 | }
87 |
88 | boolean isShutdown() {
89 | return shutdown;
90 | }
91 |
92 | void close() {
93 | if (shutdown) {
94 | fail("failed");
95 | }
96 | shutdown = true;
97 | System.out.println("shutdown:" + name);
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/test/java/com/github/phantomthief/failover/impl/benchmark/Group1PriorityFailover.java:
--------------------------------------------------------------------------------
1 | package com.github.phantomthief.failover.impl.benchmark;
2 |
3 | import org.openjdk.jmh.annotations.Benchmark;
4 | import org.openjdk.jmh.annotations.BenchmarkMode;
5 | import org.openjdk.jmh.annotations.Fork;
6 | import org.openjdk.jmh.annotations.Measurement;
7 | import org.openjdk.jmh.annotations.Mode;
8 | import org.openjdk.jmh.annotations.Param;
9 | import org.openjdk.jmh.annotations.Scope;
10 | import org.openjdk.jmh.annotations.Setup;
11 | import org.openjdk.jmh.annotations.State;
12 | import org.openjdk.jmh.annotations.Threads;
13 | import org.openjdk.jmh.annotations.Warmup;
14 | import org.openjdk.jmh.runner.Runner;
15 | import org.openjdk.jmh.runner.RunnerException;
16 | import org.openjdk.jmh.runner.options.Options;
17 | import org.openjdk.jmh.runner.options.OptionsBuilder;
18 |
19 | import com.github.phantomthief.failover.impl.PriorityFailover;
20 | import com.github.phantomthief.failover.impl.PriorityFailoverBuilder;
21 | import com.github.phantomthief.failover.impl.SimpleWeightFunction;
22 |
23 | /**
24 | * @author huangli
25 | * Created on 2020-01-21
26 | */
27 | @BenchmarkMode(Mode.Throughput)
28 | @Fork(1)
29 | @Threads(200)
30 | @Warmup(iterations = 1, time = 1)
31 | @Measurement(iterations = 2, time = 2)
32 | @State(Scope.Benchmark)
33 | public class Group1PriorityFailover {
34 |
35 | @Param({"5", "20", "100", "200", "1000"})
36 | private int totalSize;
37 |
38 | private static final int FAIL_RATE = 999;
39 |
40 | private PriorityFailover priorityFailover;
41 |
42 | private long count;
43 |
44 | @Setup
45 | public void init() {
46 | PriorityFailoverBuilder builder = PriorityFailover. newBuilder();
47 | for (int i = 0; i < totalSize; i++) {
48 | builder.addResource("key" + i, 100);
49 | }
50 | builder.weightFunction(new SimpleWeightFunction<>(0.01, 1.0));
51 | priorityFailover = builder.build();
52 | }
53 |
54 |
55 | @Benchmark
56 | public void getOneSuccess() {
57 | for (int i = 0; i < 1000; i++) {
58 | String one = priorityFailover.getOneAvailable();
59 | priorityFailover.success(one);
60 | }
61 | }
62 |
63 | @Benchmark
64 | public void getOneFail() {
65 | for (int i = 0; i < 1000; i++) {
66 | String one = priorityFailover.getOneAvailable();
67 | if (count++ % FAIL_RATE == 0) {
68 | priorityFailover.fail(one);
69 | } else {
70 | priorityFailover.success(one);
71 | }
72 | }
73 | }
74 |
75 | public static void main(String[] args) throws RunnerException {
76 | boolean useJmh = true;
77 | if (useJmh) {
78 | Options options = new OptionsBuilder()
79 | .include(Group1PriorityFailover.class.getSimpleName())
80 | .output(System.getProperty("user.home") + "/" + Group1PriorityFailover.class.getSimpleName()
81 | + ".txt")
82 | .build();
83 | new Runner(options).run();
84 | } else {
85 | Group1PriorityFailover obj = new Group1PriorityFailover();
86 | obj.totalSize = 5;
87 | obj.init();
88 | int loopCount = 1_0000_0000;
89 | for (int i = 0; i < 100000; i++) {
90 | obj.getOneFail();
91 | }
92 | long start = System.nanoTime();
93 | for (int i = 0; i < loopCount; i++) {
94 | obj.getOneFail();
95 | }
96 | long end = System.nanoTime();
97 | double seconds = (end - start) / 1000.0 / 1000.0 / 1000.0;
98 | System.out.printf("tps:%.2f", 1.0 * loopCount / seconds);
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/test/java/com/github/phantomthief/failover/impl/benchmark/Group1WeightFailover.java:
--------------------------------------------------------------------------------
1 | package com.github.phantomthief.failover.impl.benchmark;
2 |
3 | import org.openjdk.jmh.annotations.Benchmark;
4 | import org.openjdk.jmh.annotations.BenchmarkMode;
5 | import org.openjdk.jmh.annotations.Fork;
6 | import org.openjdk.jmh.annotations.Measurement;
7 | import org.openjdk.jmh.annotations.Mode;
8 | import org.openjdk.jmh.annotations.Param;
9 | import org.openjdk.jmh.annotations.Scope;
10 | import org.openjdk.jmh.annotations.Setup;
11 | import org.openjdk.jmh.annotations.State;
12 | import org.openjdk.jmh.annotations.Threads;
13 | import org.openjdk.jmh.annotations.Warmup;
14 | import org.openjdk.jmh.runner.Runner;
15 | import org.openjdk.jmh.runner.RunnerException;
16 | import org.openjdk.jmh.runner.options.Options;
17 | import org.openjdk.jmh.runner.options.OptionsBuilder;
18 |
19 | import com.github.phantomthief.failover.impl.WeightFailover;
20 | import com.google.common.collect.ImmutableMap;
21 | import com.google.common.collect.ImmutableMap.Builder;
22 |
23 | /**
24 | * @author huangli
25 | * Created on 2020-01-21
26 | */
27 | @BenchmarkMode(Mode.Throughput)
28 | @Fork(1)
29 | @Threads(200)
30 | @Warmup(iterations = 1, time = 1)
31 | @Measurement(iterations = 2, time = 2)
32 | @State(Scope.Benchmark)
33 | public class Group1WeightFailover {
34 |
35 | @Param({"5", "20", "100", "200", "1000"})
36 | private int totalSize;
37 |
38 | private static final int FAIL_RATE = 999;
39 |
40 | private WeightFailover weightFailover;
41 |
42 | private long count;
43 |
44 | @Setup
45 | public void init() {
46 | Builder builder = ImmutableMap.builder();
47 | for (int i = 0; i < totalSize; ++i) {
48 | builder.put("key" + i, 100);
49 | }
50 | weightFailover = WeightFailover. newGenericBuilder()
51 | .checker(it -> true, 1)
52 | .failReduceRate(0.01)
53 | .successIncreaseRate(1.0)
54 | .build(builder.build());
55 | }
56 |
57 | @Benchmark
58 | public void getOneSuccess() {
59 | for (int i = 0; i < 1000; i++) {
60 | String one = weightFailover.getOneAvailable();
61 | weightFailover.success(one);
62 | }
63 | }
64 |
65 | @Benchmark
66 | public void getOneFail() {
67 | for (int i = 0; i < 1000; i++) {
68 | String one = weightFailover.getOneAvailable();
69 | if (count++ % FAIL_RATE == 0) {
70 | weightFailover.fail(one);
71 | } else {
72 | weightFailover.success(one);
73 | }
74 | }
75 | }
76 |
77 | public static void main(String[] args) throws RunnerException {
78 | boolean useJmh = true;
79 | if (useJmh) {
80 | Options options = new OptionsBuilder()
81 | .include(Group1WeightFailover.class.getSimpleName())
82 | .output(System.getProperty("user.home") + "/" + Group1WeightFailover.class.getSimpleName()
83 | + ".txt")
84 | .build();
85 | new Runner(options).run();
86 | } else {
87 | Group1WeightFailover obj = new Group1WeightFailover();
88 | obj.totalSize = 5;
89 | obj.init();
90 | int loopCount = 1_0000_0000;
91 | for (int i = 0; i < 100000; i++) {
92 | obj.getOneSuccess();
93 | }
94 | long start = System.nanoTime();
95 | for (int i = 0; i < loopCount; i++) {
96 | obj.getOneSuccess();
97 | }
98 | long end = System.nanoTime();
99 | double seconds = (end - start) / 1000.0 / 1000.0 / 1000.0;
100 | System.out.printf("tps:%.2f", 1.0 * loopCount / seconds);
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/com/github/phantomthief/failover/impl/RecoverableCheckFailoverBuilder.java:
--------------------------------------------------------------------------------
1 | package com.github.phantomthief.failover.impl;
2 |
3 | import static com.google.common.base.Preconditions.checkNotNull;
4 | import static java.util.concurrent.TimeUnit.MINUTES;
5 | import static java.util.concurrent.TimeUnit.SECONDS;
6 | import static org.slf4j.LoggerFactory.getLogger;
7 |
8 | import java.util.List;
9 | import java.util.concurrent.TimeUnit;
10 | import java.util.function.Predicate;
11 |
12 | import javax.annotation.CheckReturnValue;
13 | import javax.annotation.Nonnull;
14 |
15 | import org.slf4j.Logger;
16 |
17 | @Deprecated
18 | public final class RecoverableCheckFailoverBuilder {
19 |
20 | private static final int DEFAULT_FAIL_COUNT = 10;
21 | private static final long DEFAULT_FAIL_DURATION = MINUTES.toMillis(1);
22 | private static final long DEFAULT_RECOVERY_CHECK_DURATION = SECONDS.toMillis(5);
23 | private static final Logger logger = getLogger(RecoverableCheckFailover.class);
24 | private int failCount;
25 | private long failDuration;
26 | private long recoveryCheckDuration;
27 | private boolean returnOriginalWhileAllFailed;
28 | private Predicate checker;
29 |
30 | @CheckReturnValue
31 | @Nonnull
32 | public RecoverableCheckFailoverBuilder setFailCount(int failCount) {
33 | this.failCount = failCount;
34 | return this;
35 | }
36 |
37 | @SuppressWarnings("unchecked")
38 | @CheckReturnValue
39 | @Nonnull
40 | public RecoverableCheckFailoverBuilder setChecker(Predicate super E> checker) {
41 | RecoverableCheckFailoverBuilder thisBuilder = (RecoverableCheckFailoverBuilder) this;
42 | thisBuilder.checker = thisBuilder.catching((Predicate) checker);
43 | return thisBuilder;
44 | }
45 |
46 | @CheckReturnValue
47 | @Nonnull
48 | public RecoverableCheckFailoverBuilder setRecoveryCheckDuration(long recoveryCheckDuration,
49 | TimeUnit unit) {
50 | this.recoveryCheckDuration = unit.toMillis(recoveryCheckDuration);
51 | return this;
52 | }
53 |
54 | @CheckReturnValue
55 | @Nonnull
56 | public RecoverableCheckFailoverBuilder setFailDuration(long failDuration, TimeUnit unit) {
57 | this.failDuration = unit.toMillis(failDuration);
58 | return this;
59 | }
60 |
61 | @CheckReturnValue
62 | @Nonnull
63 | public RecoverableCheckFailoverBuilder
64 | setReturnOriginalWhileAllFailed(boolean returnOriginalWhileAllFailed) {
65 | this.returnOriginalWhileAllFailed = returnOriginalWhileAllFailed;
66 | return this;
67 | }
68 |
69 | @SuppressWarnings("unchecked")
70 | @Nonnull
71 | public RecoverableCheckFailover build(List extends E> original) {
72 | RecoverableCheckFailoverBuilder thisBuilder = (RecoverableCheckFailoverBuilder) this;
73 | thisBuilder.ensure();
74 | return new RecoverableCheckFailover<>((List) original, thisBuilder.checker, failCount,
75 | failDuration, recoveryCheckDuration, returnOriginalWhileAllFailed);
76 | }
77 |
78 | private void ensure() {
79 | checkNotNull(checker);
80 |
81 | if (failCount <= 0) {
82 | failCount = DEFAULT_FAIL_COUNT;
83 | }
84 | if (failDuration <= 0) {
85 | failDuration = DEFAULT_FAIL_DURATION;
86 | }
87 | if (recoveryCheckDuration <= 0) {
88 | recoveryCheckDuration = DEFAULT_RECOVERY_CHECK_DURATION;
89 | }
90 | }
91 |
92 | private Predicate catching(Predicate predicate) {
93 | return t -> {
94 | try {
95 | return predicate.test(t);
96 | } catch (Throwable e) {
97 | logger.error("Ops. fail to test:{}", t, e);
98 | return false;
99 | }
100 | };
101 | }
102 | }
--------------------------------------------------------------------------------
/src/test/java/com/github/phantomthief/failover/util/RetryTest.java:
--------------------------------------------------------------------------------
1 | package com.github.phantomthief.failover.util;
2 |
3 | import static java.util.function.Function.identity;
4 | import static java.util.stream.Collectors.toMap;
5 | import static org.junit.jupiter.api.Assertions.assertEquals;
6 | import static org.junit.jupiter.api.Assertions.assertThrows;
7 | import static org.junit.jupiter.api.Assertions.assertTrue;
8 |
9 | import java.util.Map;
10 | import java.util.concurrent.TimeoutException;
11 | import java.util.stream.IntStream;
12 |
13 | import org.junit.jupiter.api.Test;
14 | import org.slf4j.Logger;
15 | import org.slf4j.LoggerFactory;
16 |
17 | import com.github.phantomthief.failover.impl.WeightFailover;
18 | import com.github.phantomthief.util.ThrowableFunction;
19 |
20 | /**
21 | * @author w.vela
22 | * Created on 2017-06-09.
23 | */
24 | class RetryTest {
25 |
26 | private static final Logger logger = LoggerFactory.getLogger(RetryTest.class);
27 |
28 | @Test
29 | void test() {
30 | WeightFailover failover = buildNewFailover();
31 | for (int i = 0; i < 50; i++) {
32 | String x = failover.supplyWithRetry(this::doSomething);
33 | logger.debug("test:{}", x);
34 | assertEquals(x, "r:1");
35 | }
36 | failover = buildNewFailover();
37 | System.out.println("test fail");
38 | WeightFailover thisFailover = failover;
39 | for (int i = 0; i < 50; i++) {
40 | assertThrows(Throwable.class, () -> {
41 | String x = thisFailover.supplyWithRetry(this::alwaysFail);
42 | logger.debug("test:{}", x);
43 | });
44 | }
45 | failover = buildNewFailover();
46 | logger.info("test some failed.");
47 | for (int i = 0; i < 50; i++) {
48 | try {
49 | String x = failover.supplyWithRetry(this::doSomething);
50 | logger.debug("test:{}", x);
51 | assertEquals(x, "r:1");
52 | } catch (Throwable e) {
53 | assertTrue(true);
54 | logger.info("fail, pass.");
55 | }
56 | }
57 | }
58 |
59 | @Test
60 | void testRetry(){
61 | WeightFailover failover = buildNewFailover();
62 | assertThrows(TimeoutException.class, () -> failover.supplyWithRetry(i -> {
63 | throw new TimeoutException();
64 | }));
65 | WeightFailover failover2 = buildNewFailover();
66 | assertThrows(ArithmeticException.class, () -> {
67 | ThrowableFunction func = i -> {
68 | int j = 0;
69 | System.out.println(j / 0);
70 | throw new TimeoutException();
71 | };
72 | failover2.supplyWithRetry(func);
73 | });
74 | }
75 |
76 | @Test
77 | void testRunRetry() {
78 | WeightFailover failover = buildNewFailover();
79 | for (int i = 0; i < 10; i++) {
80 | failover.runWithRetry(it -> {
81 | if (it == 1) {
82 | throw new IllegalStateException();
83 | }
84 | });
85 | }
86 | }
87 |
88 | private WeightFailover buildNewFailover() {
89 | Map weightMap = IntStream.range(1, 4).boxed()
90 | .collect(toMap(identity(), i -> 5));
91 | return WeightFailover. newGenericBuilder()
92 | .checker(it -> false, 1)
93 | .build(weightMap);
94 | }
95 |
96 | private String doSomething(Integer i) {
97 | if (i > 1) {
98 | throw new RuntimeException("failed");
99 | }
100 | return "r:" + i;
101 | }
102 |
103 | private String alwaysFail(Integer i) {
104 | if (i > 0) {
105 | throw new RuntimeException("failed");
106 | }
107 | return "r:" + i;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/test/java/com/github/phantomthief/failover/impl/benchmark/Group2PartitionFailover.java:
--------------------------------------------------------------------------------
1 | package com.github.phantomthief.failover.impl.benchmark;
2 |
3 | import org.openjdk.jmh.annotations.Benchmark;
4 | import org.openjdk.jmh.annotations.BenchmarkMode;
5 | import org.openjdk.jmh.annotations.Fork;
6 | import org.openjdk.jmh.annotations.Measurement;
7 | import org.openjdk.jmh.annotations.Mode;
8 | import org.openjdk.jmh.annotations.Param;
9 | import org.openjdk.jmh.annotations.Scope;
10 | import org.openjdk.jmh.annotations.Setup;
11 | import org.openjdk.jmh.annotations.State;
12 | import org.openjdk.jmh.annotations.Threads;
13 | import org.openjdk.jmh.annotations.Warmup;
14 | import org.openjdk.jmh.runner.Runner;
15 | import org.openjdk.jmh.runner.RunnerException;
16 | import org.openjdk.jmh.runner.options.Options;
17 | import org.openjdk.jmh.runner.options.OptionsBuilder;
18 |
19 | import com.github.phantomthief.failover.impl.PartitionFailover;
20 | import com.google.common.collect.ImmutableMap;
21 | import com.google.common.collect.ImmutableMap.Builder;
22 |
23 | /**
24 | * @author huangli
25 | * Created on 2020-01-21
26 | */
27 | @BenchmarkMode(Mode.Throughput)
28 | @Fork(1)
29 | @Threads(200)
30 | @Warmup(iterations = 1, time = 1)
31 | @Measurement(iterations = 2, time = 2)
32 | @State(Scope.Benchmark)
33 | public class Group2PartitionFailover {
34 |
35 | @Param({"1000"})
36 | private int totalSize;
37 |
38 | @Param({"5", "20"})
39 | private int coreSize;
40 |
41 | private static final int FAIL_RATE = 999;
42 |
43 | private PartitionFailover partitionFailover;
44 |
45 | private long count;
46 |
47 | @Setup
48 | public void init() {
49 | Builder builder = ImmutableMap.builder();
50 | for (int i = 0; i < totalSize; ++i) {
51 | builder.put("key" + i, 100);
52 | }
53 | partitionFailover = PartitionFailover. newBuilder()
54 | .checker(it -> true, 1)
55 | .corePartitionSize(coreSize)
56 | .failReduceRate(0.01)
57 | .successIncreaseRate(1.0)
58 | .build(builder.build());
59 | }
60 |
61 | @Benchmark
62 | public void getOneSuccess() {
63 | for (int i = 0; i < 1000; i++) {
64 | String one = partitionFailover.getOneAvailable();
65 | partitionFailover.success(one);
66 | }
67 | }
68 |
69 |
70 | @Benchmark
71 | public void getOneFail() {
72 | for (int i = 0; i < 1000; i++) {
73 | String one = partitionFailover.getOneAvailable();
74 | if (count++ % FAIL_RATE == 0) {
75 | partitionFailover.fail(one);
76 | } else {
77 | partitionFailover.success(one);
78 | }
79 | }
80 | }
81 |
82 | public static void main(String[] args) throws RunnerException {
83 | boolean useJmh = true;
84 | if (useJmh) {
85 | Options options = new OptionsBuilder()
86 | .include(Group2PartitionFailover.class.getSimpleName())
87 | .output(System.getProperty("user.home") + "/" + Group2PartitionFailover.class.getSimpleName()
88 | + ".txt")
89 | .build();
90 | new Runner(options).run();
91 | } else {
92 | Group2PartitionFailover obj = new Group2PartitionFailover();
93 | obj.totalSize = 1000;
94 | obj.coreSize = 5;
95 | obj.init();
96 | int loopCount = 5000_0000;
97 | for (int i = 0; i < 100000; i++) {
98 | obj.getOneSuccess();
99 | }
100 | long start = System.nanoTime();
101 | for (int i = 0; i < loopCount; i++) {
102 | obj.getOneSuccess();
103 | }
104 | long end = System.nanoTime();
105 | double seconds = (end - start) / 1000.0 / 1000.0 / 1000.0;
106 | System.out.printf("tps:%.2f", 1.0 * loopCount / seconds);
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/com/github/phantomthief/failover/impl/GenericWeightFailoverBuilder.java:
--------------------------------------------------------------------------------
1 | package com.github.phantomthief.failover.impl;
2 |
3 | import java.util.Collection;
4 | import java.util.Map;
5 | import java.util.concurrent.TimeUnit;
6 | import java.util.function.Consumer;
7 | import java.util.function.Predicate;
8 |
9 | import javax.annotation.CheckReturnValue;
10 | import javax.annotation.Nonnegative;
11 | import javax.annotation.Nonnull;
12 |
13 | import com.github.phantomthief.util.ThrowableFunction;
14 | import com.github.phantomthief.util.ThrowablePredicate;
15 |
16 | /**
17 | * @author w.vela
18 | */
19 | public class GenericWeightFailoverBuilder {
20 |
21 | private final WeightFailoverBuilder