redis基本使用
官网介绍
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
它支持多种类型的数据结构,包括:
Redis 内置功能:
- 复制(replication)
- LUA脚本(Lua scripting)
- LRU驱动事件(LRU eviction)
- 事务(transactions)
- 不同级别的 磁盘持久化(persistence)
- 高可用:通过 Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)。
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需要手动开启。