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需要手动开启。

原文地址:https://www.cnblogs.com/k5210202/p/13080357.html