@@ -63,7 +63,13 @@ private RawSuperStreamProducer(
6363 _config = config ;
6464 _streamInfos = streamInfos ;
6565 _clientParameters = clientParameters ;
66- _defaultRoutingConfiguration . RoutingStrategy = new HashRoutingMurmurStrategy ( _config . Routing ) ;
66+ _defaultRoutingConfiguration . RoutingStrategy = _config . RoutingStrategyType switch
67+ {
68+ RoutingStrategyType . Key => new KeyRoutingStrategy ( _config . Routing ,
69+ _config . Client . QueryRoute , _config . SuperStream ) ,
70+ RoutingStrategyType . Hash => new HashRoutingMurmurStrategy ( _config . Routing ) ,
71+ _ => new HashRoutingMurmurStrategy ( _config . Routing )
72+ } ;
6773 _logger = logger ?? NullLogger < RawSuperStreamProducer > . Instance ;
6874 }
6975
@@ -122,7 +128,8 @@ private RawProducerConfig FromStreamConfig(string stream)
122128 // The producer is created on demand when a message is sent to a stream
123129 private async Task < IProducer > InitProducer ( string stream )
124130 {
125- var p = await RawProducer . Create ( _clientParameters , FromStreamConfig ( stream ) , _streamInfos [ stream ] , _logger ) . ConfigureAwait ( false ) ;
131+ var p = await RawProducer . Create ( _clientParameters , FromStreamConfig ( stream ) , _streamInfos [ stream ] , _logger )
132+ . ConfigureAwait ( false ) ;
126133 _logger ? . LogDebug ( "Producer {ProducerReference} created for Stream {StreamIdentifier}" , _config . Reference ,
127134 stream ) ;
128135 return p ;
@@ -147,8 +154,16 @@ private async Task<IProducer> GetProducerForMessage(Message message)
147154 throw new ObjectDisposedException ( nameof ( RawSuperStreamProducer ) ) ;
148155 }
149156
150- var routes = _defaultRoutingConfiguration . RoutingStrategy . Route ( message ,
151- _streamInfos . Keys . ToList ( ) ) ;
157+ var routes = await _defaultRoutingConfiguration . RoutingStrategy . Route ( message ,
158+ _streamInfos . Keys . ToList ( ) ) . ConfigureAwait ( false ) ;
159+
160+ // we should always have a route
161+ // but in case of stream KEY the routing could not exist
162+ if ( routes is not { Count : > 0 } )
163+ {
164+ throw new RouteNotFoundException ( "No route found for the message to any stream" ) ;
165+ }
166+
152167 return await GetProducer ( routes [ 0 ] ) . ConfigureAwait ( false ) ;
153168 }
154169
@@ -263,6 +278,12 @@ public void Dispose()
263278 public int PendingCount => _producers . Sum ( x => x . Value . PendingCount ) ;
264279}
265280
281+ public enum RoutingStrategyType
282+ {
283+ Hash ,
284+ Key ,
285+ }
286+
266287public record RawSuperStreamProducerConfig : IProducerConfig
267288{
268289 public RawSuperStreamProducerConfig ( string superStream )
@@ -285,6 +306,8 @@ public RawSuperStreamProducerConfig(string superStream)
285306 public Func < Message , string > Routing { get ; set ; } = null ;
286307 public Action < ( string , Confirmation ) > ConfirmHandler { get ; set ; } = _ => { } ;
287308
309+ public RoutingStrategyType RoutingStrategyType { get ; set ; } = RoutingStrategyType . Hash ;
310+
288311 internal Client Client { get ; set ; }
289312}
290313
@@ -306,7 +329,7 @@ internal class DefaultRoutingConfiguration : IRoutingConfiguration
306329/// </summary>
307330public interface IRoutingStrategy
308331{
309- List < string > Route ( Message message , List < string > partitions ) ;
332+ Task < List < string > > Route ( Message message , List < string > partitions ) ;
310333}
311334
312335/// <summary>
@@ -322,16 +345,53 @@ public class HashRoutingMurmurStrategy : IRoutingStrategy
322345 private const int Seed = 104729 ; // must be the same to all the clients to be compatible
323346
324347 // Routing based on the Murmur hash function
325- public List < string > Route ( Message message , List < string > partitions )
348+ public Task < List < string > > Route ( Message message , List < string > partitions )
326349 {
327350 var key = _routingKeyExtractor ( message ) ;
328351 var hash = new Murmur32ManagedX86 ( Seed ) . ComputeHash ( Encoding . UTF8 . GetBytes ( key ) ) ;
329352 var index = BitConverter . ToUInt32 ( hash , 0 ) % ( uint ) partitions . Count ;
330- return new List < string > ( ) { partitions [ ( int ) index ] } ;
353+ var r = new List < string > ( ) { partitions [ ( int ) index ] } ;
354+ return Task . FromResult ( r ) ;
331355 }
332356
333357 public HashRoutingMurmurStrategy ( Func < Message , string > routingKeyExtractor )
334358 {
335359 _routingKeyExtractor = routingKeyExtractor ;
336360 }
337361}
362+
363+ /// <summary>
364+ /// KeyRoutingStrategy is a routing strategy that uses the routing key to route messages to streams.
365+ /// </summary>
366+ public class KeyRoutingStrategy : IRoutingStrategy
367+ {
368+ private readonly Func < Message , string > _routingKeyExtractor ;
369+ private readonly Func < string , string , Task < RouteQueryResponse > > _routingKeyQFunc ;
370+ private readonly string _superStream ;
371+ private readonly Dictionary < string , List < string > > _cacheStream = new ( ) ;
372+
373+ public async Task < List < string > > Route ( Message message , List < string > partitions )
374+ {
375+ var key = _routingKeyExtractor ( message ) ;
376+ // If the stream is already in the cache we return it
377+ // to avoid a query to the server for each send
378+ if ( _cacheStream . TryGetValue ( key , out var value ) )
379+ {
380+ return value ;
381+ }
382+
383+ var c = await _routingKeyQFunc ( _superStream , key ) . ConfigureAwait ( false ) ;
384+ _cacheStream [ key ] = c . Streams ;
385+ return ( from resultStream in c . Streams
386+ where partitions . Contains ( resultStream )
387+ select new List < string > ( ) { resultStream } ) . FirstOrDefault ( ) ;
388+ }
389+
390+ public KeyRoutingStrategy ( Func < Message , string > routingKeyExtractor ,
391+ Func < string , string , Task < RouteQueryResponse > > routingKeyQFunc , string superStream )
392+ {
393+ _routingKeyExtractor = routingKeyExtractor ;
394+ _routingKeyQFunc = routingKeyQFunc ;
395+ _superStream = superStream ;
396+ }
397+ }
0 commit comments