|
17 | 17 | package io.asyncer.r2dbc.mysql;
|
18 | 18 |
|
19 | 19 | import io.asyncer.r2dbc.mysql.client.Client;
|
| 20 | +import io.asyncer.r2dbc.mysql.client.FailoverClient; |
| 21 | +import io.asyncer.r2dbc.mysql.client.ReactorNettyClient; |
20 | 22 | import io.asyncer.r2dbc.mysql.constant.ProtocolDriver;
|
21 | 23 | import io.asyncer.r2dbc.mysql.internal.NodeAddress;
|
22 | 24 | import io.asyncer.r2dbc.mysql.internal.util.InternalArrays;
|
23 |
| -import io.netty.channel.ChannelOption; |
24 | 25 | import io.netty.resolver.DefaultNameResolver;
|
25 | 26 | import io.netty.resolver.NameResolver;
|
26 | 27 | import io.netty.util.concurrent.Future;
|
27 |
| -import io.r2dbc.spi.R2dbcNonTransientResourceException; |
28 |
| -import org.jetbrains.annotations.Nullable; |
29 | 28 | import reactor.core.publisher.Flux;
|
30 | 29 | import reactor.core.publisher.Mono;
|
31 | 30 | import reactor.netty.resources.LoopResources;
|
32 |
| -import reactor.netty.tcp.TcpClient; |
33 | 31 | import reactor.netty.tcp.TcpResources;
|
34 | 32 |
|
35 | 33 | import java.net.InetAddress;
|
|
46 | 44 | */
|
47 | 45 | final class MultiHostsConnectionStrategy implements ConnectionStrategy {
|
48 | 46 |
|
49 |
| - private final Mono<Client> client; |
| 47 | + private final Mono<? extends Client> client; |
50 | 48 |
|
51 | 49 | MultiHostsConnectionStrategy(
|
52 |
| - TcpSocketConfiguration tcp, |
53 | 50 | MySqlConnectionConfiguration configuration,
|
54 |
| - boolean shuffle |
| 51 | + List<NodeAddress> addresses, |
| 52 | + ProtocolDriver driver, |
| 53 | + int retriesAllDown, |
| 54 | + boolean shuffle, |
| 55 | + boolean tcpKeepAlive, |
| 56 | + boolean tcpNoDelay |
55 | 57 | ) {
|
56 |
| - this.client = Mono.defer(() -> { |
57 |
| - if (ProtocolDriver.DNS_SRV.equals(tcp.getDriver())) { |
| 58 | + Mono<ReactorNettyClient> client = configuration.getCredential().flatMap(credential -> { |
| 59 | + if (ProtocolDriver.DNS_SRV.equals(driver)) { |
| 60 | + logger.debug("Resolve hosts via DNS SRV: {}", addresses); |
| 61 | + |
58 | 62 | LoopResources resources = configuration.getClient().getLoopResources();
|
59 | 63 | LoopResources loopResources = resources == null ? TcpResources.get() : resources;
|
60 |
| - |
61 |
| - return resolveAllHosts(loopResources, tcp.getAddresses(), shuffle) |
62 |
| - .flatMap(addresses -> connectHost(addresses, tcp, configuration, false, shuffle, 0)); |
| 64 | + InetConnectFunction login = new InetConnectFunction( |
| 65 | + false, |
| 66 | + tcpKeepAlive, |
| 67 | + tcpNoDelay, |
| 68 | + credential, |
| 69 | + configuration |
| 70 | + ); |
| 71 | + |
| 72 | + return resolveAllHosts(loopResources, addresses, shuffle).flatMap(addrs -> { |
| 73 | + logger.debug("Connect to multiple addresses: {}", addrs); |
| 74 | + |
| 75 | + return connectHost( |
| 76 | + addrs, |
| 77 | + login, |
| 78 | + shuffle, |
| 79 | + 0, |
| 80 | + retriesAllDown |
| 81 | + ); |
| 82 | + }); |
63 | 83 | } else {
|
64 |
| - List<NodeAddress> availableHosts = copyAvailableAddresses(tcp.getAddresses(), shuffle); |
| 84 | + List<NodeAddress> availableHosts = copyAvailableAddresses(addresses, shuffle); |
| 85 | + logger.debug("Connect to multiple hosts: {}", availableHosts); |
| 86 | + |
65 | 87 | int size = availableHosts.size();
|
66 |
| - InetSocketAddress[] addresses = new InetSocketAddress[availableHosts.size()]; |
| 88 | + InetSocketAddress[] array = new InetSocketAddress[availableHosts.size()]; |
67 | 89 |
|
68 | 90 | for (int i = 0; i < size; i++) {
|
69 |
| - NodeAddress address = availableHosts.get(i); |
70 |
| - addresses[i] = InetSocketAddress.createUnresolved(address.getHost(), address.getPort()); |
| 91 | + array[i] = availableHosts.get(i).toUnresolved(); |
71 | 92 | }
|
72 | 93 |
|
73 |
| - return connectHost(InternalArrays.asImmutableList(addresses), tcp, configuration, true, shuffle, 0); |
| 94 | + List<InetSocketAddress> addrs = InternalArrays.asImmutableList(array); |
| 95 | + InetConnectFunction login = new InetConnectFunction( |
| 96 | + true, |
| 97 | + tcpKeepAlive, |
| 98 | + tcpNoDelay, |
| 99 | + credential, |
| 100 | + configuration |
| 101 | + ); |
| 102 | + |
| 103 | + return connectHost( |
| 104 | + addrs, |
| 105 | + login, |
| 106 | + shuffle, |
| 107 | + 0, |
| 108 | + retriesAllDown |
| 109 | + ); |
74 | 110 | }
|
75 | 111 | });
|
| 112 | + |
| 113 | + this.client = client.map(c -> new FailoverClient(c, client)); |
76 | 114 | }
|
77 | 115 |
|
78 | 116 | @Override
|
79 |
| - public Mono<Client> connect() { |
| 117 | + public Mono<? extends Client> connect() { |
80 | 118 | return client;
|
81 | 119 | }
|
82 | 120 |
|
83 |
| - private Mono<Client> connectHost( |
| 121 | + private static Mono<ReactorNettyClient> connectHost( |
84 | 122 | List<InetSocketAddress> addresses,
|
85 |
| - TcpSocketConfiguration tcp, |
86 |
| - MySqlConnectionConfiguration configuration, |
87 |
| - boolean balancedDns, |
| 123 | + InetConnectFunction login, |
88 | 124 | boolean shuffle,
|
89 |
| - int attempts |
| 125 | + int attempts, |
| 126 | + int maxAttempts |
90 | 127 | ) {
|
91 | 128 | Iterator<InetSocketAddress> iter = addresses.iterator();
|
92 | 129 |
|
93 | 130 | if (!iter.hasNext()) {
|
94 |
| - return Mono.error(fail("Fail to establish connection: no available host", null)); |
| 131 | + return Mono.error(ConnectionStrategy.retryFail("Fail to establish connection: no available host", null)); |
95 | 132 | }
|
96 | 133 |
|
97 |
| - return attemptConnect(iter.next(), tcp, configuration, balancedDns).onErrorResume(t -> |
98 |
| - resumeConnect(t, addresses, iter, tcp, configuration, balancedDns, shuffle, attempts)); |
| 134 | + InetSocketAddress address = iter.next(); |
| 135 | + |
| 136 | + return login.apply(() -> address).onErrorResume(error -> resumeConnect( |
| 137 | + error, |
| 138 | + address, |
| 139 | + addresses, |
| 140 | + iter, |
| 141 | + login, |
| 142 | + shuffle, |
| 143 | + attempts, |
| 144 | + maxAttempts |
| 145 | + )); |
99 | 146 | }
|
100 | 147 |
|
101 |
| - private Mono<Client> resumeConnect( |
| 148 | + private static Mono<ReactorNettyClient> resumeConnect( |
102 | 149 | Throwable t,
|
| 150 | + InetSocketAddress failed, |
103 | 151 | List<InetSocketAddress> addresses,
|
104 | 152 | Iterator<InetSocketAddress> iter,
|
105 |
| - TcpSocketConfiguration tcp, |
106 |
| - MySqlConnectionConfiguration configuration, |
107 |
| - boolean balancedDns, |
| 153 | + InetConnectFunction login, |
108 | 154 | boolean shuffle,
|
109 |
| - int attempts |
| 155 | + int attempts, |
| 156 | + int maxAttempts |
110 | 157 | ) {
|
| 158 | + logger.warn("Fail to connect to {}", failed, t); |
| 159 | + |
111 | 160 | if (!iter.hasNext()) {
|
112 | 161 | // The last host failed to connect
|
113 |
| - if (attempts >= tcp.getRetriesAllDown()) { |
114 |
| - return Mono.error(fail( |
115 |
| - "Fail to establish connection, retried " + attempts + " times: " + t.getMessage(), t)); |
| 162 | + if (attempts >= maxAttempts) { |
| 163 | + return Mono.error(ConnectionStrategy.retryFail( |
| 164 | + "Fail to establish connections, retried " + attempts + " times", t)); |
116 | 165 | }
|
117 | 166 |
|
118 |
| - logger.warn("All hosts failed to establish connections, auto-try again after 250ms."); |
| 167 | + logger.warn("All hosts failed to establish connections, auto-try again after 250ms.", t); |
119 | 168 |
|
120 | 169 | // Ignore waiting error, e.g. interrupted, scheduler rejected
|
121 | 170 | return Mono.delay(Duration.ofMillis(250))
|
122 | 171 | .onErrorComplete()
|
123 |
| - .then(Mono.defer(() -> connectHost(addresses, tcp, configuration, balancedDns, shuffle, attempts + 1))); |
| 172 | + .then(Mono.defer(() -> connectHost( |
| 173 | + addresses, |
| 174 | + login, |
| 175 | + shuffle, |
| 176 | + attempts + 1, |
| 177 | + maxAttempts |
| 178 | + ))); |
124 | 179 | }
|
125 | 180 |
|
126 |
| - return attemptConnect(iter.next(), tcp, configuration, balancedDns).onErrorResume(tt -> |
127 |
| - resumeConnect(tt, addresses, iter, tcp, configuration, balancedDns, shuffle, attempts)); |
128 |
| - } |
129 |
| - |
130 |
| - private Mono<Client> attemptConnect( |
131 |
| - InetSocketAddress address, |
132 |
| - TcpSocketConfiguration tcp, |
133 |
| - MySqlConnectionConfiguration configuration, |
134 |
| - boolean balancedDns |
135 |
| - ) { |
136 |
| - return configuration.getCredential().flatMap(credential -> { |
137 |
| - TcpClient tcpClient = ConnectionStrategy.createTcpClient(configuration.getClient(), balancedDns) |
138 |
| - .option(ChannelOption.SO_KEEPALIVE, tcp.isTcpKeepAlive()) |
139 |
| - .option(ChannelOption.TCP_NODELAY, tcp.isTcpNoDelay()) |
140 |
| - .remoteAddress(() -> address); |
141 |
| - |
142 |
| - return ConnectionStrategy.login(tcpClient, credential, configuration); |
143 |
| - }).doOnError(e -> logger.warn("Fail to connect: ", e)); |
| 181 | + InetSocketAddress address = iter.next(); |
| 182 | + |
| 183 | + return login.apply(() -> address).onErrorResume(error -> resumeConnect( |
| 184 | + error, |
| 185 | + address, |
| 186 | + addresses, |
| 187 | + iter, |
| 188 | + login, |
| 189 | + shuffle, |
| 190 | + attempts, |
| 191 | + maxAttempts |
| 192 | + )); |
144 | 193 | }
|
145 | 194 |
|
146 | 195 | private static Mono<List<InetSocketAddress>> resolveAllHosts(
|
@@ -199,13 +248,4 @@ private static List<NodeAddress> copyAvailableAddresses(List<NodeAddress> addres
|
199 | 248 |
|
200 | 249 | return InternalArrays.asImmutableList(addresses.toArray(new NodeAddress[0]));
|
201 | 250 | }
|
202 |
| - |
203 |
| - private static R2dbcNonTransientResourceException fail(String message, @Nullable Throwable cause) { |
204 |
| - return new R2dbcNonTransientResourceException( |
205 |
| - message, |
206 |
| - "H1000", |
207 |
| - 9000, |
208 |
| - cause |
209 |
| - ); |
210 |
| - } |
211 | 251 | }
|
0 commit comments