一、背景简介
什么是NoSQL?
- NoSQL = not only sql
- 非关系型数据库的泛称
- 用于超大规模的数据存储
- 存储数据不需要固定模式
- 可以快速横向扩展
为什么要用NoSQL?
- 高并发读写,海量数据下,读取性能优异
- 高容量存储和高效存储,数据模型灵活
- 高扩展性和高可用性,数据间无关系,易于扩展
NoSQL数据库分类?
- 键值存储数据库:Redis
- 列存储数据库:BigTable,Cassandra,HBase
- 文档性数据库:MongoDB
- 图数据库:Neo4j
关系型数据库(RDBMS)和非关系型数据库(NoSQL)各自特点:
RDBMS:
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据与关系都存储在单独的表中
- 数据操纵语言(DML),数据定义语言(DDL)
- 严格的一致性
- 基础事务
NoSQL:
- 没有声明性查询语言
- 没有预定义的模式
- 键值对存储、列存储、文档存储
- 图形数据
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- 高性能,高可用和可伸缩性
Redis
Redis=Remote DIctionary Server,是一个由Salvatore Sanfilippo写的key-value存储系统。Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日执行、key-value数据库,并提供多种语言的API。
Redis通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(MAP)、列表(list)、集合(sets)和有序集合(sorted sets)。
注意事项:
- 在Redis中,并不是所有的数据都一直存储在内存中的。这是和Memcached相比一个最大的区别;
- Redis只会缓存所有的key的信息,如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作,Redis根据“swappability = age*log(size_in_memory)”计算出哪些key对应的value需要swap到磁盘。然后再将这些key对应的value持久化到磁盘中,同时在内存中清除。这种特性使得Redis可以保持超过其机器本身内存大小的数据。当然,机器本身的内存必须要能够保持所有的key,毕竟这些数据是不会进行swap操作的;
- 同时由于Redis将内存中的数据swap到磁盘中的时候,提供服务的主线程和进行swap操作的子线程会共享这部分内存,所以如果更新需要swap的数据,Redis将阻塞这个操作,直到子线程完成swap操作后才可以进行修改;
- 当从Redis中读取数据的时候,如果读取的key对应的value不在内存中,那么Redis就需要从swap文件中加载相应数据,然后再返回给请求方。这里就存在一个I/O线程池的问题。在默认的情况下,Redis会出现阻塞,即完成所有的swap文件加载后才会相应。这种策略在客户端的数量较小,进行批量操作的时候比较合适。但是如果将Redis应用在一个大型的网站应用程序中,这显然是无法满足大并发的情况的,所以Redis允许我们设置I/O线程池的大小;
NoSQL产品对比:
二、Redis安装
redis安装:
- 下载地址:https://github.com/MSOpenTech/redis/releases;
- 操作系统:windows、linux;
- linux安装: wget http://download.redis.io/releases/redis-5.0.0.tar.gz
tar xzf redis-5.0.0.tar.gz
cd redis-5.0.0
make;
linux启动服务:src/redis-server &
python安装:pip install redis 或 esqy_install redis
配置文件:
- Redis配置文件位于Redis安装目录的根目录下,文件名为redis.conf
- 主要配置项
daemonize no redis默认不是以守护进程的方式运行,建议改为yes
port 6379 默认端口为6379
bind 127.0.0.1 默认绑定ip为本机
timeout 300 当客户端闲置300秒后关闭连接,0为关闭该功能
database 16 设置数据库数量,默认数据库号为0
dbfilename dump.rdb 本地数据库文件名,默认值为dump.rdb
masterauth <master-password> 设置连接redis的密码,默认关闭
三、基于python的操作
操作模式:
- redis.py提供了两个类:Redis,StrictRedis用于实现Redis的命令
- StrictRedis用于实现大部分官方命令,并使用官方的语法和命令
- Redis是StrictRedis的子类,用于向前兼容redis.py
- 一般情况下我们就是用StrictRedis
使用示例:
- 首先启动redis:定位到redis安装目录,cmd中执行命令(我在windows中安装的)redis-server.exe
- python中引入模块
import redis
def main():
r = redis.Redis(host='127.0.0.1', port=6379)
r.set('mykey', 'nihao')
print r.get('mykey')
if __name__ == '__main__':
main()
》》nihao
- 注意,get到的是字节类型,如果需要字符串需要decoded()
- print(r.get('foo').decode())
连接池:
- 创建一个连接要比直接使用一个连接的服务器开销大,所以redis-py内置了一个连接池的概念。
- redis.py使用ConnectionPool来管理对一个redis-server的所有连接,避免每次建立、释放连接的开销。
- 默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。
import redis
def main():
pool = redis.ConnectionPool(host='127.0.0.1', port=6379)
r = redis.Redis(connection_pool=pool)
r.set('mykey', 'nihao')
print r.get('mykey')
print r.get('foo') # 不存在返回None
if __name__ == '__main__':
main()
输出:
》》nihao
》》None
String操作
说明: String操作,redis中的String在内存中按照一个name对应一个value来存储;
set操作:
- set(name, value, ex=None, px=None, nx=False, xx=False),
- 参数ex,过期时间(秒)
- px,过期时间(毫秒)
- nx,如果设置为True,则只有name不存在时,当前set操作才执行,同setnx(name, value)
- xx,如果设置为True,则只有name存在时,当前set操作才执行
- setnx(name,value),则只有name不存在时,当前set操作才执行
- setex(name, value, time),设置过期时间(秒)
- psetex(name, time_ms, value),设置过期时间(毫秒)
- r.mset(name1='zhangsan', name2='lisi'),批量设置
- setrange(name,offset,value):用
value
参数覆写(overwrite)给定key
所储存的字符串值,从偏移量offset
开始。如果新值太长时,则向后添加
r.set("name","zhangsan")
r.setrange("name",1,"z")
print(r.get("name")) #输出:zzangsan
r.setrange("name",6,"zzzzzzz")
print(r.get("name")) #输出:zzangszzzzzzz
- append(key, value):在redis_name对应的值后面追加内容
- setbit(name, offset, value),对二进制表示位进行设置
get操作:
- mget(keys,*args)
- r.get("name1", "name2")
- li=["name1", "name2"]
- r.mget(li)
- getset(name, new_value),在设置旧值的同时获取新值
- getrange(key, start, end),获取设置值的子序列
r.set("name","zhangsan")
print(r.getrange("name",0,3))#输出:zhan
- getbit(name, offset), 获取name对应值的二进制中某位的值(0或1)
- strlen(name),获取name对应的字节长度,一个汉字三个字节。
Hash操作
说明:hash操作,redis中的hash在内存中按照一个name对应多个key-value对来存储,简单来说就是存储一个字典值;
set操作:
- hset(name, key, value),name对应的hash中设置一个键值对,没有则创建,有则修改
r.hset('hkey', 'name', 'zhangsan')
- hmset(name, mapping),在name对应的hash中批量设置键值对
dict = {"a1":"aa", "b1":"bb"}
r.hmset('dict_name', dict)
- hsetnx(name, key, value),只有当key不存在时,set操作才成立。
dict = {"a1":"aa", "b1":"bb"}
r.hsetnx('dict_name', "a1", "bb")
print r.hget('dict_name', "a1") # 输出结果:aa
r.hsetnx('dict_name', "c1", "cc")
print r.hget('dict_name', "c1") # 输出结果:cc
get操作:
- hget(name, key)
r.hget('hkey', 'name')
- hmget(name, keys, *args),获取name对应的包含多个hash值的列表。
li = ["a1", "b1"]
print r.hmget('dict_name', li) # 输出结果:['aa', 'bb']
print r.hmget('dict_name', "a1", "b1") # 输出结果:['aa', 'bb']
- hgetall(name),获取name对应的hash的左右键值
print r.hgetall('dict_name') # 输出结果:{'a1': 'aa', 'b1': 'bb'}
- hlen(name),获取name对应hash中的键的个数
- hkeys(name),获取name对应hash中所有的键
- hvals(name),获取name对应hash中的所有的值
print r.hlen('dict_name') # 输出结果:2
print r.hkeys('dict_name') # 输出结果:['a1', 'b1']
print r.hvals('dict_name') # 输出结果:['aa', 'bb']
- hexists(name, key),检查name对应的hash中是否存在传入的key值
print r.hexists('dict_name', "a1") # 输出结果:True
- hdel(name, *keys),删除制定的name对应的hash中传入的key对应的键值对
r.hdel("dic_name","a1")
List操作
说明:redis中的List在在内存中按照一个name对应一个List来存储;需要注意的是:list类型用get不行,需要用lrange(name,0,-1)取全部的值;
设置操作:
- lpush(name, values),在那么对应的list中添加元素,每个新元素都添加到列表的左侧
r.lpush('list_name', 2)
r.lpush('list_name', 3, 4, 5)
print r.lrange('list_name', 0, -1) # 输出:['5', '4', '3', '2']
- rpush(name, values),在那么对应的list中添加元素,每个新元素都添加到列表的右侧
- lpushx(name, value),只有name已经存在时,值添加到列表的左侧
- rpushx(name, value),只有name已经存在时,值添加到列表的右侧
- linsert(name, where, refvalue, value),在name对应的列表前或后插入一个新值
- 参数:
- name: redis的name
- where: BEFORE(前)或AFTER(后)
- refvalue: 列表内的值
- value: 要插入的数据
r.linsert('list_name', "BEFORE", 2, "ss") # 在列表内找到第一个元素2,在它前面插入ss
print r.lrange('list_name', 0, -1) # 输出:['5', '4', '3', 'ss', '2']
- r.lset(name, index, value) ,name所对应的list中某个索引位置重新赋值
r.lset('list_name', 0, "bbb")
print r.lrange('list_name', 0, -1) # 输出:['bbb', '4', '3', 'ss', '2']
- rpoplpush(src, dst) ,移除原列表右侧的值,并将其添加到目标列表的左侧,src:原列表,dst:目标列表。
获取操作:
- lrange(name, start, end),获取name对应的start和end范围内的list,lrange(name,0,-1)表示获取全部
- llen(name),获取name对应的list中元素的个数
- lrem(name, value, rem),删除name所对应的list中的指定值
- 参数
- name: redis的name
- value: 要删除的值
- num: num=0 删除列表中所有的指定值; num=2 从前到后,删除2个;num=-2 从后向前,删除2个;
r.lrem('list_name', "ss", num=0)
print r.lrange('list_name', 0, -1) # 输出:['bbb', '4', '3', '2']
- lpop(name),移除name所对应的list中左侧所对应的第一个元素,并将其返回
- rpop(name),移除name所对应的list中右侧所对应的第一个元素,并将其返回
- ltrim(name, start, end),移除列表内没有在所给的索引之间的值
- lindex(name, index),根据所给的索引位置获取对应位置的元素
Set操作
说明: redis中的Set是String类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据;
设置操作:
- sadd(name, values):向制定的set中添加值
r.sadd("set_name", "aa")
r.sadd("set_name", "aa", "bb") # 重复值设置不进去
print r.smembers("set_name") # 输出:set(['aa', 'bb'])
获取操作:
- smembers(name),获取制定的set中的左右值
- scard(name),获取name对应的集合中的元素个数
- sismember(name, value),检查value是否是name对应的集合内的元素
- srem(name, values),删除name对应的集合中的某些值
使用场景:集合运算
- sdiff(keys, *args),在第一个name对应的集合中且不在其他name对应的集合的元素集合
r.sadd("set_name", "aa", "bb")
r.sadd("set_name1", "bb", "cc")
r.sadd("set_name2", "bb", "cc", "dd")
print r.sdiff("set_name", "set_name1", "set_name2") # 输出:set(['aa'])
- sdiffstore(dest, keys, *args),相当于把sdiff获取的值加入到dest对应的集合中
- sinter(keys, args),获取多个name对应集合的交集
r.sadd("set_name", "aa", "bb")
r.sadd("set_name1", "bb", "cc")
r.sadd("set_name2", "bb", "cc", "dd")
print r.sinter("set_name", "set_name1", "set_name2") # 输出:set(['bb'])
- sinterstore(dest, keys, *args),返回值放到dest集合中
- sunion(keys, *args),获取多个集合的并集
r.sadd("set_name", "aa", "bb")
r.sadd("set_name1", "bb", "cc")
r.sadd("set_name2", "bb", "cc", "dd")
print r.sunion("set_name", "set_name1", "set_name2") # 输出:set(['cc', 'aa', 'dd', 'bb'])
- sunionstore(dest, keys, *args),返回值放到dest集合中
ZSet操作
说明:在集合的基础上,为每元素排序;元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序;
添加操作:
- zadd(name, *args, **kwargs),向有序集合中添加参数
r.zadd("zset_name", "a1", 6, "a2", 3, "a3", 5) # 同r.zadd('zset_name', b1=10, b2=5)
print r.zrange("zset_name", 0, -1, desc=False, withscores=False, score_cast_func=float)
# 输出:['a2', 'a3', 'a1']
- zincrby(name, value, amount),自增有序集合内value对应的分数 ,amount为增加量,这个函数只会影响后面的score,但是删除这个函数后原来的score不变。
r.zadd("zset_name", "a1", 6, "a2", 3, "a3", 5)
r.zincrby("zset_name", "a3", amount=2) # 让a3的score增加2,影响下面的数据展示
print r.zrange("zset_name", 0, -1, desc=False, withscores=True, score_cast_func=float)
# 输出:[('a2', 3.0), ('a1', 6.0), ('a3', 7.0)]
r.zadd("zset_name", "a1", 6, "a2", 3, "a3", 5)
# r.zincrby("zset_name", "a3", amount=2) 取消操作后,结果不变
print r.zrange("zset_name", 0, -1, desc=False, withscores=True, score_cast_func=float)
# 输出:[('a2', 3.0), ('a3', 5.0), ('a1', 6.0)]
获取操作:
- zrange( name, start, end, desc=False, withscores=False, score_cast_func=float),按索引范围获取集合
- 参数
- name redis的name
- start 有序集合索引起始位置
- end 有序集合索引结束位置
- desc 排序规则,默认按照分数从小到大排序
- withscores 是否获取元素的分数,默认只获取元素的值
- score_cast_func 对分数进行数据转换的函数
- zrem(name, values),删除值是values的成员
r.zrem("zset_name", "mongodb", "rabitmq", "redis")
- zremrangebyrank(name, min, max),根据排行范围进行删除
- zremrangebyscore(name, min, max),根据分数范围进行删除
- zcard(name),获取元素的数量
- zcount(name, min, max),获取有序集合中score在[min,max]之间的元素的个数
- zscore(name, value),获取对应value的score
排序操作:
- zrevrange(name, start, end, withscores=False, score_cast_func=float),从大到小排序
r.zadd("zset_name", "a1", 6, "a2", 3, "a3", 5)
print r.zrevrange("zset_name", 0, -1, withscores=True, score_cast_func=float)
# 输出:[('a1', 6.0), ('a3', 5.0), ('a2', 3.0)]
- zrank(name, value)、zrevrank(name, value),获取value在有序集合中的排序位置,从0开始
r.zadd("zset_name", "a1", 6, "a2", 3, "a3", 5)
print r.zrank("zset_name", "a1") # 从小到大,输出2
print r.zrevrank("zset_name", "a1") # 从大到小,输出0
集合操作:
- zinterstore(dest, keys, aggregate=None),获取两个有序集合的交集并放入dest集合,如果遇到相同值不同分数,则按照aggregate进行操作,aggregate的值为: SUM MIN MAX 。
r.zadd("zset_name", "a1", 6, "a2", 3, "a3", 5)
r.zadd("zset_name1", a1=2, b1=5, b2=7)
r.zinterstore("zset_name2", ("zset_name", "zset_name1"), aggregate="MAX")
print r.zscan("zset_name2") #输出:(0L, [('a1', 6.0)])
- zunionstore(dest, keys, aggregate=None),并集,其他同交集。
其他操作
- delete(*names),根据name删除任意redis数据类型
- exists(name),检测name是否存在,返回True或False
- keys(pattern='*'),根据* ?等通配符匹配获取redis的name
print r.keys(pattern='*set*')
# 输出:['set_name', 'zset_name', 'zset_name1', 'set_name2', 'zset_name2', 'set_name1']
- expire(name, time),设置超时时间
- rename(src, dst),重命名
- move(name, db),将redis的某个值移动到指定的db下
- randomkey(),随机获取一个redis的name(不删除)
- type(name),指定name的redis数据类型
参考