Introduction
Key/value server is most commonly used in distributed systems to store and retrieve data. It is a simple data storage system that stores data in the form of key-value pairs. The key is a unique identifier for the data, and the value is the data itself. Key/value servers are used in various applications, such as caching, session management, and distributed storage systems.
In this lab, we will implement a simple key/value server that supports the following operations:
Put(key, value, version)
: Store the key-value pair in the server.Get(key)
: Retrieve the value of the key from the server.
It’s running on a single server, and can be accessed by multiple clients concurrently.
Lab
Key/value server with reliable network
In this part, we will implement a key/value server that not need to handle network failures.
When the server receives a Put
request, it performs the following actions based on the version number:
- If the provided version number matches the current version of the key, the server updates the value and increments the version number.
- If the provided version number is 0 and the key does not exist, the server creates a new key-value pair.
- If the provided version number does not match the current version of the key, the server returns an error.
When the server receives a Get
request, it returns the value and version number of the key if it exists, or an error if the key does not exist.
All operations should lock the key-value store to ensure that they are atomic.
|
|
Here is the client Get
implementation:
|
|
Implementing a lock using key/value clerk
In this part, we will implement a distributed lock. Here is the constructor of the lock:
|
|
The l
parameter specifies the key used to store the “lock state”. For example, if servers A
and B
both use l
as lock1
, the server will store the lock state under the key lock1
, indicating they share the same lock. If server A
uses lock1
and lock2
, the server will store the lock state under the keys lock1
and lock2
, indicating they are different locks.
To identify the client, we can generate a unique client ID for each client by randomly generating a 8-byte string.
|
|
For the lock, we need to implement the following methods:
Acquire
: Acquire the lock. If the lock is not held by any client, the client can acquire the lock. If the lock is held by another client, the client cannot acquire the lock.Release
: Release the lock. If the lock is held by the client, the client can release the lock. If the lock is not held by the client, the client cannot release the lock.
The “lock state” is stored in the key-value store. The value of the key is the client ID that holds the lock. If the lock is not held, the value is an empty string.
|
|
When the client attempts to acquire the lock, it should keep retrying until it successfully acquires it. Since the network is reliable, we don’t need to handle the scenario where the lock was acquired by the client but the reply message was lost.
|
|
Key/value server with dropped messages
In this part, we will implement a key/value server that can handle dropped messages in unreliable networks.
It’s easy to handle dropped messages in the Get
operation because it does not change the state of the server. However, it’s more challenging to handle dropped messages in the Put
operation because it changes the state of the server.
In scenarios where messages may be dropped, the key/value server must handle the following cases:
- Successful
Put
Request: If the initialPut
request is received and processed by the server without any message loss, the server’s reply is reliable. - Lost Request Message: If the server does not receive the
Put
request, the client will retry the request until it is successfully received and processed. - Lost Reply Message: If the server processes the
Put
request but the reply message is lost, the client will retry the request. In this case, the server will returnrpc.ErrVersion
due to the incremented version number. The client cannot determine whether thePut
request was processed or if another client’sPut
request caused the version increment. To address this ambiguity, the client should returnrpc.ErrMaybe
, indicating that the client is uncertain whether thePut
request was processed.
These measures ensure that the key/value server can handle dropped messages effectively, maintaining consistency and reliability in a distributed environment.
|
|
Implementing a lock using key/value clerk and unreliable network
Applying the same principles as the previous part, we can implement a distributed lock that can handle dropped messages in unreliable networks. Here is the Acquire
method for the client:
|
|
The Acquire
method should keep retrying until it successfully acquires the lock. If the lock is already held by the client, the client should return rpc.OK
to indicate that the lock is acquired.
Conclusion
In this lab, we implemented a key/value server that supports Put
and Get
operations. We also implemented a distributed lock using the key/value clerk. By handling dropped messages in unreliable networks, we ensured that the key/value server and lock maintain consistency and reliability in distributed systems.
This lab provides valuable insights into building distributed systems that can handle network failures and maintain data consistency. By understanding the challenges and solutions presented in this lab, we can design robust and reliable distributed systems that meet the demands of modern applications.
My implementation of this lab can be found on GitHub, hope it helps you understand the concepts better. If you have any questions or feedback, feel free to send me an email. Thank you for reading!