[TOC]
Introduction
Redis 是完全开源免费的,是一个高性能的key-value数据库。
性能极高,Redis数据库依赖于主存,在关系型数据库以外再配套Redis管理缓存数据将对性能会有很大的提升
redis是什么?
redis是一种基于内存的数据库,可以用作数据库、缓存、消息中间件,是一个高性能的key-value数据库,单进程单线程,支持10W QPS(每秒内的查询次数)
redis是单线程单进程模型,为什这么快?
1.Redis 完全基于内存,绝大部分请求是纯粹的内存操作,非常迅速,
2.数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度是 O(1)。
3.数据结构简单,对数据操作也简单。采用单线程,避免了不必要的上下文切换和竞争条件,不存在多线程导致的 CPU 切换,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有死锁问题导致的性能消耗。
4.使用多路复用 IO 模型(同时监控多个IO,阻塞 IO)。
为什么选择 Redis 的缓存方案而不用 Memcached 呢?
1.存储方式上:Memcache 会把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis 有部分数据存在硬盘上,这样能保证数据的持久性。
2.数据支持类型上:Memcache 对数据类型的支持简单,只支持简单的 key-value,,而 Redis 支持五种数据类型。
3.使用底层模型不同:它们之间底层实现方式以及与客户端之间通信的应用协议不一样。Redis 直接自己构建了 VM 机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4.Value 的大小:Redis 可以达到 1GB,而 Memcache 只有 1MB。
Redis 的持久化策略
RDB:快照形式是直接把内存中的数据保存到一个 dump 的文件中,定时保存,保存策略。AOF:把所有的对 Redis 的服务器进行修改的命令都存到一个文件里,命令的集合。Redis 默认是快照 RDB 的持久化方式。# install
centos安装
$ wget http://download.redis.io/releases/redis-5.0.5.tar.gz
$ tar xzf redis-5.0.5.tar.gz
$ cd redis-5.0.5
$ make
$ /home/jw/software/redis-5.0.7/src/redis-server
# 打开另一个窗口
$ /home/jw/software/redis-5.0.7/src/redis-cli
配置(centos7, redis源码安装为例)
# 设置模式, mac才能连接,临时修改
config get protected-mode #yes
config set protected-mode no
# 修改默认配置文件,并重新启动redis
#/home/jw/software/redis-5.0.7/redis.conf
#注释掉
# bind 127.0.0.1
# 开启守护进程,即后台启动(vim搜索/daemonize)
daemonize yes
protected-mode no
注释掉这一行
# okip-grant-tables
# 重新启动redis
cd /home/jw/software/redis-5.0.7/src
./redis-server ../redis.conf
# 如果报错,ConnectionRefusedError: [Errno 61] Connection refused
# 关闭centos7防火墙
systemctl stop firewalld.service
systemctl disable firewalld.service
# 关闭redis-server
src/redis-cli shutdown
command
Help
Help <tab> 指定切换帮助????????
Help set
Help @string 查看命令组帮助
1.字符串
字符串是一种最基本简单的Redis值类型。说是字符串,其实可以是任意可以序列化的数据。 一个字符串类型的值最多能存储512M字节(521MB)的内容。
key value type
# 如果值,是数字(二进制,十进制,十六进制)例子
db = redis.Redis('172.16.177.160', port=6379)
print(db.keys('*'))
db.set('testbin', 0b01100010) # 0x62 十六进制62,ascii表对应十进制98
print(db.get('testbin')) # b'98' 占了2个字节
db.set('testbin1', 11)
print(db.get('testbin1')) # b'11'
db.set('testbin2', 0x63) # 十六进制63,ascii表对应十进制99
print(db.get('testbin2')) # b'99'
# redis的键,默认都转换为字符串的例子:
# 键为数字,使用get方法时,会先转换成十进制的数字,然后再以字符串的方式显示
# 所以get时,有多种方法。
db.set(0b11, 99) # 99等价0x63
print(db.get(0b11)) # b'99'
print(db.get(3)) # b'99'
print(db.get('3')) # b'99'
string setting
set key value [EX seconds][PX millseconds][NX|XX]
EX 设置过期时间,秒
PX 设置过期时间,毫秒
NX 键不存在才能设置
XX 键存在才能设置
mset key value [key value ...] #设置多个键值对,key存在则覆盖,key不存在则增加 原子操作
msetnx key value [key value ...] #设置多个键值对,key不存在才能设置,key存在则设置失败,原子操作
# 例子:
set k1 a k2 b
setnx k4 d # 等价set k4 d nx
set k5 e ex 5 # equal to setex k5 5
mset aa 1 bb 2
msetnx k1 a k3 c
# 获取字符串
get key
mget key [key ...]
getset key value #如果存在设置新值,不存在,则创建一个键值对
# 修改及查看
strlen key
append k7 999 #有键尾部追加,没有创建
getrange k7 0 3 #按索引切片
getrange k7 0 -1
setrange k7 1 abc #指定索引覆盖字符串
# 自增 自减, 给某个键增加或者减小值
# 字符串值会被解释成64位有符号的十进制整数来操作,
# 结果依然转成字符串
incr key
incrby key decrement
incrby key 4
decr key
decrby key decrement
decrby key 5
expire time
批量设置过期时间
/home/jw/software/redis-5.0.7/src/redis-cli keys "a*"|xargs -i /home/jw/software/redis-5.0.7/src/redis-cli expire {} 5
# two method set expire time
expire key seconds # equal to: setex key seconds 555
# 多少秒后过期
expire key timestamp # 到指定的时间点过期,在指定Unix时间戳过期
# 毫秒后过期 pexpire
# 持久化key,取消过期时间
persist key
#
# ttl (Time To Live)
# key没设置过期时间,返回-1
# key在过期时间内,返回剩余秒数
# key设置了过期时间,但是已经消亡了,返回-2(2.8版本之前返回-1)
ttl key
key operation
Redis key 需要一个二进制值,可以用任何二进制序列作为key值,可以是简单字符串,也可以是个JPEG 文件的二进制序列。空字符串也是有效key值 .
Key取值原则 :
键值不需要太长,消耗内存,而且查找这类键值的计算成本较高
键值不宜过短,可读性较差
习惯上key采用'user:123:password'形式,表示用户标号为123密码
keys pattern
pattern:
* 任意长度字符,查看所有
?任意一个字符
[] 集合中的 一个 字符
keys * # 查看所有的key
keys k?
keys k*
keys ??
keys k[13]
keys k[1-3] #表示范围
type key
exists key
rename key newkey
del key [key ...]
库操作
redis-cli --help
redis-cli -n 2 # 登陆第2个库
select 2 # 选择第2个库
flushdb # 清除当前库
flushall # 清除所有库
bitmap
**一个字符串类型的值最多能存储512M字节的内容,接近43亿个位 **
位图不是真正的数据类型,它是定义在字符串类型上,只不过把字符串按位操作
一个字符串类型的值最多能存储512M字节的内容,可以表示2**32位 位上限:
512=2**9
1M=1024*1024=2**(10+10)
1Byte=8bit=2**3bit
521M = 2(9+10+10+3)bit = 2**32 b = 4294967296 b ,接近43亿个位
bitcount str1 # 统计字符串中1的个数,equal to: bitcount str1 0 -1
bitcount str1 0 0 # 第一个字节中1的个数
bitcount str1 0 1 # 第一个和第二个字节中1的个数
set s4 7
# 字符串7
# 字符串1对应的ascii为十进制49,字符串7对应的ascii为十进制55
# ascii 55=32 + 16 + 4 +2 +1
# 对应的2进制 0011 0111
bitpos s4 1 [start][end] # 1在指定区间内第一次出现的位置 结果为2
bitpos s4 0 [start][end] # 0在指定区间内第一次出现的位置 结果为0
getbit s4 0 # 0
setbit s4 0 1
set str1 abc
setbit str1 6 1
get str1 # cbc
setbit str1 7 0
get str1 # bbc
- 位操作
# a
# ascii码为97=64 + 32 + 1
# 二进制表示为 0110 0001
# b
# ascii码为98=64 + 32 + 2
# 二进制表示为 0110 0010
set s1 ab
bitcount s1 #6
bitcount s1 0 0 #第0字节的位数
bitcount s1 1 1 #第1字节的位数
set s2 a
set s3 b
bitop and/or/xor/not s8 s2 s3
# 或操作为c
bitop or s8 s2 s3 # get s8 # 结果为c
set cn 中
get cn
bitcount # 13个1,redis以utf-8的方式显示中文
练习:
1、按天统计网站活跃用户,日活、周活、月活等统计。
分析:
以日期作为字符串,这个字符串后面可以存512字节内容
-
512字节中的42亿位,表示42亿个用户
setbit 20200413 1 1 setbit 20200413 3 1 setbit 20200413 120 1 # 13号这一天共3个用户登陆 bitcount 20200413 setbit 20200415 1 1 setbit 20200415 3 1 setbit 20200415 121 1 # 15号这一天共3个用户登陆 bitcount 20200415 # 求一周内的活跃用户,如果一周内一个用户登陆多次,只算1次? bitop or 2020413_19_week 202000413 202000415 bitcount 2020413_19_week # 结果为4 # 求一周内的活跃用户,如果一周内一个用户登陆多次,算多次? bitcount 20200415 bitcount 20200413 # 最后2个结果加起来
2、网站用户的上线次数统计(活跃用户,比如一周之内的上线次数)
分析:
- 只能用户id作为字符串,字符串对应的40亿位,选出部分作为天数
- 为什么不能天数作为字符串,42亿位作为用户id?因为比如用户1,永远出现在第一个位置,取or and 都只有一个值不能统计次数。
setbit user_100 1 1 setbit user_100 3 1 setbit user_100 5 1 setbit user_100 7 1 bitcount user_100 #一周内登陆了4次
2.list列表
- 列表中的元素是字符串类型
- 底层基于双向链表实现,两端操作O(1) ,中间操作O(n)
- 元素可重复出现
#取出元素
lrange comments 0 -1
lindex comments 0 #返回索引0对应的val
lset comments 1 comment33 #设置列表中指定索引位置的元素值,index不能超界
#增加数据
#插入
linsert comments before comment1 999
linsert comments after comment1 888
#两端增加
lpush comments comment100 comment77 comment66
lpushx comments comment 88 #key必须存在
rpush comments comment99
rpushx comments comment99
# 删除
lpop #弹出左边第1个
rpop #弹出右边第1个
rpoplpush comments1 comments2 # 右边pop一个元素,从左边加入到comments2
ltrim comments 1 5 #只留下1到5索引间的数据
ltrim comments 1 -1 #删除最新一条数据
ltrim comments 0 -1 #没删除
lrem comments 1 comment2 #从左向有搜索删除评论2,最多一次
lrem comments -2 comment3 #从右向左搜索,删除评论3,最多2次
lrem comments 0 comment4 #删除列表中所有评论4
#长度
llen comments
# 阻塞消息队列
# 如果多个客户端阻塞在同一个列表上,使用First In First Service原则,先到先服务
blpop comments #左侧弹出没有阻塞
brpop comments
brpoplpush comments1 comments2 0 #从1右侧弹出塞到2左侧,无限等待
# 一直阻塞?
# BLPOP MyQueue 0
# RPUSH MyQueue hello
例子:
1某微博评论的最后50条
lpush comments "comment 1"
lpush comments "comment 2"
lpush comments "comment 3"
ltrim comments 0 2 # 以外的全部删除
3. hash 散列
值是由field和value组成的map键值对 ,field和value都是字符串类型。
hash作用:节约内存,提升cpu效率;因为每增加一个键,就会附加键的管理信息,占用的内存会增多,管理内存cpu所用的时间也会增多,而hash可以一个键存储多对数据。
常用操作
#设置
hmset user_100 name jack id 1
hset user_100 name jack
hsetnx user_100 age 20 # 设置单个字段,要求field不存在。如果key不存在,相当于field也不存在
#字段个数
hlen user_100
#in
hexists user_100 name
#获取
hgetall user_100
hkeys user_100
hvals user_100
hmget user_100 id name
hget user_100 name
#value增量计算
hincrby user_100 id 10
hincrby user_100 id -5
hincrbyfloat user_100 id 3.14
#删除
hdel user_100 age
例子:
1缓存用户信息
hmset user_100 name jack id 10
2商品维度计算,包括喜欢数、评论数
hmset item_100 fav 50 comments 80
3用户维度统计,关注数,被关注数
hmset user_100 follow 8 followed 900
# 其他2种方法
mset user_100_follow 8
mset user_100_followed 900
set user_100 "follow,8,followed,900"
4.集合
常用操作
#添加
sadd set1 a b c
#返回元素个数
scard set1
#返回全元素和指定个数元素
smembers set1
srangemember set1 2#如果集合元素大于2返回2个,如果集合元素个数小于等于2,返回全部
srangemember set1 -2#返回一个数组可重复2个
srangemember set1 0#返回空
srangemember set1 #随机返回1个
#in
sismember set1 a
#删除
srem set1 a
spop set1 #随机删除
#移动
smove set1 set2 a #把元素a, 从集合1移动到集合2
#集合
#交集
sinter set1 set2
sinterstore new_set set1 set2
#并集
sunion set1 set2
sunionstore new_set set1 set2
#差集
sdiff set1 set2 # 从集合1中减去集合2的元素
sdiffstore new_set set1 set2
1求微博共同关注好友
sadd set1 1 2 3
sadd set2 3 a b
sinter set1 set2
5.有序集合
类似Set集合,有序的集合。 每一个元素都关联着一个浮点数分值(Score),并按照分值从小到大的顺序排列集合中的元素。分值可以相同 .
常用操作
zadd mboard 20 yellow 5 happy
zcard mboard
zcount mboard 5 20 # 返回指定score内的元素个数
zscore mboard yellow
zrank mboard yellow # 返回某个具体字段排名,默认从0开始
zrevrank mboard yellow
zincrby mboard 20 happy
zrange mboard 0 1 withscores #返回前两条数据, 索引值,如果score相同,则按照字典序lexicographical order 排列
zrevrange mboard 0 1 withscores
zrangebyscore mboard 10 19 withscores
zrangebyscore mboard (10 (19 withscores # 开区间
zrangebyscore mboard -inf +inf withscores # 无穷区间
zrevrangebyscore mboard 10 19 withscores # 反向
# 移除
zrem mboard happy
ZREMRANGEBYRANK mboard 0 1
ZREMRANGEBYSCORE mboard 4000 5000
#集合运算
聚合函数可以是max min sum
#并集
zunionstore week_rank 2 rank_day1 rank_day2 [weights 1 0.5] aggregate sum
#交集
zinterstore week_rank 2 rank_day1 rank_day2 aggregate max
例子:
1统计京东图书日排行榜,周排行榜,月排行榜
# 每一天的各类图书销售量
zadd book_it_day1 50 python 20 java 30 linux
zadd book_it_day2 51 python 15 java 25 linux
zadd book_it_day3 40 python 24 java 27 linux
#按日统计的排行榜
zrevrange book_it_day1 0 -1 withscores
# 周排行榜(每日记录当天销售量)
zunionstore book_it_weekrank 3 book_it_day1 book_it_day2 book_it_day3 aggregate sum
zrevrange book_it_weekrank 0 -1 withscores
# 周排行榜(每日记录总销售量)
zunionstore book_it_weekrank 3 book_it_day1 book_it_day2 book_it_day3 aggregate max
zrevrange book_it_weekrank 0 1 withscores # 只选出前2条数据
注意:如果参与并集元素的元素太多,会耗费大量内存和计算时间,可能会导致Redis服务阻塞,如果非要计算,选在空闲时间或备用服务器上计算。
2.新浪微博翻页
zadd blog 20200413 瑶妹新戏开播 20200414 岳绮罗出来了
zadd blog 20200413 丽颖有匪定当
zrevrange blog 0 2 withscores # 显示最新日期的3条数据
3.实现音乐榜单
zadd mboard yellow 10 happy 5
zincrby mboard 10 happy
zrevrange mboard 0 -1 withscores
redis编程
ConnectionPool
from redis import ConnectionPool, Redis
# 连接池,就是为了复用连接,而不是频繁创建TCP连接
pool = ConnectionPool.from_url("redis://172.16.177.160:6379/0")
print(pool)
redis_store1 = Redis(connection_pool=pool)
print(redis_store1.keys("*"))
print(redis_store1.connection_pool is pool)
print(pool._created_connections) # 1
print(pool._in_use_connections) # set()
print(pool._available_connections) # [Connection<host=172.16.177.160,port=6379,db=0>]
print(pool._available_connections[0]._sock)
print('*' * 40)
redis_store1 = Redis(connection_pool=pool)
print(redis_store1.keys("a77*")) # 每一命令用完后数据就又回到了_available_connections可用连接中
print(redis_store1.connection_pool is pool)
print(pool._created_connections) # 与之前一样
print(pool._in_use_connections) # 与之前一样
print(pool._available_connections) # 与之前一样
print(pool._available_connections[0]._sock)
pool.disconnect()
pipeline
from redis import ConnectionPool, Redis
pool = ConnectionPool.from_url("redis://172.16.177.160:6379/0")
redis_store1 = Redis(connection_pool=pool)
pipe = redis_store1.pipeline()
print(pipe.keys("*"))
print(pipe.keys("a77*"))
print(pipe.execute())
# pipe.set('t1', 11)
# pipe.sadd('s1', 1,2,3,4)
# results = pipe.append('t1', 'abc').get('t1').smembers('s1').execute()
# print(results) # execute()的结果是所有操作的结果列表
# # [True, 4, 5, b'11abc', {b'3', b'1', b'4', b'2'}]
pool.disconnect()