今日任务:
13.1 NoSQL简介
13.2 redis服务搭建
13.3 redis连接池
13.4 redis管道
笔记:
1. Python操作nosql数据库
NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
虽然NoSQL的流行与火起来才短短几年的时间,但是不可否认,现在已经开始了第二代运动。尽管早期的堆栈代码只能算是一种实验,然而现在的系统已经更加的成熟、稳定。不过现在也面临着一个严酷的事实:技术越来越成熟——以至于原来很好的NoSQL数据存储不得不进行重写,也有少数人认为这就是所谓的2.0版本。该工具可以为大数据建立快速、可扩展的存储库。
非关系型数据库和关系型数据库的差别:
非关系型数据库的优势:1. 性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。2. 可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。关系型数据库的优势:1. 复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。2. 事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。但是近年来这两种数据库都在向着另外一个方向进化。例如:NOSQL数据库慢慢开始具备SQL数据库的一些复杂查询功能的雏形,比如Couchbase的index以及MONGO的复杂查询。对于事务的支持也可以用一些系统级的原子操作来实现例如乐观锁之类的方法来曲线救国。SQL数据库也开始慢慢进化,比如HandlerSocker技术的实现,可以在MYSQL上实现对于SQL层的穿透,用NOSQL的方式访问数据库,性能可以上可以达到甚至超越NOSQL数据库。可扩展性上例如Percona Server,可以实现无中心化的集群。虽然这两极都因为各自的弱势而开始进化出另一极的一些特性,但是这些特性的增加也会消弱其本来具备的优势,比如Couchbase上的index的增加会逐步降低数据库的读写性能。所以怎样构建系统的短期和长期存储策略,用好他们各自的强项是架构师需要好好考虑的重要问题。
1. python操作redis
redis的概念:
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。
Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
Redis的安装
Redis一般都是安装在linux系统中,具体安装步骤如下:
#cd /usr/local/src
#wget http://download.redis.io/releases/redis-3.0.1.tar.gz
#tar xzf redis-3.0.1.tar.gz
#cd redis-3.0.1
#make
#src/redis-server &
检查redis是否正常启动
Ps –ef |grep redis
Netstat –lnp |grep 6379
安装redis的客户端
Pip install redis
1,简单操作:
redis是以key-value的形式存储的,所以我们在操作的时候。首先我们将redis所在主机的ip和发布端口作为参数实例化了一个对象r,然后执行set('name','I love you!'),这样我们就在内存中存储了一个key为shang,值为‘I love you!’的项。我们可以理解为{'shang':'I love you!'},当我们要读取的之后,keys()就是获得多有key值。
# -*- coding:utf-8 -*-
# @Time : 2017/9/17 19:19
# @Author : lingxiangxiang
# @File : demon1.py
import redis
r = redis.Redis(host="192.168.48.128", port=6379)
r.set("shang", "I love you!")
print(r.get("shang"))
print(r.keys())
# print(dir(r))
结果:
I love you!
['shang', 'ajing']
2.连接池
redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time : 2017/9/17 23:54
# @Author : lingxiangxiang
# @File : demon2.py
import redis
pool = redis.ConnectionPool(host="192.168.48.128")
r = redis.Redis(connection_pool=pool)
r.set("name", "lingxiangxiang")
print(r.get("name"))
结果:
Lingxiangxiang
管道
redis-py默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作。减少功耗
redis是一个cs模式的tcp server,使用和http类似的请求响应协议。一个client可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client。基本的通信过程如下:
Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4
基本上四个命令需要8个tcp报文才能完成。由于通信会有网络延迟,假如从client和server之间的包传输时间需要0.125秒。那么上面的四个命令8个报文至少会需要1秒才能完成。这样即使redis每秒能处理100个命令,而我们的client也只能一秒钟发出四个命令。这显示没有充分利用 redis的处理能力。除了可以利用mget,mset 之类的单条命令处理多个key的命令外我们还可以利用pipeline的方式从client打包多条命令一起发出,不需要等待单条命令的响应返回,而redis服务端会处理完多条命令后会将多条命令的处理结果打包到一起返回给客户端。通信过程如下:
Client: INCR X
Client: INCR X
Client: INCR X
Client: INCR X
Server: 1
Server: 2
Server: 3
Server: 4
假设不会因为tcp报文过长而被拆分。可能两个tcp报文就能完成四条命令,client可以将四个命令放到一个tcp报文一起发送,server则可以将四条命令的处理结果放到一个tcp报文返回。通过pipeline方式当有大批量的操作时候。我们可以节省很多原来浪费在网络延迟的时间。需要注意到是用 pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并是不是打包的命令越多越好。具体多少合适需要根据具体情况测试
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time : 2017/10/9 22:45
# @Author : lingxiangxiang
# @File : demon4.py
import datetime
import redis
def withpipe(r):
pipe = r.pipeline(transaction=True)
for i in xrange(1, 1000):
key = "test1" +str(i)
value = "test1" + str(i)
pipe.set(key, value)
pipe.execute()
def withoutpipe(r):
# pipe = r.pipeline(transaction=True)
for i in xrange(1, 1000):
key = "test1" + str(i)
value = "test1" + str(i)
r.set(key, value)
if __name__ == "__main__":
pool = redis.ConnectionPool(host="192.168.48.131", port=6379, db=0)
r1 = redis.Redis(connection_pool=pool)
r2 = redis.Redis(connection_pool=pool)
start = datetime.datetime.now()
# print(start)
withpipe(r1)
end = datetime.datetime.now()
# print((end-start).microseconds)
# print(end-start)
t_time = (end - start).microseconds
print("withpipe time is : {0}".format(t_time))
start = datetime.datetime.now()
withoutpipe(r2)
end = datetime.datetime.now()
t_time = (end - start).microseconds
print("withoutpipe time is : {0}".format(t_time))
结果:
withpipe time is : 28000
withoutpipe time is : 253000
reids目前提供5种数据类型:string类型, list类型, set集合类型, 有序集合类型, hash类型,下面我们就来详细介绍他们的使用。
string操作
redis中的string在内存中都是按照一个key对应一个value来存储的。如:
r.set(“name”, “lingxiangxiang”)
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操作才执行'''
get(name) 获取值
print(r.get("name"))
mset() 批量设置值
r.mset(name1="shang", name2="ling")
r.mset({"name3": "kong", "name4": "gu"})
mget(keys, *args) 批量获取值
print(r.mget("name1", "name2", "name3", "name4"))
append(name, value)
r.append("name","lisi")
print(r.get("name"))
list操作
redis中的List在在内存中按照一个name对应一个List来存储
lpush(name,values)
# 在name对应的list中添加元素,每个新的元素都添加到列表的最左边
r.lpush("list_name",2)
r.lpush("list_name",3,4,5)#保存在列表中的顺序为5,4,3,2
rpush(name,values)
#同lpush,但每个新的元素都添加到列表的最右边
lpushx(name,value)
#在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边
rpushx(name,value)
#在name对应的list中添加元素,只有name已经存在时,值添加到列表的最右边
llen(name)
# name对应的list元素的个数
print(r.llen("list_name"))、
linsert(name,where,refvalue,value):在name对应的列表的某一个值前后插入一个新值
#参数:
# name,redis的name
# where,BEFORE或AFTER
# refvalue,标杆值,即:在它前后插入数据
# value,要插入的数据
r.lset(name,index,value):对name对应的list中的某一个索引位置重新赋值。
# 参数:
# name,redis的name
# index,list的索引位置
# value,要设置的值
r.lrem(name,value,num):在name对应的list中删除指定的值
# 参数:
# name,redis的name
# value,要删除的值
# num, num=0,删除列表中所有的指定值;
# num=2,从前到后,删除2个;
# num=-2,从后向前,删除2个
lpop(name):在name对应的列表的左侧获取第一个元素并在列表中移除,返回值删除那个元素的值
#扩展: rpop(name) 表示从右向左操作
lindex(name,index):在name对应的列表中根据索引取列表元素
lrange(name,start,end):在name对应的列表分片获取数据
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time : 2017/10/10 21:20
# @Author : lingxiangxiang
# @File : demon6.py
import redis
pool = redis.ConnectionPool(host="192.168.48.131", port=6379, db=0)
r = redis.Redis(connection_pool=pool)
# lpush 在list的左边增加一个元素 left
# rpush 在list的右边增加一个元素 right
r.lpush("list1", "test1")
r.rpush("list1", "ling")
r.lpush("list1", "test2")
r.lpush("list1", "test3")
r.lpush("list1", "test2")
r.lpush("list1", 2, 3, 4)
r.lpush("list1", "test2")
print(r.lrange("list1", 0, -1))
# 最终的list结果是 [4, 3, 2, "test3", "test2", "test1", "ling"]
# 在中间位置增加一个元素
# linsert(name,where,refvalue,value)
# name 代表的是list对应的key值
# where AFTER BEFORE
# refvalue list中的某个元素
# value 你要增加的值是什么
r.linsert("list1", "AFTER", "test2", "hello")
print(r.lrange("list1", 0, -1))
r.lset("list1", 2, "world")
# lset 更改某个元素的值
# r.lset(name,index,value)
print(r.lrange("list1", 0, -1))
print(r.lindex("list1", 2))
# lindex 查看list某个下标的值
print(r.lpop("list1"))
# lpop从list的最左边删除一个元素,返回删除元素的值
print(r.lrange("list1", 0, -1))
r.lrem("list1", "world")
# r.lrem(name,value,num):
# num, num=0,删除列表中所有的指定值;
# num=2,从前到后,删除2个;
# num=-2,从后向前,删除2个
print(r.lrange("list1", 0, -1))
结果:
['test2', '4', '3', '2', 'test2', 'test3', 'test2', 'test1', 'ling']
['test2', 'hello', '4', '3', '2', 'test2', 'test3', 'test2', 'test1', 'ling']
['test2', 'hello', 'world', '3', '2', 'test2', 'test3', 'test2', 'test1', 'ling']
world
test2
['hello', 'world', '3', '2', 'test2', 'test3', 'test2', 'test1', 'ling']
['hello', '3', '2', 'test2', 'test3', 'test2', 'test1', 'ling']
set操作
set集合就是不重复的列表
sadd(name,values)
#给name对应的集合中添加元素
r.sadd("set_name","aa")
r.sadd("set_name","aa","bb")
smembers(name)
#获取name对应的集合的所有成员
scard(name)
#获取name对应的集合中的元素个数
r.scard("set_name")
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"))
#输出:{bb}
sismember(name, value)
#检查value是否是name对应的集合内的元素
spop(name)
#从集合的右侧移除一个元素,并将其返回
sunion(keys, *args)
#获取多个name对应的集合的并集
r.sunion("set_name","set_name1","set_name2")
srem(name, value) 删除集合中的某个元素
r.srem("set_name", "aa")
Hash类型操作
Redis在内存中存储hash类型是以name对应一个字典形式存储的
hset(name,key,value)
#name对应的hash中设置一个键值对(不存在,则创建;否则,修改)
#参数:
#name:redis的name
#key:key1对应的hash中的key
#value:value1对应的hash中的value
#注:hsetx(name,key,value),当name对应的hash中不存在当前key则创建(相当于添加)
hget(name,key)
#在name对应的hash中获取根据key获取value
hmset(name,mapping)
#在name对应的hash中批量设置键值对
#参数:
#name:redis的name
#mapping:字典,如{'k1':'v1','k2','v2'}
#例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
pool = redis.ConnectionPool(host='192.168.10.128',port=6379)
r = redis.Redis(connection_pool=pool)
r.hmset('test',{'k1':'v1','k2':'v2'})
print(r.hmget('test','k1','k2'))
#结果
[b'v1', b'v2']
hmget(name,keys,*args)
#在name对应的hash中获取多个key的值
#参数:
#name:redis对应的name
#keys:要获取key的集合,如:['k1','k2','k3']
#*args:要获取的key,如:k1,k2,k3
#例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
pool = redis.ConnectionPool(host='192.168.10.128',port=6379)
r = redis.Redis(connection_pool=pool)
r.hmset('test',{'k1':'v1','k2':'v2'})
print(r.hmget('test','k1','k2')) #获取多个值
hgetall(name):获取name对应hash的所有键值
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
pool = redis.ConnectionPool(host='192.168.10.128',port=6379)
r = redis.Redis(connection_pool=pool)
r.hmset('test',{'k1':'v1','k2':'v2'})
print(r.hgetall('test'))
#结果:
{b'k2': b'v2', b'k1': b'v1'}
hlen(name):获取name对应的hash中键值的个数
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
pool = redis.ConnectionPool(host='192.168.10.128',port=6379)
r = redis.Redis(connection_pool=pool)
r.hmset('test',{'k1':'v1','k2':'v2'})
print(r.hlen('test'))
#结果:
2
hkeys(name):获取name对应的hash中所有的key的值
hvals(name):获取name对应的hash中所有的value的值
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
pool = redis.ConnectionPool(host='192.168.10.128',port=6379)
r = redis.Redis(connection_pool=pool)
r.hmset('test',{'k1':'v1','k2':'v2'})
print(r.hkeys('test'))
print(r.hvals('test'))
#结果:
[b'k2', b'k1']
[b'v2', b'v1']
hexists(name,key):检查name对应的hash是否存在当前传入的key
hdel(name,*keys):将name对应的hash中指定key的键值对删除
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import redis
pool = redis.ConnectionPool(host='192.168.10.128',port=6379)
r = redis.Redis(connection_pool=pool)
r.hmset('test',{'k1':'v1','k2':'v2'})
print(r.exists('test'))
print(r.hdel('test','k1'))
print(r.hmget('test','k1','k2'))
#结果:
True
1
其他常用操作
delete(*names)
#根据name删除redis中的任意数据类型
exists(name)
#检测redis的name是否存在
keys(pattern='*')
#根据* ?等通配符匹配获取redis的name
expire(name ,time)
# 为某个name设置超时时间
rename(src, dst)
# 重命名
move(name, db))
将redis的某个值移动到指定的db下
# 将redis的某个值移动到指定的db下
type(name)
# 获取name对应值的类型