一个简单的键值存储微服务






4.14/5 (7投票s)
我有一个非常具体的用例,需要一个微服务来管理一个简单的内存数据存储,我称之为“bucket(桶)”。
引言
我有一个非常具体的用例,需要一个微服务来管理一个简单的内存数据存储,我称之为“bucket(桶)”。令我惊讶的是,我简单地在Google上搜索一个现有的简单解决方案却一无所获,但这很正常,因为简单的事情会变得复杂,而简单的事情最终会被遗忘。
我的具体用例是,我需要跟踪谁在使用资源以及使用了多长时间。用户可以表明他们正在使用资源,以及他们何时完成资源的使用,并且用户可以添加关于资源使用的注释。因此,其他用户可以看到谁在使用资源,他们已经使用了多长时间,以及关于他们使用的任何注释。鉴于此,我的需求更多的是关于我不需要什么,而不是我需要什么。
我需要什么
- 将一个键设置为一个值的能力。
我不需要什么
- 我甚至不需要管理不同桶的能力,但如果不实现这个功能似乎有点愚蠢,所以它被实现了。
- 我不在乎服务器重启时内存是否丢失。这仅用于在不同客户端之间共享的信息数据。从技术上讲,其中一个前端客户端(因为客户端将拥有完整的数据)甚至可以为其他所有人恢复数据。
- 我不需要复杂的数据结构,只需要键值对,其中值是一些值类型,而不是结构。
- 我甚至不在乎一个用户覆盖另一个用户 - 在实际使用中,这种情况不会发生,即使发生了,我也不在乎 - 谁最后更新一个键,谁就赢了。
Redis
"Redis 是一个开源(BSD 许可)的,内存中的数据结构存储,用作数据库、缓存和消息代理。Redis 提供诸如字符串、哈希、列表、集合、排序集合与范围查询、位图、超日志、地理空间索引和流等数据结构。 Redis 具有内置的复制、Lua 脚本、LRU 驱逐、事务和不同级别的磁盘持久化,并通过 Redis Sentinel 提供高可用性,并通过 Redis Cluster 提供自动分区。"
哇。绝对是我需要的过度。也就是说,除非您的需求确实符合这里的要求,否则请不要使用它来替换像 Redis 之类的东西。
API
浏览到 https://:7672/swagger/index.html,我们看到
从 Swagger 文档中可以看出(某种程度上),我们有以下端点
- 按名称获取桶的内容。
- 列出当前内存中的所有桶,按其名称。
- 获取桶对象,其中将包括桶的数据和桶的元数据,元数据只是桶的名称。
- 删除一个桶。
- 将桶的数据设置为
POST
正文中的键值对。 - 使用指定的键值更新桶的数据。
实现
该实现 (C# ASP.NET Core 3.1) 是 123 行控制器代码。非常简单。 这是它的全部内容。
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using MemoryBucket.Classes;
namespace MemoryBucket.Controllers
{
[ApiController]
[Route("[controller]")]
public class MemoryBucketController : ControllerBase
{
private static Buckets buckets = new Buckets();
private static object locker = new object();
public MemoryBucketController()
{
}
/// <summary>
/// Returns the contents of an existing bucket.
/// </summary>
[HttpGet]
public object Get([FromQuery, BindRequired] string bucketName)
{
lock (locker)
{
Assertion.That(buckets.TryGetValue(bucketName, out Bucket bucket),
$"No bucket with name {bucketName} exists.");
return bucket.Data;
}
}
/// <summary>
/// Lists all buckets.
/// </summary>
[HttpGet("List")]
public object GetBucket()
{
lock (locker)
{
return buckets.Select(b => b.Key);
}
}
/// <summary>
/// Returns the bucket itself.
/// </summary>
[HttpGet("Bucket")]
public object GetBucket([FromQuery, BindRequired] string bucketName)
{
lock (locker)
{
Assertion.That(buckets.TryGetValue(bucketName, out Bucket bucket),
$"No bucket with name {bucketName} exists.");
return bucket;
}
}
/// <summary>
/// Deletes an existing bucket.
/// </summary>
[HttpDelete("{bucketName}")]
public void Delete(string bucketName)
{
lock (locker)
{
Assertion.That(buckets.TryGetValue(bucketName, out Bucket bucket),
$"No bucket with name {bucketName} exists.");
buckets.Remove(bucketName);
}
}
/// <summary>
/// Creates or gets an existing bucket and replaces its data.
/// </summary>
[HttpPost("Set")]
public object Post(
[FromQuery, BindRequired] string bucketName,
[FromBody] Dictionary<string, object> data)
{
lock (locker)
{
var bucket = CreateOrGetBucket(bucketName);
bucket.Data = data;
return data;
}
}
/// <summary>
/// Creates or gets an existing bucket and updates the specified key with the
/// specified value.
/// </summary>
[HttpPost("Update")]
public object Post(
[FromQuery, BindRequired] string bucketName,
[FromQuery, BindRequired] string key,
[FromQuery, BindRequired] string value)
{
lock (locker)
{
var bucket = CreateOrGetBucket(bucketName);
var data = bucket.Data;
data[key] = value;
return data;
}
}
private Bucket CreateOrGetBucket(string bucketName)
{
if (!buckets.TryGetValue(bucketName, out Bucket bucket))
{
bucket = new Bucket();
bucket.Name = bucketName;
buckets[bucketName] = bucket;
}
return bucket;
}
}
}
辅助类
尽量不要感到敬畏。 我真的认为这不需要解释。
Bucket 类
using System.Collections.Generic;
namespace MemoryBucket.Classes
{
public class Bucket
{
public string Name { get; set; }
public Dictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
}
}
Buckets 类
using System.Collections.Generic;
namespace MemoryBucket.Classes
{
public class Buckets : Dictionary<string, Bucket>
{
}
}
测试
这里一些Postman测试就足够了。
创建一个桶
curl --location --request POST 'https://:7672/memoryBucket/Set?bucketName=Soup' \ --header 'Content-Type: application/json' \ --data-raw '{ "Name":"Marc", "Resource": "Garlic", "Note": "For the soup" }'
我们看到的响应是
{
"Name": "Marc",
"Resource": "Garlic",
"Note": "For the soup"
}
更新一个桶
Kate正在接手做汤
curl --location --request POST 'https://:7672/memoryBucket/Update?bucketName=Soup&key=Name&value=Kate'
我们看到的响应是
{
"Name": "Kate",
"Resource": "Garlic",
"Note": "For the soup"
}
列出桶
我正在添加另一个桶
curl --location --request POST 'https://:7672/memoryBucket/Set?bucketName=Salad' \
--header 'Content-Type: application/json' \
--data-raw '{
"Name":"Laurie",
"Resource": "Lettuce"
}'
当我列出桶时
curl --location --request GET 'https://:7672/memoryBucket/List'
我看到
[
"Salad",
"Soup"
]
获取桶本身
curl --location --request GET 'https://:7672/memoryBucket/Bucket?bucketName=Soup'
我看到
{
"name": "Soup",
"data": {
"Name": "Marc",
"Resource": "Garlic",
"Note": "For the soup"
}
}
删除桶
晚餐准备好了
curl --location --request DELETE '<a href="https://:7672/memoryBucket/Soup">
https://:7672/memoryBucket/Soup</a>'
curl --location --request DELETE '<a href="https://:7672/memoryBucket/Salad">
https://:7672/memoryBucket/Salad</a>'
curl --location --request GET 'https://:7672/memoryBucket/List'
没有更多的桶了
[]
结论
简单,对吧? 这让我想知道为什么我们不再做简单的事情了。
历史
- 2021年6月26日:初始版本