text: #!/usr/bin/env python3
  # -*- coding: utf-8 -*-
  """
  Created on Sun Jan 24 00:25:23 2021
  
  @author: onebula
  """
  
  import time
  from itertools import chain
  from json import JSONDecodeError
  from json import dumps as js_dumps
  from json import loads as js_loads
  
  from orjson import loads as orjs_loads
  from ciso8601 import parse_datetime
  import redis  # pip install redis hiredis
  from redis.exceptions import ResponseError
  
  
  def json_dumps(data):
      # 去掉 json.dumps 后的大量空格，缩短字符串
      data_str = js_dumps(data, separators=(',', ':'))
      return data_str
  
  
  def json_loads(data_str):
      # 使用orjson加速解析json
      try:
          data = orjs_loads(data_str)
      except JSONDecodeError:
          data = js_loads(data_str)
      return data
  
  
  class RedisKV(object):
      def __init__(self, name=None, ip=None, port=None, pwd=None, ex=3600,
                   n=None,
                  ):
          self.name = name
          if name is not None:
              ip, port = self.address(self.name, ip, port)
          self.connect(ip, port, pwd)
          self.pwd = pwd
          self.ex = ex
          self.n = n
          
      def connect(self, ip, port, pwd):
          pool = redis.ConnectionPool(host=ip, port=port, password=pwd, decode_responses=True)
          self.redis = redis.Redis(connection_pool=pool)
          self.pipe = self.redis.pipeline(transaction=False)
          self.ip, self.port = ip, port
      
      def address(self, name, ip=None, port=None):
          return None, None
      
      def retry(func):
          def wrapper(self, *args, **kwargs):
              try:
                  return func(self, *args, **kwargs)
              except Exception as err:  # pylint: disable=broad-except
                  print(err)
                  ip, port = self.address(self.name)
                  if ip is not None and port is not None:
                      self.connect(ip, port, self.pwd)
                  return func(self, *args, **kwargs)
          return wrapper
      
      @retry
      def setone(self, k, v, ex=None):
          # 赋值
          if ex is None:
              ex = self.ex
          self.redis.set(k, v, ex=ex)
      
      @retry
      def mset(self, kvs, ex=None, n=None):
          # 批量赋值，使用redis的pipeline一次提交多个命令，避免多次请求造成的网络等待
          if not kvs:
              return
          if ex is None:
              ex = self.ex
          if n is None:
              n = self.n
          for idx, k in enumerate(kvs):
              vs = kvs[k]
              if not vs:
                  continue
              self.pipe.set(k, vs, ex=ex)
              if n is not None and idx % n == 0:
                  self.pipe.execute()
          self.pipe.execute()
          
      @retry
      def getone(self, k):
          # 取值
          v = self.redis.get(k)
          return v
      
      def safe_mget(self, ks):
          # 采用二分递归自适应批量取值，避免单次数据量过大而失败
          if not ks:
              return []
          try:
              vs = self.redis.mget(ks)
          except ResponseError:
              half = int(len(ks) / 2)
              vs = self.safe_mget(ks[0:half]) + self.safe_mget(ks[half:])
          return vs
      
      @retry
      def mget(self, ks, n=None):
          # 批量取值，设置单次最大取值数量n，避免请求失败
          # mget不能使用pipeline(transaction = False)，非事务时的pipeline不具有原子性，多次取值结果返回的先后顺序不固定，导致错配
          # 但是 transaction = True时，即使单条get的pipeline在execute之后会出错，暂时不确定原因，因此弃用pipeline，改用多次mget
          if not ks:
              return []
          if n is None:
              n = self.n
          if n is None:
              vs = self.safe_mget(ks)
          else:
              vs = []
              for i in range(0, len(ks), n):
                  vs += self.safe_mget(ks[i:i+n])
          return vs
          
      
  class RedisList(object):
      def __init__(self, name=None, ip=None, port=None, pwd=None, ex=3600,
                   n=None,
                  ):
          self.name = name
          if name is not None:
              ip, port = self.address(self.name, ip, port)
          self.connect(ip, port, pwd)
          self.pwd = pwd
          self.ex = ex
          self.n = n
      
      def connect(self, ip, port, pwd):
          pool = redis.ConnectionPool(host=ip, port=port, password=pwd, decode_responses=True)
          self.redis = redis.Redis(connection_pool=pool)
          self.pipe = self.redis.pipeline(transaction=False)
          self.ip, self.port = ip, port
      
      def address(self, name, ip=None, port=None):
          return None, None
      
      def retry(func):
          def wrapper(self, *args, **kwargs):
              try:
                  return func(self, *args, **kwargs)
              except Exception as err:  # pylint: disable=broad-except
                  print(err)
                  ip, port = self.address(self.name)
                  if ip is not None and port is not None:
                      self.connect(ip, port, self.pwd)
                  return func(self, *args, **kwargs)
          return wrapper
      
      @retry
      def push(self, list_name, items, ex=None):
          # 选取list的左侧为出口，右侧为入口，这样迭代list时从出口开始迭代
          if not isinstance(items, list):
              items = [items]
          if not items:
              return
          if ex is None:
              ex = self.ex
          self.pipe.rpush(list_name, *items)
          if ex is not None:
              self.pipe.expire(list_name, ex)
          self.pipe.execute()
      
      @retry
      def init_list(self, list_name, items=None, ex=None):
          # 若list_name已经存在，则删除该键
          if self.redis.exists(list_name):
              self.redis.delete(list_name)
          if items is not None:
              self.push(list_name, items, ex)
              
      @retry
      def get_len(self, list_name):
          size = self.redis.llen(list_name)
          return size
      
      def format_start_end(self, list_name, start, end):
          if start >= 0 and end < 0:
              size = self.get_len(list_name)
              end = size + end
          return start, end
      
      def safe_lrange(self, list_name, start, end):
          # 采用二分递归自适应批量取值，避免单次数据量过大而失败
          try:
              vs = self.redis.lrange(list_name, start, end)
          except ResponseError:
              start, end = self.format_start_end(list_name, start, end)
              half = int((start + end) / 2)
              vs = self.safe_lrange(list_name, start, half) + self.safe_lrange(list_name, half, end)
          return vs
      
      @retry
      def slicing(self, list_name, start=0, end=-1, n=None):
          if n is None:
              n = self.n
          if n is None:
              queue = self.safe_lrange(list_name, start, end)
          else:
              queue = []
              start, end = self.format_start_end(list_name, start, end)
              for left in range(start, end + 1, n):
                  right = min(end, left + n - 1)
                  queue += self.safe_lrange(list_name, left, right)
          return queue
      
      @retry
      def get(self, list_name, n=None):
          res = self.slicing(list_name, 0, -1, n)
          return res
      
      @retry
      def get_last(self, list_name):
          return self.redis.lindex(list_name, -1)
      
      @retry
      def pop(self, list_name):
          item = self.redis.lpop(list_name)
          return item
      
  
  class RedisSet(object):
      def __init__(self, name=None, ip=None, port=None, pwd=None, ex=3600,
                   n=None,
                  ):
          self.name = name
          if name is not None:
              ip, port = self.address(self.name, ip, port)
          self.connect(ip, port, pwd)
          self.pwd = pwd
          self.ex = ex
          self.n = n
      
      def connect(self, ip, port, pwd):
          pool = redis.ConnectionPool(host=ip, port=port, password=pwd, decode_responses=True)
          self.redis = redis.Redis(connection_pool=pool)
          self.pipe = self.redis.pipeline(transaction=False)
          self.ip, self.port = ip, port
      
      def address(self, name, ip=None, port=None):
          return None, None
      
      def retry(func):
          def wrapper(self, *args, **kwargs):
              try:
                  return func(self, *args, **kwargs)
              except Exception as err:  # pylint: disable=broad-except
                  print(err)
                  ip, port = self.address(self.name)
                  if ip is not None and port is not None:
                      self.connect(ip, port, self.pwd)
                  return func(self, *args, **kwargs)
          return wrapper
      
      @retry
      def addone(self, set_name, item, ex=None):
          self.pipe.sadd(set_name, item)
          if ex is None:
              ex = self.ex
          if ex is not None:
              self.pipe.expire(set_name, ex)
          self.pipe.execute()
      
      def safe_sadd(self, set_name, items):
          # 采用二分递归自适应批量赋值，避免单次数据量过大而失败
          try:
              self.redis.sadd(set_name, *items)
          except ResponseError:
              half = int(len(items) / 2)
              left_items = items[0:half]
              right_items = items[half:]
              self.safe_sadd(set_name, left_items)
              self.safe_sadd(set_name, right_items)
      
      @retry
      def add(self, set_name, items, ex=None, n=None):
          if not isinstance(items, (set, list)):
              items = [items]
          if not items:
              return
          items = list(items)
          if ex is None:
              ex = self.ex
          if n is None:
              n = self.n
          if n is None:
              self.safe_sadd(set_name, items)
          else:
              for idx in range(0, len(items), n):
                  self.safe_sadd(set_name, items[idx:idx + n])
          if ex is not None:
              self.pipe.expire(set_name, ex)
          self.pipe.execute()
          
      @retry
      def update(self, kvs, ex=None, n=None):
          # kvs: {k: [v1, v2, ...], ...}
          if not kvs:
              return
          if ex is None:
              ex = self.ex
          if n is None:
              n = self.n
          for idx, k in enumerate(kvs):
              vs = kvs[k]
              if not vs:
                  continue
              self.pipe.sadd(k, *vs)
              if ex is not None:
                  self.pipe.expire(k, ex)
              if n is not None and idx % n == 0:
                  self.pipe.execute()
          self.pipe.execute()
          
      @retry
      def get(self, set_name):
          items = self.redis.smembers(set_name)
          return items
      
      @retry
      def get_union(self, set_names):
          for set_name in set_names:
              self.pipe.smembers(set_name)
          sets = self.pipe.execute()
          union_sets = set().union(*sets)
          return union_sets
      
      @retry
      def get_size(self, set_name):
          size = self.redis.scard(set_name)
          return size
      
      @retry
      def isin(self, set_name, item):
          flag = self.redis.sismember(set_name, item)
          return flag
      
      @retry
      def empty(self, set_name):
          self.redis.delete(set_name)
          
  
  class RedisZSet(object):
      def __init__(self, name=None, ip=None, port=None, pwd=None, ex=3600,
                   limit_size=0, n=None,
                  ):
          self.name = name
          if name is not None:
              ip, port = self.address(self.name, ip, port)
          self.connect(ip, port, pwd)
          self.pwd = pwd
          self.ex = ex
          self.limit_size = limit_size  # limit_size > 0时取 top k，limit_size < 0 时取 bottom k，limit_size = 0 时不限制
          self.n = n
      
      def connect(self, ip, port, pwd):
          pool = redis.ConnectionPool(host=ip, port=port, password=pwd, decode_responses=True)
          self.redis = redis.Redis(connection_pool=pool)
          self.pipe = self.redis.pipeline(transaction=False)
          self.ip, self.port = ip, port
      
      def address(self, name, ip=None, port=None):
          return None, None
      
      def retry(func):
          def wrapper(self, *args, **kwargs):
              try:
                  return func(self, *args, **kwargs)
              except Exception as err:  # pylint: disable=broad-except
                  print(err)
                  ip, port = self.address(self.name)
                  if ip is not None and port is not None:
                      self.connect(ip, port, self.pwd)
                  return func(self, *args, **kwargs)
          return wrapper
      
      @retry
      def addone(self, zset_name, k, v, limit_size=0, ex=None):
          if limit_size == 0:
              limit_size = self.limit_size
          if ex is None:
              ex = self.ex
          self.pipe.zadd(zset_name, {k: v})
          if limit_size > 0:
              self.pipe.zremrangebyrank(zset_name, 0, -limit_size - 1)
          if limit_size < 0:
              self.pipe.zremrangebyrank(zset_name, -limit_size, -1)
          if ex is not None:
              self.pipe.expire(zset_name, ex)
          self.pipe.execute()
      
      def safe_zadd(self, zset_name, kv):
          # 采用二分递归自适应批量赋值，避免单次数据量过大而失败
          try:
              self.redis.zadd(zset_name, kv)
          except ResponseError:
              items = list(kv.items())
              half = int(len(items) / 2)
              self.safe_zadd(zset_name, dict(items[0:half]))
              self.safe_zadd(zset_name, dict(items[half:]))
      
      @retry
      def add(self, zset_name, kv, limit_size=0, ex=None, n=None):
          # kv: {k1: v1, k2: v2, ...}
          if not kv:
              return
          if limit_size == 0:
              limit_size = self.limit_size
          if ex is None:
              ex = self.ex
          if n is None:
              n = self.n
          if n is None:
              self.safe_zadd(zset_name, kv)
          else:
              items = kv.items()
              for idx in range(0, len(kv), n):
                  self.safe_zadd(zset_name, dict(items[idx:idx + n]))
          if limit_size > 0:
              self.pipe.zremrangebyrank(zset_name, 0, -limit_size - 1)
          if limit_size < 0:
              self.pipe.zremrangebyrank(zset_name, -limit_size, -1)
          if ex is not None:
              self.pipe.expire(zset_name, ex)
          self.pipe.execute()
          
      @retry
      def update(self, kkv, limit_size=0, ex=None, n=None):
          # kkv: {k1: {kk1: v1, kk2: v2, ...}, ...}
          if not kkv:
              return
          if limit_size == 0:
              limit_size = self.limit_size
          if ex is None:
              ex = self.ex
          if n is None:
              n = self.n
          for idx, k in enumerate(kkv):
              kv = kkv[k]
              if not kv:
                  continue
              self.pipe.zadd(k, kv)
              if limit_size > 0:
                  self.pipe.zremrangebyrank(k, 0, -limit_size - 1)
              if limit_size < 0:
                  self.pipe.zremrangebyrank(k, -limit_size, -1)
              if ex is not None:
                  self.pipe.expire(k, ex)
              if n is not None and idx % n == 0:
                  self.pipe.execute()
          self.pipe.execute()
      
      @retry
      def get_size(self, zset_name):
          return self.redis.zcard(zset_name)
      
      @retry
      def isin(self, zset_name, k):
          rank = self.redis.zrank(k)
          if rank >= 0:
              return True
          else:
              return False
      
      def format_start_end(self, zset_name, start, end):
          if start >= 0 and end < 0:
              size = self.get_size(zset_name)
              end = size + end
          return start, end
      
      def safe_zrange(self, zset_name, start, end):
          # 采用二分递归自适应批量取值，避免单次数据量过大而失败
          try:
              vs = self.redis.zrange(zset_name, start, end)
          except ResponseError:
              start, end = self.format_start_end(zset_name, start, end)
              half = int((start + end) / 2)
              vs = self.safe_zrange(zset_name, start, half) + self.safe_zrange(zset_name, half, end)
          return vs
      
      def safe_zrangebyscore(self, zset_name, start_v, end_v):
          # 采用二分递归自适应批量取值，避免单次数据量过大而失败
          try:
              vs = self.redis.zrangebyscore(zset_name, start_v, end_v)
          except ResponseError:
              half_v = int((start_v + end_v) / 2)
              vs = \
                  self.safe_zrange(zset_name, start_v, half_v) + self.safe_zrange(zset_name, half_v, end_v)
          return vs
      
      @retry
      def slicing(self, zset_name, start=0, end=-1, n=None):
          # 根据编号筛选数据
          if n is None:
              n = self.n
          if n is None:
              res = self.safe_zrange(zset_name, start, end)
          else:
              res = []
              start, end = self.format_start_end(zset_name, start, end)
              for left in range(start, end + 1, n):
                  right = min(end, left + n - 1)
                  res += self.safe_zrange(zset_name, left, right)
          return res
      
      @retry
      def ranging(self, zset_name, start_v, end_v):
          # 根据范围筛选数据
          res = self.safe_zrangebyscore(zset_name, start_v, end_v)
          return res
      
      @retry
      def ranging_union(self, zset_names, start_v, end_v):
          for zset_name in zset_names:
              self.pipe.zrangebyscore(zset_name, start_v, end_v)
          items_lst = self.pipe.execute()
          union_items = chain(*items_lst)
          return union_items
      
      @retry
      def get(self, zset_name, n=None):
          res = self.slicing(zset_name, 0, -1, n)
          return res
      
      @retry
      def trim(self, zset_name, limit_size=0):
          if limit_size == 0:
              limit_size = self.limit_size
          if limit_size > 0:
              self.pipe.zremrangebyrank(zset_name, 0, -limit_size - 1)
          if limit_size < 0:
              self.pipe.zremrangebyrank(zset_name, -limit_size, -1)
          self.pipe.execute()
  
          
  class RedisTS(object):
      def __init__(self, name=None, ip=None, port=None, pwd=None, ex=3600,
                   limit_len=None, limit_time=None, n=None,
                  ):
          self.name = name
          if name is not None:
              ip, port = self.address(self.name, ip, port)
          self.connect(ip, port, pwd)
          self.pwd = pwd
          self.ex = ex
          self.limit_len = limit_len
          self.limit_time = limit_time
          self.n = n
      
      def connect(self, ip, port, pwd):
          pool = redis.ConnectionPool(host=ip, port=port, password=pwd, decode_responses=True)
          self.redis = redis.Redis(connection_pool=pool)
          self.pipe = self.redis.pipeline(transaction=False)
          self.ip, self.port = ip, port
      
      def address(self, name, ip=None, port=None):
          return None, None
      
      def retry(func):
          def wrapper(self, *args, **kwargs):
              try:
                  return func(self, *args, **kwargs)
              except Exception as err:  # pylint: disable=broad-except
                  print(err)
                  ip, port = self.address(self.name)
                  if ip is not None and port is not None:
                      self.connect(ip, port, self.pwd)
                  return func(self, *args, **kwargs)
          return wrapper
      
      def datestr2timestamp(self, datestr):
          # 将字符串格式时间转换成时间戳
          if isinstance(datestr, str):
              return parse_datetime(datestr).timestamp()
          else:
              return datestr
      
      def data2kv(self, ts_data):
          # ts_data: [(t1, data1), (t2, data2), ...]
          ts_kv = {json_dumps([t, data]): self.datestr2timestamp(t)
                   for t, data in sorted(ts_data, key=lambda item: item[0])
                  }
          return ts_kv
      
      def res2data(self, res, step=None):
          if step is not None:
              res = res[::step]
          ts_data = [tuple(json_loads(item)) for item in res]
          return ts_data
      
      @retry
      def record(self, ts_name, data, t=None, limit_len=None, ex=None):
          if t is None:
              t = int(time.time())
          ts_data = [(t, data)]
          ts_kv = self.data2kv(ts_data)
          self.pipe.zadd(ts_name, ts_kv)
          if limit_len is None:
              limit_len = self.limit_len
          if limit_len is not None:
              self.pipe.zremrangebyrank(ts_name, 0, -limit_len - 1)
          if ex is None:
              ex = self.ex
          if ex is not None:
              self.pipe.expire(ts_name, ex)
          self.pipe.execute()
      
      def safe_zadd(self, ts_name, kv):
          # 采用二分递归自适应批量赋值，避免单次数据量过大而失败
          try:
              self.redis.zadd(ts_name, kv)
          except ResponseError:
              items = list(kv.items())
              half = int(len(items) / 2)
              self.safe_zadd(ts_name, dict(items[0:half]))
              self.safe_zadd(ts_name, dict(items[half:]))
      
      @retry
      def update_ts(self, ts_name, ts_data, limit_len=None, ex=None, n=None):
          # 更新时间序列，不要求按时间排序
          # ts_data: [(t1, data1), (t2, data2), ...]
          if not ts_data:
              return
          if limit_len is None:
              limit_len = self.limit_len
          if ex is None:
              ex = self.ex
          if n is None:
              n = self.n
          if n is None:
              ts_kv = self.data2kv(ts_data)
              self.safe_zadd(ts_name, ts_kv)
          else:
              for idx in range(0, len(ts_data), n):
                  ts_kv = self.data2kv(ts_data[idx:idx + n])
                  self.safe_zadd(ts_name, ts_kv)
          if limit_len is not None:
              self.pipe.zremrangebyrank(ts_name, 0, -limit_len - 1)
          if ex is not None:
              self.pipe.expire(ts_name, ex)
          self.pipe.execute()
      
      @retry
      def record_flow(self, ts_flow, limit_len=None, ex=None, n=None):
          # 同时写入多条时间序列的流水数据，不要求按时间排序
          # ts_flow: [(ts_name1, t1, data1), (ts_name2, t2, data2), ...]
          if not ts_flow:
              return
          if limit_len is None:
              limit_len = self.limit_len
          if ex is None:
              ex = self.ex
          if n is None:
              n = self.n
          for idx, (ts_name, t, data) in enumerate(ts_flow):
              ts_data = [(t, data)]
              ts_kv = self.data2kv(ts_data)
              self.pipe.zadd(ts_name, ts_kv)
              if limit_len is not None:
                  self.pipe.zremrangebyrank(ts_name, 0, -limit_len - 1)
              if ex is not None:
                  self.pipe.expire(ts_name, ex)
              if n is not None and idx % n == 0:
                  self.pipe.execute()
          self.pipe.execute()
      
      @retry
      def get_len(self, ts_name):
          # 获取时间序列长度
          return self.redis.zcard(ts_name)
      
      @retry
      def get_last(self, ts_name, only_timestamps=False):
          # 获取最新的数据
          if only_timestamps:
              last = self.redis.zrange(ts_name, start=-1, end=-1, withscores=True)
              if last:
                  _, last_time = last[-1]
                  return last_time
              else:
                  return None
          else:
              last = self.redis.zrange(ts_name, start=-1, end=-1)
              last = self.res2data(last)
              if last:
                  last_time, last_data = last[-1]
                  return last_time, last_data
              else:
                  return None, None
      
      def format_start_end(self, ts_name, start, end):
          if start >= 0 and end < 0:
              size = self.get_len(ts_name)
              end = size + end
          return start, end
      
      def safe_zrange(self, ts_name, start, end):
          # 采用二分递归自适应批量取值，避免单次数据量过大而失败
          try:
              vs = self.redis.zrange(ts_name, start, end)
          except ResponseError:
              start, end = self.format_start_end(ts_name, start, end)
              half = int((start + end) / 2)
              vs = self.safe_zrange(ts_name, start, half) + self.safe_zrange(ts_name, half, end)
          return vs
      
      def safe_zrangebyscore(self, ts_name, start_timestamp, end_timestamp):
          # 采用二分递归自适应批量取值，避免单次数据量过大而失败
          try:
              vs = self.redis.zrangebyscore(ts_name, start_timestamp, end_timestamp)
          except ResponseError:
              half_timestamp = int((start_timestamp + end_timestamp) / 2)
              vs = \
                  self.safe_zrangebyscore(ts_name, start_timestamp, half_timestamp) + \
                  self.safe_zrangebyscore(ts_name, half_timestamp, end_timestamp)
          return vs
      
      @retry
      def slicing(self, ts_name, start=0, end=-1, n=None, step=None):
          # 根据编号筛选数据
          if n is None:
              n = self.n
          if n is None:
              res = self.safe_zrange(ts_name, start, end)
          else:
              res = []
              start, end = self.format_start_end(ts_name, start, end)
              for left in range(start, end + 1, n):
                  right = min(end, left + n - 1)
                  res += self.safe_zrange(ts_name, left, right)
          ts_data = self.res2data(res, step)
          return ts_data
      
      @retry
      def ranging(self, ts_name, start_time, end_time, step=None):
          # 根据时间范围筛选数据
          start_timestamp = self.datestr2timestamp(start_time)
          end_timestamp = self.datestr2timestamp(end_time)
          res = self.safe_zrangebyscore(ts_name, start_timestamp, end_timestamp)
          ts_data = self.res2data(res, step)
          return ts_data
      
      @retry
      def get(self, ts_name, only_timestamps=False, n=None):
          if only_timestamps:
              res = self.redis.zrange(ts_name, 0, -1, withscores=True)
              if res:
                  ts_data, times = zip(*res)
                  return times
              else:
                  return []
          else:
              ts_data = self.slicing(ts_name, 0, -1, n)
              return ts_data
          
      @retry
      def sliding_window(self, ts_name, time_window, start_time=None, end_time=None, step=None):
          # 获取滑动窗内的数据
          if start_time is not None:
              start_timestamp = self.datestr2timestamp(start_time)
              end_timestamp = start_timestamp + time_window
              return self.ranging(ts_name, start_timestamp, end_timestamp, step)
          if end_time is not None:
              end_timestamp = self.datestr2timestamp(end_time)
              start_timestamp = end_timestamp - time_window
              return self.ranging(ts_name, start_timestamp, end_timestamp, step)
          end_timestamp = self.get_last(ts_name, only_timestamps=True)
          if end_timestamp:
              start_timestamp = end_timestamp - time_window
              return self.ranging(ts_name, start_timestamp, end_timestamp, step)
          else:
              return []
          
      @retry
      def slicing_trim(self, ts_name, limit_len):
          # 裁剪时间序列，仅保留最近的 limit_len 个数据
          self.redis.zremrangebyrank(ts_name, 0, -limit_len - 1)
          
      @retry
      def ranging_trim(self, ts_name, limit_time):
          # 裁剪时间序列，仅保留最近的 limit_time 范围内的数据
          last = self.get_last(ts_name, with_timestamp=True)
          if last:
              end_timestamp, _ = last
              if end_timestamp > limit_time:
                  self.redis.zremrangebyscore(ts_name, 0, end_timestamp - limit_time)
      
      @retry
      def trim(self, ts_name, limit_len=None, limit_time=None):
          # 裁剪时间序列
          if limit_len is None:
              limit_len = self.limit_len
          if limit_len is not None:
              self.slicing_trim(ts_name, limit_len)
          if limit_time is None:
              limit_time = self.limit_time
          if limit_time is not None:
              self.ranging_trim(ts_name, limit_time)
          
          
      
  '''RedisTS数据结构
  ts_data: [(t1, data1), 
            (t2, data2), 
            ...
           ]
  ts_kv: {(t1, data1): timestamp1,
          (t2, data2): timestamp2,
          ...
         }
  
  ts_name: {timestamp1: (t1, data1),
            timestamp2: (t2, data2),
            ...
           }
  '''
      
  if __name__ == '__main__':
      pass
  