zoukankan      html  css  js  c++  java
  • redis基本使用

    官网介绍

    Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

    它支持多种类型的数据结构,包括:

    Redis 内置功能:

    redis的用途:

    • k-v数据库
    • 消息中间件:主要通过内部list数据结构实现
    • 缓存

    支持主流的开发语言例如python,GO,java,C++,C等

    库操作

    redis默认有16个库,安装redis后回安装redis得客户端程序,客户端程序安装目录和服务端程序在同一个路径下,执行redis-cli使用客户端连接服务,并默认使用0号库进行操作。客户端中可以使用命令进行操作。

    select n              # 选择第n号库  0<=n<=15
    也可以在进入客户端时候直接指定使用得库
    reids-cli -n num      # num 即库得编号
    
    flushdb               # 清除该库中内容
    flushall              # 清除所有库中得内容

    通过python操作redis

    python中使用redis-py库连接redis进行操作

    安装:pip install redis

    import redis
    
    db = redis.Redis(host="localhost", port="6379", db=0) # 常用得三个参数,主机,端口和库名
    
    # 返回一个db对象,为库对象。库对象方法对应了自带客户端中操作命令。
    db.set("user", "tom")    # 对应命令  set user tom 命令

    redis的数据模型

    key

    redis的key实际储存的一个二进制值,可以使用任何二进制序列作为key,通常使用字符串,空串也是合法的key

    key虽然可以是任意的二进制序列,但是为了可读性和计算性能以及内存大小的考虑,都需要简单易读的。习惯性采用:分割关键信息,例如user:123:password这个key值代表123号用户的密码。简单且易读。

    使用客户端时,我们输入key值和储存的真实key值可能存在一定的差异。下面是实例

    import redis
    
    db.redis.Redis(host="127.0.0.1", port="6379", db=1)  # 连接并选择库(0-15),返回库对象
    # 调用方法直接操作
    
    # 数值类型储存在redis中的真实数据是
    db.set("k1", 0x63)        # 0x63对应10进制的99, 所以储存的值实际上是字符串“99”
    db.set("k2", "63")        # 而值本身为字符串,所以直接储存
    db.set(97, "97")          # 10进制的97作为key也会自动转化为"97"进行储存

    所以使用python作为客户端操作redis储存数值类型时候, 实际上是将数值类型的十进制字符串进行储存。set和get函数内部完成了该功能。但是一般使用字符串作为key即可。

    keys的命令

    keys命令根据模式匹配获取查看key信息

    keys(partten="*")  # 类似正则的表达
        * 任意长度,任意字符
        ?任意单个字符
        [] 字符集合
        取消这些字符的特殊匹配使用转义即可
    exists(name)       # 判断是否存在
    type(name)         # 返回key对应值的类型, 一个字符串表示
    rename(scr, dst)   renamenx(src, dst)  # 重命名,不存在新建
    del(name)          # 删除key

    每一个key可以储存redis内部的任意数据结构,其value的值类型可以是以下的类型。

    字符串

    字符串类型,实际上是任意一种可序列化的数据,单个这样得数据最大为512M,也就是2*8*1024*1024字节的数据

    添加字符串
    
    # 增加单个值
    set(self, name, value, ex=None, px=None, nx=False, xx=False)
        过期时间:ex=秒, px=毫秒
        设置限制:nx=key必须不存在,xx=key必须存在才设置
        setex(self, name, time, value)
        psetex(self, name, time_ms, value)
        setnx(self, name, value)
    
    # 设置多个值,多值设置是原子操作,全部成功或者失败
    mset(self, mapping)     # 基于字典,设置多个值
    msetnx(self, mapping)   # key不存在才设置
    
    
    获取字符串
    get(key)
    mget(keys, *args)   # 获取key的值
    getset(key, value)  # 获取,如果没有则设置该key-value值
    strlen(key)         # 返回值的长度,字节数
    
    
    片段操作
    append(key, value)             # key存在,在字符串后追加,不存在设置,返回新得长度
    getrange(key, start, end)      # 查找到得字符串进行[start:end]切片,前包后包
    setrange(key, offset, value)   # 从原字符串指定位置插入并覆盖
    
    
    自增自减
    使用incr key 和 decr key 命令对存储在指定key的数值执行原子的加1和减1操作。
    incrby 和 decrby 可以指定增减的值。
    db.incr(key)            #  key对应的值+1,数字的字符串
    db.incrby(key, amount)  #  key对应的值+amount

    过期操作和生存时间

    设置过期
    expire(self, name, time)      # 指定时常过期
    expireat(self, name, when)    # 指定过期时间点,when为时间戳或者datetime对象
    pexpire和pexpireat是毫秒级的
    
    取消过期
    persist(self, key)   # 取消该key的过期
    
    查看key剩余时间
    ttl(self, name)   # 返回过期时间,返回-1说明永久有效,-2表示该key不存在
    pttl(self, name)

    位图bitmap

    位图是按照位来操作字符串。计算机中储存的都是二进制的0和1,一条数据例如字符a 的ascii码位97, 二进制储存方式即为 0110 0001, 而位图操作则是对其中某一位进行操作。

    位图通常作为标记量,分别用0和1标记两种状态,例如一个电影院将已出售的座位标记为1,未出售的标记为0,这种标记量不需要占用一个字节的空间,使用一位即可表示,而单个字符串的最大长度为512M,总位数可以到达42亿,单个字符串就足够使用。

    redis提供了对字符串每个位进行操作的接口

    setbit(self, name, offset, value)     # 将偏移量为offset位置的值设置为value, value默认为0
    getbit(self, name, offset)            # 获取offset位置上的值
    
    bitpos(self, key, bit, start=None, end=None)  # 返回为bit(0或1)的第一个位置,在start:end的范围内
    
    bitop(self, operation, dest, *keys)   # 将多个bitmap进行位运算 
        operation:运算方式,支持AND与、OR或、NOT非、XOR异或
        *keys,需要进行计算的key对应bitmap,NOT运算只能有一个
        dest:计算结果保存
    bitcount(self, key, start=None, end=None)     # 计算该范围内的所有1的个数
    
    bitfield(self, key, default_overflow=None)
        bitfield是一个更加复杂但是更加灵活的使用位图的命令,它可以将一个位序列中任意一个片段作为一个指定的
        有符号或无符号数进行操作。包括get值,set,incr,decr的进行增减

    bitfield使用详解

    bitfield可以将任意的位序列指定为有符号或者无符号整数来识别,解析一段bit序列时候,需要指定这段位序列"解码方式",如 u8,表示使用无符号8位整数来解析这段bit序列,而i8表示使用有符号8位整数来解析。如果将 “1000 0001”这个二进制序列使用u8解析,其值为129,而使用i8有符号,值为-1(首位为1为负数)

    set key abc   # 储存了字符串abc,储存的真实二进制为,01100001 01100010 01100101(97,98,99)
    
    # bitfiled的基本语法形式 BITFIELD key GET u/i OFFSET  ,  BITFIELD key SET u/i OFFSET value
    
    获取值
    bitfield key get u8 0
        # get指定 key的值,结果偏移0位,然后使用u8解析,abc的二进制为 01100001 0..., 偏移0,从第一位解析,
        # 结果为01100001对应的无符号整数,即为97
    bitfield key get i5 3 
        # 偏移三位,得到00001 01...,使用i5(有符号5位整数),取前5位00001,结果为1
    
    set值
    bitfield key set u5 3 2   # 原key偏移三位后的5个bit位为00001,所以返回1,并将这5位重新赋值为2

    位图的操作常常作为用户上线次数统计,统计用户上线日期,上线天数等,如果每个字节为8位,一周七天,使用每个字节前7位作为统计,只需要一个52个字节的字符串就能储存用户的一年的上线天数和对应的上线日期,或者使用5个字节按月份进行储存, 使用60个字节即可。

    列表List

    列表是基于双向链表实现的,列表两端均可以操作且效率高,列表中的元素数据是字符串,可以重复出现,支持正负索引访问。

    常用命令

    列表的双端均可以操作,使用L,R指定操作头或者尾部,push表示在该位置推入一个元素,pop表示弹出

    llen(self, name)     # 返回长度
    
    # 增删改查
    lpop(self, name)             
    lpush(self, name, *values)       # push时key不存在,会新建key和List
        # push可以同时添加多个数据,push key a,b,c 进入的顺序是 c,b,a
    lpushx(self, name, value)        # 必须存在该key,且是list才会操作
    lrem(self, name, count, value)   # 移除指定位置数据
    rpop(self, name)
    rpoplpush(self, src, dst)
    rpush(self, name, *values)       
    rpushx(self, name, value)
    # 阻塞系 B..
    bpop(self, name, timeout=0)    # timeout:阻塞时间,为0表示没有数据可以pop会一直阻塞等待。
         其他阻塞方法相同
    lindex(self, name, index)        # 获取指定位置的元素
    lset(self, name, index, value)   # 设置指定位置的值
    linsert(self, name, where, refvalue, value)  # 指定位置插入一个元素
    
    lrange(self, name, start, end)
    ltrim(self, name, start, end)    # 剪枝,只留下start:end范围内的数据,start,end可以超界

    hash散列

    在一个key中储存了一个散列,散列中各个元素使用field:value键值对来表示

    hdel(self, name, *keys)     # 删除指定field
    hexists(self, name, key)    # 判断存在field
    
    hget(self, name, key)       # 获取指定field
    hmget(self, name, keys, *args)   # 获取多个field的值
    hincrby(self, name, key, amount=1)   # hincrbyfloat, 自增,
    
    hkeys(self, name)           # 所有的field
    hvals(self, name)                # 所有的值
    hgetall(self, name)         # 获取hash中的全部
    hlen(self, name)            # hash表中的个数
    
    hset(self, name, key, value)     # 添加元素
    hsetnx(self, name, key, value)   # 不存在才添加
    hmset(self, name, mapping)       # 添加多个值
            
    hstrlen(self, name, key)         # 指定field值得长度

    hash散列相当于一个嵌套的mapping结构,例如key为一个user, 则user对应的哈希散列中可以记录name,age,addr等键值对信息。如果不使用hash散列,直接使用字符串的方式记录就需要记录则需要大量独立键记录,reids得键在维护时储存一些管理信息,比如类型,长度,最后一次访问时间等。

    set集合

    集合内部可以储存多个字符串元素,各个元素是无序的,去重的。

    sadd(self, name, *values)             #
    smove(self, src, dst, value)          # src中移动value元素到dst中
    spop(self, name, count=None)          # 随机弹出一个
    srem(self, name, *values)             # 删除指定的元素
    
    scard(self, name)                     # 返回个数
    
    sdiff(self, keys, *args)              # args中不存在于集合中的元素,o(n)
    sdiffstore(self, dest, keys, *args)   # 将args中不存在的元素储存在新得key的结果集中
    
    sinter(self, keys, *args)             # 返回多个key对应集合的交集
    sinterstore(self, dest, keys, *args)  # 将与args中的交集储存在新的key中
    
    sunion(self, keys, *args)             # 返回多个key对应集合的并集
    sunionstore(self, dest, keys, *args)  # 将并集结果储存在dest中
    
    sismember(self, name, value)          # 是否是集合的成员
    smembers(self, name)                  # 查看所有的元素
    
    srandmember(self, name, number=None)  # 返回n个元素,number>0不放回拿取,<0放回拿取,为0返回1个

    有序集合

    sortedSet在set的基础上添加了一个浮点数分值score, 用于对这个集合进行排序,如果分数值相同将会按照字符串字母排序(ascii码)。sortedSet中的元素一旦存入就会对其进行排序,并从小到大排列。

    zadd(self, name, mapping, nx=False, xx=False, ch=False, incr=False)
                # mapping为{分值:元素}
    zscore(self, name, value)             # 查看指定元素score分值
    zcard(self, name)                     # 元素总数
    zcount(self, name, min, max)          # 分值为(min, max)范围的个数
    
    zincrby(self, name, amount, value)    # 为成员value增加分数值amount,key不存在新增
    zunionstore(self, dest, keys, aggregate=None)   # 并集
    zinterstore(self, dest, keys, aggregate=None)   # 交集存入新key
                                          # aggregate指定分数聚合方式,可以SUM,Max, min,
                                          # key对应的set后接weights参数指定该set的指定权重
    
    zlexcount(self, name, min, max)  # 集合中两个元素之间的元素个数,"[a, [b",元素a,b之间,“— +”最小最大
    
    zpopmax(self, name, count=None)  # zpopmin 从set中弹出socre最大值或最小值的元素
    zbzpopmax(self, keys, timeout=0) # 可以从多个集合中弹出最大的,返回(集合名,分数值,value),timeout设置阻塞时间。
    
    # lex的含义:元素分值作为判断,min,max为元素
    zrange(self, name, start, end, desc=False, withscores=False, score_cast_func=float)
        # 低到高输出,超界不报错,desc降序,withscores输出分值,
    zrangebylex(self, name, min, max, start=None, num=None)
        # min和max元素得分值范围内,start:结果跳过几条,类似offset, num返回的条数,类似limit
    zrevrangebylex(self, name, max, min, start=None, num=None)
    
    # 直接使用, mix和max为分值,分值还包括两个特殊值,-inf和+inf表示最小值和最大值
    zrangebyscore(self, name, min, max, start=None, num=None,withscores=False, score_cast_func=float)
    
    # 排名:
    有序集合会按照score值从小到大排列,最小值排名为0
    zrank(self, key, member)   # 返回该元素的排名  zrecrank反向排名
    
    zrem(self, name, *values)  # 删除指定的成员们
    zremrangebylex(self, name, min, max)   # 删除min元素到max元素这个区间内的元素
    zremrangebyscore(self, name, min, max) # 删除指定score分数范围内的元素
    zremrangebyrank(self, name, min, max)  # 删除指定排名的元素,例如3-5名删除

    redis连接池对象

    使用连接池是为了更好的管理连接,避免在不断的通信过程中反复的创建连接和断开连接,造成资源浪费。使用连接池可以根据需求建立合适数量的连接数,反复使用。

    redis.Connection连接类

    用于和redis服务器建立连接的基础类,每一个实例对应一条tcp连接,并提供了与redis服务器实现基本通信的方法。其子类SSLConnection, UnixDomainSocketConnection都继承于该类。

    class Connection:   # 实例化参数
        def __init__(self, host='localhost', port=6379, db=0, password=None,
                     socket_timeout=None, socket_connect_timeout=None,
                     socket_keepalive=False, socket_keepalive_options=None,
                     socket_type=0, retry_on_timeout=False, encoding='utf-8',
                     encoding_errors='strict', decode_responses=False,
                     parser_class=DefaultParser, socket_read_size=65536,
                     health_check_interval=0): pass

    一个连接对象包含了上面的参数信息,包括地址,端口,数据库编号,密码,单次消息超时时间,是否为keepalive状态等基本的信息,这些信息用于和reids服务器建立一个socket对象并建立通信,通常配置ip端口,数据库编号即可。

     

    连接对象并不会直接连接服务器,需要调用其connect方法才会真的创建连接,并返回一个sokcet对象用于和服务器通信。connection对象中定义了以下的方法

    # 向一个列表中注册或清除callback(可调用对象即可), 连接建立完成后会遍历列表依次调用callback(self)
    def register_connect_callback(self, callback)
    def clear_connect_callbacks(self)
    
    def connect(self)      # 建立连接,得到sock对象,如果sock对象已存在,直接返回
    def disconnect(self)   # 断开连接,关闭sock对象
    def on_connect(self)   # 第一次connect连接后初始化redis,进行密码验证(如果有)和选择redis的库

    redis.ConnectionPool连接池

    连接池相当于一个连接对象的容器,用于管理多个连接对象。

    ConnectionPool:
        def __init__(self, connection_class=Connection, max_connections=None, **connect_kwargs):
            self.connection_class = connection_class       # 用于创建连接的类,默认为内置的Connection
            self.connection_kwargs = connection_kwargs     # 连接的参数,作为连接类的参数,
            self.max_connections = max_connections or 2 ** 31    # 最大连接数
            self.pid = os.getpid()                         # 获取pid
            self._created_connections = 0                  # 已经创建连接的数量
            self._available_connections = []               # 可用连接数,连接正常,可以使用
            self._in_use_connections = set()               # 正在被使用中的连接,暂时无法提供服务
            self._check_lock = threading.Lock()            # 锁,保证线程安全  

    通过连接池发送数据给服务器,将会从可用链接中获取一个链接发送,如果没有可用链接则创建链接(未达到最大连接数)或者等待其他链接使用完毕。

    直接实例化连接池类,并通过**connect_kwargs参数传参可以得到一个池对象,也可以调用该类的类方法ConnectionPool.from_url使用url的方式创建:from_url(cls, url, db=None, decode_components=False, **kwargs)使其中**kwargs参数会作为ConnectPool(**args)参数,根据__init__方法传参即可

    from reids import Redis, ConnectionPool
    # pool = redis.ConnectPool(host="", port="", db="", max_connections=10)  # 直接实例化
    pool = redis.ConeectionPool.from_url("redis://192.168.236.100:6379/8", max_connections=10)  #

    连接池的基本使用

    redis客户端每次执行一条命令,就会向服务器发送一次请求,并得到返回的结果

    from redis import Redis, ConnectionPool
    pool = redis.ConeectionPool.from_url("redis://192.168.236.100:6379/8", max_connections=10)
    
    # 得到db对象
    db = Redis(connection_pool=pool)  # 客户端提供了使用pool创建的接口
    db.keys()           # 发送keys * 的指令。

    使用连接池后,可以在多条线程与服务器通信时,提供稳定的连接,避免了每次命令的执行后,直接销毁连接,下一次又重新创建。

    Pipeline

    Pipeline对象是可以将一个个redis命令暂存到一个"管道"中,不立即执行。当执行excute方法时候才会将这些命令全部执行。并且单次excute的命令具有原子性,即这些命令要么全部执行成功,否则将全部失败,回到执行前的状态。

    import redis
    
    db = redis.Redis()
    pipe = db.pipeline()          # 通过db对象的pipeline 方法得到一个pipeline对象。
    
    # 直接调用各种方法操作
    pipe.set("abc", 123)
    pipe.sadd("key1", "tom","jack", "peter")
    
    pipe.excute()     # 执行excute,上面的命令才会被全部执行
    
    # pipe.set("abc", 123).sadd("key1", "tom","jack", "peter").excute()   # 链式执行,每次返回pipe自身

    redis持久化

    redis数据储存在内存中,掉电易失,一旦服务器服务器发生故障或者服务重启,服务崩溃,这些存在与内存中的数据将不复存在,所以redis提供了两种持久化的方案,RDB(Redis DB)和AOF(AppendOnlyFile)

    RDB

    持久化方式

    将某一时刻内存中的数据直接序列化,写入一个文件中来保存内存中的数据,恢复数据时,直接读取文件中的内容恢复到内存中。默认情况下,会将数据持久化到/var/lib/redis目录下名为dump.rdb的文件中。一个二进制文件。

    持久化策略

    我们可以手动或者设置自动的调用BGASAVE或者SAVE命令执行持久化操作,BGSAVE使用其他线程执行,不阻塞当前的线程。

    配置策略

    文件备份时间间隔过长,一旦服务故障将可能丢失更多的数据,时间过短会浪费大量的性能在数据备份上,综合考虑redis使用了下面的备份方式,启动时配置文件中的配置如下。

    save 900                   # 900秒内,发生了至少一次key值改动则持久化
    save 300 10                # 300秒内发生了10次改动则持久化
    save 60 10000              # 60秒内发生了10000次改动则立即持久化
    dir /var/lib/redis/6379      # 持久化文件位置

    持久化文件会不断的覆盖更新上一次的内容,也就是只能保存最新的一次持久化数据。这种方式每次将内存中全部数据备份,耗费时间较多,但是恢复数据快。只能恢复最后一次备份的内容,最新更新但未来得及备份的数据将会丢失

    AOF

    AOF(append only file)采用追加的方式保存,默认文件为appendonly.aof, 在该文件中记录了所有成功执行的写操作的命令 ,恢复内存中数据时,将该文件中的命令依次执行即可恢复。

    写入策略

    AOF的append文件操作并非每条命令写入,而是将内容放入缓冲区,待一定大小或一定时间一次性写入一次,主要为了降低磁盘读写数从而提高性能。但是在为写入时如果服务崩溃,缓冲区数据将会丢失,这部分数据将无法恢复。

    配置

    appendfsync:
      always    # 每一条命令都立即写入磁盘,最多丢失一次写入
      everysec  # 默认,每一秒调用一次,最多丢失1秒内的写入
      No        # 不主动调用,由操作系统决定 

    一般使用everysec和No模式,always模式较影响redis的性能。

    AOF重写

    文件中记录的命令可能存在相反的操作。例如set abc 123设置abc后del abc删除了,则该操作不用执行,得到的数据和执行后是相同的。这就是AOF文件的重写,从而降低执行命令数,提高效率。

    • 重写会新启一个进程完成,实际上是对aof文件中命令的简化,该进程会创建临时文件保存重写后的内容
    • 原进程会开辟内存缓冲区接受新的命令
    • 重写完成后,父进程获得完成一个信号,将父进程新命令写入文件保存,等待下一次重写。

    重写触发

    • 手动执行 BGREWRITEAOF命令
    • 自动执行
      • 配置文件中配置文件达到指定大小后执行:auto-aof-rewrite-min-size <size> 
      • 达到上次aof文件体积的百分比后执行:auto-aof-rewrite-percentage <percent>,如果该值为0关闭重写
    auto-aof-rewrite-min-size 64mb  
    auto-aof-rewrite-percentage 100
    
    appendonly yes      # 默认关闭aof,配置开启

    使用AOF方式恢复速度较RDB更慢,但是使用fysnc每秒写入不会造车大量的数据丢失。在默认一般情况下使用RDB,如果使用AOF需要手动开启。

  • 相关阅读:
    使用DRF视图集时自定义action方法
    DRF视图集的路由设置
    DRF视图集的使用
    DRF最高封装的子类视图
    SQL Stored Procedure and Function
    Struts & Hibernate & Spring
    Java Knowledge series 5
    Android OS Startup
    Java Knowledge series 4
    Refactoring in Coding
  • 原文地址:https://www.cnblogs.com/k5210202/p/13080357.html
Copyright © 2011-2022 走看看