我搜索存储与键值数据库中的键相关联的列表的最佳方式(如berkleydb
或leveldb
)
例如: 我有用户和用户的订单 我想存储每个用户的订单ID列表,以便使用范围选择(用于分页)快速访问
如何存储此结构?
我不想为每个用户以序列化格式存储它:
user_1_orders = serialize(1,2,3..)
user_2_orders = serialize(1,2,3..)
beacuse list可以很长
我认为每个用户的单独db文件都有商店订单ID作为其中的键,但是这不能解决范围选择问题..如果我想获得范围为[5000:5050]
的用户ID怎么办?
我了解redis
,但对berkleydb
或leveldb
等关键值实施感兴趣。
答案 0 :(得分:0)
您可以使用Redis在zset
(排序集)中存储列表,如下所示:
// this line is called whenever a user place an order
$redis->zadd($user_1_orders, time(), $order_id);
// list orders of the user
$redis->zrange($user_1_orders, 0, -1);
Redis足够快。但是你应该知道的关于Redis的一件事是它将所有数据存储在内存中,所以如果数据最终超过物理内存,你必须自己对数据进行分片。
此外,您可以使用SSDB
(https://github.com/ideawu/ssdb),它是leveldb
的包装,具有与Redis类似的API,但将大多数数据存储在磁盘中,内存仅用于缓存。这意味着SSDB的容量是Redis的100倍 - 高达TBs。
答案 1 :(得分:0)
您可以在支持扫描的键值存储(如leveldb)中对此进行建模的一种方法是将订单ID添加到每个用户的键中。因此,每个订单的新密钥都是userId_orderId。现在要获取特定用户的订单,您可以执行简单的前缀扫描 - 扫描(userId *)。现在这使得userId范围查询变慢,在这种情况下,您可以仅为userIds维护另一个表或使用另一个键约定:Id_userId用于获取[5000-5050]之间的userIds
最近我看到hyperdex在leveldb:ex:http://hyperdex.org/doc/04.datatypes/#lists之上添加数据类型支持,所以你也可以尝试一下。
答案 2 :(得分:0)
在BerkeleyDB中,您可以按排序或未排序的顺序存储每个键的多个值。这将是最自然的解决方案。 LevelDB没有这样的功能。您应该查看LMDB
(http://symas.com/mdb/),但它也支持排序的多值键,并且比其他任何键都更小,更快,更可靠。
答案 3 :(得分:0)
让我们从一个列表开始。您可以使用单个hashmap:
0
中存储用户订单的数量所以yoru hashmap如下所示:
key | value
-------------
0 | 5
1 | tomato
2 | celery
3 | apple
4 | pie
5 | meat
密钥的稳定增量可确保每个密钥都是唯一的。鉴于db是按键排序的,并且pack函数将整数转换为一组正确排序的字节数组,您可以获取列表的切片。要获取5000到5050之间的订单,您可以使用bsddb Cursor.set_range
或leveldb' s createReadStream
(js api)
现在让我们扩展到多个用户订单。如果你可以打开几个hashmap,你可以使用上面的几个hashmap。也许你会遇到一些系统问题(每个目录最多nb个开放fds或最大文件数)。因此,您可以使用单个并为多个用户共享相同的哈希映射。
我在以下的leveldb和bsddb工作中解释了这一点,因为你pack
正确使用了词典顺序(byteorder)。所以我假设你有一个pack
函数。在bsddb中,您必须自己构建pack
函数。请查看wiredtiger.packing
或bytekey获取灵感。
原则是使用用户的id命名密钥。它也被称为关键组成。
假设您的数据库如下所示:
key | value
-------------------
1 | 0 | 2 <--- count column for user 1
1 | 1 | tomato
1 | 2 | orange
... ...
32 | 0 | 1 <--- count column for user 32
32 | 1 | banna
... | ...
使用以下(伪)代码创建此数据库:
db.put(pack(1, make_uid(1)), 'tomato')
db.put(pack(1, make_uid(1)), 'orange')
...
db.put(pack(32, make_uid(32)), 'bannana')
make_uid
实现如下:
def make_uid(user_uid):
# retrieve the current count
counter_key = pack(user_uid, 0)
value = db.get(counter_key)
value += 1 # increment
# save new count
db.put(counter_key, value)
return value
然后你必须进行正确的范围查找,它与单个复合键类似。使用bsddb api cursor.set_range(key)
我们检索所有项目
用户5000
的{{1}}和5050
之间:
42
未完成错误检查。除其他事项外,如果从列表中删除项目,则不能保证切片def user_orders_slice(user_id, start, end):
key, value = cursor.set_range(pack(user_id, start))
while True:
user_id, order_id = unpack(key)
if order_id > end:
break
else:
# the value is probably packed somehow...
yield value
key, value = cursor.next()
可以撕掉51个项目。查询说user_orders_slice(42, 5000, 5050)
项的正确方法是实现user_orders_query(user_id,start,limit)`。
我希望你明白这个主意。