day13 memcache,redis上篇

memcache

memcache简介

Memcached是一个自由开源的,高性能,分布式内存对象缓存系统。

Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的一款软件。现在已成为mixi、hatena、Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。

Memcached是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。这些数据可以是数据库调用、API调用或者是页面渲染的结果。

Memcached简洁而强大。它的简洁设计便于快速开发,减轻开发难度,解决了大数据量缓存的很多问题。它的API兼容大部分流行的开发语言。

本质上,它是一个简洁的key-value存储系统。通过socket连接和发送命令

一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

特征

协议简单

基于libevent的事件处理

内置内存存储方式

memcached不互相通信的分布式

memcached安装

参考安装链接地址:http://www.runoob.com/memcached/window-install-memcached.html

当前安装方法:

 

[root@centos6 ~]# yum install memcached -y
[root@centos6 ~]# memcached -d -m 10 -u root -l 172.1.1.7 -p 11211 –P /tmp/memcached.pid   #启动

参数说明:

-d 是启动一个守护进程

-m 是分配给Memcache使用的内存数量,单位是MB

-u 是运行Memcache的用户

-l 是监听的服务器IP地址

-p 是设置Memcache监听的端口,最好是1024以上的端口

-c 选项是最大运行的并发连接数,默认是1024,按照你服务器的负载量来设定

-P 是设置保存Memcache的pid文件

 

python操作memcached:

  • 安装API:
python操作Memcached使用Python-memcached模块

下载安装:https://pypi.python.org/pypi/python-memcached
  • python操作memcached
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import memcache
memc=memcache.Client(['172.1.1.7:11211'],debug=True)     #连接远程mencache
memc.set('key1','value1')                                 #写入键值对
rec=memc.get('key1')                                       #按照key读取参数
print(rec)
  • 连接memcached集群

memc=memcache.Client([(‘172.1.1.6:11211’,1),(‘172.1.1.2:11211’,2)],debug=True)

权重的计算:

     python-memcached模块原生支持集群操作,其原理是在内存维护一个主机列表,且集群中主机的权重值和主机在列表中重复出现的次数成正比;

如果用户根据如果要在内存中创建一个键值对(如:k1 = "v1"),那么要执行一下步骤:

  1. 根据算法将 k1 转换成一个数字
  2. 将数字和主机列表长度求余数,得到一个值 N( 0 <= N < 列表长度 )
  3. 在主机列表中根据 第2步得到的值为索引获取主机,例如:host_list[N]
  4. 连接 将第3步中获取的主机,将 k1 = "v1" 放置在该服务器的内存中
  • add操作
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import memcache
#添加一条键值对,如果已经存在的 key,重复执行add操作异常
memc = memcache.Client(['172.1.1.7:11211'],debug=True)   #连接memcache服务端
memc.add('k1','v1')     #添加参数
# memc.add('k1','v2')
  • replace操作
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import memcache
#replace 修改某个key的值,如果key不存在,则异常
memc=memcache.Client(['172.1.1.7:11211'],debug=True)
memc.replace('k1','vvvv')
kk=memc.get('k1')
print(kk)
  • set&set_multi
    • set 设置一个键值对,如果key不存在,则创建,
    • 如果key存在,可以修改 set_multi 设置多个键值对,如果key不存在,则创建,如果key存在,可以修改
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import memcache
memc=memcache.Client(['172.1.1.7:11211'],debug=True)
#memc.set('s1','t1')
memc.set_multi({'s2':'k2','s3':'k3'})     #千万要记住此处是引号
  • delete&delete_multi
    • delete 在Memcached中删除指定的一个键值对
    • delete_multi 在Memcached中删除指定的多个键值对
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import memcache
memc = memcache.Client(['172.1.1.7:11211'],debug=True)
#memc.delete('s1')
memc.delete_multi(['s2','s3'])
  • get&get_multi
    • get            获取一个键值对
    • get_multi   获取多个或者一个键值对
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import memcache
memc=memcache.Client(['172.1.1.7:11211'],debug=True)
val1 = memc.get('s1')     #取的值不存在就为None
print(val1)
val2 = memc.get_multi(['s1','s2','s3'])
print(val2)
  • append&prepend
    • append    修改指定key的值,在该值 后面 追加内容
      prepend   修改指定key的值,在该值 前面 插入内容
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import memcache
memc = memcache.Client(['172.1.1.7:11211'],debug=True)
memc.append('s7','after')
memc.prepend('s7','before')
t=memc.get('s7')
print(t)

decr&incr

  • incr  自增,将Memcached中的某一个值增加 N ( N默认为1 )
    decr 自减,将Memcached中的某一个值减少 N ( N默认为1 )
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import memcache
memc=memcache.Client(['172.1.1.7:11211'],debug=True)
memc.set('k1','789')
t1=memc.incr('k1')
print(t1)
t2=memc.decr('k1')
print(t2)
  • gets&cas(最重要的)

    如商城商品剩余个数,假设改值保存在memcache中,product_count = 900
    A用户刷新页面从memcache中读取到product_count = 900
    B用户刷新页面从memcache中读取到product_count = 900

    如果A、B用户均购买商品

    A用户修改商品剩余个数 product_count=899
    B用户修改商品剩余个数 product_count=899

    如此一来缓存内的数据便不在正确,两个用户购买商品后,商品剩余还是 899
    如果使用python的set和get来操作以上过程,那么程序就会如上述所示情况!

    如果想要避免此情况的发生,只要使用 gets 和 cas 即可,如:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import memcache
    memc=memcache.Client(['172.1.1.7:11211'],debug=True)
    v=memc.gets('product_count')
    memc.cas('product_count','211')     #再次修改则会出错
    print(v)
    memc.cas('product_count','122')     #设定值

      本质上每次执行gets时,会从memcache中获取一个自增的数字,通过cas去修改gets的值时,会携带之前获取的自增值和memcache中的自增值进行比较,如果相等,则可以提交,如果不想等,那表示在gets和cas执行之间,又有其他人执行了gets(获取了缓冲的指定值), 如此一来有可能出现非正常数据,则不允许修改。

redis

  • 简介:

       redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

  • redis的安装
[root@centos6 opt]# tar xf redis-3.0.3.tar.gz 
[root@centos6 opt]# mv redis-3.0.3 redis
[root@centos6 opt]# cd redis
[root@centos6 redis]# make && make install
[root@centos6 redis]# /opt/redis/src/redis-server /opt/redis/redis.conf &     #启动服务
  • 客户端操作
[root@centos6 ~]# /opt/redis/src/redis-cli      #客户端连接
127.0.0.1:6379> set 'foo' 'bar'       #设定键值对
OK
127.0.0.1:6379> get 'foo'              #按照key取出值        
"bar"
  • python操作redis

    redis-py 的API的使用可以分类为:

      • 连接方式
      • 连接池
      • 操作
        • String 操作
        • Hash 操作
        • List 操作
        • Set 操作
        • Sort Set 操作
      • 管道
      • 发布订阅

连接方式

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)                       #创建连接
red.set('foo','bar')                                              #存入键值对
print(red.get('foo'))                                             #按照key取出值

连接池

     redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.ConnectionPool(host='172.1.1.7',port=6379)     #创建连接池
poo=redis.Redis(connection_pool=red)                             #创建连接
poo.set('hello','nimei')
print(poo.get('hello'))

操作

set(name, value, ex=None, px=None, nx=False, xx=False)

在Redis中设置值,默认,不存在则创建,存在则修改

参数:

ex,过期时间(秒)

px,过期时间(毫秒)

nx,如果设置为True,则只有name不存在时,当前set操作才执行

xx,如果设置为True,则只有name存在时,当前set操作才执行

String操作:

  • setnx:设置值,只有name不存在时,执行设置操作(添加)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red = redis.Redis(host='172.1.1.7',port=6379)
r=red.setnx('kk','vv')      #setnx设置值,只有name不存在时,执行设置操作(添加)
print(red.get('kk'))         #打印key对应的值
  • setex:设置超时时间(超时时间过后,key value失效)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
import time
red = redis.Redis(host='172.1.1.7',port=6379)
red.setex('kk1','vv1',2)     #设置有效时长为2
print(red.get('kk1'))
time.sleep(3)                 #3秒超时
print(red.get('kk1'))        #返回值为空
  • mset&mget:批量设置值
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)
red.set('k6','v7')                     #单个设值
red.mset(k1='v1',k2='v2',k3='v3')      #第一种方法设值
red.mset({'k4':'v4','k5':'v5'})        #第二种方法
print(red.mget('k1','k2'))          #多个取值
print(red.get('k3'))                #单个取值
  • getset:设置新值并获取原来的值
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red = redis.Redis(host='172.1.1.7',port=6379)
red.set('k1','vvv')
print(red.getset('k1','afternew'))     #设置新值,返回旧值
print(red.get('k1'))      #此时是新值
  • getrange:获取子序列
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)
red.set('k1','我爱北京天安门')
tt=red.getrange('k1',0,3)
print(red.getrange('k1',0,3))    #汉子的话是每三个字节算一个汉子
  • setrange(name, offset, value):修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)
red.set('k1','我爱北京天安门')
print(red.setrange('k1',3,'hah'))
  • setbit(name, offset, value):对name对应值的二进制表示的位进行操作
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)
red.set('k1','vava')
red.setbit('k1',7,1)      #将第七位设置成一
print(red.get('k1'))
source = "哈哈哈"
for i in source:
    num=ord(i)
    print(bin(num).replace('b',''))      #打印二进制格式字符串

 

  • getbit(name, offset):获取name对应的值的二进制表示中的某位的值 (0或1)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)
red.set('k1','abc')
print(red.getbit('k1',2))
  • bitcount(key, start=None, end=None):获取name对应的值的二进制表示中 1 的个数
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)
red.set('k1','abc')
print(red.bitcount('k1',0,8))
  • bitop(operation, dest, *keys):获取多个值,并将值做位运算,将最后的结果保存至新的name对应的值
    • 参数:
      • operation:AND,OR,NOT,XOR
      • dest:new data
      • src:source data
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)
red.set('k1','abc')
print(red.bitop('AND','kk','k1'))
  • strlen(name):返回name对应值的字节长度(一个汉字3个字节)

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)
red.set('k1','abc')
print(red.strlen('k1'))

 

  • incr(self, name, amount=1):自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增
  • decr(self, name, amount=1):自减name对应的值
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)
red.set('k1','12345')     #确保value是整数
print(red.incr('k1',amount=1))     #amount代表步进值
  • incrbyfloat(self, name, amount=1.0)   带浮点的自增
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='172.1.1.7',port=6379)
red.set('k1','12345.999')     #确保value是整数或者浮点数
print(red.incrbyfloat('k1',amount=0.1))
  • append(key, value):追加模式
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='10.70.18.66',port=6379)
red.set('k1','v1')
red.append('k1','v2')      #对应的k1键值后面追加v2,
print(red.get('k1'))       #结果b'v1v2'

Hash操作

redis中Hash在内存中的存储格式如下图:

 

  • hset(‘name’,’key’,’value’)&hget(’name’,’key’),getall(‘name’):设置和获取哈希值
import redis
red=redis.Redis(host='10.70.18.100',port=6379)
red.hset("a1","k1","v1")
print(red.hget('a1','k1')) #要有key和对应的hash键b'v1'
print(red.hgetall('a1')) #获取全部{b'k1': b'v1'}
  • mset(name, mapping)&mget(name, mapping)&getall()
import redis
red=redis.Redis(host='10.70.18.100',port=6379)
dic={'k1':'v1','k2':'v20'}
red.hmset('a2',dic)
print(red.hget('a2','k1'))     #返回值b'v1'
print(red.hgetall('a2'))        #返回一个字典
  • hlen(name):获取name对应的hash中键值对的个数
import redis
red=redis.Redis(host='10.70.18.100',port=6379)
dic={'k1':'v1','k2':'v20'}
red.hmset('a2',dic)
print(red.hgetall('a2'))        #返回一个字典
print(red.hlen('a2'))          #打印元素个数
  • hkeys(name):获取name对应的hash中所有的key的值
import redis
red=redis.Redis(host='10.70.18.100',port=6379)
dic={'k1':'v1','k2':'v20'}
red.hmset('a2',dic)
print(red.hgetall('a2'))        #返回一个字典
print(red.hkeys('a2'))         #打印key
  • hvals(name):获取name对应的hash中所有的value的值
import redis
red=redis.Redis(host='10.70.18.100',port=6379)
dic={'k1':'v1','k2':'v20'}
red.hmset('a2',dic)
print(red.hvals('a2'))         #打印value值
  • hexists(name, key):检查name对应的hash是否存在当前传入的key
import redis
red=redis.Redis(host='10.70.18.100',port=6379)
dic={'k1':'v1','k2':'v20'}
red.hmset('a2',dic)
print(red.hexists('a2','ds'))  #是否存在a2对应的ds,返回False
  • hdel(name,*keys):将name对应的hash中指定key的键值对删除
import redis
red=redis.Redis(host='10.70.18.100',port=6379)
dic={'k1':'v1','k2':'v20'}
red.hmset('a2',dic)
print(red.hdel('a2','k1'))     #删除对应的a2对应的看k1
  • hincrby(name, key, amount=1):自增name对应的hash中的指定key的值,不存在则创建key=amount
  • hincrbyfloat(name, key, amount=1.0):自增name对应的hash中的指定key的值,不存在则创建key=amount
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='10.70.18.100',port=6379)
dic={'k1':'999','k2':'111'}
red.hmset('a2',dic)
print(red.hincrby('a2','k1',amount=100))            #获取到的值自增amount值 
print(red.hincrbyfloat('a2','k2',amount=199.999))   #获取到的值自增amount值 
  • hscan(name, cursor=0, match=None, count=None):增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,并非一次性将数据全部获取完,从而防止内存被撑爆
# 增量式迭代获取,对于数据大的数据非常有用,hscan可以实现分片的获取数据,并非一次性将数据全部获取完,从而放置内存被撑爆
 
# 参数:
    # name,redis的name
    # cursor,游标(基于游标分批取获取数据)
    # match,匹配指定key,默认None 表示所有的key
    # count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
 
# 如:
    # 第一次:cursor1, data1 = r.hscan('xx', cursor=0, match=None, count=None)
    # 第二次:cursor2, data1 = r.hscan('xx', cursor=cursor1, match=None, count=None)
    # ...
    # 直到返回值cursor的值为0时,表示数据已经通过分片获取完毕
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='10.70.18.100',port=6379)
dic={'k1':'999','k2':'111'}
dic1={'k3':'2asd','k4':'dadsadadsd'}
red.hmset('a2',dic)
red.hmset('a3',dic1)

cursor1,data=red.hscan('a2',cursor=0,match=None,count=None)
print(cursor1,data)
cursor2,data2=red.hscan('a3',cursor=0,match=None,count=None)
print(cursor2,data2)
  • hscan_iter(name, match=None, count=None):利用yield封装hscan创建生成器,实现分批去redis中获取数据
# 利用yield封装hscan创建生成器,实现分批去redis中获取数据
 
# 参数:
    # match,匹配指定key,默认None 表示所有的key
    # count,每次分片最少获取个数,默认None表示采用Redis的默认分片个数
 
# 如:
    # for item in r.hscan_iter('xx'):
    #     print item

List操作

redis中的List在在内存中按照一个name对应一个List来存储。如图:

  • lpush(name,values):在name对应的list中添加元素,每个新的元素都添加到列表的最左边
  • rpush(name, values) :表示从右向左操作
  • lpop(name):在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素
  • rpop(name): 表示从右向左操作
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.Redis(host='10.70.18.100',port=6379)
red.lpush('k2',11,22,33,44)      #左插入
print(red.lpop('k2'))      #左取值,右出栈----》b'44'
print(red.rpop('k2'))      #右取值,左出栈----》b'11'
red.rpush('k3',12,1111)   #右插入
print(red.rpop('k3'))     #右弹出
print(red.lpop('k3'))     #左弹出
  • lpushx(name,value):在name对应的list中添加元素,只有name已经存在时,值添加到列表的最左边
  • rpushx(name, value): 表示从右向左操作
  • llen(name):name对应的list元素的个数
  • r.lset(name, index, value):对name对应的list中的某一个索引位置重新赋值
  • r.lrem(name, value, num):在name对应的list中删除指定的值
  • brpoplpush(src, dst, timeout=0):从一个列表的右侧移除一个元素并将其添加到另一个列表的左侧
  • blpop(keys, timeout):将多个列表排列,按照从左到右去pop对应列表的元素
  • rpoplpush(src, dst):从一个列表取出最右边的元素,同时将其添加至另一个列表的最左边
  • ltrim(name, start, end):在name对应的列表中移除没有在start-end索引之间的值
  • lrange(name, start, end):在name对应的列表分片获取数据
  • lindex(name, index):在name对应的列表中根据索引获取列表元素
  • 自定义增量迭代
# 由于redis类库中没有提供对列表元素的增量迭代,如果想要循环name对应的列表的所有元素,那么就需要:
    # 1、获取name对应的所有列表
    # 2、循环列表
# 但是,如果列表非常大,那么就有可能在第一步时就将程序的内容撑爆,所有有必要自定义一个增量迭代的功能:
 
def list_iter(name):
    """
    自定义redis列表增量迭代
    :param name: redis中的name,即:迭代name对应的列表
    :return: yield 返回 列表元素
    """
    list_count = r.llen(name)
    for index in xrange(list_count):
        yield r.lindex(name, index)
 
# 使用
for item in list_iter('pp'):
    print item

 

Set操作,Set集合就是不允许重复的列表

更多详情参考:http://www.cnblogs.com/wupeiqi/articles/5132791.html

管道

redis-py默认在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline 是原子性操作。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
red=redis.ConnectionPool(host='10.70.18.100',port=6379)  #创建连接池
red_s=redis.Redis(connection_pool=red)

pipe=red_s.pipeline(transaction=True)
# pipe=red_s.pipeline(transaction=False)
pipe.set('name',"hehehe")
pipe.set('role','sbbb')
pipe.execute()

发布订阅

 

 

helper:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis

class RedisHelper:
    def __init__(self):
        self.__conn = redis.Redis(host='10.70.18.100')
        self.chan_sub = 'fm104.5'
        self.chan_pub = 'fm104.5'

    def public(self, msg):
        self.__conn.publish(self.chan_pub, msg)
        return True

    def subscribe(self):
        pub = self.__conn.pubsub()
        pub.subscribe(self.chan_sub)
        pub.parse_response()
        return pub

publisher:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from redis_helper import RedisHelper
obj = RedisHelper()
obj.public('hello')

receiver:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from redis_helper import RedisHelper
obj=RedisHelper()
redis_sub=obj.subscribe()
while True:
    msg = redis_sub.parse_response()
    print(msg)
posted @ 2017-01-11 15:34  valiente  阅读(208)  评论(0编辑  收藏  举报