一、Redis数据库介绍
1、Redis简介
Remote Dictionary Server(Redis)是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map),列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。
注意
-
Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念。
-
Redis是一个字典结构的存储服务器,而实际上一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。这与我们熟知的在一个关系数据库实例中可以创建多个数据库类似,所以可以将其中的每个字典都理解成一个独立的数据库。
-
每个数据库对外都是一个从0开始的递增数字命名,Redis默认支持16个数据库(可以通过配置文件支持更多,无上限),可以通过配置databases来修改这一数字。客户端与Redis建立连接后会自动选择0号数据库,不过可以随时使用SELECT命令更换数据库,如要选择1号数据库:
redis> SELECT 1
OK
redis [1] > GET foo
(nil)
- 然而这些以数字命名的数据库又与我们理解的数据库有所区别。首先Redis不支持自定义数据库的名字,每个数据库都以编号命名,开发者必须自己记录哪些数据库存储了哪些数据。另外Redis也不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问。最重要的一点是多个数据库之间并不是完全隔离的,比如FLUSHALL命令可以清空一个Redis实例中所有数据库中的数据。综上所述,这些数据库更像是一种命名空间,而不适宜存储不同应用程序的数据。比如可以使用0号数据库存储某个应用生产环境中的数据,使用1号数据库存储测试环境中的数据,但不适宜使用0号数据库存储A应用的数据而使用1号数据库B应用的数据,不同的应用应该使用不同的Redis实例存储数据。由于Redis非常轻量级,一个空Redis实例占用的内存只有1M左右,所以不用担心多个Redis实例会额外占用很多内存。
2、特点
- 完全开源免费的高性能的 key-value 数据库;
- 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进
行使用; - 不仅仅支持简单的 key-value 类型的数据,同时还提供 list , set , zset , hash 等
数据结构的存储; - 支持数据的备份,即 master-slave 模式的数据备份;
- 性能极高 ,Redis 能读的速度是 110000 次 /s, 写的速度是 81000 次 /s ;
- Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操
作是原子性的。多个操作也支持事务,即原子性,通过 MULTI 和 EXEC 指令包起来; - 支持 publish/subscribe, 通知 ,key 过期等等特性。
3、应用场景
(1)缓存
- DB缓存,减轻DB服务器压力
- 提高系统响应
作为Key-Value形态的内存数据库,Redis 最先会被想到的应用场景便是作为数据缓存。而使用 Redis 缓存数据非常简单,只需要通过string类型将序列化后的对象存起来即可,不过也有一些需要注意的地方:
-
必须保证不同对象的 key 不会重复,并且使 key 尽量短,一般使用类名(表名)加主键拼接而成。
-
选择一个优秀的序列化方式也很重要,目的是提高序列化的效率和减少内存占用。
-
缓存内容与数据库的一致性,这里一般有两种做法:
1、只在数据库查询后将对象放入缓存,如果对象发生了修改或删除操作,直接清除对应缓存(或设为过期)。
2、在数据库新增和查询后将对象放入缓存,修改后更新缓存,删除后清除对应缓存(或设为过期)。
(2) 数据共享分布式
String 类型,因为 Redis 是分布式的独立服务,可以在多个应用之间共享。
例如:分布式Session
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
(3) 分布式锁
如今都是分布式的环境下java自带的单体锁已经不适用的。在 Redis 2.6.12 版本开始,string的set命令增加了一些参数:
-
EX:设置键的过期时间(单位为秒)
-
PX:设置键的过期时间(单位为毫秒)
-
NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
-
XX :只在键已经存在时,才对键进行设置操作。
由于这个操作是原子性的,可以简单地以此实现一个分布式的锁,例如:
set lock_key locked NX EX 1
如果这个操作返回false,说明 key 的添加不成功,也就是当前有人在占用这把锁。而如果返回true,则说明得了锁,便可以继续进行操作,并且在操作后通过del命令释放掉锁。并且即使程序因为某些原因并没有释放锁,由于设置了过期时间,该锁也会在 1 秒后自动释放,不会影响到其他程序的运行。
推荐使用Redisson第三方库实现分布式锁。
(4)全局ID
int类型,incrby,利用原子性
incrby userid 1000
分库分表的场景,一次性拿一段
(5)计数器
int类型,incr方法
例如:文章的阅读量、微博点赞数、允许一定的延迟,先写入Redis再定时同步到数据库
计数功能应该是最适合 Redis 的使用场景之一了,因为它高频率读写的特征可以完全发挥 Redis 作为内存数据库的高效。在 Redis 的数据结构中,string、hash和sorted set都提供了incr方法用于原子性的自增操作,下面举例说明一下它们各自的使用场景:
-
如果应用需要显示每天的注册用户数,便可以使用string作为计数器,设定一个名为REGISTERED_COUNT_TODAY的 key,并在初始化时给它设置一个到凌晨 0 点的过期时间,每当用户注册成功后便使用incr命令使该 key 增长 1,同时当每天凌晨 0 点后,这个计数器都会因为 key 过期使值清零。
-
每条微博都有点赞数、评论数、转发数和浏览数四条属性,这时用hash进行计数会更好,将该计数器的 key 设为weibo:weibo_id,hash的 field 为like_number、comment_number、forward_number和view_number,在对应操作后通过hincrby使hash 中的 field 自增。
-
如果应用有一个发帖排行榜的功能,便选择sorted set吧,将集合的 key 设为POST_RANK。当用户发帖后,使用zincrby将该用户 id 的 score 增长 1。sorted set会重新进行排序,用户所在排行榜的位置也就会得到实时的更新。
(6)限流
int类型,incr方法;以访问者的ip和其他信息作为key,访问一次增加一次计数,超过次数则返回false。
(7)位统计
String类型的bitcount(1.6.6的bitmap数据结构介绍),字符是以8位二进制存储的。
set k1 a
setbit k1 6 1
setbit k1 7 0
get k1
/* 6 7 代表的a的二进制位的修改
a 对应的ASCII码是97,转换为二进制数据是01100001
b 对应的ASCII码是98,转换为二进制数据是01100010
因为bit非常节省空间(1 MB=8388608 bit),可以用来做大数据量的统计。
*/
参考:
使用Redis的bitmaps统计用户留存率、活跃用户
用户日活月活怎么统计 - Redis HyperLogLog 详解
(8) 时间轴(Timeline)
list
作为双向链表,不光可以作为队列使用。如果将它用作栈便可以成为一个公用的时间轴。当用户发完微博后,都通过lpush
将它存放在一个 key 为LATEST_WEIBO
的list
中,之后便可以通过lrange
取出当前最新的微博。
(9)消息队列
Redis 中list的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列(生产者 / 消费者模型)。消息的生产者只需要通过lpush将消息放入 list,消费者便可以通过rpop取出该消息,并且可以保证消息的有序性。如果需要实现带有优先级的消息队列也可以选择sorted set。而pub/sub功能也可以用作发布者 / 订阅者模型的消息。无论使用何种方式,由于 Redis 拥有持久化功能,也不需要担心由于服务器故障导致消息丢失的情况。
List提供了两个阻塞的弹出操作:blpop/brpop,可以设置超时时间
-
blpop:blpop key1 timeout 移除并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
-
brpop:brpop key1 timeout 移除并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
上面的操作。其实就是java的阻塞队列。学习的东西越多。学习成本越低
- 队列:先进先除:rpush blpop,左头右尾,右边进入队列,左边出队列
- 栈:先进后出:rpush brpop
(10)抽奖
利用set结构的无序性,通过 Spop( Redis Spop 命令用于移除集合中的指定 key 的一个或多个随机元素,移除后会返回移除的元素。 ) 随机获得值;
redis> SADD myset "one"
(integer) 1
redis> SADD myset "two"
(integer) 1
redis> SADD myset "three"
(integer) 1
redis> SPOP myset
"one"
redis> SMEMBERS myset
1) "three"
2) "two"
redis> SADD myset "four"
(integer) 1
redis> SADD myset "five"
(integer) 1
redis> SPOP myset 3
1) "five"
2) "four"
3) "two"
redis> SMEMBERS myset
1) "three"
redis>
(11) 点赞、签到、打卡
假如上面的微博ID是t1001,用户ID是u3001
用 like:t1001 来维护 t1001 这条微博的所有点赞用户
- 点赞了这条微博:sadd like:t1001 u3001
- 取消点赞:srem like:t1001 u3001
- 是否点赞:sismember like:t1001 u3001
- 点赞的所有用户:smembers like:t1001
- 点赞数:scard like:t1001
(12)商品标签
老规矩,用 tags:i5001 来维护商品所有的标签。
- sadd tags:i5001 画面清晰细腻
- sadd tags:i5001 真彩清晰显示屏
- sadd tags:i5001 流程至极
(13) 好友关系、用户关注、推荐模型
这个场景最开始是是一篇介绍微博 Redis 应用的 PPT 中看到的,其中提到微博的 Redis 主要是用在在计数和好友关系两方面上,当时对好友关系方面的用法不太了解,后来看到《Redis 设计与实现》中介绍到作者最开始去使用 Redis 便是希望能通过set解决传统数据库无法快速计算集合中交集这个功能。后来联想到微博当前的业务场景,确实能够以这种方式实现,所以姑且猜测一下:
对于一个用户 A,将它的关注和粉丝的用户 id 都存放在两个 set 中:
-
A:follow:存放 A 所有关注的用户 id
-
A:follower:存放 A 所有粉丝的用户 id
-
那么通过sinter命令便可以根据A:follow和A:follower的交集得到与 A 互相关注的用户。当 A 进入另一个用户 B 的主页后,A:follow和B:follow的交集便是 A 和 B 的共同专注,A:follow和B:follower的交集便是 A 关注的人也关注了 B。
举例
follow 关注 fans 粉丝
相互关注:
- sadd 1:follow 2
- sadd 2:fans 1
- sadd 1:fans 2
- sadd 2:follow 1
我关注的人也关注了他(取交集):
- sinter 1:follow 2:fans
可能认识的人:
- 用户1可能认识的人(差集):sdiff 2:follow 1:follow
- 用户2可能认识的人:sdiff 1:follow 2:follow
(14) 排行榜
使用sorted set(有序set)和一个计算热度的算法便可以轻松打造一个热度排行榜,zrevrangebyscore可以得到以分数倒序排列的序列,zrank可以得到一个成员在该排行榜的位置(是分数正序排列时的位置,如果要获取倒序排列时的位置需要用zcard-zrank)。
id 为6001 的新闻点击数加1:
zincrby hotNews:20190926 1 n6001
获取今天点击最多的15条:
zrevrange hotNews:20190926 0 15 withscores
(15) 倒排索引
倒排索引是构造搜索功能的最常见方式,在 Redis 中也可以通过set进行建立倒排索引,这里以简单的拼音 + 前缀搜索城市功能举例:
假设一个城市北京,通过拼音词库将北京转为beijing,再通过前缀分词将这两个词分为若干个前缀索引,有:北、北京、b、be…beijin和beijing。将这些索引分别作为set的 key(例如:index:北)并存储北京的 id,倒排索引便建立好了。接下来只需要在搜索时通过关键词取出对应的set并得到其中的 id 即可。
(16) 显示最新的项目列表
比如说,我们的一个Web应用想要列出用户贴出的最新20条评论。在最新的评论边上我们有一个“显示全部”的链接,点击后就可以获得更多的评论。
每次新评论发表时,我们会将它的ID添加到一个Redis列表。可以限定列表的长度为5000
LPUSH latest.comments
在Redis中我们的最新ID使用了常驻缓存,这是一直更新的。但是我们做了限制不能超过5000个ID,因此我们的获取ID函数会一直询问Redis。只有在超出了这个范围的时候,才需要去访问数据库。
二、Redis数据库安装
1、下载zip压缩包
2、解压zip包并重命名
将下载好的文件解压,将文件夹重新命名为 Redis ,此文件只需解压无需安装;
3、cmd输入命令启动Redis
-
1、进入到Redis所在目录:
cd D:\tools\Redis
;- 如果想方便的话,可以把 redis 的路径加到系统的环境变量里,这样就省得再输路径了;
- 如果想方便的话,可以把 redis 的路径加到系统的环境变量里,这样就省得再输路径了;
-
2、输入命令:
redis-server.exe redis.windows.conf
;- 注意:后面的那个 redis.windows.conf 可以省略,如果省略,会启用默认的;
- 3、如果运行命令报如下错误
- 解决方案:
win+R
然后输入services.msc
,调出 Windows 服务,然后找到 Redis ,右键手动停止即可;
4、重新打开一个cmd窗口连接Redis
注意:之前打开的cmd窗口是Redis服务窗口,如果关闭就相当于关闭了Redis,所以需要重新打开一个cmd窗口来使用Redis;
- 连接Redis数据库命令:
redis-cli.exe -h 127.0.0.1 -p 6379
或者redis-cli -h 127.0.0.1 -p 6379
三、Redis配置文件
Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf(Windows 名为 redis.windows.conf)。可以通过 CONFIG SET 命令查看或设置配置项,也可以通过修改 redis.conf 文件来修改配置。
- Redis CONFIG 命令格式:
CONFIG GET CONFIG_SETTING_NAME
- 使用
*
号获取所有配置项
四、Redis数据类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(无效集合)及zset(sorted set:有序集合)。
1、 String(字符串)
-
string 是 redis 最基本的类型,一个 key 对应一个 value。
-
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
-
string 类型的值最大能存储 512MB。
-
命令:使用set命令设置key和value,使用get命令+key获取值;
2、 Hash(哈希)
-
Redis hash 是一个键值(key=>field=>value)对集合。
-
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
-
命令:
- del:删除一个键;
- hmset:定义hash类型的键值对;
- hget:获取hash类型的值;
3、 List(列表)
Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)。
-
命令:
- lpush:给列表添加元素,一次可以添加一个或多个;
- lrange start end:获取列表列表中脚本区间[start,end]的数据;
4、 Set(集合)
-
Redis 的 Set 是 string 类型的无序、不重复集合。
-
集合是通过哈希表实现的。
-
添加成功返回1,重复添加返回0;
-
命令:
- sadd:添加元素到集合,一次可以添加一个或者多个;
- smembers:通过key获取值;
5、 zset(sorted set:有序集合)
-
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
-
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
-
zset的成员是唯一的,但分数(score)却可以重复。
-
命令:
- 添加元素:
zadd key score value
,元素在集合中存在则更新对应score; - 获取值:
zrangebyscore key startscore endscore
,通过分数区间获取元素;
- 添加元素:
6、各数据类型应用场景
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
String(字符串) | 二进制安全 | 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M | — |
Hash(字典) | 键值对集合,即编程语言中的Map类型 | 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
List(列表) | 链表(双向链表) | 增删快,提供了操作某一段元素的API | 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 |
Set(集合) | 哈希表实现,元素不重复 | 1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 | 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐 |
Sorted Set(有序集合) | 将Set中的元素增加一个权重参数score,元素按score有序排列 | 数据插入集合时,已经进行天然排序 | 1、排行榜 2、带权重的消息队列 |
五、Redis命令
1、连接远程Redis服务
- 命令:
redis-cli -h host -p port -a password
2、 Redis 键(key)命令
序号 | 命令 | 描述 |
---|---|---|
1 | DEL key | 该命令用于在 key 存在时删除 key。 |
2 | DUMP key | 序列化给定 key ,并返回被序列化的值。 |
3 | EXISTS key | 检查给定 key 是否存在。 |
4 | EXPIRE key seconds | 为给定 key 设置过期时间,以秒计。 |
5 | EXPIREAT key timestamp | EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
6 | PEXPIRE key milliseconds | 设置 key 的过期时间以毫秒计。 |
7 | PEXPIREAT key milliseconds-timestamp | 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 |
8 | KEYS pattern | 查找所有符合给定模式( pattern)的 key 。 |
9 | MOVE key db | 将当前数据库的 key 移动到给定的数据库 db 当中。 |
10 | PERSIST key | 移除 key 的过期时间,key 将持久保持。 |
11 | PTTL key | 以毫秒为单位返回 key 的剩余的过期时间。 |
12 | TTL key | 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 |
13 | RANDOMKEY | 从当前数据库中随机返回一个 key 。 |
14 | RENAME key newkey | 修改 key 的名称 |
15 | RENAMENX key newkey | 仅当 newkey 不存在时,将 key 改名为 newkey 。 |
16 | SCAN cursor [MATCH pattern] [COUNT count] | 迭代数据库中的数据库键。 |
17 | TYPE key | 返回 key 所储存的值的类型。 |
3、数据库备份与恢复命令
语法 | 作用 | 说明 |
---|---|---|
SAVE | 用于创建当前数据库的备份 | 该命令将在 redis 安装目录中创建dump.rdb文件。 |
CONFIG GET dir | 获取 redis 目录 | 如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。 |
BGSAVE | 创建 redis 备份文件 | 该命令在后台执行。 |
六、python Redis库使用
-
redis提供两个类Redis和StrictRedis用于实现Redis的命令;
- StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令;
- Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。
-
说明:
edis连接实例是线程安全的,可以直接将redis连接实例设置为一个全局变量,直接使用。如果需要另一个Redis实例(or Redis数据库)时,就需要重新创建redis连接实例来获取一个新的连接。同理,python的redis没有实现select命令。
1、redis连接
- 安装:
pip install redis
- 连接redis数据库:
redis.Redis(host,port,decode_responses)
- host:数据库的ip;
- port:数据库端口;
- decode_responses:指定value的数据类型,默认False字节类型,True为string类型;
import redis
# 连接redis数据库,并指定写入的value为string类型
r = redis.Redis(host='127.0.0.1',port=6379,decode_responses=True)
# 添加一个键值对
r.set(name='test',value="hello world")
# 获取值
print(r['test'])# hello world
print(r.get(name='test'))# hello world
print(type(r.get(name='test')))# <class 'str'>
2、连接池
- redis-py使用
connectionpool
来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。 - 可以直接建立一个连接池,然后作为参数传给Redis,这样就可以实现多个Redis实例共享一个连接池;
import redis
# 创建一个连接池来管理所有的连接
pool = redis.ConnectionPool(host='127.0.0.1',port=6379,decode_responses=True)
# 将连接池对象传给Redis来实例化一个数据库对象
r = redis.Redis(connection_pool=pool)
r.set('type','pool')
print(r.get('type'))# pool
print(r.get('test'))# hello world