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

目录

介绍

我需要的

我不需要的东西

Redis

应用程序接口

实现

助手类

Bucket类

Buckets类

测试

创建存储桶

更新存储桶

列出存储桶

获取桶本身

删除桶

结论


https://www.codeproject.com/KB/Articles/5306500/memoryBucket.png

介绍

我有一个非常具体的用例,我需要一个微服务来管理一个简单的内存数据存储,我称之为存储桶。令我惊讶的是,我对现有简单解决方案的简短谷歌搜索是空洞的,但这对于课程来说是一样的,因为简单的事情会发展成复杂的事情,而简单的事情最终会被遗忘。

我的具体用例是我需要跟踪谁在使用资源以及使用了多长时间。用户能够指出他们正在使用资源以及何时使用完该资源,并且用户能够添加关于资源使用情况的注释。因此,其他用户可以查看谁在使用资源、他们使用了多长时间以及有关其使用情况的任何注释。鉴于此,我的要求更多是关于我不需要的东西而不是我需要的东西。

我需要的

  1. 将键设置为值的能力。

我不需要的东西

  1. 我甚至不需要管理不同存储桶的能力,但不实现该功能似乎有点愚蠢,所以它被实现了。
  2. 如果服务器重新启动,我不在乎内存是否丢失。这仅适用于将在不同客户端之间共享的信息数据。从技术上讲,前端客户端之一(因为客户端将拥有完整数据)甚至可以为其他所有人恢复数据。
  3. 我不需要复杂的数据结构,只需要键值对,其中值是某种值类型,而不是结构。
  4. 我甚至不关心一个用户踩到另一个用户的顶部——在实际使用中,这不会发生,如果发生了,我也不在乎——最后更新密钥的人获胜。

Redis

“Redis是一种开源(BSD许可)、内存中数据结构存储,用作数据库、缓存和消息代理。Redis提供诸如字符串、哈希、列表、集合、带有范围查询的排序集合、位图的数据结构、超级日志、地理空间索引和流。Redis具有内置复制、Lua脚本、LRU驱逐、事务和不同级别的磁盘持久性,并通过Redis SentinelRedis Cluster自动分区提供高可用性。

哎呀。对于我需要的东西来说绝对是大材小用。也就是说,请不要用它来代替像Redis这样的东西,除非你的要求真的符合这里的要求。

应用程序接口

浏览到http://localhost:7672/swagger/index.html,我们看到:

https://www.codeproject.com/KB/Articles/5306500/api.png

正如您从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 'http://localhost: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"
}

更新存储桶

凯特正在接手做汤:

curl --location 
--request POST 'http://localhost:7672/memoryBucket/Update?bucketName=Soup&key=Name&value=Kate'

我们看到响应如下:

{
  "Name": "Kate",
  "Resource": "Garlic",
  "Note": "For the soup"
}

列出存储桶

我正在添加另一个存储桶:

curl --location --request POST 'http://localhost:7672/memoryBucket/Set?bucketName=Salad' \
--header 'Content-Type: application/json' \
--data-raw '{
"Name":"Laurie",
"Resource": "Lettuce"
}'

当我列出桶时:

curl --location --request GET 'http://localhost:7672/memoryBucket/List'

我懂了:

[
  "Salad",
  "Soup"
]

获取桶本身

curl --location --request GET 'http://localhost:7672/memoryBucket/Bucket?bucketName=Soup'

我看到:

{
  "name": "Soup",
  "data": {
    "Name": "Marc",
    "Resource": "Garlic",
    "Note": "For the soup"
  }
}

删除桶

晚餐准备好了:

curl --location --request DELETE '<a href="http://localhost:7672/memoryBucket/Soup">
http://localhost:7672/memoryBucket/Soup</a>'
curl --location --request DELETE '<a href="http://localhost:7672/memoryBucket/Salad">
http://localhost:7672/memoryBucket/Salad</a>'
curl --location --request GET 'http://localhost:7672/memoryBucket/List'

没有更多的桶:

[]

结论

很简单吧?让我想知道为什么我们不再做简单的事情了。

A Simple Key-Value Store Microservice - CodeProject

etcd 和 RocksDB 都是用于键值存储的数据结构库,它们各自有着不同的设计目标、应用场景及特性。 ### etcd **设计目标**: etcd 主要由CoreOS团队开发,是一个分布式键值存储系统,旨在提供可靠的服务发现、配置管理等能力。它支持强一致性模型,并具备容错机制,使得在集群节点间数据可以始终保持一致状态,适用于需要高可用性和高可靠性的场景。 **应用场景**: - **服务发现**: 在微服务体系中,etcd 可用于存储服务实例的地址信息,便于客户端查找并连接到相应服务。 - **配置管理**: 各种配置文件(如应用程序的参数设置)通过 etcd 存储和管理,能够动态更新而不需要重启应用。 - **集中式存储**: 对于需要集中控制的应用场景,etcd 提供了强大的键值存储功能,可用于存放各种类型的配置和元数据。 **特性**: - **原子性操作**: 支持读取、创建、删除、更新等操作的原子性保证,确保数据的一致性。 - **版本化**: 每次更新都会记录历史版本,方便回滚或其他历史查询。 - **多副本**: 支持数据的多副本复制,提高系统的容灾能力和可靠性。 ### RocksDB **设计目标**: RocksDB 是 Facebook 开发的一款基于 Google 的 LevelDB 的开源键值存储系统,主要用于本地持久化存储和高速缓存。它以高性能和易于集成的特点,在大数据处理领域广泛应用。 **应用场景**: - **大数据缓存**: 利用其高效读写的特性,作为大数据应用的缓存层,减少对昂贵的大规模数据访问成本。 - **日志系统**: 作为一种可靠的底层数据存储机制,适合构建高性能的日志系统。 - **数据库后端**: 在某些特定场合下,RocksDB 被用作传统关系型数据库的替代品,用于存储非结构化的数据。 **特性**: - **低延迟**: 优化了读写性能,特别适合于频繁读写操作的场景。 - **高度压缩**: 利用多种压缩算法,有效降低磁盘空间需求。 - **内存映射**: 支持将大部分数据驻留在内存中,显著提升数据访问速度。 **比较**: 尽管 etcd 和 RocksDB 都提供键值存储能力,但在设计目标和使用场景上存在差异: - **etcd 更侧重于分布式环境下的高可用性和一致性保障**,适合于需要复杂事务处理和高可靠性的系统。 - **RocksDB 强调的是高速度和大容量的本地存储**,适用于大数据缓存、实时分析等领域。 选择合适的工具取决于具体的业务需求、数据量以及性能目标。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值