Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 4 additions & 20 deletions docs/articles/clustering/cluster-metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,32 +234,16 @@ akka.cluster.metrics {
}
}

# Cluster metrics extension serializers and routers.
# Router short names for the metrics routers. This mapping ships in Akka.NET core's akka.conf,
# not in the Akka.Cluster.Metrics reference config.
akka.actor {

# Protobuf serializer for remote cluster metrics messages.
serializers {
akka-cluster-metrics = "Akka.Cluster.Metrics.Serialization.ClusterMetricsMessageSerializer, Akka.Cluster.Metrics"
}

# Interface binding for remote cluster metrics messages.
serialization-bindings {
"Akka.Cluster.Metrics.Serialization.MetricsGossipEnvelope, Akka.Cluster.Metrics" = akka-cluster-metrics
"Akka.Cluster.Metrics.AdaptiveLoadBalancingPool, Akka.Cluster.Metrics" = akka-cluster-metrics
"Akka.Cluster.Metrics.MixMetricsSelector, Akka.Cluster.Metrics" = akka-cluster-metrics
"Akka.Cluster.Metrics.CpuMetricsSelector, Akka.Cluster.Metrics" = akka-cluster-metrics
"Akka.Cluster.Metrics.MemoryMetricsSelector, Akka.Cluster.Metrics" = akka-cluster-metrics
}

# Globally unique metrics extension serializer identifier.
serialization-identifiers {
"Akka.Cluster.Metrics.Serialization.ClusterMetricsMessageSerializer, Akka.Cluster.Metrics" = 10
}

# Provide routing of messages based on cluster metrics.
router.type-mapping {
cluster-metrics-adaptive-pool = "Akka.Cluster.Metrics.AdaptiveLoadBalancingPool, Akka.Cluster.Metrics"
cluster-metrics-adaptive-group = "Akka.Cluster.Metrics.AdaptiveLoadBalancingGroup, Akka.Cluster.Metrics"
}
}
```

The metrics serializer (alias `akka-cluster-metrics`, id 10) and its bindings register from code when Akka.Cluster.Metrics is deployed, so no `serializers`, `serialization-bindings` or `serialization-identifiers` rows appear in the configuration. Your own HOCON can still override the alias or a binding.
46 changes: 19 additions & 27 deletions docs/articles/serialization/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ subtype of the other, a warning will be issued.

Akka.NET provides serializers for POCO's (Plain Old C# Objects) and for `Google.Protobuf.IMessage` by default, so you don't usually need to add configuration for that.

#### Built-in Serializers Register From Code

Akka.NET registers the serializers, bindings and serializer ids of its own modules from code: Akka.Remote, Akka.Cluster, Akka.Cluster.Tools, Akka.Cluster.Sharding, Akka.DistributedData, Akka.Cluster.Metrics, Akka.Persistence and Akka.Streams. A module's serializers are in place as soon as its assembly is deployed with your application, with no HOCON row behind them. These modules no longer put rows for them under `akka.actor.serializers`, `akka.actor.serialization-bindings` or `akka.actor.serialization-identifiers`, so you will not find those rows in `ActorSystem.Settings.Config`. The serializers in core, `json` and `bytes`, also declare their ids in code. Core still ships its own rows for them in `akka.conf`.

To find out which serializer handles a type, ask the `Serialization` extension (`FindSerializerForType`, `FindSerializerFor`) instead of reading the configuration.

You can still change them. A row of your own under `akka.actor.serializers` replaces a built-in alias, and a row under `akka.actor.serialization-bindings` rebinds a built-in type. Both win over the built-in default, and a `SerializationSetup` wins over both.

### Configuring Serialization Bindings Programmatically

As of Akka.NET v1.4 it is now possible to bind serializers to their target types programmatically using the [`SerializationSetup` class](xref:Akka.Serialization.SerializationSetup).
Expand Down Expand Up @@ -186,41 +194,25 @@ The only thing left to do for this class would be to fill in the serialization l
Afterwards the configuration would need to be updated to reflect which name to bind to and the classes that use this
serializer.

### Overriding Default Serializer Ids

> [!WARNING]
> Changing the identification does not change the serialization binding, it only change the identification used by the serializer when it serialize a message. In fact, depending on the code, this might actually break the serializer as it might expect a specific identifier. You have been warned.

Generally, overriding a default serializer identification is not recommended. The more recommended way is to change the serialization binding as was done when we [replace the default serializer with Hyperion](xref:serialization#how-to-setup-hyperion-as-the-default-serializer).
### Serializer Ids

In the rare case where you do need to override them, you can do it in one of two ways:
Serializer ids are part of the wire format: two nodes that disagree about an id cannot read each other's messages. Ids from 0 to 40 are reserved for Akka.NET.

* Overriding the `Identifier` property in your custom serializer class that inherits from the `Akka.Serialization.Serializer` abstract class. The `Identifier` property will override any HOCON settings; in fact, you will get a warning in your log when you do that, reminding you that you actually did that.
* Overriding the identifier inside your HOCON settings. To do this, you have to both change the original default serializer id and declare your own serializer using the original default serializer id.
Every serializer Akka.NET ships declares its id in code: `json` and `bytes` in core, and the serializers of Akka.Remote, Akka.Cluster and the other first-party modules. Nothing in HOCON changes them, so there is nothing to override. To change which serializer handles a type, change the serialization binding instead, as when we [replace the default serializer with Hyperion](xref:serialization#how-to-setup-hyperion-as-the-default-serializer).

```c#
serialization-identifiers {
"Akka.Serialization.NewtonSoftJsonSerializer, Akka" : 1000001
"MyAssembly.MyDefaultSerializer, SomeAssembly" : 1
}
```
A custom serializer gets its id one of two ways:

this would in effect results this final HOCON settings:
* It overrides the `Identifier` property. This is the simplest way, and it wins over any HOCON row.
* It leaves `Identifier` alone, and Akka.NET reads the id from `akka.actor.serialization-identifiers`, using the serializer's own type name as the key:

```c#
serialization-identifiers : {
"Akka.Serialization.ByteArraySerializer, Akka" : 4
"Akka.Serialization.NewtonSoftJsonSerializer, Akka" : 1000001
"Akka.Remote.Serialization.ProtobufSerializer, Akka.Remote" : 2
"Akka.Remote.Serialization.DaemonMsgCreateSerializer, Akka.Remote" : 3
"Akka.Remote.Serialization.MessageContainerSerializer, Akka.Remote" : 6
"Akka.Remote.Serialization.MiscMessageSerializer, Akka.Remote" : 16
"Akka.Remote.Serialization.PrimitiveSerializers, Akka.Remote" : 17
"Akka.Remote.Serialization.SystemMessageSerializer, Akka.Remote" : 22
"MyAssembly.MyDefaultSerializer, SomeAssembly" : 1
```hocon
akka.actor.serialization-identifiers {
"MyAssembly.MySerializer, MyAssembly" = 1001
}
```

This applies to a subclass of a built-in serializer too. A subclass of `NewtonSoftJsonSerializer` reads its own row from `akka.actor.serialization-identifiers`, never the row of its base type, so give it an id that no other serializer uses. See [Built-in Serializers Register From Code](#built-in-serializers-register-from-code).

### Programmatically Change NewtonSoft JSON Serializer Settings

You can change the JSON serializer behavior by using the `NewtonSoftJsonSerializerSetup` class to programmatically
Expand Down
16 changes: 7 additions & 9 deletions docs/articles/serialization/source-generated-serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,25 +149,23 @@ serializer. See
A generated serializer derives from `AkkaSerializer`, then `SerializerV2`, then `Serializer`. So it
satisfies Akka's classic reflection contract: a public constructor that takes
`ExtendedActorSystem`, and a stable `Identifier`. You can register it exactly like any hand-written
serializer, through the classic `akka.actor.serializers` and `serialization-bindings` HOCON blocks.
Akka.Remote registers its own generated Artery control-message serializer this same way today, in
`Remote.conf`:
serializer, through the classic `akka.actor.serializers` and `serialization-bindings` HOCON blocks:

```hocon
akka.actor {
serializers {
artery-control = "Akka.Remote.Artery.ArteryControlMessageSerializer, Akka.Remote"
order-benchmark = "MyApp.OrderBenchmarkSerializer, MyApp"
}
serialization-bindings {
"Akka.Remote.Artery.IArteryControlMessage, Akka.Remote" = artery-control
}
serialization-identifiers {
"Akka.Remote.Artery.ArteryControlMessageSerializer, Akka.Remote" = 23
"MyApp.IOrderBenchmarkProtocol, MyApp" = order-benchmark
}
}
```

Nothing about this is generator-specific. It is the same three HOCON blocks the
The generated serializer emits its own `Identifier` in code, from the id in `[AkkaSerializer<T>]`, so
it needs no `akka.actor.serialization-identifiers` row. A row for it would be ignored.

Nothing about this is generator-specific. It is the same two HOCON blocks the
[classic serializer documentation](xref:serialization#configuration) already describes. Use
`CreateRegistration()` and `CreateSetup()` when you want AOT-safe, compiler-checked composition.
Use HOCON when you follow an existing configuration-driven setup.
Expand Down
4 changes: 3 additions & 1 deletion docs/cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@
"DeadLetter",
"Blocksharp",
"Akkatecture",
"SharpPulsar"
"SharpPulsar",
"remoted",
"blocklist"
],
"ignoreRegExpList": [
"\\((.*)\\)", // Markdown links
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using Akka.Actor;
using Akka.Benchmarks.Configurations;
using Akka.Cluster;
using Akka.Configuration;
using Akka.DistributedData;
using Akka.DistributedData.Serialization;
using Akka.Serialization;
Expand Down Expand Up @@ -50,18 +49,8 @@ public void SetupSystem()
BindingFlags.Instance | BindingFlags.NonPublic)
.MakeGenericMethod(new []{KeyType,ValueType})
.Invoke(this, new object[]{});
var conf = ConfigurationFactory.ParseString(@"akka.actor {
serializers {
akka-replicated-data = ""Akka.DistributedData.Serialization.ReplicatedDataSerializer, Akka.DistributedData""
}
serialization-bindings {
""Akka.DistributedData.IReplicatedDataSerialization, Akka.DistributedData"" = akka-replicated-data
}
serialization-identifiers {
""Akka.DistributedData.Serialization.ReplicatedDataSerializer, Akka.DistributedData"" = 11
}
}");
sys = ActorSystem.Create("rddsb", conf);
// Akka.DistributedData registers its serializer from code; no HOCON rows are needed
sys = ActorSystem.Create("rddsb");
ser = sys.Serialization.FindSerializerForType(
typeof(IReplicatedDataSerialization));
_c1Ser = ser.ToBinary(_c1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using Akka.Actor;
using Akka.Benchmarks.Configurations;
using Akka.Cluster;
using Akka.Configuration;
using Akka.DistributedData;
using Akka.DistributedData.Serialization;
using Akka.Serialization;
Expand Down Expand Up @@ -59,18 +58,8 @@ public void SetupSystem()
_c1 = _c1.SetItem(node, new RDDBenchTypes.TestKey(j), _elements);
j++;
}
var conf = ConfigurationFactory.ParseString(@"akka.actor {
serializers {
akka-replicated-data = ""Akka.DistributedData.Serialization.ReplicatedDataSerializer, Akka.DistributedData""
}
serialization-bindings {
""Akka.DistributedData.IReplicatedDataSerialization, Akka.DistributedData"" = akka-replicated-data
}
serialization-identifiers {
""Akka.DistributedData.Serialization.ReplicatedDataSerializer, Akka.DistributedData"" = 11
}
}");
sys = ActorSystem.Create("rddsb", conf);
// Akka.DistributedData registers its serializer from code; no HOCON rows are needed
sys = ActorSystem.Create("rddsb");
ser = sys.Serialization.FindSerializerForType(
typeof(IReplicatedDataSerialization));
_c1Ser = ser.ToBinary(_c1);
Expand Down
15 changes: 2 additions & 13 deletions src/benchmark/Akka.Benchmarks/DData/SerializerORSetBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using Akka.Actor.Setup;
using Akka.Benchmarks.Configurations;
using Akka.Cluster;
using Akka.Configuration;
using Akka.DistributedData;
using Akka.DistributedData.Serialization;
using Akka.Serialization;
Expand Down Expand Up @@ -58,18 +57,8 @@ public void SetupSystem()
_c1 = _c1.Add(node, _elements.ToList());
}

var conf = ConfigurationFactory.ParseString(@"akka.actor {
serializers {
akka-replicated-data = ""Akka.DistributedData.Serialization.ReplicatedDataSerializer, Akka.DistributedData""
}
serialization-bindings {
""Akka.DistributedData.IReplicatedDataSerialization, Akka.DistributedData"" = akka-replicated-data
}
serialization-identifiers {
""Akka.DistributedData.Serialization.ReplicatedDataSerializer, Akka.DistributedData"" = 11
}
}");
sys = ActorSystem.Create("rddsb", conf);
// Akka.DistributedData registers its serializer from code; no HOCON rows are needed
sys = ActorSystem.Create("rddsb");
ser = sys.Serialization.FindSerializerForType(
typeof(IReplicatedDataSerialization));
_c1Ser = ser.ToBinary(_c1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#nullable enable
using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Cluster.Metrics.Serialization;
Expand All @@ -28,29 +29,31 @@ public sealed class DynamicTypeLoadingCollection
}

/// <summary>
/// Keeps <see cref="MetricsSerializers"/> in sync with Akka.Cluster.Metrics' reference.conf. The shared checks
/// live in <see cref="ModuleSerializerSpecs"/>.
/// Checks how <see cref="MetricsSerializers"/> behaves in a running system. What the table contains is approved in
/// Akka.API.Tests (<c>SerializerTableSpec</c>). The shared checks live in <see cref="ModuleSerializerSpecs"/>.
/// </summary>
[Collection(DynamicTypeLoadingCollection.Name)]
public class MetricsSerializersSpec : AkkaSpec
{
private static readonly Config MetricsRows = ClusterMetrics.DefaultConfig();

private static readonly Type[] BoundSamples =
{
typeof(MetricsGossipEnvelope), typeof(AdaptiveLoadBalancingPool), typeof(MixMetricsSelector),
typeof(CpuMetricsSelector), typeof(MemoryMetricsSelector)
};

public MetricsSerializersSpec(ITestOutputHelper output) : base(MetricsRows, output)
public MetricsSerializersSpec(ITestOutputHelper output) : base(ClusterMetrics.DefaultConfig(), output)
{
}

[Fact(DisplayName = "MetricsSerializers should list no serializer or type that reference.conf does not")]
public void Should_have_a_reference_conf_row_When_the_table_lists_a_type()
private ImmutableHashSet<SerializerDetails> Table => new MetricsSerializers().Create((ExtendedActorSystem)Sys);

[Fact(DisplayName = "Serialization should resolve every Metrics table entry on a plain system, with dynamic type loading on and off")]
public async Task Should_resolve_the_table_When_a_plain_system_has_no_Metrics_rows()
{
var table = new MetricsSerializers();
ModuleSerializerSpecs.AssertTableMatchesConfig(MetricsRows, table.Create((ExtendedActorSystem)Sys));
// core's module map names Akka.Cluster.Metrics, and MetricsSerializers is what it loads for it
ModuleSerializerTable.Default.ForAssembly("Akka.Cluster.Metrics").Should().NotBeNull();

await ModuleSerializerSpecs.AssertPlainSystemResolvesTable("metrics-plain", Table, (s, id) => s.GetSerializerById(id));
}

[Fact(DisplayName = "MetricsSerializers should build without throwing on a system that never loaded its reference.conf")]
Expand All @@ -59,15 +62,21 @@ public async Task Should_build_without_throwing_When_its_config_is_absent()

[Fact(DisplayName = "Serialization should resolve reference.conf rows spelled as Akka.Hosting writes them when dynamic type loading is off")]
public async Task Should_resolve_assembly_qualified_names_When_dynamic_type_loading_is_disabled()
=> await ModuleSerializerSpecs.AssertHostingSpellingResolves("metrics-aqn", MetricsRows, Sys);
=> await ModuleSerializerSpecs.AssertHostingSpellingResolves("metrics-aqn", Table, Sys);

[Fact(DisplayName = "Serialization should let application.conf override a reference.conf row when dynamic type loading is off")]
public async Task Should_honor_an_application_override_When_it_rebinds_a_Metrics_type()
/// <remarks>
/// With the rows present, the config is one an application copied from 1.5; without them, it is the
/// shipped one. Either way the application's own row beats the module default.
/// </remarks>
[Theory(DisplayName = "Serialization should let application.conf override a built-in Metrics binding when dynamic type loading is off")]
[InlineData(false)]
[InlineData(true)]
public async Task Should_honor_an_application_override_When_it_rebinds_a_Metrics_type(bool withCopiedRows)
{
var overrides = ConfigurationFactory.ParseString(
@"akka.actor.serialization-bindings { ""Akka.Cluster.Metrics.Serialization.MetricsGossipEnvelope, Akka.Cluster.Metrics"" = bytes }");

await ModuleSerializerSpecs.WithSystem("metrics-override", overrides, MetricsRows, system =>
await ModuleSerializerSpecs.WithSystem("metrics-override", overrides, withCopiedRows ? ModuleSerializerSpecs.RowsOf(Table) : null, system =>
{
var serialization = ModuleSerializerSpecs.BuildDefault(system, dynamicTypeLoading: false);

Expand All @@ -76,6 +85,10 @@ await ModuleSerializerSpecs.WithSystem("metrics-override", overrides, MetricsRow
});
}

[Fact(DisplayName = "Serialization should let application.conf replace the built-in Metrics alias when dynamic type loading is off")]
public async Task Should_honor_an_alias_override_When_it_replaces_the_Metrics_alias()
=> await ModuleSerializerSpecs.AssertAliasOverrideWins("metrics-alias-override", "akka-cluster-metrics", BoundSamples);

[Fact(DisplayName = "Serialization should build ClusterMetricsMessageSerializer under id 10, without a warning, when dynamic type loading is off")]
public async Task Should_keep_the_metrics_serializer_id_When_dynamic_type_loading_is_disabled()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
namespace Akka.Cluster.Metrics.Serialization
{
/// <summary>
/// INTERNAL API. The serializer and binding rows of Akka.Cluster.Metrics' reference.conf, so they resolve without reflection.
/// INTERNAL API. Akka.Cluster.Metrics' serializers, bindings and ids. They register as defaults when the module is deployed; its reference.conf carries no rows for them.
/// </summary>
internal sealed class MetricsSerializers : ModuleSerializers
{
Expand Down
20 changes: 1 addition & 19 deletions src/contrib/cluster/Akka.Cluster.Metrics/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,4 @@ akka.cluster.metrics {
}
}

# Cluster metrics extension serializers and routers.
akka.actor {
# Protobuf serializer for remote cluster metrics messages.
serializers {
akka-cluster-metrics = "Akka.Cluster.Metrics.Serialization.ClusterMetricsMessageSerializer, Akka.Cluster.Metrics"
}
# Interface binding for remote cluster metrics messages.
serialization-bindings {
"Akka.Cluster.Metrics.Serialization.MetricsGossipEnvelope, Akka.Cluster.Metrics" = akka-cluster-metrics
"Akka.Cluster.Metrics.AdaptiveLoadBalancingPool, Akka.Cluster.Metrics" = akka-cluster-metrics
"Akka.Cluster.Metrics.MixMetricsSelector, Akka.Cluster.Metrics" = akka-cluster-metrics
"Akka.Cluster.Metrics.CpuMetricsSelector, Akka.Cluster.Metrics" = akka-cluster-metrics
"Akka.Cluster.Metrics.MemoryMetricsSelector, Akka.Cluster.Metrics" = akka-cluster-metrics
}
# Globally unique metrics extension serializer identifier.
serialization-identifiers {
"Akka.Cluster.Metrics.Serialization.ClusterMetricsMessageSerializer, Akka.Cluster.Metrics" = 10
}
}
# The metrics serializer and its bindings register from code (MetricsSerializers), not from this file.
Loading
Loading