我有一个打开了Tornado websocket的Django项目,并在我的Redis Pub / Sub中订阅了一个主题。我正在使用asyncio和aioredis。在刷新页面,关闭浏览器或导航到该页面之前,我将要求关闭Websocket,这将取消订阅该主题。
这里的问题有时是当我更改页面或刷新页面时,过去的一堆消息将被泵回到新打开的Websocket中。它不会每次都发生,并且我不确定还能做些什么来确保过去的消息不会在页面刷新时返回。我已经确定websocket将关闭并退订页面刷新/窗口卸载的主题。
在客户端退订时,Redis Pub / Sub是否将旧消息保留在某处?当客户端订阅同一主题时,旧消息被发送出去了吗?这是Redis Pub / Sub的正常行为吗?我的印象是Redis Pub / Sub不会保留消息,并且如果取消订阅客户端,则只会为该客户端删除消息。
我需要确保页面重新加载时,旧邮件不会被抽回到websocket。
这是我编写RedisChannel来执行pub / sub函数的方式:
import aioredis
class RedisChannel(object):
'''
Redis backed pub-sub websocket channel.
'''
async def subscribe(self, **kwargs):
'''
Subscribe to topics
'''
topics = kwargs.get('topics')
return await self.conn.subscribe(*topics)
async def unsubscribe(self, **kwargs):
'''
Unsubscribe to topics
'''
topics = kwargs.get('topics')
return await self.conn.unsubscribe(*topics)
async def send(self, **kwargs):
data = {}
# If client socket is provided, only send to this socket.
ws = kwargs.get('ws')
# Topic for this message. Compulsory for broadcast.
topic = kwargs.get('topic')
# Usually JSON
if kwargs.get('data'):
data['data'] = kwargs.get('data')
# I'm using 60 seconds right now just to try to limit the list of past messages
# But the behaviour I need is 0 past messages on page reload in browser
push_event = True
if kwargs.get('timestamp'):
event_timestamp = kwargs.get("timestamp", 0)
data['timestamp'] = event_timestamp
# logger.debug(data)
current_time = timezone.now()
if event_timestamp:
event_dt = get_utc_time(datetime.utcfromtimestamp(event_timestamp))
if event_dt:
time_apart = current_time - event_dt
duration = abs(time_apart.total_seconds())
logger.debug("Time apart between event and current time = {}".format(duration))
if duration >= 60:
push_event = False
if not push_event:
data = {}
return await self.conn.publish_json(topic, json.dumps(data, separators=(',', ': ')))
async def connect(self):
redis_settings = settings['redis']['channel']
self.conn = await aioredis.create_redis_pool(
(
redis_settings.get('host'),
redis_settings.get('port')
),
db=redis_settings.get('db'),
minsize=2,
maxsize=redis_settings.get('max_connections'),
encoding='utf-8'
)
这是我编写websocket处理程序以订阅/取消订阅Redis主题的方式:
import asyncio, json
ws_channel = RedisChannel()
asyncio.get_event_loop().create_task(ws_channel.connect())
async def reader(ch, ws):
while (await ch.wait_message()):
data = await ch.get_json()
if data:
ws.write_message(data)
await asyncio.sleep(0.001)
# time.sleep
class ResultsWsHandler(tornado.websocket.WebSocketHandler):
def open(self):
try:
self.write_message(json.dumps('Websocket opened.'))
except Exception as e:
logger.error(str(e))
def on_message(self, message):
asyncio.ensure_future(self.on_message_async(message))
async def on_message_async(self, message):
# async def on_message(self, message):
data = json.loads(message)
action = data.get('action', None)
topics = data.get('cameras', [])
if topics or action is not None:
try:
action = int(action)
if action == 0: # 0 - heartbeat
logger.debug('Heartbeat.')
param = {'type': 0}
self.write_message(json.dumps(param))
elif action == 1: # 1 - subscribe
channels = await ws_channel.subscribe(topics=topics)
logger.debug(f'Successfully subscribed from {topics}.')
self.write_message(json.dumps(f'Successfully subscribed to {topics}.'))
task_list = []
for c in channels:
task_list.append(asyncio.ensure_future(reader(c, self)))
await asyncio.wait(task_list)
elif action == 2: # 2 - unsubscribe
await ws_channel.unsubscribe(topics=topics)
logger.debug(f'Successfully unsubscribe from {topics}.')
self.write_message(json.dumps(f'Successfully unsubscribe from {topics}.'))
else:
logger.debug(f'Other: {data}')
except Exception as e:
logger.error(json.dumps(str(e), separators=(',', ': ')))
self.write_message(json.dumps(str(e), separators=(',', ': ')))