非关系型数据库之Redis
一、Redis简介
REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。
Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。经常被用作数据库,缓存和消息代理。它支持数据结构,如字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志,带有半径查询和流的地理空间索引。Redis具有内置复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel提供高可用性并使用Redis Cluster自动分区。
为什么选择Redis?
|
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
1. 使用Redis有哪些好处?(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)(2) 支持丰富数据类型,支持string,list,set,sorted set,hash(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除2. redis相比memcached有哪些优势?(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型(2) redis的速度比memcached快很多(3) redis可以持久化其数据3. redis常见性能问题和解决方案:(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内(4) 尽量避免在压力很大的主库上增加从库(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。 4. MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据 相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略:voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰no-enviction(驱逐):禁止驱逐数据 5. Memcache与Redis的区别都有哪些?1)、存储方式Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis有部份存在硬盘上,这样能保证数据的持久性。2)、数据支持类型Memcache对数据类型支持相对简单。Redis有复杂的数据类型。3),value大小redis最大可以达到1GB,而memcache只有1MB6. Redis 常见的性能问题都有哪些?如何解决? 1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。 3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内7, redis 最适合的场景Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢? 如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点:、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。、Redis支持数据的备份,即master-slave模式的数据备份。、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。(1)、会话缓存(Session Cache)最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。(2)、全页缓存(FPC)除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。(3)、队列Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。(4),排行榜/计数器Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:ZRANGE user_scores 0 10 WITHSCORESAgora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。(5)、发布/订阅最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。Redis提供的所有特性中,我感觉这个是喜欢的人最少的一个,虽然它为用户提供如果此多功能。 |
二、下载和安装
1. windows
在redis官网 http://www.redis.net.cn/download/

选择对应版本安装即可。

随后把下载文件夹目录添加到环境变量。
配置文件
|
1
2
3
|
bind 0.0.0.0port 6379requirepass 密码 |
2. linux
下载和安装
|
1
2
3
4
5
6
7
8
9
10
11
|
yum install redis - redis-server /etc/redis.conf 启动服务器或者wget http://download.redis.io/releases/redis-5.0.3.tar.gztar xzf redis-3.0.6.tar.gzcd redis-3.0.6makevi redis.conf 修改配置文件 - bind 0.0.0.0 - port 6379 - requirepass 0000 |
启动服务端
|
1
|
src/redis-server redis.conf |
启动客户端
|
1
2
3
4
5
|
src/redis-cliredis> set foo barOKredis> get foo"bar" |
三、启动客户端:redis-cli

redis默认有15个数据库

选择1号数据库

四.数据操作
redis是key-value的数据,所以每个数据都是一个键值对。键的类型是字符串。
值的类型分为五种:
- 字符串string
- 哈希hash
- 列表list
- 集合set
- 有序集合zset
1.string
- string是redis最基本的类型
- 最大能存储512MB数据
- string类型是二进制安全的,即可以为任何数据,比如数字、图片、序列化对象等
命令:
- 设置键值:set key value(单个值),setex key seconds value(设置时间), mset key1 value1 key2 value2 ..(为多个值赋值).
- 获取键值:get key(获取单个值), mget key1 key2(获取多个值)
- 运算:incr,incrby,decr,decrby,append key value, strlen key 要求 value是数字




键命令
- keys pattern:查看键值 keys * 查看所有键值 keys article*
- exists key:查看键值是否存在
- type key:查看key对应的类型
- del key:删除key
- expire key seconds:设置key过期时间
- ttl key:查看key过期时间

2.hash: 用于存储对象,对象的格式为键值对。
hset key field value: 设置hash key对象指定数据类型的一个值
hmset key field1 value1 filed2 value2 ...:设置hash key对象多个数据类型的值
hget key field:获取指定key的指定数据类型的值
hmget key field1 field2 : 获取key的field1和field中的value
hkeys key : 返回key的field
hlen key:返回key的键值的个数
hvals key:返回key的value
hexists key field: 判断key的field的值是否存在
hdel key filed: 删除key 的field的值
strlen key field: 判断key中field的值的长度
3. list
- 列表的元素类型为string
- 按照插入顺序排序
- 在列表的头部或者尾部添加元素
命令:lpush key value: 往列表key的左边插入一个value
rpush key value:往列表key的右边插入一个value
linsert key before|after value new_value:往列表key中value前|后插入new_value
lset key index new_value: 将列表key的第index个value设置为new_value
lpop key:左弹出key列表中的值
rpop key:右弹出key列表中的值
lrange key start end:查看key列表中start-end中的值


4.set
-
- 无序集合
- 元素为string类型
- 元素具有唯一性,不重复
命令:sadd key value : 往无序集合key中插入value值,位置随机
spop key:在无序集合key中随机弹出集合一个值
smembers key:查看无序集合key中的所有元素
scard key:查看无序集合key的值的个数
5.zset
-
- sorted set,有序集合
- 元素为string类型
- 元素具有唯一性,不重复
- 每个元素都会关联一个double类型的score,表示权重,通过权重将元素从小到大排序
- 元素的score可以相同
命令:zadd key score1 value1 score2 value2 : 向有序集合key中添加value1,value2并制定相应权重
zrem key value:删除有序集合中的value
zrange key start end:查看有序集合中start-end中的值
zcard key:查看有序集合中元素的个数
zsocre key value:查看有序集合key中value的score
zcount key min max:查看有序集合key中score在min-max之间的元素

五、python连接redis
1. 安装
|
1
|
pip install redis |
redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类
2. 创建连接
|
1
2
3
4
5
|
from redis import Redis, ConnectionPool# 创建连接result = Redis(host='127.0.0.1', port=6379)print(result.keys()) |
3. 使用连接池
|
1
2
3
4
5
6
7
8
|
from redis import Redis, ConnectionPool# 连接池pool = ConnectionPool(host='127.0.0.1', port=6379)conn = Redis(connection_pool=pool)# print(conn.keys())# print(conn.smembers('visited_urls'))print(conn.smembers('dupefilter:test_scrapy_redis')) |
注意:连接池只创建一次
|
1
2
3
4
5
6
7
8
9
10
11
|
import redis# 最简单的单例模式:写一个py文件导入from redis_pool import POOLwhile True: key = input('请输入key:') value = input('请输入value:') # 去连接池中获取连接 conn = redis.Redis(connection_pool=POOL) # 设置值 conn.set(key, value) |
4. 数据操作
- 五大数据类型
|
1
2
3
4
5
6
7
|
redis = { k1:'123', 字符串 k2:[1,2,3,4,5], 列表 k3:{1,2,3,4}, 集合 k4:{name:'root','age':23}, 字典 k5:{('alex',60),('eva-j',80),('rt',70),},有序集合 } |
a.使用字典
- 基本操作
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# HASH COMMANDS# 创建字典<br># 将字典name的key设置为valuehset(self, name, key, value):<br># 若字典name的key不存在时将value设置给key,否则不设置hsetnx(self, name, key, value):hmset(self, name, mapping):# 获取字典的值<br># 获取单个key的值hget(self, name, key):<br># 获取多个key的值hmget(self, name, keys, *args):<br># 获取字典name所有的值hgetall(self, name):<br># 获取字典name所有的keyhkeys(self, name):<br># 获取字典name所有的valuehvals(self, name):# 判断某个key是否存在hexists(self, name, key):# 获取字典name元素的长度hlen(self, name):<br># 获取字典name的指定key的value的长度 hstrlen(self, name, key):# 删除字典的keyhdel(self, name, *keys):# 计数器hincrby(self, name, key, amount=1):hincrbyfloat(self, name, key, amount=1.0):# 性能相关:迭代器hscan(self, name, cursor=0, match=None, count=None):hscan_iter(self, name, match=None, count=None): |
# -*- coding: utf-8 -*-
"""
@Datetime: 2019/1/25
@Author: Zhang Yafei
"""
import redis
pool = redis.ConnectionPool(host='192.168.137.191', port=6379, password='0000', max_connections=1000)
conn = redis.Redis(connection_pool=pool)
# 字典
"""
redis = {
k4:{
'username': 'zhangyafei',
'age': 23,
}
}
"""
# 1. 创建字典
# conn.hset('k4','username','zhangyafei')
# conn.hset('k4','age',23)
# conn.hsetnx('k4','username','root') # 若key不存在则将value赋值给key, 如果赋值成功则返回1,否则返回0
# conn.hsetnx('k4', 'hobby', 'basketball')
# conn.hmset('k4',{'username':'zhangyafei','age':23})
# 2. 获取字典的值
# 获取一个值
val = conn.hget('k4', 'username') # b'zhangyafei'
# print(val)
# 获取多个值
# vals = conn.mget('k4', ['username','age'])
# vals = conn.mget('k4', 'username','age') # {b'username': b'zhangyafei', b'age': b'23'}
# 获取所有值
vals = conn.hgetall('k4') # {b'username': b'zhangyafei', b'age': b'23'}
print(vals)
# 获取长度
lens = conn.hlen('k4') # 2
str_lens = conn.hstrlen('k4', 'username') # 10
keys = conn.hkeys('k4') # [b'username', b'age']
values = conn.hvals('k4') # [b'zhangyafei', b'23']
judge = conn.hexists('k4', 'username') # True
# conn.hdel('k4', 'age', 'username')
# print(conn.hkeys('k4')) # []
# 计算器
# print(conn.hget('k4', 'age'))
# conn.hincrby('k4','age',amount=2)
# conn.hincrbyfloat('k4','age',amount=-1.5)
# print(conn.hget('k4', 'age'))
# 问题:如果redis的k4对应的字典中有1000w条数据,请打印所有数据
# 不可取:redis取到数据之后,服务器内存无法承受,爆栈
# result = conn.hgetall('k4')
# print(result)
for item in conn.hscan_iter('k4'):
print(item)
b. 使用列表
def blpop(self, keys, timeout=0):
"""
LPOP a value off of the first non-empty list
named in the ``keys`` list.
If none of the lists in ``keys`` has a value to LPOP, then block
for ``timeout`` seconds, or until a value gets pushed on to one
of the lists.
If timeout is 0, then block indefinitely.
"""
if timeout is None:
timeout = 0
if isinstance(keys, basestring):
keys = [keys]
else:
keys = list(keys)
keys.append(timeout)
return self.execute_command('BLPOP', *keys)
def brpop(self, keys, timeout=0):
"""
RPOP a value off of the first non-empty list
named in the ``keys`` list.
If none of the lists in ``keys`` has a value to RPOP, then block
for ``timeout`` seconds, or until a value gets pushed on to one
of the lists.
If timeout is 0, then block indefinitely.
"""
if timeout is None:
timeout = 0
if isinstance(keys, basestring):
keys = [keys]
else:
keys = list(keys)
keys.append(timeout)
return self.execute_command('BRPOP', *keys)
def brpoplpush(self, src, dst, timeout=0):
"""
Pop a value off the tail of ``src``, push it on the head of ``dst``
and then return it.
This command blocks until a value is in ``src`` or until ``timeout``
seconds elapse, whichever is first. A ``timeout`` value of 0 blocks
forever.
"""
if timeout is None:
timeout = 0
return self.execute_command('BRPOPLPUSH', src, dst, timeout)
def lindex(self, name, index):
"""
Return the item from list ``name`` at position ``index``
Negative indexes are supported and will return an item at the
end of the list
"""
return self.execute_command('LINDEX', name, index)
def linsert(self, name, where, refvalue, value):
"""
Insert ``value`` in list ``name`` either immediately before or after
[``where``] ``refvalue``
Returns the new length of the list on success or -1 if ``refvalue``
is not in the list.
"""
return self.execute_command('LINSERT', name, where, refvalue, value)
def llen(self, name):
"Return the length of the list ``name``"
return self.execute_command('LLEN', name)
def lpop(self, name):
"Remove and return the first item of the list ``name``"
return self.execute_command('LPOP', name)
def lpush(self, name, *values):
"Push ``values`` onto the head of the list ``name``"
return self.execute_command('LPUSH', name, *values)
def lpushx(self, name, value):
"Push ``value`` onto the head of the list ``name`` if ``name`` exists"
return self.execute_command('LPUSHX', name, value)
def lrange(self, name, start, end):
"""
Return a slice of the list ``name`` between
position ``start`` and ``end``
``start`` and ``end`` can be negative numbers just like
Python slicing notation
"""
return self.execute_command('LRANGE', name, start, end)
def lrem(self, name, count, value):
"""
Remove the first ``count`` occurrences of elements equal to ``value``
from the list stored at ``name``.
The count argument influences the operation in the following ways:
count > 0: Remove elements equal to value moving from head to tail.
count < 0: Remove elements equal to value moving from tail to head.
count = 0: Remove all elements equal to value.
"""
return self.execute_command('LREM', name, count, value)
def lset(self, name, index, value):
"Set ``position`` of list ``name`` to ``value``"
return self.execute_command('LSET', name, index, value)
def ltrim(self, name, start, end):
"""
Trim the list ``name``, removing all values not within the slice
between ``start`` and ``end``
``start`` and ``end`` can be negative numbers just like
Python slicing notation
"""
return self.execute_command('LTRIM', name, start, end)
def rpop(self, name):
"Remove and return the last item of the list ``name``"
return self.execute_command('RPOP', name)
def rpoplpush(self, src, dst):
"""
RPOP a value off of the ``src`` list and atomically LPUSH it
on to the ``dst`` list. Returns the value.
"""
return self.execute_command('RPOPLPUSH', src, dst)
def rpush(self, name, *values):
"Push ``values`` onto the tail of the list ``name``"
return self.execute_command('RPUSH', name, *values)
def rpushx(self, name, value):
"Push ``value`` onto the tail of the list ``name`` if ``name`` exists"
return self.execute_command('RPUSHX', name, value)
def sort(self, name, start=None, num=None, by=None, get=None,
desc=False, alpha=False, store=None, groups=False):
"""
Sort and return the list, set or sorted set at ``name``.
``start`` and ``num`` allow for paging through the sorted data
``by`` allows using an external key to weight and sort the items.
Use an "*" to indicate where in the key the item value is located
``get`` allows for returning items from external keys rather than the
sorted data itself. Use an "*" to indicate where int he key
the item value is located
``desc`` allows for reversing the sort
``alpha`` allows for sorting lexicographically rather than numerically
``store`` allows for storing the result of the sort into
the key ``store``
``groups`` if set to True and if ``get`` contains at least two
elements, sort will return a list of tuples, each containing the
values fetched from the arguments to ``get``.
"""
if (start is not None and num is None) or \
(num is not None and start is None):
raise RedisError("``start`` and ``num`` must both be specified")
pieces = [name]
if by is not None:
pieces.append(Token.get_token('BY'))
pieces.append(by)
if start is not None and num is not None:
pieces.append(Token.get_token('LIMIT'))
pieces.append(start)
pieces.append(num)
if get is not None:
# If get is a string assume we want to get a single value.
# Otherwise assume it's an interable and we want to get multiple
# values. We can't just iterate blindly because strings are