缓存
一、memcached(value只能是str):
- 关键字:
- 旁路代理:
- 缓存中若无数据,app自行从db获取,至于获取后是否更新缓存,由代码自行决定
- 分布式:
- 各个节点彼此无视,分布式功能完全依赖于app或调度器实现(haproxy)
- 缓存清理:
- memcache不会主动清理过期数据,只会在系统给memcache分配的存储空间使用完毕后,根据LUR算法,数据更新时对不活跃数据进行覆盖
- 集群实现:
- 通过app自身,搞笑,雪崩了谁背锅
- 通过haproxy,正解!只有haproxy调度器有一致性hash算法
- 安装:
- server:
- 1、yum install memcached ;
- 2、memadmin.tar.gz 管理控制台,php实现的web版本
- client:
- 1、安装API:python-memcache # --trusted-host=pypi.douban.com
- 使用:
- 连接:
- import memcache
- server = memcache.Client( [('1.1.1.1:11211', 1), ('1.1.1.2:11211', 2)] )
- 解释:
- 该API模块支持权重,即其内部维护一个主机列表,权重为几,则该节点在列表中出现几次
- 如上则:1.1.1.1 1.1.1.2 1.1.1.2
- 插入数据时,将Key先进行hash取数值,再将数值与列表长度取模,余数为几则数据存储到第几个个节点
- add/replace:
- server.add('key1', value1) # 添加一条键值对,若key已经存在,则操作异常
- server.replace('key1', '11111') # 修改某个key的值,若key不存在,则异常
- set/set_nulti
- server.set('key1', 'value1') # 设置一个键值对,key不存在则创建;key存在则修改
- server.set_nulti( {'key1':'value1', 'key2':'value2'} ) # 设置多个键值对,key不存在则创建,key存在则修改
- delete/delete_nulti
- server.delet('key1') # 删除一个键值对
- server.delete_nulti(['key1', 'key2']) # 删除多个键值对
- get/get_nulti
- ret = server.get( 'key1' ) # 获取一个键值对
- ret_dic = server.get_multi( ['key1', 'key2'] ) # 获取多个键值对
- append/prepend
- server.append('key', 'after') # 在指定key值后,追加内容
- server.prepend('key', 'before') # 在指定key值前,插入内容
- incr/decr
- server.incr('key1') # 将某一个值自增N ,N默认为1, key的值自增了1
- server.incr('key1', 10 ) # 将某一个值自减N, N默认为1, key的值自增了10
- server.decr('key1') # key的值自减1
- server.decr('key1', 10) # key的值自减10
- gets/cas
- 场景:若商品剩余数为100,A B 同时购买1个,则本应为98,但是,A和B均会在本地运算得到99 ,再写入memcahce中
- ret = server.gets('product_count') # 每个 client 通过 gets 获取数据的同时,会获取该key的一个自增的数字,
- server.cas('product_count', 99) # 修改数据前,对比之前获取的数字是否相同,不同则表示有人也获取了数据,可能已经被别人在本地修改,即抛出异常
二、redis:
1、概述
- 安装:
- server: yum install redis ; sed -i 's/127.0.0.1/0.0.0.0/g' /etc/redis.conf
- client:安装API :redis # --trusted-host=pypi.douban.com
- import redis
- redis-py 提供了两个类:Redis, StrictRedis 用于实现redis的命令,
- StrictRedis用于实现大部分官方的语法和命令,
- Redis 是strictredis的子类,用于向前兼容旧版本的redis-py
- 连接方式:
- 直接连接:conn = redis.Redis(host='1.1.1.1', port=6379)
- 连接池:
pool = redis.ConnectionPool(host='1.1.1.1', port=6379)conn = redis.Redis(connection_pool=pool)conn.set('key1', 'value1')
2、基本数据类型
- string 操作 (name -- key 一一对应):
- set/mset
set( name, value, ex=None, px=None, nx=False, xx=False )
ex 过期时间,秒px 过期时间,毫秒nx 若为True,则表示只有该key不存在,才执行setxx 若为True,则表示只有该key存在,才执行set
setns(name, value) # 当name不存在时,创建一个键值对setex(name, value, time) # 创建一个键值对,并指定过期时间mset(* args, **kwargs) # 批量设置,比如 mset( k1='v1', k2='v2' )
- get(name) / mget(keys, *args)
get('key1')mget('key1', 'key2') # 批量获取
- 自增、自减、追加
incr(self, name, amount=1) # 当name不存在,则创建name=amount,否则其值自增1infcbyfloat(self, name, amount=1.0) # 同上,只不过自增可浮点数decr(self, name, amount=1) # 自减name对应的值,当name不存在则创建name=amountappend(key, value) # 在指定key后面追加内容
- 其他:
getset(name, value) # 设置一个新值,并获取原先的值getrange(name, start, end) # 切片,获取指定key的值,并进行切片strlen(name) # 获取指定key的字节长度
- hash 操作(一个name对应一个字典)
- 创建
hset(name, key, value) # 为指定name关联一个键值对,不存在则创建;存在则修改hmset(name, mapping) # 批量设置键值对,mapping为字典,比如dic1 , dic2
- 获取指定字典中指定key值
hget(name, key) # 在name对应的dict中获取指定keyhmget(name, keys, lst) # 在name对应的hash中获取多个key值,lst中为各key名称
- 获取指定字典中所有key值
hgetall(name) # 获取name对应的所有键值hlen(name) # 获取name对应的所有键值个数
- 获取指定字典中所有key,或value
hkeys(name) # 获取name对应的hash中所有的key名称hvals(name) # 获取name对应的hash中所有的value的值
- 判断:
hexists(name, key) # 检测name对应的hash中是否存在指定key
- 删除:
hdel(name, *keys) # 将name对应的hash中指定key键值对删除, *keys可为列表或元组
- 自增、自减:
hincrby(name, key, amount=1) # 自增指定name中的指定key值,不存在,则创建key=amounthincrbyfloat(name, key, amount=1.0) # 同上,只不过可以浮点
- 迭代获取:
--------------- hscanhscan(name, cursor=0, match=None, coiunt=None) # 迭代式获取键值对cursor1, data = server.hscan(name, cursor=0, match=None, count=None)cursor2, data = server.hscan(name, cursor=cursor1, match=None, count=None)# cursor表示游标指针,当cusor为0,则表示已经获取完成-------------- hscan_iter# 利用yield封装hscan创建生成器,实现分批获取数据for item in server.hscan_iter(name)print(item)
- list 操作(每个name对应一个lst)
- 添加:
lpush(name, value) # 在name对应的list中添加元素,每个新的元素都添加到列表的最左边server.lpush('name1', 11,22,33) # 保存顺序为33,22,11
- 列表长度:
llen(name) # 返回对应list的元素个数
- 插入:
linsert(name, where, refvalue, value) # 在指定值前后插入数据name: 名称where: BEFORE 或 AFTERrefvalue: 标杆值,即在该值前后插入数据value: 要插入的数据
- 删除:
lrem(name, value, num) # 在name对应的list中删除指定的值name::列表名称value:要删除的值num:正数,从前到后删N个,负数,从后到前删N个
- pop弹出:
lpop(name) # 从列表左侧弹出一个元素并移除,rpop(name) # 从右侧弹出
- 切片、索引:
lset(name, index,value) # 在索引处重新赋值lindex(name , index) # 在列表中根据索引获取列表元素lrange(name, start, end) # 切片获取数据ltrim(name, start, end) # 切片移除
- 迭代:
redis的列表没有提供迭代功能,因此需要自定义增量迭代# 定义迭代器def list_iter(name):list_count = server.llen(name)for index in range(list_count):yield server.lindex(name, index)# 使用for item in list_iter('name'):print(item)
- 集合、有序集合
3、管道:
- redis-py 默认在执行每次请求时,都会创建(连接池获取链接)和断开(释放链接),
- 若想在一次请求中完成多个指令,则可以使用pipline,默认情况下,一次pipline是个原子操作
import redispool = redis.ConnectionPool(host='1.1.1.1', port=6379)server = redis.Redis(connection_pool=pool)
pipe = server.pipline(transaction=True) # 下面的指令会缓存到cmd buffer中server.set('name', 'qiao')server.set('role', 'kk')pipe.execute() # 发送并执行cmd buffer中指令
4、发布订阅:
- 自定义公共类
import redis
class RedisHelper:def __init__(self):self.__conn = redis.Redis(host='61.129.46.111', port=6379)self.chan_sub = 'xxx'
def public(self, msg): # 发布消息self.__conn.publish(self.chan_sub, msg)return True
def subscribe(self):pub = self.__conn.pubsub() # 返回一个发布订阅对象pub.subscribe(self.chan_sub) # 订阅一个频道pub.parse_response() # 订阅频道的时候也会有响应,需要解析return pub # 返回该订阅了频道的“发布/订阅”对象
- client端(订阅者)
obj = RedisHelper() # 实例化一个自定义类redis_sub = obj.subscribe() # 获取一个已经订阅了频道的“发布/订阅”对象
while True:msg = redis_sub.parse_response() # 响应为列表结构,且元素为字节,[b'message', b'xxx', b'test1'] <class 'list'>data = msg[2].decode()print(msg, type(msg), data, type(data))
- server 端(发布者)
obj = RedisHelper() # 实例化一个自定义类obj.public('test1') # 发布消息