一、背景简介

什么是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封装python工具类 redis python_Redis

 二、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数据类型

参考