24
24
import com .rabbitmq .client .amqp .impl .Utils .StopWatch ;
25
25
import com .rabbitmq .client .amqp .metrics .MetricsCollector ;
26
26
import java .time .Duration ;
27
- import java .util .ArrayList ;
28
- import java .util .LinkedHashMap ;
29
- import java .util .List ;
30
- import java .util .Map ;
27
+ import java .util .*;
31
28
import java .util .concurrent .*;
32
29
import java .util .concurrent .atomic .AtomicBoolean ;
33
30
import java .util .concurrent .atomic .AtomicLong ;
34
31
import java .util .concurrent .atomic .AtomicReference ;
35
32
import java .util .function .BiConsumer ;
33
+ import java .util .function .Function ;
36
34
import java .util .function .Predicate ;
37
35
import java .util .function .Supplier ;
36
+ import java .util .stream .Collectors ;
38
37
import org .apache .qpid .protonj2 .client .ConnectionOptions ;
39
38
import org .apache .qpid .protonj2 .client .DisconnectionEvent ;
40
39
import org .apache .qpid .protonj2 .client .Session ;
@@ -57,6 +56,7 @@ final class AmqpConnection extends ResourceBase implements Connection {
57
56
private final AmqpManagement management ;
58
57
private volatile org .apache .qpid .protonj2 .client .Connection nativeConnection ;
59
58
private volatile Address connectionAddress ;
59
+ private volatile String connectionNodename ;
60
60
private final AtomicBoolean closed = new AtomicBoolean (false );
61
61
private volatile Session nativeSession ;
62
62
private final List <AmqpPublisher > publishers = new CopyOnWriteArrayList <>();
@@ -68,16 +68,15 @@ final class AmqpConnection extends ResourceBase implements Connection {
68
68
private final Future <?> recoveryLoop ;
69
69
private final BlockingQueue <Runnable > recoveryRequestQueue ;
70
70
private final AtomicBoolean recoveringConnection = new AtomicBoolean (false );
71
- private final DefaultConnectionSettings <?> connectionSettings =
72
- DefaultConnectionSettings . instance () ;
73
- private Supplier < SessionHandler > sessionHandlerSupplier ;
71
+ private final DefaultConnectionSettings <?> connectionSettings ;
72
+ private final Supplier < SessionHandler > sessionHandlerSupplier ;
73
+ private final ConnectionUtils . ConnectionAffinity affinity ;
74
74
75
75
AmqpConnection (AmqpConnectionBuilder builder ) {
76
76
super (builder .listeners ());
77
77
this .id = ID_SEQUENCE .getAndIncrement ();
78
78
this .environment = builder .environment ();
79
- builder .connectionSettings ().copyTo (this .connectionSettings );
80
- this .connectionSettings .consolidate ();
79
+ this .connectionSettings = builder .connectionSettings ().consolidate ();
81
80
this .sessionHandlerSupplier =
82
81
builder .isolateResources ()
83
82
? () -> new SessionHandler .SingleSessionSessionHandler (this )
@@ -116,8 +115,26 @@ final class AmqpConnection extends ResourceBase implements Connection {
116
115
this .recoveryRequestQueue = null ;
117
116
this .recoveryLoop = null ;
118
117
}
119
- this .nativeConnection = connect (this .connectionSettings , builder .name (), disconnectHandler );
118
+ this .affinity =
119
+ this .connectionSettings .affinity ().activated ()
120
+ ? new ConnectionUtils .ConnectionAffinity (
121
+ this .connectionSettings .affinity ().queue (),
122
+ this .connectionSettings .affinity ().operation ())
123
+ : null ;
124
+ NativeConnectionWrapper ncw =
125
+ connect (this .connectionSettings , builder .name (), disconnectHandler , null );
126
+ this .nativeConnection = ncw .connection ;
120
127
this .management = createManagement ();
128
+ ncw =
129
+ enforceAffinity (
130
+ ncw ,
131
+ addrs -> connect (this .connectionSettings , builder .name (), disconnectHandler , addrs ),
132
+ this .management ,
133
+ this .affinity ,
134
+ this .environment .affinityCache ());
135
+ this .connectionAddress = ncw .address ;
136
+ this .connectionNodename = ncw .nodename ;
137
+ this .nativeConnection = ncw .connection ;
121
138
this .state (OPEN );
122
139
this .environment .metricsCollector ().openConnection ();
123
140
}
@@ -133,7 +150,7 @@ Management managementNoCheck() {
133
150
return this .management ;
134
151
}
135
152
136
- protected AmqpManagement createManagement () {
153
+ AmqpManagement createManagement () {
137
154
return new AmqpManagement (
138
155
new AmqpManagementParameters (this ).topologyListener (this .topologyListener ));
139
156
}
@@ -167,11 +184,11 @@ public void close() {
167
184
168
185
// internal API
169
186
170
- private org . apache . qpid . protonj2 . client . Connection connect (
187
+ private NativeConnectionWrapper connect (
171
188
DefaultConnectionSettings <?> connectionSettings ,
172
189
String name ,
173
- BiConsumer <org .apache .qpid .protonj2 .client .Connection , DisconnectionEvent >
174
- disconnectHandler ) {
190
+ BiConsumer <org .apache .qpid .protonj2 .client .Connection , DisconnectionEvent > disconnectHandler ,
191
+ List < Address > addresses ) {
175
192
176
193
ConnectionOptions connectionOptions = new ConnectionOptions ();
177
194
if (connectionSettings .credentialsProvider () instanceof UsernamePasswordCredentialsProvider ) {
@@ -200,26 +217,69 @@ private org.apache.qpid.protonj2.client.Connection connect(
200
217
sslOptions .sslContextOverride (tlsSettings .sslContext ());
201
218
sslOptions .verifyHost (tlsSettings .isHostnameVerification ());
202
219
}
203
- this . connectionAddress = connectionSettings .selectAddress ();
220
+ Address address = connectionSettings .selectAddress (addresses );
204
221
StopWatch stopWatch = new StopWatch ();
205
222
try {
206
223
LOGGER .debug ("Connecting..." );
207
224
org .apache .qpid .protonj2 .client .Connection connection =
208
- this .environment
209
- .client ()
210
- .connect (
211
- this .connectionAddress .host (), this .connectionAddress .port (), connectionOptions );
225
+ this .environment .client ().connect (address .host (), address .port (), connectionOptions );
212
226
ExceptionUtils .wrapGet (connection .openFuture ());
213
227
LOGGER .debug ("Connection attempt succeeded" );
214
228
checkBrokerVersion (connection );
215
- return connection ;
229
+ return new NativeConnectionWrapper ( connection , extractNode ( connection ), address ) ;
216
230
} catch (ClientException e ) {
217
231
throw ExceptionUtils .convert (e );
218
232
} finally {
219
233
LOGGER .debug ("Connection attempt took {}" , stopWatch .stop ());
220
234
}
221
235
}
222
236
237
+ private static NativeConnectionWrapper enforceAffinity (
238
+ NativeConnectionWrapper connectionWrapper ,
239
+ Function <List <Address >, NativeConnectionWrapper > connectionFactory ,
240
+ AmqpManagement management ,
241
+ ConnectionUtils .ConnectionAffinity affinity ,
242
+ ConnectionUtils .AffinityCache affinityCache ) {
243
+ if (connectionWrapper .nodename == null || affinity == null ) {
244
+ return connectionWrapper ;
245
+ } else {
246
+ affinityCache .put (connectionWrapper .nodename , connectionWrapper .address );
247
+ try {
248
+ management .init ();
249
+ Management .QueueInfo info = management .queueInfo (affinity .queue ());
250
+ NativeConnectionWrapper pickedConnection = null ;
251
+ int attemptCount = 0 ;
252
+ while (pickedConnection == null && ++attemptCount <= 5 ) {
253
+ List <String > nodesWithAffinity = ConnectionUtils .findAffinity (affinity , info );
254
+ LOGGER .debug ("Currently connected to node {}" , connectionWrapper .nodename );
255
+ if (nodesWithAffinity .contains (connectionWrapper .nodename )) {
256
+ LOGGER .debug ("Affinity {} found with node {}" , affinity , connectionWrapper .nodename );
257
+ pickedConnection = connectionWrapper ;
258
+ } else {
259
+ LOGGER .debug (
260
+ "Affinity {} not found with node {}" , affinity , connectionWrapper .nodename );
261
+ connectionWrapper .connection .close ();
262
+ management .releaseResources ();
263
+ List <Address > addressHints =
264
+ nodesWithAffinity .stream ()
265
+ .map (affinityCache ::get )
266
+ .filter (Objects ::nonNull )
267
+ .collect (Collectors .toList ());
268
+ connectionWrapper = connectionFactory .apply (addressHints );
269
+ affinityCache .put (connectionWrapper .nodename , connectionWrapper .address );
270
+ }
271
+ }
272
+ return pickedConnection ;
273
+ } catch (Exception e ) {
274
+ LOGGER .warn (
275
+ "Cannot enforce affinity because of error when looking up queue '{}': {}" ,
276
+ affinity .queue (),
277
+ e .getMessage ());
278
+ return connectionWrapper ;
279
+ }
280
+ }
281
+ }
282
+
223
283
private static void checkBrokerVersion (org .apache .qpid .protonj2 .client .Connection connection )
224
284
throws ClientException {
225
285
String version = (String ) connection .properties ().get ("version" );
@@ -231,6 +291,15 @@ private static void checkBrokerVersion(org.apache.qpid.protonj2.client.Connectio
231
291
}
232
292
}
233
293
294
+ private static String extractNode (org .apache .qpid .protonj2 .client .Connection connection )
295
+ throws ClientException {
296
+ String node = (String ) connection .properties ().get ("node" );
297
+ if (node == null ) {
298
+ throw new AmqpException ("The broker node name is not available" );
299
+ }
300
+ return node ;
301
+ }
302
+
234
303
TopologyListener createTopologyListener (AmqpConnectionBuilder builder ) {
235
304
TopologyListener topologyListener ;
236
305
if (builder .recoveryConfiguration ().topology ()) {
@@ -354,15 +423,12 @@ private org.apache.qpid.protonj2.client.Connection recoverNativeConnection(
354
423
}
355
424
356
425
try {
357
- org .apache .qpid .protonj2 .client .Connection result =
358
- connect (this .connectionSettings , connectionName , disconnectedHandlerReference .get ());
359
- result .openFuture ().get ();
426
+ NativeConnectionWrapper result =
427
+ connect (
428
+ this .connectionSettings , connectionName , disconnectedHandlerReference .get (), null );
429
+ this .connectionAddress = result .address ;
360
430
LOGGER .debug ("Reconnected to {}" , this .currentConnectionLabel ());
361
- return result ;
362
- } catch (InterruptedException ex ) {
363
- Thread .currentThread ().interrupt ();
364
- LOGGER .info ("Thread interrupted while waiting for connection opening" );
365
- throw ex ;
431
+ return result .connection ;
366
432
} catch (Exception ex ) {
367
433
LOGGER .info ("Error while trying to recover connection" , ex );
368
434
if (!RECOVERY_PREDICATE .test (ex )) {
@@ -573,9 +639,22 @@ Address connectionAddress() {
573
639
return this .connectionAddress ;
574
640
}
575
641
642
+ String connectionNodename () {
643
+ return this .connectionNodename ;
644
+ }
645
+
646
+ ConnectionUtils .ConnectionAffinity affinity () {
647
+ return this .affinity ;
648
+ }
649
+
650
+ long id () {
651
+ return this .id ;
652
+ }
653
+
576
654
private void close (Throwable cause ) {
577
655
if (this .closed .compareAndSet (false , true )) {
578
656
this .state (CLOSING , cause );
657
+ this .environment .removeConnection (this );
579
658
if (this .recoveryLoop != null ) {
580
659
this .recoveryLoop .cancel (true );
581
660
}
@@ -613,4 +692,31 @@ private void close(Throwable cause) {
613
692
public String toString () {
614
693
return this .environment .toString () + "-" + this .id ;
615
694
}
695
+
696
+ private static class NativeConnectionWrapper {
697
+
698
+ private final org .apache .qpid .protonj2 .client .Connection connection ;
699
+ private final String nodename ;
700
+ private final Address address ;
701
+
702
+ private NativeConnectionWrapper (
703
+ org .apache .qpid .protonj2 .client .Connection connection , String nodename , Address address ) {
704
+ this .connection = connection ;
705
+ this .nodename = nodename ;
706
+ this .address = address ;
707
+ }
708
+ }
709
+
710
+ @ Override
711
+ public boolean equals (Object o ) {
712
+ if (this == o ) return true ;
713
+ if (o == null || getClass () != o .getClass ()) return false ;
714
+ AmqpConnection that = (AmqpConnection ) o ;
715
+ return id == that .id ;
716
+ }
717
+
718
+ @ Override
719
+ public int hashCode () {
720
+ return Objects .hashCode (id );
721
+ }
616
722
}
0 commit comments