1.Memcached

Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信

memcached服务端安装部署

安装libevent
memcached依赖于libevent API,因此要事先安装之,项目主页:http://libevent.org/,读者可自行选择需要的版本下载。本文采用的是目前最新版本的源码包libevent-2.0.16-stable.tar.gz。安装过程:

# tar xf libevent-2.0.20-stable.tar.gz
# cd libevent-2.0.20
# ./configure --prefix=/usr/local/libevent
# make && make install

# echo "/usr/local/libevent/lib" > /etc/ld.so.conf.d/libevent.conf
# ldconfig 

安装配置memcached

1、安装memcached
# tar xf memcached-1.4.15.tar.gz 
# cd memcached-1.4.15
# ./configure --prefix=/usr/local/memcached --with-libevent=/usr/local/libevent
# make && make install

 

memcached的常用选项说明
-l <ip_addr>:指定进程监听的地址;
-d: 以服务模式运行;
-u <username>:以指定的用户身份运行memcached进程;
-m <num>:用于缓存数据的最大内存空间,单位为MB,默认为64MB;
-c <num>:最大支持的并发连接数,默认为1024;
-p <num>: 指定监听的TCP端口,默认为11211;
-U <num>:指定监听的UDP端口,默认为11211,0表示关闭UDP端口;
-t <threads>:用于处理入站请求的最大线程数,仅在memcached编译时开启了支持线程才有效;
-f <num>:设定Slab Allocator定义预先分配内存空间大小固定的块时使用的增长因子;
-M:当内存空间不够使用时返回错误信息,而不是按LRU算法利用空间;
-n: 指定最小的slab chunk大小;单位是字节;
-S: 启用sasl进行用户认证;

 

python操作memcached

安装python的memcached的模块
pip install python-memcached

  • 常用方法
    • Client() 连接memcache服务器
    • set设置key/value key存在则更新,不存在则创建
    • set_multi 设置多个键值对,如果key不存在,则创建,如果key存在,则修改
    • get 根据单个key获取value
    • get_multi 根据key列表获取value,最终返回一个key/value的字典
    • add 添加一对key/value,如果已经存在的 key,则添加失败
    • replace 修改某个key的value ,key不存在则失败
    • delete 根据key删除对应的key/value
    • delete_multi 根据key 列表删除对应的key/value
    • append 根据key在对应的value后面追加值
    • prepend 根据key在对应的value前面追加值
    • incr 自增,将Memcached中的某一个值增加 N ( N默认为1 )
    • decr 自减,将Memcached中的某一个值减少 N ( N默认为1 )
  • 举例
import memcache

host_port = '127.0.0.1:11211'    #memcache的ip和端口

mc = memcache.Client([host_port], debug=True)        #debug表示开启调试,

mc.set('name','fuzj')               #设置key
mc.set('name','jeck')               #设置key,key存在则更新
mc.set('name1','test')               #设置key,key存在则更新
mc.set('name2','test')               #设置key,key存在则更新
mc.set_multi({'k1':'v1','k2':'v2'})     #批量设置key

print(mc.get('name'))   #获取key
print(mc.get('123'))    #获取不存在的key,返回None
print(mc.get_multi('k1','k2'))

mc.add('age','22')
mc.add('age','123')  #key存在会抱错

mc.replace('age','26')    #修改key的value
mc.replace('abc','26')    #修改不存在的key,会抱错

mc.delete('name1')     #删除age键值对
mc.delete_multi('k1','k2')  #删除k1 k2 键值对

mc.append('name','你好')    #在name的value 的后面加 '你好'
mc.prepend('name','hi')   #在name的value 的前面加'hi'

mc.incr('age')          #age对应的value增加1
mc.decr('age',10)           #age对应的value 减少10
mc.decr('name')     #name的value不能被加减,会抱错

 

  • 支持集群

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

     主机    权重
    1.1.1.1   1
    1.1.1.2   2
    1.1.1.3   1
 
那么在内存中主机列表为:
    host_list = ["1.1.1.1", "1.1.1.2", "1.1.1.2", "1.1.1.3", ]
 

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

根据算法将 k1 转换成一个数字
将数字和主机列表长度求余数,得到一个值 N( 0 <= N < 列表长度 )
在主机列表中根据 第2步得到的值为索引获取主机,例如:host_list[N]
连接 将第3步中获取的主机,将 k1 = v1 放置在该服务器的内存中
代码实现如下:

mc = memcache.Client([('1.1.1.1:12000', 1), ('1.1.1.2:12000', 2), ('1.1.1.3:12000', 1)], debug=True)
 
mc.set('k1', 'v1')

 

  • 解决乐观锁 gets/cas

如果两个客户端同时操作一个key,就会导致数据冲突
如商城商品剩余个数,假设改值保存在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来操作以上过程,那么程序就会如上述所示情况!

如果采用CAS协议,则是如下的情景。
第一步,A取出product_count=900,并获取到CAS-ID1;
第二步,B取出product_count=900,并获取到CAS-ID2;
第三步,A购买时,修改product_count=899,在写入缓存前,检查CAS-ID与缓存空间中该数据的CAS-ID是否一致。结果是“一致”,就将修改后的带有CAS-ID1的product_count写入到缓存。
第四步,B购买时,修改product_count=899,在写入缓存前,检查CAS-ID与缓存空间中该数据的CAS-ID是否一致。结果是“不一致”,则拒绝写入,返回存储失败。

2.Redis

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

redis服务端安装部署

1. 下载Redis
目前,最新的Redist版本为3.0,使用wget下载,命令如下:

# wget http://download.redis.io/releases/redis-3.0.4.tar.gz

2. 解压Redis
下载完成后,使用tar命令解压下载文件:

# tar -xzvf redis-3.0.4.tar.gz

3. 编译安装Redis
切换至程序目录,并执行make命令编译:

# cd redis-3.0.4
# make
执行安装命令

# make install
make install安装完成后,会在/usr/local/bin目录下生成下面几个可执行文件,它们的作用分别是:

redis-server:Redis服务器端启动程序
redis-cli:Redis客户端操作工具。也可以用telnet根据其纯文本协议来操作
redis-benchmark:Redis性能测试工具
redis-check-aof:数据修复工具
redis-check-dump:检查导出工具

备注

有的机器会出现类似以下错误:

make[1]: Entering directory `/root/redis/src'
You need tcl 8.5 or newer in order to run the Redis test
……
这是因为没有安装tcl导致,yum安装即可:

yum install tcl

4. 配置Redis
复制配置文件到/etc/目录:

# cp redis.conf /etc/
为了让Redis后台运行,一般还需要修改redis.conf文件:

vi /etc/redis.conf
修改daemonize配置项为yes,使Redis进程在后台运行:

daemonize yes

5. 启动Redis
配置完成后,启动Redis:

# cd /usr/local/bin
# ./redis-server /etc/redis.conf
检查启动情况:

# ps -ef | grep redis
看到类似下面的一行,表示启动成功:

root     18443     1  0 13:05 ?        00:00:00 ./redis-server *:6379 

6. 添加开机启动项
让Redis开机运行可以将其添加到rc.local文件,也可将添加为系统服务service。本文使用rc.local的方式,添加service请参考:Redis 配置为 Service 系统服务 。

为了能让Redis在服务器重启后自动启动,需要将启动命令写入开机启动项:

echo "/usr/local/bin/redis-server /etc/redis.conf" >>/etc/rc.local

7. Redis配置参数
在前面的操作中,我们用到了使Redis进程在后台运行的参数,下面介绍其它一些常用的Redis启动参数:

daemonize:是否以后台daemon方式运行
pidfile:pid文件位置
port:监听的端口号
timeout:请求超时时间
loglevel:log信息级别
logfile:log文件位置
databases:开启数据库的数量
save * *:保存快照的频率,第一个*表示多长时间,第三个*表示执行多少次写操作。在一定时间内执行一定数量的写操作时,自动保存快照。可设置多个条件。
rdbcompression:是否使用压缩
dbfilename:数据快照文件名(只是文件名)
dir:数据快照的保存目录(仅目录)
appendonly:是否开启appendonlylog,开启的话每次写操作会记一条log,这会提高数据抗风险能力,但影响效率。
appendfsync:appendonlylog如何同步到磁盘。三个选项,分别是每次写都强制调用fsync、每秒启用一次fsync、不调用fsync等待系统自己同步

 

python操作redis

安装redis模块
pip install redis

连接方式

  • 单连接

    使用redis.Redis()方法,直接和redis建立连接,操作完成之后,连接释放

import redis

r = redis.Redis(host='127.0.0.1', port=6379)
r.set('foo', 'Bar')
print(r.get('foo'))

 

  • 连接池 redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池。
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379,)
r = redis.Redis(connection_pool=pool)
r.set('foo1', 'Bar')
print(r.get('foo1'))

 

操作

  • 字符串存储操作

    • 存储形式:
      python对缓存(memcached,redis)的操作

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

      参数:
      name:key的名称
      value:key的值
      ex,过期时间(秒)
      px,过期时间(毫秒)
      nx,如果设置为True,则只有name不存在时,当前set操作才执行
      xx,如果设置为True,则只有name存在时,当前set操作才执行

    • setnx(name, value) 设置值,只有name不存在时,执行设置操作(添加)

    • setex(name, value, time) 设置值,同时支持设置过期时间,单位:秒s

    • psetex(name, time_ms, value) 设置值,同时支持设置过期时间,单位:毫秒ms

    • mset(*args, **kwargs) 批量设置值 ,支持 字典形式{'k1':'v1','k2':'v2'}和key=value形式k1=v1,k2=v2

    • get(name) 获取key的value

    • mget(keys, *args) 批量获取值 支持k1,k2,k3形式和[k1,k2]形式

    • getset(name, value) 获取原来的值,并设置新的值

    • getrange(key, start, end) 获取指定key的value的子序列 start和end为value的起始位置

    • setrange(name, offset, value) 修改字符串内容,从指定字符串索引开始向后替换(新值太长时,则向后添加),offset,字符串的索引,字节(一个汉字三个字节),value,要设置的值

    • setbit(name, offset, value) 对name对应值的二进制表示的位进行操作,offset,字符串的索引,字节(一个汉字三个字节),value,要设置的值

    • getbit(name, offset) 获取name对应的值的二进制表示中的某位的值 (0或1)

    • bitcount(key, start=None, end=None) 获取name对应的值的二进制表示中 1 的个数

    • bitop(operation, dest, *keys) 获取多个值,并将值做位运算,将最后的结果保存至新的name对应的值

      参数:
      operation,AND(并) 、 OR(或) 、 NOT(非) 、 XOR(异或)
      dest, 新的Redis的name
      *keys,要查找的Redis的name

    • strlen(name) 返回name对应值的字节长度(一个汉字3个字节)

    • incr(name, amount=1) 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增 ,amount必须为整数

    • incrbyfloat(name, amount=1.0) 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增,amount为浮点数

    • decr(name, amount=1) 自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减

    • decrbyfloat(name, amount=1.0) 自减 name对应的值,当name不存在时,则创建name=amount,否则,则自减,amount为浮点数

    • append(key, value) 在redis name对应的值后面追加内容

    • 用法示例:

import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379,)
r = redis.Redis(connection_pool=pool)


r.set('k1','v1',ex=2)
r.setnx('k2','v2')  #设置k1 v1

r.mset({'k3':'v3','k4':'v4'})           #批量设置kv
print(r.get('k1'))       #获取k1的value
print(r.mget(['k2','k3']))         #批量获取k2,k3
print(r.getset('k2','v22'))        #获取k2的值,并将k2的值设置为v22
print(r.get('k2'))
print(r.getrange('k2',1,2))     #获取k2的value的子序列1-2,默认从0开始
r.setrange('k4',2,'12313')
print(r.get('k4'))
print(r.strlen('k4'))         #获取k4的value长度
r.set('k5','123')
r.incr('k5',10)      #设置k5的值自增10
r.append('k5','')   #在k5的值后面追加元
print(r.get('k5').decode())
View Code

相关文章: