Python测开28期-偕行-Reids数据库

一、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_WEIBOlist 中,之后便可以通过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) 点赞、签到、打卡

image
假如上面的微博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)商品标签

image
老规矩,用 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 ,此文件只需解压无需安装;
image

3、cmd输入命令启动Redis

  • 1、进入到Redis所在目录:cd D:\tools\Redis

    • 如果想方便的话,可以把 redis 的路径加到系统的环境变量里,这样就省得再输路径了;
      image
  • 2、输入命令:redis-server.exe redis.windows.conf

    • 注意:后面的那个 redis.windows.conf 可以省略,如果省略,会启用默认的;

  • 3、如果运行命令报如下错误

  • 解决方案:win+R 然后输入 services.msc ,调出 Windows 服务,然后找到 Redis ,右键手动停止即可;
    image

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 文件来修改配置。

image

  • 使用 *号获取所有配置项

image

四、Redis数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(无效集合)及zset(sorted set:有序集合)。

1、 String(字符串)

  • string 是 redis 最基本的类型,一个 key 对应一个 value。

  • string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。

  • string 类型的值最大能存储 512MB。
    image

  • 命令:使用set命令设置key和value,使用get命令+key获取值;

2、 Hash(哈希)

  • Redis hash 是一个键值(key=>field=>value)对集合。

  • Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
    image

  • 命令:

    • del:删除一个键;
    • hmset:定义hash类型的键值对;
    • hget:获取hash类型的值;

3、 List(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)。
image

  • 命令
    • lpush:给列表添加元素,一次可以添加一个或多个;
    • lrange start end:获取列表列表中脚本区间[start,end]的数据;

4、 Set(集合)

  • Redis 的 Set 是 string 类型的无序、不重复集合。

  • 集合是通过哈希表实现的

  • 添加成功返回1,重复添加返回0;
    image

  • 命令:

    • sadd:添加元素到集合,一次可以添加一个或者多个;
    • smembers:通过key获取值;

5、 zset(sorted set:有序集合)

  • Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

  • 不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

  • zset的成员是唯一的,但分数(score)却可以重复。
    image

  • 命令:

    • 添加元素: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

3、Redis命令API