tornado-redis github 地址:https://github.com/leporo/tornado-redis
介绍
tornado-redis 包,一个 tornado 可用的异步 redis client。但已不在维护。
tornado-redis连接数据库
1.普通连接(未使用连接池池)
import tornadoredis c = tornadoredis.Client(host="127.0.0.1",port=6379) # 测试是否连接成功,写一个key,并查看redis数据库是否存在该key c.set("name","zhangsan")
执行结果
127.0.0.1:6379> get name "zhangsan" 127.0.0.1:6379>
2.连接池连接
具体连接池的概念就不说了,很简单
import tornadoredis CONNECTION_POOL = tornadoredis.ConnectionPool(max_connections=100, wait_for_available=True) c = tornadoredis.Client(host="127.0.0.1", port="6379", connection_pool=CONNECTION_POOL) # 测试是否连接成功,写一个key,并查看redis数据库是否存在该key c.set("age",18)
执行结果
127.0.0.1:6379> get age "18" 127.0.0.1:6379>
tornado 可用的异步 redis 客户端,使用很方便。
下面的例子介绍了怎么使用管道设置值和读取值的简单操作
#coding:utf-8 import tornadoredis import tornado.httpserver import tornado.web import tornado.ioloop import tornado.gen #设置连接池 CONNECTION_POOL = tornadoredis.ConnectionPool(max_connections=500,wait_for_available=True) # 生成一个客户端,这里并没有给出host和port,使用的默认的参数,我的redis使用默认的参数也可以连接上 # Client的参数为 # def __init__(self, host='localhost', port=6379, unix_socket_path=None, # password=None, selected_db=None, io_loop=None, # connection_pool=None): c = tornadoredis.Client(connection_pool=CONNECTION_POOL) class MainHandler(tornado.web.RequestHandler): @tornado.web.asynchronous @tornado.gen.engine def get(self): name = yield tornado.gen.Task(c.get,'name') age = yield tornado.gen.Task(c.get,'age') height = yield tornado.gen.Task(c.get,'height') self.finish("name: {0} age: {1} height: {2}".format(name, age, height)) application = tornado.web.Application([ (r'/', MainHandler), ]) @tornado.gen.engine def create_test_data(): c = tornadoredis.Client() # 使用管道设置值 with c.pipeline() as pipe: pipe.set('name', 'zhangsan') pipe.set('age', '10') pipe.set('height', '1.8') yield tornado.gen.Task(pipe.execute) if __name__ == '__main__': # Start the data initialization routine create_test_data() http_server = tornado.httpserver.HTTPServer(application) http_server.listen(8888) print 'Demo is runing at 0.0.0.0:8888\nQuit the demo with CONTROL-C' tornado.ioloop.IOLoop.instance().start()
执行结果
name: zhangsan age: 10 height: 1.8
1、scan的使用
yield tornado.gen.Task(c.scan, cursor = 0, match = "*:{0}".format(sha1))
2、incr的使用
tornado.gen.Task(async.incr, 'file_upload_idx')
3、hmget的使用
yield tornado.gen.Task(async.hmget,key,["uid","file_type"])
注意
在使用tornado-redis的时候一定要在形如class MainHandler(tornado.web.RequestHandler)中使用,不要想着脱离它再使用,否则会出现很多的错误,这个坑我已经踩过了,大家注意,如果在其他的地方使用可以选择使用redis这个包。
tornado-redis 改编
tornado-redis改编,使用tornado tcpclient 改变了原来的网络io
支持python3.5、tornado6.0,
已测试
1、redis set 和 get
2、redis 订阅和发布
#!/usr/bin/env python # encoding: utf-8 import asyncio import datetime import collections import time as mod_time from asyncio import Future from collections import namedtuple, deque from tornado.tcpclient import TCPClient from tornado.escape import to_unicode, to_basestring class RedisError(Exception): pass class ConnectionError(RedisError): pass class RequestError(RedisError): def __init__(self, message, cmd_line=None): self.message = message self.cmd_line = cmd_line def __repr__(self): if self.cmd_line: return 'RequestError (on %s [%s, %s]): %s' \ % (self.cmd_line.cmd, self.cmd_line.args, self.cmd_line.kwargs, self.message) return 'RequestError: %s' % self.message __str__ = __repr__ Message = namedtuple('Message', ('kind', 'channel', 'body', 'pattern')) GeoData = namedtuple('GeoData', ('member', 'dist', 'coords', 'hash')) def string_keys_to_dict(key_string, callback): return dict([(key, callback) for key in key_string.split()]) def dict_merge(*dicts): merged = {} for d in dicts: merged.update(d) return merged def reply_to_bool(r, *args, **kwargs): return bool(r) def make_reply_assert_msg(msg): def reply_assert_msg(r, *args, **kwargs): return r == msg return reply_assert_msg def reply_set(r, *args, **kwargs): return set(r) def reply_dict_from_pairs(r, *args, **kwargs): return dict(zip(r[::2], r[1::2])) def reply_str(r, *args, **kwargs): return r or '' def reply_int(r, *args, **kwargs): return int(r) if r is not None else None def reply_number(r, *args, **kwargs): if r is not None: num = float(r) if not num.is_integer(): return num else: return int(num) return None def reply_datetime(r, *args, **kwargs): return datetime.datetime.fromtimestamp(int(r)) def reply_pubsub_message(r, *args, **kwargs): """ Handles a Pub/Sub message and packs its data into a Message object. """ if len(r) == 3: (kind, channel, body) = r pattern = channel elif len(r) == 4: (kind, pattern, channel, body) = r elif len(r) == 2: (kind, channel) = r body = pattern = None else: raise ValueError('Invalid number of arguments') return Message(kind, channel, body, pattern) def reply_zset(r, *args, **kwargs): if r and 'WITHSCORES' in args: return reply_zset_withscores(r, *args, **kwargs) else: return r def reply_zset_withscores(r, *args, **kwargs): return list(zip(r[::2], list(map(reply_number, r[1::2])))) def reply_hmget(r, key, *fields, **kwargs): return dict(list(zip(fields, r))) def reply_info(response, *args): info = {} def get_value(value): # Does this string contain subvalues? if (',' not in value) or ('=' not in value): return value sub_dict = {} for item in value.split(','): k, v = item.split('=') try: sub_dict[k] = int(v) except ValueError: sub_dict[k] = v return sub_dict for line in response.splitlines(): line = line.strip() if line and not line.startswith('#'): key, value = line.split(':') try: info[key] = int(value) except ValueError: info[key] = get_value(value) return info def reply_ttl(r, *args, **kwargs): return r != -1 and r or None def reply_map(*funcs): def reply_fn(r, *args, **kwargs): if len(funcs) != len(r): raise ValueError('more results than functions to map') return [f(part) for f, part in zip(funcs, r)] return reply_fn def reply_coords(r, *args, **kwargs): return [(float(c[0]), float(c[1])) for c in r] def reply_geo_radius(r, *args, **kwargs): geo_data = [] for member in r: name = member[0] dist = coords = hs = None if 'WITHDIST' in args: dist = float(member[1]) if 'WITHHASH' in args and 'WITHDIST' in args: hs = int(member[2]) elif 'WITHHASH' in args: hs = int(member[1]) if 'WITHCOORD' in args and 'WITHHASH' in args and 'WITHDIST' in args: coords = (float(member[3][0]), float(member[3][1])) elif 'WITHCOORD' in args and ('WITHHASH' in args or 'WITHDIST' in args): coords = (float(member[2][0]), float(member[2][1])) elif 'WITHCOORD' in args: coords = (float(member[1][0]), float(member[1][1])) geo_data.append(GeoData(name, dist, coords, hs)) return geo_data def to_list(source): if isinstance(source, str): return [source] else: return list(source) PUB_SUB_COMMANDS = ( 'SUBSCRIBE', 'PSUBSCRIBE', 'UNSUBSCRIBE', 'PUNSUBSCRIBE', # Not a command at all 'LISTEN', ) REPLY_MAP = dict_merge( string_keys_to_dict('AUTH BGREWRITEAOF BGSAVE DEL EXISTS ' 'EXPIRE HDEL HEXISTS ' 'HMSET MOVE PERSIST RENAMENX SISMEMBER SMOVE ' 'SETEX SAVE SETNX MSET', reply_to_bool), string_keys_to_dict('BITCOUNT DECRBY GETBIT HLEN INCRBY LINSERT ' 'LPUSHX RPUSHX SADD SCARD SDIFFSTORE SETBIT SETRANGE ' 'SINTERSTORE STRLEN SUNIONSTORE SETRANGE', reply_int), string_keys_to_dict('FLUSHALL FLUSHDB SELECT SET SETEX ' 'SHUTDOWN RENAME RENAMENX WATCH UNWATCH', make_reply_assert_msg('OK')), string_keys_to_dict('SMEMBERS SINTER SUNION SDIFF', reply_set), string_keys_to_dict('HGETALL BRPOP BLPOP', reply_dict_from_pairs), string_keys_to_dict('HGET', reply_str), string_keys_to_dict('SUBSCRIBE UNSUBSCRIBE LISTEN ' 'PSUBSCRIBE UNSUBSCRIBE', reply_pubsub_message), string_keys_to_dict('ZRANK ZREVRANK', reply_int), string_keys_to_dict('ZCOUNT ZCARD', reply_int), string_keys_to_dict('ZRANGE ZRANGEBYSCORE ZREVRANGE ' 'ZREVRANGEBYSCORE', reply_zset), string_keys_to_dict('ZSCORE ZINCRBY', reply_number), string_keys_to_dict('SCAN HSCAN SSCAN', reply_map(reply_int, reply_set)), string_keys_to_dict('GEODIST', reply_number), string_keys_to_dict('GEOPOS', reply_coords), string_keys_to_dict('GEORADIUS GEORADIUSBYMEMBER', reply_geo_radius), {'HMGET': reply_hmget, 'PING': make_reply_assert_msg('PONG'), 'LASTSAVE': reply_datetime, 'TTL': reply_ttl, 'INFO': reply_info, 'MULTI_PART': make_reply_assert_msg('QUEUED'), 'TIME': lambda x: (int(x[0]), int(x[1])), 'ZSCAN': reply_map(reply_int, reply_zset_withscores)} ) class ResponseError(RedisError): def __init__(self, message, cmd_line=None): self.message = message self.cmd_line = cmd_line def __repr__(self): if self.cmd_line: return 'ResponseError (on %s [%s, %s]): %s' \ % (self.cmd_line.cmd, self.cmd_line.args, self.cmd_line.kwargs, self.message) return 'ResponseError: %s' % self.message __str__ = __repr__ class InvalidResponse(RedisError): pass class LockError(RedisError): "Errors thrown from the Lock" pass class CmdLine(object): def __init__(self, cmd, *args, **kwargs): self.cmd = cmd self.args = args self.kwargs = kwargs def __repr__(self): return self.cmd + '(' + str(self.args) + ',' + str(self.kwargs) + ')' class TornadoRedis: def __init__( self, host='localhost', port=6379, password=None, selected_db=None, max_connections=None ): self.host = host self.port = port self.password = password self.selected_db = selected_db # 连接池 self.max_connections = max_connections or 2048 self._created_connections = 0 # 已创建的连接数 self._available_connections = set() # 可以使用的连接 self._used_connections = set() # 正在使用中的连接 self._waiting_connections = set() # 等待使用的连接 def __del__(self): """ Python 垃圾回收机制 Python 采用自动引用计数(ARC)方式来回收对象所占用的空间,当程序中有一个变量引用该 Python 对象时, Python 会自动保证该对象引用计数为 1;当程序中有两个变量引用该 Python 对象时,Python 会自动保证该 对象引用计数为 2,依此类推,如果一个对象的引用计数变成了 0,则说明程序中不再有变量引用该对象, 表明程序不再需要该对象,因此 Python 就会回收该对象。 """ for conn in self._available_connections.union(self._used_connections): conn.disconnect() for conn in self._waiting_connections: conn.cancel() # Cancel the future and schedule callbacks. async def get_connection(self): """ Returns a pooled Redis server connection """ try: connection = self._available_connections.pop() except KeyError: connection = self.make_connection() if isinstance(connection, Future): connection = await connection if connection: await connection.connect() self._used_connections.add(connection) return connection def make_connection(self): """ Creates a new connection to Redis server """ if self._created_connections >= self.max_connections: f = Future() self._waiting_connections.add(f) return f self._created_connections += 1 return Connection(self.host, self.port, selected_db=self.selected_db, password=self.password) def release(self, connection): """ Releases the connection back to the pool """ if self._waiting_connections: waiting = self._waiting_connections.pop() waiting.set_result(connection) else: try: self._used_connections.remove(connection) except (KeyError, ValueError): pass self._available_connections.add(connection) class Connection(object): def __init__( self, host='localhost', port=6379, selected_db=None, password=None, ): self.host = host self.port = port self.subscribed = set() self.subscribe_callbacks = deque() self.unsubscribe_callbacks = [] self.password = password self.selected_db = selected_db or 0 self._stream = None self.client = TCPClient() self.stream = None self.info = {'db': 0, 'pass': None} def __del__(self): self.disconnect() async def connect(self): if not self.stream: self.stream = await self.client.connect(host=self.host, port=self.port) async def _consume_bulk(self, tail, callback=None): response = await self.stream.read_until(b'\r\n') # response = await self.stream.read_bytes(int(tail) + 2,partial=True) if isinstance(response, Exception): raise response if not response: raise ResponseError('EmptyResponse') else: response = to_unicode(response) response = response[:-2] return response async def consume_multibulk(self, length, cmd_line): tokens = [] while len(tokens) < length: data = await self.stream.read_until(b'\r\n') if not data: raise ResponseError('Not enough data in response to %s, accumulated tokens: %s' % (cmd_line, tokens), cmd_line) token = await self.process_data(data, cmd_line) tokens.append(token) return tokens async def process_data(self, data, cmd_line): data = to_basestring(data) data = data[:-2] # strip \r\n if data == '$-1': response = None elif data == '*0' or data == '*-1': response = [] else: head, tail = data[0], data[1:] if head == '*': response = await self.consume_multibulk(int(tail), cmd_line) elif head == '$': response = await self._consume_bulk(tail) elif head == '+': response = tail elif head == ':': response = int(tail) elif head == '-': if tail.startswith('ERR'): tail = tail[4:] response = ResponseError(tail, cmd_line) else: raise ResponseError('Unknown response type %s' % head, cmd_line) return response async def listen(self, callback=None, exit_callback=None): cmd_listen = CmdLine('LISTEN') while self.subscribed: try: data = await self.stream.read_until(b'\r\n') except Exception as e: # Maybe wrong! import logging logging.exception(e) data = None if data is None: # If disconnected from the redis server clear the list # of subscriber this client has subscribed to channels = self.subscribed self.subscribed = set() # send a message to caller: # Message(kind='disconnect', channel=set(channel1, ...)) callback(reply_pubsub_message(('disconnect', channels))) return response = await self.process_data(data, cmd_listen) if isinstance(response, Exception): raise response result = self.format_reply(cmd_listen, response) if result and result.kind in ('subscribe', 'psubscribe'): self.on_subscribed(result) if result and result.kind in ('unsubscribe', 'punsubscribe'): self.on_unsubscribed([result.channel]) callback(result) def format_reply(self, cmd_line, data): if cmd_line.cmd not in REPLY_MAP: return data try: res = REPLY_MAP[cmd_line.cmd](data, *cmd_line.args, **cmd_line.kwargs) except Exception as e: raise ResponseError( 'failed to format reply to %s, raw data: %s; err message: %s' % (cmd_line, data, e), cmd_line ) return res def encode(self, value): if not isinstance(value, str): value = str(value) value = value.encode('utf-8') return value def format_command(self, *tokens, **kwargs): cmds = [] for t in tokens: e_t = self.encode(t) e_t_s = to_basestring(e_t) cmds.append('$%s\r\n%s\r\n' % (len(e_t), e_t_s)) data = '*%s\r\n%s' % (len(tokens), ''.join(cmds)) return bytes(data, encoding='utf-8') async def execute_command(self, cmd, *args, **kwargs): result = None cmd_line = CmdLine(cmd, *args, **kwargs) n_tries = 2 while n_tries > 0: n_tries -= 1 if not self.subscribed and cmd not in ('AUTH', 'SELECT'): if self.password and self.info.get('pass', None) != self.password: await self.auth(self.password) if self.selected_db and self.info.get('db', 0) != self.selected_db: await self.select(self.selected_db) command = self.format_command(cmd, *args, **kwargs) await self.stream.write(command) listening = ((cmd in PUB_SUB_COMMANDS) or (self.subscribed and cmd == 'PUBLISH')) if listening: break data = await self.stream.read_until(b'\r\n') resp = await self.process_data(data, cmd_line) result = self.format_reply(cmd_line, resp) return result ### MAINTENANCE def bgrewriteaof(self, callback=None): self.execute_command('BGREWRITEAOF', callback=callback) def dbsize(self, callback=None): self.execute_command('DBSIZE', callback=callback) def flushall(self, callback=None): self.execute_command('FLUSHALL', callback=callback) def flushdb(self, callback=None): self.execute_command('FLUSHDB', callback=callback) def ping(self, callback=None): self.execute_command('PING', callback=callback) def object(self, infotype, key, callback=None): self.execute_command('OBJECT', infotype, key, callback=callback) def info(self, section_name=None, callback=None): args = ('INFO',) if section_name: args += (section_name,) self.execute_command(*args, callback=callback) def echo(self, value, callback=None): self.execute_command('ECHO', value, callback=callback) def time(self, callback=None): """ Returns the server time as a 2-item tuple of ints: (seconds since epoch, microseconds into this second). """ self.execute_command('TIME', callback=callback) def select(self, db, callback=None): self.selected_db = db # if self.connection.info.get('db', None) != db: # self.connection.info['db'] = db # self.execute_command('SELECT', '%s' % db, callback=callback) # elif callback: # callback(True) def shutdown(self, callback=None): self.execute_command('SHUTDOWN', callback=callback) def save(self, callback=None): self.execute_command('SAVE', callback=callback) def bgsave(self, callback=None): self.execute_command('BGSAVE', callback=callback) def lastsave(self, callback=None): self.execute_command('LASTSAVE', callback=callback) def keys(self, pattern='*', callback=None): self.execute_command('KEYS', pattern, callback=callback) async def auth(self, password, callback=None): self.password = password if self.info.get('pass', None) != password: self.info['pass'] = password await self.execute_command('AUTH', password, callback=callback) # elif callback: # callback(True) ### BASIC KEY COMMANDS def append(self, key, value, callback=None): self.execute_command('APPEND', key, value, callback=callback) def getrange(self, key, start, end, callback=None): """ Returns the substring of the string value stored at ``key``, determined by the offsets ``start`` and ``end`` (both are inclusive) """ self.execute_command('GETRANGE', key, start, end, callback=callback) def expire(self, key, ttl, callback=None): self.execute_command('EXPIRE', key, ttl, callback=callback) def expireat(self, key, when, callback=None): """ Sets an expire flag on ``key``. ``when`` can be represented as an integer indicating unix time or a Python datetime.datetime object. """ if isinstance(when, datetime.datetime): when = int(mod_time.mktime(when.timetuple())) self.execute_command('EXPIREAT', key, when, callback=callback) def ttl(self, key, callback=None): self.execute_command('TTL', key, callback=callback) def type(self, key, callback=None): self.execute_command('TYPE', key, callback=callback) def randomkey(self, callback=None): self.execute_command('RANDOMKEY', callback=callback) def rename(self, src, dst, callback=None): self.execute_command('RENAME', src, dst, callback=callback) def renamenx(self, src, dst, callback=None): self.execute_command('RENAMENX', src, dst, callback=callback) def move(self, key, db, callback=None): self.execute_command('MOVE', key, db, callback=callback) def persist(self, key, callback=None): self.execute_command('PERSIST', key, callback=callback) def pexpire(self, key, time, callback=None): """ Set an expire flag on key ``key`` for ``time`` milliseconds. ``time`` can be represented by an integer or a Python timedelta object. """ if isinstance(time, datetime.timedelta): ms = int(time.microseconds / 1000) time = time.seconds + time.days * 24 * 3600 * 1000 + ms self.execute_command('PEXPIRE', key, time, callback=callback) def pexpireat(self, key, when, callback=None): """ Set an expire flag on key ``key``. ``when`` can be represented as an integer representing unix time in milliseconds (unix time * 1000) or a Python datetime.datetime object. """ if isinstance(when, datetime.datetime): ms = int(when.microsecond / 1000) when = int(mod_time.mktime(when.timetuple())) * 1000 + ms self.execute_command('PEXPIREAT', key, when, callback=callback) def pttl(self, key, callback=None): "Returns the number of milliseconds until the key will expire" self.execute_command('PTTL', key, callback=callback) def substr(self, key, start, end, callback=None): self.execute_command('SUBSTR', key, start, end, callback=callback) def delete(self, *keys, **kwargs): self.execute_command('DEL', *keys, callback=kwargs.get('callback')) async def set(self, key, value, expire=None, pexpire=None, only_if_not_exists=False, only_if_exists=False, callback=None): args = [] if expire is not None: args.extend(("EX", expire)) if pexpire is not None: args.extend(("PX", pexpire)) if only_if_not_exists and only_if_exists: raise ValueError("only_if_not_exists and only_if_exists " "cannot be true simultaneously") if only_if_not_exists: args.append("NX") if only_if_exists: args.append("XX") await self.execute_command('SET', key, value, *args, callback=callback) def setex(self, key, ttl, value, callback=None): self.execute_command('SETEX', key, ttl, value, callback=callback) def setnx(self, key, value, callback=None): self.execute_command('SETNX', key, value, callback=callback) def setrange(self, key, offset, value, callback=None): self.execute_command('SETRANGE', key, offset, value, callback=callback) def strlen(self, key, callback=None): self.execute_command('STRLEN', key, callback=callback) def mset(self, mapping, callback=None): items = [i for k, v in mapping.items() for i in (k, v)] self.execute_command('MSET', *items, callback=callback) def msetnx(self, mapping, callback=None): items = [i for k, v in mapping.items() for i in (k, v)] self.execute_command('MSETNX', *items, callback=callback) async def get(self, key, callback=None): return await self.execute_command('GET', key, callback=callback) def mget(self, keys, callback=None): self.execute_command('MGET', *keys, callback=callback) def getset(self, key, value, callback=None): self.execute_command('GETSET', key, value, callback=callback) def exists(self, key, callback=None): self.execute_command('EXISTS', key, callback=callback) def sort(self, key, start=None, num=None, by=None, get=None, desc=False, alpha=False, store=None, callback=None): if ((start is not None and num is None) or (num is not None and start is None)): raise ValueError("``start`` and ``num`` must both be specified") tokens = [key] if by is not None: tokens.append('BY') tokens.append(by) if start is not None and num is not None: tokens.append('LIMIT') tokens.append(start) tokens.append(num) if get is not None: tokens.append('GET') tokens.append(get) if desc: tokens.append('DESC') if alpha: tokens.append('ALPHA') if store is not None: tokens.append('STORE') tokens.append(store) self.execute_command('SORT', *tokens, callback=callback) def getbit(self, key, offset, callback=None): self.execute_command('GETBIT', key, offset, callback=callback) def setbit(self, key, offset, value, callback=None): self.execute_command('SETBIT', key, offset, value, callback=callback) def bitcount(self, key, start=None, end=None, callback=None): args = [a for a in (key, start, end) if a is not None] kwargs = {'callback': callback} self.execute_command('BITCOUNT', *args, **kwargs) def bitop(self, operation, dest, *keys, **kwargs): """ Perform a bitwise operation using ``operation`` between ``keys`` and store the result in ``dest``. """ kwargs = {'callback': kwargs.get('callback', None)} self.execute_command('BITOP', operation, dest, *keys, **kwargs) ### COUNTERS COMMANDS def incr(self, key, callback=None): self.execute_command('INCR', key, callback=callback) def decr(self, key, callback=None): self.execute_command('DECR', key, callback=callback) def incrby(self, key, amount, callback=None): self.execute_command('INCRBY', key, amount, callback=callback) def incrbyfloat(self, key, amount=1.0, callback=None): self.execute_command('INCRBYFLOAT', key, amount, callback=callback) def decrby(self, key, amount, callback=None): self.execute_command('DECRBY', key, amount, callback=callback) ### LIST COMMANDS def blpop(self, keys, timeout=0, callback=None): tokens = to_list(keys) tokens.append(timeout) self.execute_command('BLPOP', *tokens, callback=callback) def brpop(self, keys, timeout=0, callback=None): tokens = to_list(keys) tokens.append(timeout) self.execute_command('BRPOP', *tokens, callback=callback) def brpoplpush(self, src, dst, timeout=1, callback=None): tokens = [src, dst, timeout] self.execute_command('BRPOPLPUSH', *tokens, callback=callback) def lindex(self, key, index, callback=None): self.execute_command('LINDEX', key, index, callback=callback) def llen(self, key, callback=None): self.execute_command('LLEN', key, callback=callback) def lrange(self, key, start, end, callback=None): self.execute_command('LRANGE', key, start, end, callback=callback) def lrem(self, key, value, num=0, callback=None): self.execute_command('LREM', key, num, value, callback=callback) def lset(self, key, index, value, callback=None): self.execute_command('LSET', key, index, value, callback=callback) def ltrim(self, key, start, end, callback=None): self.execute_command('LTRIM', key, start, end, callback=callback) def lpush(self, key, *values, **kwargs): callback = kwargs.get('callback', None) self.execute_command('LPUSH', key, *values, callback=callback) def lpushx(self, key, value, callback=None): self.execute_command('LPUSHX', key, value, callback=callback) def linsert(self, key, where, refvalue, value, callback=None): self.execute_command('LINSERT', key, where, refvalue, value, callback=callback) def rpush(self, key, *values, **kwargs): callback = kwargs.get('callback', None) self.execute_command('RPUSH', key, *values, callback=callback) def rpushx(self, key, value, **kwargs): "Push ``value`` onto the tail of the list ``name`` if ``name`` exists" callback = kwargs.get('callback', None) self.execute_command('RPUSHX', key, value, callback=callback) def lpop(self, key, callback=None): self.execute_command('LPOP', key, callback=callback) def rpop(self, key, callback=None): self.execute_command('RPOP', key, callback=callback) def rpoplpush(self, src, dst, callback=None): self.execute_command('RPOPLPUSH', src, dst, callback=callback) ### SET COMMANDS def sadd(self, key, *values, **kwargs): callback = kwargs.get('callback', None) self.execute_command('SADD', key, *values, callback=callback) def srem(self, key, *values, **kwargs): callback = kwargs.get('callback', None) self.execute_command('SREM', key, *values, callback=callback) def scard(self, key, callback=None): self.execute_command('SCARD', key, callback=callback) def spop(self, key, callback=None): self.execute_command('SPOP', key, callback=callback) def smove(self, src, dst, value, callback=None): self.execute_command('SMOVE', src, dst, value, callback=callback) def sismember(self, key, value, callback=None): self.execute_command('SISMEMBER', key, value, callback=callback) def smembers(self, key, callback=None): self.execute_command('SMEMBERS', key, callback=callback) def srandmember(self, key, number=None, callback=None): if number: self.execute_command('SRANDMEMBER', key, number, callback=callback) else: self.execute_command('SRANDMEMBER', key, callback=callback) def sinter(self, keys, callback=None): self.execute_command('SINTER', *keys, callback=callback) def sdiff(self, keys, callback=None): self.execute_command('SDIFF', *keys, callback=callback) def sunion(self, keys, callback=None): self.execute_command('SUNION', *keys, callback=callback) def sinterstore(self, keys, dst, callback=None): self.execute_command('SINTERSTORE', dst, *keys, callback=callback) def sunionstore(self, keys, dst, callback=None): self.execute_command('SUNIONSTORE', dst, *keys, callback=callback) def sdiffstore(self, keys, dst, callback=None): self.execute_command('SDIFFSTORE', dst, *keys, callback=callback) ### SORTED SET COMMANDS def zadd(self, key, *score_value, **kwargs): callback = kwargs.get('callback', None) self.execute_command('ZADD', key, *score_value, callback=callback) def zcard(self, key, callback=None): self.execute_command('ZCARD', key, callback=callback) def zincrby(self, key, value, amount, callback=None): self.execute_command('ZINCRBY', key, amount, value, callback=callback) def zrank(self, key, value, callback=None): self.execute_command('ZRANK', key, value, callback=callback) def zrevrank(self, key, value, callback=None): self.execute_command('ZREVRANK', key, value, callback=callback) def zrem(self, key, *values, **kwargs): callback = kwargs.get('callback', None) self.execute_command('ZREM', key, *values, callback=callback) def zcount(self, key, start, end, callback=None): self.execute_command('ZCOUNT', key, start, end, callback=callback) def zscore(self, key, value, callback=None): self.execute_command('ZSCORE', key, value, callback=callback) def zrange(self, key, start, num, with_scores=True, callback=None): tokens = [key, start, num] if with_scores: tokens.append('WITHSCORES') self.execute_command('ZRANGE', *tokens, callback=callback) def zrevrange(self, key, start, num, with_scores, callback=None): tokens = [key, start, num] if with_scores: tokens.append('WITHSCORES') self.execute_command('ZREVRANGE', *tokens, callback=callback) def zrangebyscore(self, key, start, end, offset=None, limit=None, with_scores=False, callback=None): tokens = [key, start, end] if offset is not None: tokens.append('LIMIT') tokens.append(offset) tokens.append(limit) if with_scores: tokens.append('WITHSCORES') self.execute_command('ZRANGEBYSCORE', *tokens, callback=callback) def zrevrangebyscore(self, key, end, start, offset=None, limit=None, with_scores=False, callback=None): tokens = [key, end, start] if offset is not None: tokens.append('LIMIT') tokens.append(offset) tokens.append(limit) if with_scores: tokens.append('WITHSCORES') self.execute_command('ZREVRANGEBYSCORE', *tokens, callback=callback) def zremrangebyrank(self, key, start, end, callback=None): self.execute_command('ZREMRANGEBYRANK', key, start, end, callback=callback) def zremrangebyscore(self, key, start, end, callback=None): self.execute_command('ZREMRANGEBYSCORE', key, start, end, callback=callback) def zinterstore(self, dest, keys, aggregate=None, callback=None): return self._zaggregate('ZINTERSTORE', dest, keys, aggregate, callback) def zunionstore(self, dest, keys, aggregate=None, callback=None): return self._zaggregate('ZUNIONSTORE', dest, keys, aggregate, callback) def _zaggregate(self, command, dest, keys, aggregate, callback): tokens = [dest, len(keys)] if isinstance(keys, dict): items = list(keys.items()) keys = [i[0] for i in items] weights = [i[1] for i in items] else: weights = None tokens.extend(keys) if weights: tokens.append('WEIGHTS') tokens.extend(weights) if aggregate: tokens.append('AGGREGATE') tokens.append(aggregate) self.execute_command(command, *tokens, callback=callback) ### HASH COMMANDS def hgetall(self, key, callback=None): self.execute_command('HGETALL', key, callback=callback) def hmset(self, key, mapping, callback=None): items = [i for k, v in mapping.items() for i in (k, v)] self.execute_command('HMSET', key, *items, callback=callback) def hset(self, key, field, value, callback=None): self.execute_command('HSET', key, field, value, callback=callback) def hsetnx(self, key, field, value, callback=None): self.execute_command('HSETNX', key, field, value, callback=callback) def hget(self, key, field, callback=None): self.execute_command('HGET', key, field, callback=callback) def hdel(self, key, *fields, **kwargs): callback = kwargs.get('callback') self.execute_command('HDEL', key, *fields, callback=callback) def hlen(self, key, callback=None): self.execute_command('HLEN', key, callback=callback) def hexists(self, key, field, callback=None): self.execute_command('HEXISTS', key, field, callback=callback) def hincrby(self, key, field, amount=1, callback=None): self.execute_command('HINCRBY', key, field, amount, callback=callback) def hincrbyfloat(self, key, field, amount=1.0, callback=None): self.execute_command('HINCRBYFLOAT', key, field, amount, callback=callback) def hkeys(self, key, callback=None): self.execute_command('HKEYS', key, callback=callback) def hmget(self, key, fields, callback=None): self.execute_command('HMGET', key, *fields, callback=callback) def hvals(self, key, callback=None): self.execute_command('HVALS', key, callback=callback) ### SCAN COMMANDS def scan(self, cursor, count=None, match=None, callback=None): self._scan('SCAN', cursor, count, match, callback) def hscan(self, key, cursor, count=None, match=None, callback=None): self._scan('HSCAN', cursor, count, match, callback, key=key) def sscan(self, key, cursor, count=None, match=None, callback=None): self._scan('SSCAN', cursor, count, match, callback, key=key) def zscan(self, key, cursor, count=None, match=None, callback=None): self._scan('ZSCAN', cursor, count, match, callback, key=key) def _scan(self, cmd, cursor, count, match, callback, key=None): tokens = [cmd] key and tokens.append(key) tokens.append(cursor) match and tokens.extend(['MATCH', match]) count and tokens.extend(['COUNT', count]) self.execute_command(*tokens, callback=callback) ### GEO COMMANDS def geoadd(self, key, longitude, latitude, member, *args, **kwargs): self.execute_command('GEOADD', key, longitude, latitude, member, *args, **kwargs) def geodist(self, key, member1, member2, unit='m', callback=None): self.execute_command('GEODIST', key, member1, member2, unit, callback=callback) def geohash(self, key, member, *args, **kwargs): self.execute_command('GEOHASH', key, member, *args, **kwargs) def geopos(self, key, member, *args, **kwargs): self.execute_command('GEOPOS', key, member, *args, **kwargs) def georadius(self, key, longitude, latitude, radius, unit='m', with_coord=False, with_dist=False, with_hash=False, count=None, sort=None, callback=None): args = [] if with_coord: args.append('WITHCOORD') if with_dist: args.append('WITHDIST') if with_hash: args.append('WITHHASH') if count and count > 0: args.append(count) if sort and sort in ['ASC', 'DESC']: args.append(sort) self.execute_command('GEORADIUS', key, longitude, latitude, radius, unit, callback=callback, *args) def georadiusbymember(self, key, member, radius, unit='m', with_coord=False, with_dist=False, with_hash=False, count=None, sort=None, callback=None): args = [] if with_coord: args.append('WITHCOORD') if with_dist: args.append('WITHDIST') if with_hash: args.append('WITHHASH') if count and count > 0: args.append(count) if sort and sort in ['ASC', 'DESC']: args.append(sort) self.execute_command('GEORADIUSBYMEMBER', key, member, radius, unit, callback=callback, *args) ### PUBSUB async def subscribe(self, channels, callback=None): await self._subscribe('SUBSCRIBE', channels, callback=callback) async def psubscribe(self, channels, callback=None): await self._subscribe('PSUBSCRIBE', channels, callback=callback) async def _subscribe(self, cmd, channels, callback=None): if isinstance(channels, str): channels = [channels] if not self.subscribed: self.on_subscribed(Message(kind='subscribe', channel=channels[0], body=None, pattern=None) ) for channel in channels: self.subscribe_callbacks.append((channel, None)) # Do not execute the same callback multiple times await self.execute_command(cmd, *channels, callback=callback) def on_subscribed(self, result): self.subscribed.add(result.channel) def on_unsubscribed(self, channels, *args, **kwargs): channels = set(channels) self.subscribed -= channels async def unsubscribe(self, channels, callback=None): await self._unsubscribe('UNSUBSCRIBE', channels, callback=callback) async def punsubscribe(self, channels, callback=None): await self._unsubscribe('PUNSUBSCRIBE', channels, callback=callback) async def _unsubscribe(self, cmd, channels, callback=None): if isinstance(channels, str): channels = [channels] await self.execute_command(cmd, *channels) async def publish(self, channel, message, callback=None): await self.execute_command('PUBLISH', channel, message, callback=callback) ### CAS def watch(self, *key_names, **kwargs): callback = kwargs.get('callback', None) self.execute_command('WATCH', *key_names, callback=callback) def unwatch(self, callback=None): self.execute_command('UNWATCH', callback=callback) ### SCRIPTING COMMANDS def eval(self, script, keys=None, args=None, callback=None): if keys is None: keys = [] if args is None: args = [] num_keys = len(keys) _args = keys + args self.execute_command('EVAL', script, num_keys, *_args, callback=callback) def evalsha(self, shahash, keys=None, args=None, callback=None): if keys is None: keys = [] if args is None: args = [] num_keys = len(keys) keys.extend(args) self.execute_command('EVALSHA', shahash, num_keys, *keys, callback=callback) def script_exists(self, shahashes, callback=None): # not yet implemented in the redis protocol self.execute_command('SCRIPT EXISTS', *shahashes, callback=callback) def script_flush(self, callback=None): # not yet implemented in the redis protocol self.execute_command('SCRIPT FLUSH', callback=callback, verbose=True) def script_kill(self, callback=None): # not yet implemented in the redis protocol self.execute_command('SCRIPT KILL', callback=callback) def script_load(self, script, callback=None): # not yet implemented in the redis protocol self.execute_command('SCRIPT LOAD', script, callback=callback) def disconnect(self): self.stream.close() self.client.close() async def main(): # 测试get and set """ host="192.168.1.252", port=6379, password="123456" """ client = TornadoRedis(host="192.168.1.252", port=6379, password="123456", max_connections=2) conn1 = await client.get_connection() client.release(conn1) conn2 = await client.get_connection() conn3 = await client.get_connection() await conn1.set(key="abc", value="data", expire=100) tmp = await conn1.get("abc") print(tmp) if __name__ == "__main__": loop = asyncio.get_event_loop() # 执行coroutine loop.run_until_complete(main()) loop.close()
websocket
from __future__ import print_function import tornado.httpserver import tornado.web import tornado.websocket import tornado.ioloop import tornado.gen from test_my import TornadoRedis class MainHandler(tornado.web.RequestHandler): def get(self): self.render("template.html", title="PubSub + WebSocket Demo") class NewMessageHandler(tornado.web.RequestHandler): async def post(self,*args): c = TornadoRedis() await c.connect() message = self.get_body_argument("message") await c.publish('test_channel', message) self.set_header('Content-Type', 'text/plain') self.write('sent: %s' % (message,)) class MessageHandler(tornado.websocket.WebSocketHandler): def check_origin(self, origin: str) -> bool: return True async def open(self): await self.listen() async def listen(self): self.client = TornadoRedis() await self.client.connect() await self.client.subscribe( 'test_channel') await self.client.listen(self.on_message) def on_message(self, msg): if msg.kind == 'message': self.write_message(str(msg.body)) if msg.kind == 'disconnect': # Do not try to reconnect, just send a message back # to the client and close the client connection self.write_message('The connection terminated due to a Redis server error.') self.close() def on_close(self): if self.client.subscribed: # self.client.unsubscribe('test_channel') self.client.disconnect() application = tornado.web.Application([ (r'/', MainHandler), (r'/msg', NewMessageHandler), (r'/track', MessageHandler), ]) if __name__ == '__main__': http_server = tornado.httpserver.HTTPServer(application) http_server.listen(8888) print('Demo is runing at 0.0.0.0:8888\nQuit the demo with CONTROL-C') tornado.ioloop.IOLoop.instance().start()
参考地址
tornado 异步客户端tornado-redis的使用「guoqianqian5812」https://blog.csdn.net/guoqianqian5812/article/details/68587921
tornado-redis连接池的使用「guoqianqian5812」原文链接:https://blog.csdn.net/guoqianqian5812/article/details/68560689