Java 如何在RBDMS或NOSQL数据存储或其他消息传递系统(例如rabbitmq)之上实现分布式队列?
从“如果”类问题中 所谓“类队列”是指支持以下操作:Java 如何在RBDMS或NOSQL数据存储或其他消息传递系统(例如rabbitmq)之上实现分布式队列?,java,python,nosql,message-queue,rabbitmq,Java,Python,Nosql,Message Queue,Rabbitmq,从“如果”类问题中 所谓“类队列”是指支持以下操作: 追加(条目:条目)-将条目添加到队列尾部 take():Entry-从队列头删除条目并返回它 提升(入口id)-将入口移动到离头部更近的位置;当前占据该位置的条目将移动到旧位置 降级(条目id)-与升级(条目id)相反 可选操作如下所示: 升级(条目id,金额)-类似于升级(条目id),除非您指定了职位数量 降级(条目id,金额)-与升级(条目id,金额)相反 当然,如果我们允许数量为正或负,我们可以使用单个移动(entry_id,am
- 追加(条目:条目)-将条目添加到队列尾部
- take():Entry-从队列头删除条目并返回它
- 提升(入口id)-将入口移动到离头部更近的位置;当前占据该位置的条目将移动到旧位置
- 降级(条目id)-与升级(条目id)相反
- 升级(条目id,金额)-类似于升级(条目id),除非您指定了职位数量
- 降级(条目id,金额)-与升级(条目id,金额)相反
- 当然,如果我们允许数量为正或负,我们可以使用单个移动(entry_id,amount)方法合并升级/降级方法
解决方案的扩展性应该非常好。Redis支持列表和有序集: 它还支持事务和发布/订阅消息。所以,是的,我想说这在redis上很容易做到 更新:事实上,大约80%的工作已经做了很多次: 这些点击中的一些可以升级以添加您想要的内容。您必须使用事务来实现升级/降级操作 可以在服务器端使用lua来创建该功能,而不是在客户端代码中使用它。或者,您可以在服务器上围绕redis创建一个瘦包装器,它只实现您想要的操作。Websphere MQ可以完成几乎所有这一切
通过从队列中删除消息并以较高/较低的优先级将其放回队列,或者使用“CORRELID”作为序列号,几乎可以进行升级/降级。RabbitMQ有什么问题?听起来正是你需要的
我们在生产环境中也广泛使用Redis,但它没有队列通常具有的一些功能,例如将任务设置为已完成,或者在某些TTL中未完成任务时重新发送任务。另一方面,它确实具有队列所不具备的其他功能,比如它是一个通用存储,而且速度非常快。如果出于某种原因决定使用SQL数据库作为后端,我不会使用MySQL,因为它需要轮询(也不会出于许多其他原因使用它),但PostgreSQL支持向其他客户端发送信号,这样它们就不必轮询更改。但是,它会同时向所有侦听客户端发送信号,因此您仍然需要一种机制来选择一个获胜的侦听器 作为旁注,我不确定升级/降级机制是否有用;最好在插入…Python时适当安排作业:“包括电池” 我认为python和两个库足以解决这个问题,而不是像RabbitMQ、Redis或RDBMS这样的数据存储。有些人可能会抱怨,这种自己动手的方法是在重新发明轮子,但我更喜欢运行100行python代码,而不是管理另一个数据存储 实现优先级队列 您定义的操作:追加、获取、提升和降级,描述了优先级队列。不幸的是,python没有内置的优先级队列数据类型。但它确实有一个名为heapq的堆库,优先级队列通常作为堆实现。以下是我对满足您要求的优先级队列的实现:
class PQueue:
"""
Implements a priority queue with append, take, promote, and demote
operations.
"""
def __init__(self):
"""
Initialize empty priority queue.
self.toll is max(priority) and max(rowid) in the queue
self.heap is the heap maintained for take command
self.rows is a mapping from rowid to items
self.pris is a mapping from priority to items
"""
self.toll = 0
self.heap = list()
self.rows = dict()
self.pris = dict()
def append(self, value):
"""
Append value to our priority queue.
The new value is added with lowest priority as an item. Items are
threeple lists consisting of [priority, rowid, value]. The rowid
is used by the promote/demote commands.
Returns the new rowid corresponding to the new item.
"""
self.toll += 1
item = [self.toll, self.toll, value]
self.heap.append(item)
self.rows[self.toll] = item
self.pris[self.toll] = item
return self.toll
def take(self):
"""
Take the highest priority item out of the queue.
Returns the value of the item.
"""
item = heapq.heappop(self.heap)
del self.pris[item[0]]
del self.rows[item[1]]
return item[2]
def promote(self, rowid):
"""
Promote an item in the queue.
The promoted item swaps position with the next highest item.
Returns the number of affected rows.
"""
if rowid not in self.rows: return 0
item = self.rows[rowid]
item_pri, item_row, item_val = item
next = item_pri - 1
if next in self.pris:
iota = self.pris[next]
iota_pri, iota_row, iota_val = iota
iota[1], iota[2] = item_row, item_val
item[1], item[2] = iota_row, iota_val
self.rows[item_row] = iota
self.rows[iota_row] = item
return 2
return 0
降级命令与升级命令几乎相同,因此为了简洁起见,我将省略它。注意,这只取决于python的列表、dict和heapq库
为我们的优先队列服务
现在使用PQueue数据类型,我们希望允许与实例的分布式交互。这是一个很好的图书馆。虽然gevent是相对较新的测试版,但它的速度非常快,并且经过了良好的测试。有了gevent,我们可以很容易地在localhost:4040上设置一个监听套接字服务器。这是我的服务器代码:
pqueue = PQueue()
def pqueue_server(sock, addr):
text = sock.recv(1024)
cmds = text.split(' ')
if cmds[0] == 'append':
result = pqueue.append(cmds[1])
elif cmds[0] == 'take':
result = pqueue.take()
elif cmds[0] == 'promote':
result = pqueue.promote(int(cmds[1]))
elif cmds[0] == 'demote':
result = pqueue.demote(int(cmds[1]))
else:
result = ''
sock.sendall(str(result))
print 'Request:', text, '; Response:', str(result)
if args.listen:
server = StreamServer(('127.0.0.1', 4040), pqueue_server)
print 'Starting pqueue server on port 4040...'
server.serve_forever()
在生产中运行之前,您当然需要做一些更好的错误/缓冲区处理。但对于快速原型来说,它可以很好地工作。请注意,这不需要在pqueue对象周围进行任何锁定。Gevent实际上并没有并行运行代码,它只是给人一种印象。缺点是更多的内核没有帮助,但好处是无锁代码
别误会,gevent SocketServer将同时处理多个请求。但它通过协作多任务处理在应答请求之间切换。这意味着您必须给出协同程序的时间片。虽然gevents套接字I/O函数的设计目的是为了实现,但我们的PQUE实现并非如此。幸运的是,PQUE完成任务的速度非常快
我也是客户
在进行原型设计时,我发现拥有一个客户机也很有用。编写客户端需要一些谷歌搜索,因此我也将分享这些代码:
if args.client:
while True:
msg = raw_input('> ')
sock = gsocket.socket(gsocket.AF_INET, gsocket.SOCK_STREAM)
sock.connect(('127.0.0.1', 4040))
sock.sendall(msg)
text = sock.recv(1024)
sock.close()
print text
要使用新的数据存储,首先启动服务器,然后启动客户端。在客户端提示下,您应该能够执行以下操作:
> append one
1
> append two
2
> append three
3
> promote 2
2
> promote 2
0
> take
two
伸缩性非常好
考虑到您对数据存储的想法,似乎您真正关心的是吞吐量和耐久性。但是“非常好地扩展”并不能量化您的需求。所以我决定用一个测试f对上面的内容进行基准测试
> append one
1
> append two
2
> append three
3
> promote 2
2
> promote 2
0
> take
two
def test():
import time
import urllib2
import subprocess
import random
random = random.Random(0)
from progressbar import ProgressBar, Percentage, Bar, ETA
widgets = [Percentage(), Bar(), ETA()]
def make_name():
alphabet = 'abcdefghijklmnopqrstuvwxyz'
return ''.join(random.choice(alphabet)
for rpt in xrange(random.randrange(3, 20)))
def make_request(cmds):
sock = gsocket.socket(gsocket.AF_INET, gsocket.SOCK_STREAM)
sock.connect(('127.0.0.1', 4040))
sock.sendall(cmds)
text = sock.recv(1024)
sock.close()
print 'Starting server and waiting 3 seconds.'
subprocess.call('start cmd.exe /c python.exe queue_thing_gevent.py -l',
shell=True)
time.sleep(3)
tests = []
def wrap_test(name, limit=10000):
def wrap(func):
def wrapped():
progress = ProgressBar(widgets=widgets)
for rpt in progress(xrange(limit)):
func()
secs = progress.seconds_elapsed
print '{0} {1} records in {2:.3f} s at {3:.3f} r/s'.format(
name, limit, secs, limit / secs)
tests.append(wrapped)
return wrapped
return wrap
def direct_append():
name = make_name()
pqueue.append(name)
count = 1000000
@wrap_test('Loaded', count)
def direct_append_test(): direct_append()
def append():
name = make_name()
make_request('append ' + name)
@wrap_test('Appended')
def append_test(): append()
...
print 'Running speed tests.'
for tst in tests: tst()
Starting server and waiting 3 seconds.
Running speed tests.
100%|############################################################|Time: 0:00:21
Loaded 1000000 records in 21.770 s at 45934.773 r/s
100%|############################################################|Time: 0:00:06
Appended 10000 records in 6.825 s at 1465.201 r/s
100%|############################################################|Time: 0:00:06
Promoted 10000 records in 6.270 s at 1594.896 r/s
100%|############################################################|Time: 0:00:05
Demoted 10000 records in 5.686 s at 1758.706 r/s
100%|############################################################|Time: 0:00:05
Took 10000 records in 5.950 s at 1680.672 r/s
100%|############################################################|Time: 0:00:07
Mixed load processed 10000 records in 7.410 s at 1349.528 r/s
def save_heap(heap, toll):
name = 'heap-{0}.txt'.format(toll)
with open(name, 'w') as temp:
for val in heap:
temp.write(str(val))
gevent.sleep(0)
def save(self):
heap_copy = tuple(self.heap)
toll = self.toll
gevent.spawn(save_heap, heap_copy, toll)
Redisson redisson = Redisson.create();
RDeque<SomeObject> queue = redisson.getDeque("anyDeque");
queue.addFirst(new SomeObject());
queue.addLast(new SomeObject());
SomeObject obj = queue.removeFirst();
SomeObject someObj = queue.removeLast();
redisson.shutdown();