Clean up docs

This commit is contained in:
Viktor Lofgren 2024-02-22 19:53:20 +01:00
parent 8d4ef982d0
commit 6357d30ea0
2 changed files with 95 additions and 37 deletions

View File

@ -6,22 +6,20 @@ and managing connections between them.
## Service Registry
The service registry is a class that keeps track of the services
that are currently running, and their connection information.
that are currently running, and their connection information.
There are two implementations:
The service register implementation is based on [Zookeeper](https://zookeeper.apache.org/),
which is a distributed coordination service. This lets services register
themselves and announce their liveness, and then discover each other.
* A simple implementation that effectively hard-codes the services.
This is sufficient when running in docker, although loses some
smart features. It is fundamentally incompatible with running
the system bare-metal, and does not permit multiple instances
of a service to run.
It supports multiple instances of a service running, and
supports running the system bare-metal, where it will assign
ports to the services from a range.
* A more advanced implementation that is based on [Zookeeper](https://zookeeper.apache.org/),
which is a distributed coordination service. This implementation
lets services register themselves and announce their liveness,
and then discover each other. It supports multiple instances of
a service running, and supports running the system bare-metal,
where it will assign ports to the services from a range.
* REST services are registered on a per-node basis, and are always non-partitioned.
* gRPC services are registered on a per-api basis, and can be partitioned
or non-partitioned. This means that if a gRPC api is moved between nodes,
the clients will not need to be reconfigured.
To be discoverable, the caller must first register their
services:
@ -30,24 +28,25 @@ services:
// Register one or more services
serviceRegistry.registerService(
ServiceKey.forRest(serviceId, nodeId),
instanceUuid, // unique
externalAddress); // bind-address
instanceUuid, // unique
externalAddress); // bind-address
// Non-partitioned GRPC service
serviceRegistry.registerService(
serviceRegistry.registerService(
ServiceKey.forServiceDescriptor(descriptor, ServicePartition.any()),
instanceUuid,
externalAddress);
instanceUuid,
externalAddress);
// Partitioned GRPC service
serviceRegistry.registerService(
serviceRegistry.registerService(
ServiceKey.forServiceDescriptor(descriptor, ServicePartition.partition(5)),
instanceUuid,
externalAddress);
instanceUuid,
externalAddress);
// (+ any other services)
```
Then, the caller must announce their instance. Before this is done,
Then, the caller must announce their instance. Before this is done,
the service is not discoverable.
```java
@ -55,7 +54,7 @@ registry.announceInstance(instanceUUID);
```
All of this is done automatically by the `Service` base class
in the [service](../service/) module.
in the [service](../service/) module.
To discover a service, the caller can query the registry:
@ -67,18 +66,9 @@ It's also possible to subscribe to changes in the registry, so that
the caller can be notified when a service comes or goes, with `registry.registerMonitor()`.
However the `GrpcChannelPoolFactory` is a more convenient way to access the services,
it will let the caller create a pool of channels to the services, and manage their
it will let the caller create a pool of channels to the services, and manage their
lifecycle, listen to lifecycle notifications and so on.
The ChannelPools exist in two flavors, one for partitioned services, and one for non-partitioned services.
### Central Classes
* [ServiceRegistryIf](src/main/java/nu/marginalia/service/discovery/ServiceRegistryIf.java)
* [ZkServiceRegistry](src/main/java/nu/marginalia/service/discovery/ZkServiceRegistry.java)
* [FixedServiceRegistry](src/main/java/nu/marginalia/service/discovery/FixedServiceRegistry.java)
## gRPC Channel Pool
From the [GrpcChannelPoolFactory](src/main/java/nu/marginalia/service/client/GrpcChannelPoolFactory.java), two types of channel pools can be created
@ -86,8 +76,74 @@ that are aware of the service registry:
* [GrpcMultiNodeChannelPool](src/main/java/nu/marginalia/service/client/GrpcMultiNodeChannelPool.java) - This pool permits 1-n style communication with partitioned services
* [GrpcSingleNodeChannelPool](src/main/java/nu/marginalia/service/client/GrpcSingleNodeChannelPool.java) - This pool permits 1-1 style communication with non-partitioned services.
if multiple instances are running, it will use one of them and fall back
to another if the first is not available.
if multiple instances are running, it will use one of them and fall back
to another if the first is not available.
The pools manage the lifecycle of the gRPC channels, and will permit the caller
to access Stub interfaces for the services.
The pools can generate calls to the gRPC services, and will manage the lifecycle of the channels.
The API is designed to be simple to use, and will permit the caller to access the Stub interfaces
for the services through a fluent API.
### Example Usage of the GrpcSingleNodeChannelPool
```java
// create a pool for a non-partitioned service
channelPool = factory.createSingle(
ServiceKey.forGrpcApi(MathApiGrpc.class, ServicePartition.any()),
MathApiGrpc::newBlockingStub);
// blocking call
Response response = channelPool
.call(MathApiGrpc.MathApiBlockingStub::dictionaryLookup)
.run(request);
// sequential blocking calls
List<Response> response = channelPool
.call(MathApiGrpc.MathApiBlockingStub::dictionaryLookup)
.runFor(request1, request2);
// async call
Future<Response> response = channelPool
.call(MathApiGrpc.MathApiBlockingStub::dictionaryLookup)
.async(myExecutor)
.run(request);
// multiple async calls
Future<List<Response>> response = channelPool
.call(MathApiGrpc.MathApiBlockingStub::dictionaryLookup)
.async(myExecutor)
.runFor(request1, request2);
```
### Example Usage of the GrpcSingleNodeChannelPool
```java
// create a pool for a partitioned service
channelPool = factory.createMulti(
ServiceKey.forGrpcApi(MathApiGrpc.class, ServicePartition.multi()),
MathApiGrpc::newBlockingStub);
// blocking call
List<Response> response = channelPool
.call(MathApiGrpc.MathApiBlockingStub::dictionaryLookup)
.run(request);
// async call
Future<List<Response>> response = channelPool
.call(MathApiGrpc.MathApiBlockingStub::dictionaryLookup)
.async(myExecutor)
.runEach(request);
// async call, will fail or succeed as a group
Future<List<Response>> response = channelPool
.call(MathApiGrpc.MathApiBlockingStub::dictionaryLookup)
.async(myExecutor)
.runAll(request1, request2);
```
### Central Classes
* [ServiceRegistryIf](src/main/java/nu/marginalia/service/discovery/ServiceRegistryIf.java)
* [ZkServiceRegistry](src/main/java/nu/marginalia/service/discovery/ZkServiceRegistry.java)

View File

@ -100,6 +100,7 @@ public class GrpcMultiNodeChannelPool<STUB> {
this.method = method;
}
/** Run the given method on each node, returning a future of a list of results */
public CompletableFuture<List<T>> runAll(I arg) {
var futures = getEligibleNodes().stream()
.map(GrpcMultiNodeChannelPool.this::getPoolForNode)
@ -113,6 +114,7 @@ public class GrpcMultiNodeChannelPool<STUB> {
.thenApply(v -> futures.stream().map(CompletableFuture::join).toList());
}
/** Run the given method on each node, returning a list of futures. */
public List<CompletableFuture<T>> runEach(I arg) {
return getEligibleNodes().stream()
.map(GrpcMultiNodeChannelPool.this::getPoolForNode)