Redis

Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库,非关系型数据库。

Redis介绍

特点及优点

1
2
3
4
开源的,使用C编写,基于内存且支持持久化
高性能的Key-Value的NoSQL数据库
支持数据类型丰富,字符串strings,散列hashes,列表lists,集合sets,有序集合sorted sets 等
支持多种编程语言(C C++ Python Java PHP ... )

与其他数据库对比

1
2
3
4
MySQL : 关系型数据库,表格,基于磁盘,慢
MongoDB:键值对文档型数据库,值为JSON文档,基于磁盘,慢,存储数据类型单一
Redis的诞生是为了解决什么问题??
# 解决硬盘IO带来的性能瓶颈

应用场景

1
2
3
4
5
6
7
8
使用Redis来缓存一些经常被用到、或者需要耗费大量资源的内容,通过这些内容放到redis里面,程序可以快速读取这些内容
一个网站,如果某个页面经常会被访问到,或者创建页面时消耗的资源比较多,比如需要多次访问数据库、生成时间比较长等,我们可以使用redis将这个页面缓存起来,减轻网站负担,降低网站的延迟,比如说网站首页等
比如新浪微博
# 新浪微博,基于TB级的内存数据库
# 内容 :存储在MySQL数据库
# 关系 :存储在redis数据库
# 数字 :粉丝数量,关注数量,存储在redis数据库
# 消息队列

数据库排名

redis版本

1
2
3
4
5
6
最新版本:5.0
常用版本:2.4、2.6、2.8
3.0(里程碑)、3.2、3.4、4.0、5.0
图形界面管理工具(写的一般)
RedisDesktopManager
为了解决负载问题,所以发明了redis

诞生历程

  1. 历史
    LLOOGG.com 帮助别的网站统计用户信息,各个网站发送的浏览记录都会存储到存储队列,5-10000条记录,多余5条需要收费

  2. 原理
    FIFO机制,先进先出,满了进一条就出一条,网站越多,队列越多,推入和弹出操作越多

  3. 技术及问题
    开始使用MySQL进行硬盘读写,速度很慢,导致无法实时显示,所以自己写了一个列表结构的内存数据库,程序性能不会受到硬盘IO的限制,加了持久化的功能

  4. redis数据库戛然而生

Redis附加功能

  1. 持久化
    将内存中数据保存到磁盘中,保证数据安全,方便进行数据备份和恢复
  2. 发布与订阅功能
    将消息同时分发给多个客户端,用于构建广播系统
  3. 过期键功能
    为键设置一个过期时间,让它在指定时间内自动删除
    <节省内存空间>
    音乐播放器,日播放排名,过期自动删除
  4. 事务功能
    原子的执行多个操作
  5. 主从复制
  6. Sentinel哨兵

安装

Ubuntu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装
sudo apt-get install redis-server
# 确认是否启动
ps -aux | grep redis
# 服务端启动
sudo /etc/init.d/redis-server status
sudo /etc/init.d/redis-server start
sudo /etc/init.d/redis-server stop
sudo /etc/init.d/redis-server restart
# 客户端连接
redis-cli -h IP地址 -p 端口
redis-cli # 默认连接本机的6379端口
127.0.0.1:6379>ping
PONG

Windows

1
2
3
4
5
6
7
1、下载安装包
https://github.com/ServiceStack/redis-windows/blob/master/downloads/redis-64.3.0.503.zip
2、解压
3、启动服务端
双击解压后的 redis-server.exe
4、客户端连接
双击解压后的 redis-cli.exe

问题:关闭终端后服务终止
解决:将Redis服务安装到本地服务

1
2
3
4
1、重命名 redis.windows.conf 为 redis.conf,作为redis服务的配置文件
2、cmd命令行,进入到redis-server.exe所在目录
3、执行:redis-server --service-install redis.conf --loglevel verbose
4、计算机-管理-服务-Redis-启动

卸载

1
2
3
到 redis-server.exe 所在路径执行:
1、redis-server --service-uninstall
2、sc delete Redis

配置文件详解

配置文件所在路径

Ubuntu

1
/etc/redis/redis.conf

windows

下载解压后的redis文件夹中

1
2
redis.windows.conf 
redis.conf

设置连接密码

  1. requirepass 密码
  2. 重启服务

    1
    sudo /etc/init.d/redis-server restart
  1. 客户端连接
    1
    2
    redis-cli -h 127.0.0.1 -p 6379 -a 123456
    127.0.0.1:6379>ping

允许远程连接

  1. 注释掉IP地址绑定

    1
    bind 127.0.0.1
  2. 关闭保护模式(默认开始,不允许外部网络访问)

    1
    protected-mode no
  3. 重启redis服务

    1
    sudo /etc/init.d/redis-server restart

远程连接测试

Windows连接Ubuntu的Redis服务**

1
2
3
4
5
# cmd命令行
1、d:
2、cd Redis3.0
3、redis-cli -h x.x.x.x -a 123456
4、x.x.x.x:6379>ping

数据类型

字符串类型(string)

特点

1
2
字符串、数字,都会转为字符串来存储
以二进制的方式存储在内存中

必须掌握命令

1
2
3
4
5
6
7
8
9
10
set key value
setnx key value
set key value ex seconds
get key
mset key1 value1 key2 value2
mget key1 key2 key3
stren key
# 数字操作
incr key
decr key

扩展命令

1
2
3
4
5
append key value
setrange key index value
getrange key start stop
incrby key step
decrby key step

常用命令

set | get命令

作用:
设置键值,获取键对应的值

命令格式:
set key value
get key

1
2
3
4
5
6
7
8
9
10
11
12
13
tarena@tedu:~$ redis-cli -h 127.0.0.1 -p 6379 -a 123456
127.0.0.1:6379> set name 'Lucy'
OK
127.0.0.1:6379> get name
"Lucy"
127.0.0.1:6379> set number 10
OK
127.0.0.1:6379> get number
"10"
127.0.0.1:6379> set number 6.66
OK
127.0.0.1:6379> get number
"6.66"

set命令之 - setnx

setnx key value : 键不存在时才能进行设置(必须掌握)

1
2
3
4
# 键不存在,进行设置(此处name键已经存在)
127.0.0.1:6379> setnx name 'Tom'
(nil)
127.0.0.1:6379>

set命令之 - ex

作用:
设置过期时间

命令格式:
set key value ex seconds

1
2
3
4
5
6
7
8
127.0.0.1:6379> set name 'Tiechui' ex 3
OK
127.0.0.1:6379> get name
"Tiechui"
# 3秒后再次获取,得到 nil
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379>

mset | mget

作用:
同时设置多个值,获取多个值

1
2
3
4
5
6
7
127.0.0.1:6379> mset name1 'lucy' name2 'tom' name3 'jim'
OK
127.0.0.1:6379> mget name1 name2 name3
1) "lucy"
2) "tom"
3) "jim"
127.0.0.1:6379>

键的命名规范

​mset wang::email *@qq.com

1
2
3
4
5
6
127.0.0.1:6379> mset wang::email wangweichao@tedu.cn guo::email guods@tedu.cn
OK
127.0.0.1:6379> mget wang::email guo::email
1) "wangweichao@tedu.cn"
2) "guods@tedu.cn"
127.0.0.1:6379>

strlen

作用:
获取值的长度

命令格式:
strlen key

1
2
3
127.0.0.1:6379> strlen name
(integer) 11
127.0.0.1:6379>

字符串索引操作

setrange
key 索引值 value

作用:
从索引值开始,value替换原内容

1
2
3
4
5
6
7
127.0.0.1:6379> get message
"hello world"
127.0.0.1:6379> setrange message 6 'tarena'
(integer) 12
127.0.0.1:6379> get message
"hello tarena"
127.0.0.1:6379>

getrange key 起始值 终止值

作用:
获取指定范围切片内容

1
2
3
4
5
6
7
127.0.0.1:6379> get message
"hello tarena"
127.0.0.1:6379> getrange message 0 4
"hello"
127.0.0.1:6379> getrange message 0 -1
"hello tarena"
127.0.0.1:6379>

append key value

作用:
追加拼接value的值

1
2
3
4
5
6
7
127.0.0.1:6379> set message 'hello '
OK
127.0.0.1:6379> append message 'world'
(integer) 11
127.0.0.1:6379> get message
"hello world"
127.0.0.1:6379>

整数操作

INCRBY key 步长

DECRBY key 步长

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> set number 10
OK
127.0.0.1:6379> get number
"10"
127.0.0.1:6379> INCRBY number 5
(integer) 15
127.0.0.1:6379> get number
"15"
127.0.0.1:6379> DECRBY number 5
(integer) 5
127.0.0.1:6379> get number
"5"

INCR key : +1操作

DECR key : -1操作

1
2
3
4
5
6
7
127.0.0.1:6379> incr number
(integer) 7
127.0.0.1:6379> decr number
(integer) 6
127.0.0.1:6379> get number
"6"
127.0.0.1:6379>

应用场景

抖音上有人关注你了,是不是可以用INCR呢,如果取消关注了是不是可以用DECR

浮点数操作

incrbyfloat key step

1
2
3
4
5
6
7
8
127.0.0.1:6379> get number
"10"
127.0.0.1:6379> INCRBYFLOAT number 6.66
"12.66"
127.0.0.1:6379> INCRBYFLOAT number -6.66
"6"
127.0.0.1:6379>
# 先转为数字类型,然后再进行相加减,不能使用append

命令汇总

字符串操作

1
2
3
4
5
6
7
1、set key value
2、setnx key value
3、get key
3、mset
4、mget
5、set key value ex seconds
6、strlen key

数字操作

1
2
3
4
5
7、incrby key 步长
8、decrby key 步长
9、incr key
10、decr key
11、incrbyfloat key number

设置过期时间的两种方式

方式一

1
1、set key value ex 3

方式二

1
2
3
1、set key value
2、expire key 5 # 秒
3、pexpire key 5 # 毫秒

查看存活时间

1
ttl key

删除过期

1
persist key

通用命令

  1. 切换库

    1
    select number
  2. 查看键

    1
    keys *
  1. 键类型

    1
    TYPE key
  2. 键是否存在

    1
    exists key
  3. 删除键

    1
    del key
  4. 键重命名

    1
    rename key newkey
  5. 返回旧值并设置新值(如果键不存在,就创建并赋值)

    1
    getset key value
  6. 清除当前库中所有数据(慎用)

    1
    flushdb
  7. 清除所有库中所有数据(慎用)

    1
    flushall

string数据类型注意

1
2
3
4
5
# key值取值原则
1、key值不宜过长,消耗内存,且在数据中查找这类键值的计算成本高
2、不宜过短,可读性较差
# 值
1、一个字符串类型的值最多能存储512M内容

列表数据类型(List)

特点

元素是字符串类型
列表头尾增删快,中间增删慢,增删元素是常态
元素可重复
最多可包$2^{32} -1$个元素
索引同python列表

头尾压入元素(LPUSH | RPUSH)

​1、LPUSH key value

​2、RPUSH key value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> LPUSH mylist1 0 1 2 3 4
(integer) 5
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "0"
127.0.0.1:6379> RPUSH mylist2 0 1 2 3 4
(integer) 5
127.0.0.1:6379> LRANGE mylist2 0 -1
1) "0"
2) "1"
3) "2"
4) "3"
5) "4"
127.0.0.1:6379>

查看|设置 列表元素

查看(LRANGE)

1
2
3
LRANGE key start stop
# 查看列表中所有元素
LRANGE key 0 -1

获取指定位置元素(LINDEX)

1
LINDEX key index

设置指定位置元素的值(LSET)

1
LSET key index value

获取列表长度(LLEN)

1
LLEN key
  • 头尾弹出元素(LPOP | RPOP)

    LPOP key : 从列表头部弹出一个元素

    RPOP key : 从列表尾部弹出一个元素

    RPOPLPUSH source destination : 从一个列表尾部弹出元素压入到另一个列表头部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "8"
127.0.0.1:6379> LPOP mylist1
"4"
127.0.0.1:6379> RPOP mylist1
"8"
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> RPOPLPUSH mylist1 mylist2
"1"
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "3"
2) "2"
127.0.0.1:6379> LRANGE mylist2 0 -1
1) "1"
2) "0"
3) "1"
4) "2"

移除指定元素(LREM)

LREM key count value

1
2
3
count>0:表示从头部开始向表尾搜索,移除与value相等的元素,数量为count
count<0:表示从尾部开始向表头搜索,移除与value相等的元素,数量为count
count=0:移除表中所有与value相等的值

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "3"
2) "2"
127.0.0.1:6379> LPUSH mylist1 3 2
(integer) 4
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "2"
2) "3"
3) "3"
4) "2"
127.0.0.1:6379> LREM mylist1 1 2
(integer) 1
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "3"
2) "3"
3) "2"
127.0.0.1:6379> LREM mylist1 1 3
(integer) 1
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "3"
2) "2"
127.0.0.1:6379>

去除指定范围外元素(LTRIM)

LTRIM key start stop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> LRANGE mylist2 0 -1
1) "1"
2) "0"
3) "1"
4) "2"
5) "3"
6) "4"
127.0.0.1:6379> LTRIM mylist2 0 -2
OK
127.0.0.1:6379> LRANGE mylist2 0 -1
1) "1"
2) "0"
3) "1"
4) "2"
5) "3"
127.0.0.1:6379>

应用场景: 保存微博评论最后500条

1
LTRIM user001::comments 0 499

列表中插入值(LINSERT)

LINSERT key BEFORE|AFTER pivot value

key和pivot不存在,不进行任何操作

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
127.0.0.1:6379> LRANGE mylist2 0 -1
1) "0"
2) "1"
3) "2"
4) "3"
5) "4"
127.0.0.1:6379> LINSERT mylist2 after 2 666
(integer) 6
127.0.0.1:6379> LINSERT mylist2 before 4 888
(integer) 7
127.0.0.1:6379> LRANGE mylist2 0 -1
1) "0"
2) "1"
3) "2"
4) "666"
5) "3"
6) "888"
7) "4"
127.0.0.1:6379>

阻塞弹出(BLPOP | BRPOP)

BLPOP key timeout

BRPOP key timeout

1
2
3
1、如果弹出的列表不存在或者为空,就会阻塞
2、超时时间设置为0,就是永久阻塞,直到有数据可以弹出
3、如果多个客户端阻塞再同一个列表上,使用First In First Service原则,先到先服务

示例

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> BLPOP mylist2 0
1) "mylist2"
2) "3"
127.0.0.1:6379> BLPOP mylist2 0
1) "mylist2"
2) "2"
127.0.0.1:6379> BLPOP mylist2 0
1) "mylist2"
2) "1"
127.0.0.1:6379> BLPOP mylist2 0
# 阻塞了

列表常用命令总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 增
LPUSH key value1 value2
RPUSH key value1 value2
RPOPLPUSH source destination
LINSERT key after|before value newvalue
# 查
LRANGE key start stop
LLEN key
# 删
LPOP key
RPOP key
BLPOP key timeout
BRPOP key timeout
LREM key count value
LTRIM key start stop
# 改
LSET key index newvalue

与python交互

  • 模块

Ubuntu

1
sudo pip3 install redis

Windows

1
python -m pip install redis
  • 使用流程
1
2
3
import redis
# 创建数据库连接对象
r = redis.Redis(host='127.0.0.1',port=6379,db=0,password='123456')
  • 通用命令代码示例
1
2
3
4
5
6
7
8
9
10
11
12
import redis

# 创建数据库连接对象
r = redis.Redis(host='192.168.43.49',port=6379,db=0,password='123456')
# [b'key1',b'key2']
print(r.keys('*'))
# 键类型:string
print(type('spider::urls'))
# 是否存在:1 或者 0
print(r.exists('spider::urls'))
# 删除key:spider::urls
r.delete('spider::urls')

字符串命令代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import redis

r = redis.Redis(host='192.168.43.49',port=6379,db=0)

r.set('mystring','python')
# b'python'
print(r.get('mystring'))
# False
print(r.setnx('mystring','socket'))
# mset:参数为字典
r.mset({'mystring2':'mysql','mystring3':'mongodb'})
# mget:结果为一个列表
print(r.mget('mystring','mystring2','mystring3'))
# mystring长度:6
print(r.strlen('mystring'))
# 数字类型操作
r.set('number',10)
r.incrby('number',5)
r.decrby('number',5)
r.incr('number')
r.decr('number')
r.incrbyfloat('number',6.66)
r.incrbyfloat('number',-6.66)
# b'10'
print(r.get('number'))

python操作list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import redis

r = redis.Redis(host='192.168.43.49',port=6379,db=0)
# ['mysql','redis']
r.lpush('pylist','redis','mysql')
# ['mysql','redis','django','spider']
r.rpush('pylist','django','spider')
# ['mysql','redis','django','spider','AI']
r.linsert('pylist','after','spider','AI')
# 5
print(r.llen('pylist'))
# ['redis','django','spider']
r.lpop('pylist')
r.rpop('pylist')
# ['redis','django','spider']
print(r.lrange('pylist',0,-1))
# ['redis','spider']
r.lrem('pylist',0,'django')
# 返回True,['redis']
r.ltrim('pylist',0,0)
# 返回True,['spiderman']
r.lset('pylist',0,'spiderman')

r.delete('pylist')

位图操作bitmap(重要)

位图不是真正的数据类型,它是定义在字符串类型中
一个字符串类型的值最多能存储512M字节的内容,位上限:2^32

强势点

1
可以实时的进行统计,极其节省空间。官方在模拟1亿28百万用户的模拟环境下,在一台MacBookPro上,典型的统计如“日用户数”的时间消耗小于50ms, 占用16MB内存

设置某一位上的值

1
2
setbit key offset value
# offset是偏移量,从0开始

示例

1
2
3
4
5
6
7
8
9
10
# 默认扩展位以0填充
127.0.0.1:6379> set mykey ab
OK
127.0.0.1:6379> get mykey
"ab"
127.0.0.1:6379> SETBIT mykey 0 1
(integer) 0
127.0.0.1:6379> get mykey
"\xe1b"
127.0.0.1:6379>

获取某一位上的值

GETBIT key offset

1
2
3
4
5
127.0.0.1:6379> GETBIT mykey 3
(integer) 0
127.0.0.1:6379> GETBIT mykey 0
(integer) 1
127.0.0.1:6379>

bitcount

统计键所对应的值中有多少个 1

1
2
3
4
5
6
7
127.0.0.1:6379> SETBIT user001 1 1
(integer) 0
127.0.0.1:6379> SETBIT user001 30 1
(integer) 0
127.0.0.1:6379> bitcount user001
(integer) 2
127.0.0.1:6379>

应用场景案例

网站用户的上线次数统计(寻找活跃用户)

用户名为key,上线的天作为offset,上线设置为1

示例: 用户名为 user001 的用户,今年第1天上线,第30天上线

SETBIT user001 1 1

SETBIT user001 30 1

BITCOUNT user001

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import redis

r = redis.Redis(host='192.168.43.49',port=6379,db=2,password='123456')

# user1,一年之中第1天和第5天登录
r.setbit('user001',1,1)
r.setbit('user001',5,1)
# user2,一年之中第100天和第200天登录
r.setbit('user002',100,1)
r.setbit('user002',200,1)
# user3,一年之中好多天登录
for i in range(0,365,2):
r.setbit('user003',i,1)
# user4,一年之中好多天登录
for i in range(0,365,3):
r.setbit('user004',i,1)

user_list = r.keys('user*')
print(user_list)

# 活跃用户
active_users = []
# 不活跃用户
noactive_user = []

for user in user_list:
# 统计位图中有多少个 1
login_count = r.bitcount(user)
if login_count >= 100:
active_users.append((user,login_count))
else:
noactive_user.append((user,login_count))

# 打印活跃用户
for active in active_users:
print('活跃用户:',active)

list案例: 一个进程负责生产url,一个进程负责消费url

进程1: 生产者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import redis
import random
import time

urls_list = [
'01_baidu.com',
'02_sina.com',
'03_taobao.com',
'04_tmall.com',
'05_jd.com'
]
r = redis.Redis(host='192.168.43.49',db=0,password='123456')
while True:
url = random.choice(urls_list)
r.lpush('spider::urls',url)
time.sleep(random.randint(1,5))

进程2: 消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
import redis

r = redis.Redis(host='192.168.43.49',db=0,password='123456')

while True:
# 结果为元组
try:
url = r.blpop('spider::urls',3)
print(url[1])
r.lrem('spider::urls',count=0,value=url[1])
except:
print('爬取结束')
break