You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ratis.apache.org by dr...@apache.org on 2022/08/16 13:18:31 UTC

[ratis] 02/12: RATIS-1639. Added getting started document (#696)

This is an automated email from the ASF dual-hosted git repository.

dragonyliu pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/ratis.git

commit 8d44e8893a42272645375e54aa8ce814f5db90b0
Author: Riguz Lee <so...@gmail.com>
AuthorDate: Thu Jul 28 02:07:40 2022 +0800

    RATIS-1639. Added getting started document (#696)
    
    
    (cherry picked from commit 95243a7ac501567d5c76dfaadf59b69a4a28fa03)
---
 ratis-docs/src/site/markdown/index.md       |  42 +++-
 ratis-docs/src/site/markdown/start/index.md | 338 +++++++++++++++++++++++++++-
 2 files changed, 366 insertions(+), 14 deletions(-)

diff --git a/ratis-docs/src/site/markdown/index.md b/ratis-docs/src/site/markdown/index.md
index 5af7adaa6..d5b31e516 100644
--- a/ratis-docs/src/site/markdown/index.md
+++ b/ratis-docs/src/site/markdown/index.md
@@ -17,12 +17,36 @@
 
 # Apache Ratis
 Apache Ratis is a highly customizable Raft protocol implementation in Java.
-Raft is a easily understandable consensus algorithm to manage replicated state.
-Apache Ratis could be used in any Java application where state should be replicated between multiple instances.
-
-## Ratis Features
-TODO: complete this section
-#### Multi-group servers
-TODO: complete this section
-#### Separate RAFT log storage from actual data (client-data)
-TODO: complete this section
\ No newline at end of file
+[Raft](https://raft.github.io/) is an easily understandable consensus algorithm to manage replicated state. 
+
+The Ratis project was started at 2016,
+entered Apache incubation in 2017,
+and graduated as a top level Apache project on Feb 17, 2021.
+Originally, Ratis was built for using Raft in [Apache Ozone](https://ozone.apache.org)
+in order to replicate raw data and to provide high availability.
+The correctness and the performance of Ratis have been heavily tested with Ozone.
+
+## Pluggability
+
+Unlike many other raft implementations,
+Ratis is designed to be pluggable,
+it could be used in any Java applications
+where state should be replicated between multiple instances.
+Ratis provides abstractions over Raft protocol for users,
+which make Raft library fully decoupled from the applications.
+
+### Pluggable transport
+Ratis provides a pluggable transport layer. 
+Applications may use their own implementation.
+By default, gRPC, Netty+Protobuf and Apache Hadoop RPC based transports are provided.
+
+### Pluggable state machine
+Ratis supports a log and state machine. 
+State machine typically contains the data that you want to make highly available.
+Applications usually define its own state machine for the application logic.
+Ratis makes it easy to use your own state machine.
+
+### Pluggable raft log
+Raft log is also pluggable, 
+users can provide their own log implementation. 
+The default implementation stores log in local files.
diff --git a/ratis-docs/src/site/markdown/start/index.md b/ratis-docs/src/site/markdown/start/index.md
index 88eddaf5c..7aca21f9b 100644
--- a/ratis-docs/src/site/markdown/start/index.md
+++ b/ratis-docs/src/site/markdown/start/index.md
@@ -16,11 +16,339 @@
 -->
 
 # Getting Started
-TODO: complete this section
-### Add the dependency
+Let's get started to use Raft in your application.
+To demonstrate how to use Ratis,
+we'll implement a simple counter service,
+which maintains a counter value across a cluster.
+The client could send the following types of commands to the cluster:
 
-### Create Your StateMachine
+* `INCREMENT`: increase the counter value
+* `GET`: query the current value of the counter,
+we call such kind of commands as read-only commands
 
-### Build and Start a RaftServer
+Note: The full source could be found at [Ratis examples](https://github.com/apache/ratis/tree/master/ratis-examples).
+This article is mainly intended to show the steps of integration of Ratis,
+if you wish to run this example or find more examples,
+please refer to [the README](https://github.com/apache/ratis/tree/master/ratis-examples#example-3-counter).
 
-### Configuration
+## Add the dependency
+
+First, we need to add Ratis dependencies into the project,
+it's available in maven central:
+
+```xml
+<dependency>
+   <artifactId>ratis-server</artifactId>
+   <groupId>org.apache.ratis</groupId>
+</dependency>
+```
+
+Also, one of the following transports need to be added:
+
+* grpc
+* netty
+* hadoop
+
+For example, let's use grpc transport:
+
+```xml
+<dependency>
+   <artifactId>ratis-grpc</artifactId>
+   <groupId>org.apache.ratis</groupId>
+</dependency>
+```
+
+Please note that Apache Hadoop dependencies are shaded,
+so it’s safe to use hadoop transport with different versions of Hadoop.
+
+## Create the StateMachine
+A state machine is used to maintain the current state of the raft node,
+the state machine is responsible for:
+
+* Execute raft logs to get the state. In this example, when a `INCREMENT` log is executed, 
+the counter value will be increased by 1.
+And a `GET` log does not affect the state but only returns the current counter value to the client.
+* Managing snapshots loading/saving.
+Snapshots are used to speed the log execution,
+the state machine could start from a snapshot point and only execute newer logs.
+
+### Define the StateMachine
+To define our state machine,
+we can extend a class from the base class `BaseStateMachine`.
+
+Also, a storage is needed to store snapshots,
+and we'll use the build-in `SimpleStateMachineStorage`,
+which is a file-based storage implementation.
+
+Since we're going to implement a count server,
+the `counter` instance is defined in the state machine,
+represents the current value.
+Below is the declaration of the state machine: 
+
+```java
+public class CounterStateMachine extends BaseStateMachine {
+    private final SimpleStateMachineStorage storage =
+            new SimpleStateMachineStorage();
+    private AtomicInteger counter = new AtomicInteger(0);
+    // ...
+}
+```
+
+### Apply Raft Log Item
+
+Once the raft log is committed,
+Ratis will notify state machine by invoking the `public CompletableFuture<Message> applyTransaction(TransactionContext trx)` method,
+and we need to override this method to decode the message and apply it. 
+
+First, get the log content and decode it:
+
+```java
+public class CounterStateMachine extends BaseStateMachine {
+    // ...
+    public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
+        final RaftProtos.LogEntryProto entry = trx.getLogEntry();
+        String logData = entry.getStateMachineLogEntry().getLogData()
+                .toString(Charset.defaultCharset());
+        if (!logData.equals("INCREMENT")) {
+            return CompletableFuture.completedFuture(
+                    Message.valueOf("Invalid Command"));
+        }
+        // ...
+    }
+}
+```
+
+After that, if the log is valid,
+we could apply it by increasing the counter value.
+Remember that we also need to update the committed indexes:
+
+```java
+public class CounterStateMachine extends BaseStateMachine {
+    // ...
+    public CompletableFuture<Message> applyTransaction(TransactionContext trx) {
+        // ...
+        final long index = entry.getIndex();
+        updateLastAppliedTermIndex(entry.getTerm(), index);
+
+        //actual execution of the command: increment the counter
+        counter.incrementAndGet();
+
+        //return the new value of the counter to the client
+        final CompletableFuture<Message> f =
+                CompletableFuture.completedFuture(Message.valueOf(counter.toString()));
+        return f;
+    }
+}
+```
+
+### Handle Readonly Command
+Note that we only handled `INCREMENT` command,
+what about the `GET` command?
+The `GET` command is implemented as a readonly command,
+so we'll need to implement `public CompletableFuture<Message> query(Message request)` instead of `applyTransaction`.
+
+```java
+public class CounterStateMachine extends BaseStateMachine {
+    // ...
+    @Override
+    public CompletableFuture<Message> query(Message request) {
+        String msg = request.getContent().toString(Charset.defaultCharset());
+        if (!msg.equals("GET")) {
+            return CompletableFuture.completedFuture(
+                    Message.valueOf("Invalid Command"));
+        }
+        return CompletableFuture.completedFuture(
+                Message.valueOf(counter.toString()));
+    }
+}
+```
+
+### Save and Load Snapshots
+When taking a snapshot,
+we persist every state in the state machine,
+and the value could be loaded directly to the state in the future.
+In this example,
+the only state is the counter value,
+we're going to use `ObjectOutputStream` to write it to a snapshot file:
+
+```java
+public class CounterStateMachine extends BaseStateMachine {
+    // ...
+    @Override
+    public long takeSnapshot() {
+        //get the last applied index
+        final TermIndex last = getLastAppliedTermIndex();
+
+        //create a file with a proper name to store the snapshot
+        final File snapshotFile =
+                storage.getSnapshotFile(last.getTerm(), last.getIndex());
+
+        //serialize the counter object and write it into the snapshot file
+        try (ObjectOutputStream out = new ObjectOutputStream(
+                new BufferedOutputStream(new FileOutputStream(snapshotFile)))) {
+            out.writeObject(counter);
+        } catch (IOException ioe) {
+            LOG.warn("Failed to write snapshot file \"" + snapshotFile
+                    + "\", last applied index=" + last);
+        }
+
+        //return the index of the stored snapshot (which is the last applied one)
+        return last.getIndex();
+    }
+}
+```
+
+When loading it,
+we could use `ObjectInputStream` to deserialize it.
+Remember that we also need to implement `initialize` and `reinitialize` method,
+so that the state machine will be correctly initialized.
+
+## Build and Start a RaftServer
+In order to build a raft cluster,
+each node must start a `RaftServer` instance,
+which is responsible for communicating to each other through Raft protocol.
+
+It's important to keep in mind that,
+each raft server knows exactly how many raft peers are in the cluster,
+and what are the addresses of them.
+In this example, we'll set a 3 node cluster.
+For simplicity,
+each peer listens to specific port on the same machine,
+and we can define the addresses of the cluster in a configuration file:
+
+```properties
+raft.server.address.list=127.0.0.1:10024,127.0.0.1:10124,127.0.0.1:11124
+```
+
+We name those peers as 'n-0', 'n-1' and 'n-2',
+and then we will create a `RaftGroup` instance representing them.
+Since they are immutable,
+we'll put them in the `Constant` class:
+
+```java
+public final class Constants {
+    public static final List<RaftPeer> PEERS;
+    private static final UUID CLUSTER_GROUP_ID = UUID.fromString("02511d47-d67c-49a3-9011-abb3109a44c1");
+
+    static {
+        // load addresses from configuration file
+        // final String[] addresses = ...
+        List<RaftPeer> peers = new ArrayList<>(addresses.length);
+        for (int i = 0; i < addresses.length; i++) {
+            peers.add(RaftPeer.newBuilder().setId("n" + i).setAddress(addresses[i]).build());
+        }
+        PEERS = Collections.unmodifiableList(peers);
+    }
+    
+    public static final RaftGroup RAFT_GROUP = RaftGroup.valueOf(
+            RaftGroupId.valueOf(Constants.CLUSTER_GROUP_ID), PEERS);
+    // ...
+}
+```
+
+Except for the cluster info,
+another important thing is that we need to know the information of the current peer.
+To achieve this,
+we could pass the current peer's id as a program argument,
+and then the raft server could be created:
+
+```java
+public final class CounterServer implements Closeable {
+    private final RaftServer server;
+
+    // the current peer will be passed as argument
+    public CounterServer(RaftPeer peer, File storageDir) throws IOException {
+        // ...
+        CounterStateMachine counterStateMachine = new CounterStateMachine();
+
+        //create and start the Raft server
+        this.server = RaftServer.newBuilder()
+                .setGroup(Constants.RAFT_GROUP)
+                .setProperties(properties)
+                .setServerId(peer.getId())
+                .setStateMachine(counterStateMachine)
+                .build();
+    }
+
+    public void start() throws IOException {
+        server.start();
+    }
+}
+```
+
+Each `RaftServer` will own a `CounterStateMachine` instance,
+as previously defined by us.
+After that, all we need to do is to start it along with our application:
+
+```java
+public final class CounterServer implements Closeable {
+    // ...
+    public static void main(String[] args) throws IOException {
+        // ...
+        //find current peer object based on application parameter
+        final RaftPeer currentPeer = Constants.PEERS.get(Integer.parseInt(args[0]) - 1);
+
+        //start a counter server
+        final File storageDir = new File("./" + currentPeer.getId());
+        final CounterServer counterServer = new CounterServer(currentPeer, storageDir);
+        counterServer.start();
+        // ...
+    }
+
+}
+```
+
+After the server is started,
+it will try to communicate with other peers in the cluster,
+and perform raft actions like leader election, append log entries, etc.
+
+## Build Raft Client
+
+To send commands to the cluster,
+we need to use a `RaftClient` instance.
+All we need to know is the peers in the cluster, ie. the raft group. 
+
+```java
+public final class CounterClient {
+    // ...
+    private static RaftClient buildClient() {
+        RaftProperties raftProperties = new RaftProperties();
+        RaftClient.Builder builder = RaftClient.newBuilder()
+                .setProperties(raftProperties)
+                .setRaftGroup(Constants.RAFT_GROUP)
+                .setClientRpc(
+                        new GrpcFactory(new Parameters())
+                                .newRaftClientRpc(ClientId.randomId(), raftProperties));
+        return builder.build();
+    }
+}
+```
+
+With this raft client,
+we can then send commands by `raftClient.io().send` method,
+and use `raftClient.io().sendReadonly` method for read only commands.
+In this example,
+to send `INCREMENT` and `GET` command,
+we can do it like this:
+
+```java
+raftClient.io().send(Message.valueOf("INCREMENT")));
+```
+
+and
+
+```java
+RaftClientReply count = raftClient.io().sendReadOnly(Message.valueOf("GET"));
+String response = count.getMessage().getContent().toString(Charset.defaultCharset());
+System.out.println(response);
+```
+
+## Summary
+It might seem a little complicated for beginners,
+but since Raft itself is a hard topic,
+this is already the simplest example we've found as a 'Hello World' for Ratis.
+After you have a basic understanding of Ratis,
+you'll find it really easy to be integrated into any projects. 
+
+Next, you can take a look at other [examples](https://github.com/apache/ratis/tree/master/ratis-examples),
+to know more about the features of Ratis.
\ No newline at end of file