Redis+KVStore: Disk-based Storage for Massive Data

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: What do we do when data exceeds the capacity but has to be stored on disks? How can we encapsulate KVStore and integrate it into Redis?

Redis_and_Memcached

Abstract:What do we do when data exceeds the capacity but has to be stored on disks? How can we encapsulate KVStore and integrate it into Redis? How is Redis encoding implemented?

This article aims to address these questions by using the Ardb protocol, specifically at the encoding/decoding layer during the integration between Redis and KVStore.

Redis is currently a hot property in the NoSQL circle. It is multipurpose and practical, and especially suitable for cracking some challenges that fall beyond the capability of traditional relational databases. Redis, as a memory database, stores all the data in memory.

Ardb is a NoSQL storage service fully compatible with the Redis protocol. Its storage is implemented based on the existing mature KVStore engine. Theoretically, any KVStore implementations similar to B-Tree/LSM Tree can be used as Ardb's underlying storage. Ardb currently supports LevelDB/RocksDB/LMDB.

Encoding Mode

The encoding/decoding layer is a very important part of the Redis and KVStore integration solution. Through this layer, we can remove the differences between various KVStore implementations. You can encapsulate and implement complicated data structures in Redis such as string, hash, list, set, and sorted set with any simple KVStore engine.

For strings, it is clear that it can be mapped to a KV pair in a one-to-one manner in KVStore. For other container types, we need to do the following:

• One KV stores the metadata of the entire key (such as the number of members of the list and their expiration time).
• Each member needs a KV to save the member's name and value.

For sorted set, each member has two attributes: score and rank, so we need to do the following:

• One KV stores the metadata of the entire key.
• Each member needs a KV to store the score information.
• Each member needs a KV to store the rank information of each member.

Key Encoding Format

All keys contain the same prefix, and the encoding format is defined as follows:

[<namespace>] <key> <type> <element...>
The "namespace" is used to support something similar to database in Redis. It can be any string, and is not limited to a number.

The "key" is a varbinary string.
The "type" is used to define a simple key-value container. This type implicitly indicates the data structure type of the key. It is one byte long.

The key type in the "meta" information is fixed as KEY_META. Specific types will be defined in the value (refer to the next section).

In addition to the above three parts, different types of keys may have additional fields. For example, hash's key may need an additional "field" field.

Value Encoding Format

Internal values are complex, but their encoding all starts with "type", as defined in the previous section.

<type> <element...>

Subsequent formats vary based on various type definitions.

Encoding of various data types

The encoding of each type of data is shown as follows: "ns" stands for namespace.

            KeyObject                             ValueObject
String      [<ns>] <key> KEY_META                 KEY_STRING <MetaObject>
Hash        [<ns>] <key> KEY_META                 KEY_HASH <MetaObject>
            [<ns>] <key> KEY_HASH_FIELD <field>   KEY_HASH_FIELD <field-value>
Set         [<ns>] <key> KEY_META                 KEY_SET <MetaObject>
            [<ns>] <key> KEY_SET_MEMBER <member>  KEY_SET_MEMBER
List        [<ns>] <key> KEY_META                 KEY_LIST <MetaObject>
            [<ns>] <key> KEY_LIST_ELEMENT <index> KEY_LIST_ELEMENT <element-value>
Sorted Set  [<ns>] <key> KEY_META                 KEY_ZSET <MetaObject>
            [<ns>] <key> KEY_ZSET_SCORE <member>  KEY_ZSET_SCORE <score>
            [<ns>] <key> KEY_ZSET_SORT <score> <member> KEY_ZSET_SORT

ZSet encoding example

Here we use the most complex type, sorted set, as an example. Suppose that there is a Sorted Set A: {member = first, score = 1}, {member = second, score = 2}. Its storage mode in Ardb is as follows:

The storage encoding of Key A is:

// // The "|" in the pseudo-code represents the division of the domain and does not mean to store the data as "|". During actual serialization, each field is serialized at a specific location.
The key is: ns|1|A (1 stands for KEY_META metadata type). 
The value is: metadata encoding (redis data type/zset, expiration time, number of members, maximum and minimum score among others).

The core information storage encoding of Member "first" is:

The key is: ns|11|A|first (11 stands for the KEY_ZSET_SCORE type).
The value is: 11|1 (11 stands for the KEY_ZSET_SCORE type. 1 stands for the score of the Member "first").

The rank information storage encoding of Member "first" is:

The key is: ns|10|A|1|first (10 stands for the KEY_ZSET_SORT type and 1 stands for the score).
The value is: 10 (representing the KEY_ZSET_SORT type, insignificant.  RocksDB automatically sorts the values by key, so it is easy to calculate the rank, requiring no storage and updating).

The score information storage encoding of Member "second" is skipped.

When you use the zcard A command, you can access namespace_1_A directly to get the number of ordered collections in the metadata.

When you use zscore A first, you can directly access namespace_A_first to get the score of Member "first".

When you use zrank A first, first you run zscore to get the score, and then find the serial number of namespace_10_A_1_first.

The specific storage code is as follows:

KeyObject meta_key(ctx.ns, KEY_META, key);
ValueObject meta_value;
for (each_member) {
  // KEY_ZSET_SORT stores the rank information. 
  KeyObject zsort(ctx.ns, KEY_ZSET_SORT, key);
  zsort.SetZSetMember(str);
  zsort.SetZSetScore(score);
  ValueObject zsort_value;
  zsort_value.SetType(KEY_ZSET_SORT);
  GetDBWriter().Put(ctx, zsort, zsort_value); 

  // Store the score information. 
  KeyObject zscore(ctx.ns, KEY_ZSET_SCORE, key);
  zscore.SetZSetMember(str);
  ValueObject zscore_value;
  zscore_value.SetType(KEY_ZSET_SCORE);
  zscore_value.SetZSetScore(score);
  GetDBWriter().Put(ctx, zscore, zscore_value);
}
if (expiretime > 0)
{
    meta_value.SetTTL(expiretime);
}
// Metadata. 
GetDBWriter().Put(ctx, meta_key, meta_value);

Implementation of Del

All data structures store a key-value of the metadata using a uniform encoding format, so it is impossible to have the same name for different data structures. (That is why in the KV pairs storing the key, the K is fixed to the KEY_META type, and the corresponding type of information exists in the Value field of the Metadata type in Redis.)
When implementing Del, the system will first query the metadata key-value to get the specific data structure type, and then perform the corresponding deletion, following steps similar to the following:

• Query the meta information of the specified key to get the data structure type;
• Perform deletion according to the specific type;
• One "del" will require at least one read + subsequent write operation for the deletion.

The specific code is as follows:

int Ardb::DelKey(Context& ctx, const KeyObject& meta_key, Iterator*& iter)
{
  ValueObject meta_obj;
  if (0 == m_engine->Get(ctx, meta_key, meta_obj))
  {
    // Delete directly if the data is of the string type. 
    if (meta_obj.GetType() == KEY_STRING)
    {
      int err = RemoveKey(ctx, meta_key);
      return err == 0 ? 1 : 0;
    }
  }
  else
  {
    return 0;
  }

  if (NULL == iter)
  {
    // If the data is of a complicated type, the database will be traversed based on the namespace, key and type prefix. 
    // Search all the members with the prefix of  namespace|type|Key. 
    iter = m_engine->Find(ctx, meta_key);
  }
  else
  {
    iter->Jump(meta_key);
  }

  while (NULL != iter && iter->Valid())
  {
    KeyObject& k = iter->Key();
    ...
    iter->Del();
    iter->Next();
  }
}

The prefix search code is as follows:

Iterator* RocksDBEngine::Find(Context& ctx, const KeyObject& key) {
  ...
  opt.prefix_same_as_start = true;
  if (!ctx.flags.iterate_no_upperbound)
  {
    KeyObject& upperbound_key = iter->IterateUpperBoundKey();
    upperbound_key.SetNameSpace(key.GetNameSpace());
    if (key.GetType() == KEY_META)
    {
      upperbound_key.SetType(KEY_END);
    }
    else
    {
      upperbound_key.SetType(key.GetType() + 1);
    }
    upperbound_key.SetKey(key.GetKey());
    upperbound_key.CloneStringPart();
  }
  ...
}

Implementation of Expire

It is relatively difficult to support the data expiration mechanism of complicated data structures on a key-value storage engine. Ardb, however, uses special methods to support the expiration mechanism for all data structures.
The specific implementation is as follows:

• The expiration information is stored in the meta value field in the absolute Unix format (ms).
• Based on the above design, TTL/PTTL and other TTL queries only require one meta read.
• Based on the above design, any reads of the metadata will trigger an expiration decision. Since read operations on the metadata are a requisite step, no extra read operations are required here (it is triggered on access like in Redis).
• Create a namespace TTL_DB to specifically store TTL sorting information.
• When the expiration time setting is stored int the metadata and is not zero, an additional key-value will be stored as KEY_TTL_SORT. The key encoding format is [TTL_DB] "" KEY_TTL_SORT, and the value is empty. Therefore, operations similar to expire settings in Ardb will require an additional write operation for implementation.
• In the custom comparator, the key comparison rule for the KEY_TTL_SORT type is to compare first, so that the KEY_TTL_SORT data will be saved in the order of the expiration time.
• A thread is started independently in Ardb to scan the KEY_TTL_SORT data in order at regular intervals (100 ms). When the expiration time is earlier than the current time, the delete operation will be triggered. When the expiration time is later than the current time, the scan will be terminated (equivalent to the timed serverCron task in Redis for processing expired keys).

Concluding remarks

Through the conversion on the encoding layer, we can well encapsulate KVStore for integration with Redis. All operations on Redis data, after the conversion on the encoding layer, will eventually be converted to n reads and writes (N> = 1) to the KVStore. On the premise of conforming to the Redis command semantics, we tried to minimize the number of "n"s in encoding.
The most important point is that the integration of Redis and KVStore is not meant to replace Redis, but to enable the single machine to support a data size that far exceeds the memory capacity while maintaining an acceptable level of performance. In a particular situation, it can also serve as a cold data storage solution to achieve interconnection with the hotspot data in Redis.

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
9月前
|
NoSQL Redis 容器
【Azure Cache for Redis】Redis的导出页面无法配置Storage SAS时通过az cli来完成
【Azure Cache for Redis】Redis的导出页面无法配置Storage SAS时通过az cli来完成
|
NoSQL Redis Memcache
Redis vs. Memcached: In-Memory Data Storage Systems
Redis and Memcached are both in-memory data storage systems. Memcached is a high-performance distributed memory cache service, and Redis is an open-source key-value store.
2002 0
Redis vs. Memcached: In-Memory Data Storage Systems
|
监控 NoSQL Redis
阿里云Redis云数据库(KVStore For Redis)控制台介绍
阿里云Redis云数据库(KVStore For Redis)控制台介绍
13775 0
|
NoSQL Redis 数据库
因稳定、收缩强 阿里云云数据库Redis(Kvstore)版获央视技术保障商点赞
在这封感谢信中,央视技术保障商北京中视广信科技有限公司称,通过多次实践证明,阿里云云数据稳定性极高,每次使用阿里云,都能够让他们安心踏实的为央视这样的超级用户提供服务和技术支持。
5787 0
|
2月前
|
缓存 监控 NoSQL
Redis--缓存击穿、缓存穿透、缓存雪崩
缓存击穿、缓存穿透和缓存雪崩是Redis使用过程中可能遇到的常见问题。理解这些问题的成因并采取相应的解决措施,可以有效提升系统的稳定性和性能。在实际应用中,应根据具体场景,选择合适的解决方案,并持续监控和优化缓存策略,以应对不断变化的业务需求。
111 29
|
2月前
|
缓存 NoSQL Java
Redis应用—8.相关的缓存框架
本文介绍了Ehcache和Guava Cache两个缓存框架及其使用方法,以及如何自定义缓存。主要内容包括:Ehcache缓存框架、Guava Cache缓存框架、自定义缓存。总结:Ehcache适合用作本地缓存或与Redis结合使用,Guava Cache则提供了更灵活的缓存管理和更高的并发性能。自定义缓存可以根据具体需求选择不同的数据结构和引用类型来实现特定的缓存策略。
136 16
Redis应用—8.相关的缓存框架
|
28天前
|
人工智能 缓存 NoSQL
Redis 与 AI:从缓存到智能搜索的融合之路
Redis 已从传统缓存系统发展为强大的 AI 支持平台,其向量数据库功能和 RedisAI 模块为核心,支持高维向量存储、相似性搜索及模型服务。文章探讨了 Redis 在实时数据缓存、语义搜索与会话持久化中的应用场景,并通过代码案例展示了与 Spring Boot 的集成方式。总结来看,Redis 结合 AI 技术,为现代应用提供高效、灵活的解决方案。
|
2月前
|
存储 缓存 NoSQL
Redis缓存设计与性能优化
Redis缓存设计与性能优化涵盖缓存穿透、击穿、雪崩及热点key重建等问题。针对缓存穿透,可采用缓存空对象或布隆过滤器;缓存击穿通过随机设置过期时间避免集中失效;缓存雪崩需确保高可用性并使用限流熔断组件;热点key重建利用互斥锁防止大量线程同时操作。此外,开发规范强调键值设计、命令使用和客户端配置优化,如避免bigkey、合理使用批量操作和连接池管理。系统内核参数如vm.swappiness、vm.overcommit_memory及文件句柄数的优化也至关重要。慢查询日志帮助监控性能瓶颈。
78 9
|
2月前
|
消息中间件 缓存 NoSQL
缓存与数据库的一致性方案,Redis与Mysql一致性方案,大厂P8的终极方案(图解+秒懂+史上最全)
缓存与数据库的一致性方案,Redis与Mysql一致性方案,大厂P8的终极方案(图解+秒懂+史上最全)