初窥Redis

导读

本文是基于python的redis学习笔记,采用python的redis模块操作,而非命令行,如果未满足您的需求还请见谅。本文的书写顺序是基于本人项目需求所写的,可能顺序有前后颠倒

1. Redis的几个用途

  • 缓存
    将部分操作频繁的数据放置到缓存中,以减少后台的压力,达到加快响应速度的目的。

  • 网站排行榜
    使用有序集合或列表结构,将计算好的排行榜存入,无需请求后台数据库,还能达到实时更新的要求。

  • 计数器 – 点赞量、浏览量、剩余量
    当一有人点赞、浏览或购买,这个数字就需要加1,这些数据如果使用关系型数据库储存,并发量会相当低。可以使用redis的incr(name, amount=1)来进行处理,自增1.

  • 消息队列
    不太懂

2.Redis不适用的方向吧

Redis的特性:redis是一款非关系型、单线程的数据库,其数据是加载到缓存中的。
我们看这两个词条 单线程缓存,这就意味着它的数据量不能无限大,不能做无底线的遍历查询。

  • 数据量方向
    当预计数据量非常大时,如几十GB,几百GB,明显不能使用redis,虽说内存已经很便宜了。
  • 冷热数据方向
    热数据:操作比较频繁的数据;冷数据:操作不频繁的数据.
    热数据放到redis中,加快读写速度,从而加快响应速度。
    冷数据放到redis中,由于这种数据操作频率很低,放到后台关系数据库中也无伤大雅,放到reds中算是资源的浪费。

3.Redis中键过期的使用

3.1键过期简介

python的redis包提供了conn.expire(name, time)conn.expireat(name, when)两种设置键失效的模式。很容易理解,一个是多久之后过期,后面为秒或者datetime格式。后一个是设置什么时候过期,后面为秒或者datetime格式。
值得注意的是:过期后redis并不会主动去遍历删除过期的键,作为单线程来说,这么做会造成阻塞。

3.2过期后的处理

那么这些过期的键是怎么删除的呢?
redis内存管理策略提供了这么几种思路。

  • 惰性删除
    当请求设置过期的键时,如果已经过期,redis将会删除该键并返回空。这么做节省CPU消耗,但是反过来想,如果不请求是不是就会一直存在。这样就造成了内存的溢出。
  • 定时任务删除
    redis默认维护着一个定时任务,依照从每个库随机抽20个键进行检测,如果超过25%的键过期,执行删除操作,直到过期比例低于25%或者超出运行时间为止。所以就算不执行任何操作,redis也是在优化着服务器的缓存,但是只会趋于健康值,不会深度优化。
  • 内存溢出控制策略
    在服务器内存溢出时,redis是有对应的策略去优化内存的,默认是noeviction策略,即不删除任何数据,拒绝所有写操作,只允许读操作。显然这个默认的策略是不能去优化缓存的。还有其他的策略,如随机删除键,直到腾出足够空间为止,等等。这些还没研究明白,研究一段时间再补吧。


4.Redis的数据库管理

4.1 切换数据库

以MySQL为例,在关系型数据库中,我们可以切换database,名称可以起的很复杂来区分不同的库。与关系型数据库类似,redis也支持更换库,库与库之间相互独立。区别于关系型,redis只是使用数字来区分不同库的,默认0号数据库,redis的配置文件中,默认开启16个db,名称为[0, 1, 2, … 15]。

在实例化conn对象时,有一个db参数,其默认值是0,可以在此更改db。
但是由于种种原因吧,redis的切换库一直不被人看好,也不建议去切换库。

class RedisProxy(object):
    def __init__(self, host, port, password, db):
        self.host = host
        self.port = port
        self.password = password
        self.db = db
    
    def connection_pool(self):
	    try:
	        pool = redis.ConnectionPool(
	            host=self.host,
	            port=self.port,
	            db=self.db,
	            password=self.password
	        )
	        RedisProxy.pool = pool
	        return pool
	    except Exception as e:
	        print('redis连接失败,错误信息%s'%e)
	        
    def get_conn(self):
        pool = RedisProxy.pool or self.connection_pool()
        conn = redis.Redis(connection_pool=pool)
        return conn

4.2 清空数据库

有时确实需要完全将redis清空,而也不想以来redis自己的内存管理策略。这时,conn.flushdb()会清空当前库的所有数据。conn.flushall()会清空所有库的数据。

5. 数据类型简介

5.1字符串类型(string)

  • 简介
    字符串类型算是redis中最基础的数据类型吧,key: value模式。value的数据类型不唯一,可以是str、json、xml、int、float、bytes等,最大不超过512Mb。
  • 方法
  1. conn.set(key, value)
    设置单个键的值
  2. conn.get(key)
    获取单个键的值,返回bytes格式,如果不存在就返回None
  3. conn.mset({key: value})
    批量设置键的值,传入的是字典
  4. conn.mget([key,1 key2])
    批量获取键的值,返回list[value1, value2]
  5. conn.incr(key)
    指定键的值自增1, 多用来计数,如浏览量。如果值不为整数,则会报错。
  6. conn.incrby(key, amount=1)
    指定键自增指定数量。
  7. conn.incrbyfloat(key, amount=1.1)
    指定键自增指定浮点数。
  8. conn.decr(key)
    指定键的值自减1,多用来计数,如剩余量。
  9. conn.decrby(key, amount=2)
    指定键的值自减指定数字。
  • 使用场景
    缓存技术,如用户基础信息,热门商品信息等。
    键名建议使用db_name + table_name + id
    例如 db_2020库里面的user表中的3用户db_2020:user:3分隔符号使用什么都行。
    计数器
    限时抢购、排行榜、阅读量类似场景。
    共享信息,如session
    分布式服务器部署,假设第一次请求进到A服务器,进行了登陆操作,如果session不共享,第二次请求进到C服务器,还需要进行登陆,这个显然是个失败的分布式。我们可以将session存到一个服务器,所有服务器到一个地方去统一验证session。
    限制访问频率
    如某接口需要限制10秒限制访问5次,可以== user_name:3:port_name == 设置一个过期时间为10秒。每次访问到redis里面取一次。如果有值判断是否大于5;如果没值创建新的键值对。

5.2哈希类型(hash)

  • 简介
    前面介绍了key:value的string数据,下面介绍一个key: {c_key1:value1, c_key2:value2}的hash类型结构。此处的key对应的value是一个对象。
  • 方法
  1. conn.hset(name, key, value)
    设置一个hash键,conn.hset(‘user:1’, ‘name’, ‘leesin’)。{‘user:1’: {‘name’: ‘leesin’}}
  2. conn.get(name, key)
    获取值,conn.hget(‘user:1’, ‘name’)。leesin
  3. conn.hdel(name, [key1, key2, ...])
    批量删除键
  4. conn.hmset(name, {key1: 1, key2: 2})
    插入多个值
  5. conn.hmget(name, [key1, ley2])
    获取多个值
  6. conn.hexists(name, key)
    判断某值是否存在
  7. conn.scan(cursor=0, match=None, count=None)
    遍历整个reids进行筛选,不推荐使用hgetall(), hkeys(), hvals()等,可能会造成阻塞。
    第一次查询,cursor为0,查询结果返回新的cursor,直到cursor再次为0;match可理解为模糊匹配
    conn.scan(cursor=0, match=‘user:*’);count为一次scan查询几个结果,默认是10个。
  • 使用场景
    pass

5.3列表类型(list)

  • 简介
    值是一列有序的数组,可从左或从右进行操作,多用于队列情景。
  • 方法
  1. conn.lpush(name, *value)
    将一列数据从左插入到name中。
  2. conn.rpush(name, *value)
    将一列数据从右侧插入
  3. conn.lrange(name, start, end)
    将name中从[start, end]元素取出
  4. conn.linsert(self, name, where, refvalue, value)
    where [‘befor’, ‘after’]
    在name中refvalue的where处插入value值
  5. conn.lindex(name, index)
    返回列表索引为index的元素
  6. conn.llen(name)
    返回列表的长度
  7. conn.lpop(name), conn.rpop(name)
    将列表左侧/右侧元素删除
  8. conn.lrem(name, count, value)
    将值为value的元素,从左删除count个
    count >0,从左向右删除;count<0,从右向左删除;count=0,全部删除
  9. conn.ltrim(name, start, end)
    按照索引,删除索引之外的值
  10. conn.lset(name, index, value)
    将索引为index的值替换为value
  11. conn.brpop(keys, timeout=0),conn.blpop(keys, timeout=0)
    lpop/rpop的阻塞版,如果timeout为0,当keys均为空时,一直阻塞,当设置timeout=5时,5秒后结束。
    当多个redis客户端同时针对一个列表阻塞式删除,列表内为空时。所有客户端阻塞,当向该列表插入一条值时,第一个客户端弹出,其余客户端继续阻塞。实现消息队列的契机
  • 使用场景
  1. 消息队列
    可使用lpushbrpop实现阻塞式消息队列;生产者使用lpush向队列内发布消息,多个消费者使用brpop对消息进行争抢,排在第一个的消费者总能抢到第一条消息。
  2. 用户文章列表
    pass

5.4集合类型(set)

  • 简介
    集合与列表类似,可用来保存多个元素。但是不同点也很突出,不允许有重复,无序,所以不能使用索引来操作。同时set支持多个集合取交集、并集和差集(虽说本人并不清楚什么是差集)。
  • 方法
  1. conn.sadd(name, values)
    向name集合中插入一个或多个值
  2. conn.srem(name, values)
    Remove values from set name
    删除name集合中的values值
  3. conn.scard(name)
    Return the number of elements in set name
    将name集合中的元素个数返回。该操作并不会遍历集合,而是取用的redis的内部变量,所以操作会比较快。
  4. conn.sismember(name, value)
    Return a boolean indicating if value is a member of set name
    检测value是否在name集合内,在返回1,不在返回0
  5. conn.srandmember(name, number=None)
    If number is None, returns a random member of set name.
    If number is supplied, returns a list of number random members of set name.
    随机返回number个name集合内的元素,当不指定number时,仅返回一个
  6. conn.spop(name, count=None)
    Remove and return a random member of set name
    随机删除count个name集合中的元素,并返回这些元素。不指定count时,仅删除一个元素。
  7. conn.smembers(name)
    Return all members of the set name
    返回name集合内的所有元素。该方法与lrange,hgetall类似都是可能阻塞reids的方法,能不用就别用了吧。如果确实需要遍历时,可使用sscan方法
  8. conn.sscan(name, cursor, match=None, count=None)
    Incrementally return lists of elements in a set. Also return a cursor indicating the scan position.
    match allows for filtering the keys by pattern
    count allows for hint the minimum number of returns
    查询name集合中的元素,返回下一次查询的cursor及指定count个元素个数。首次查询时cursor为0,mathc还支持匹配模式。
  9. conn.sinter(keys, *args)
    Return the intersection of sets specified by keys
    求多个集合的交集,返回均存在于集合的元素
  10. conn.sunion(keys, *args)
    Return the union of sets specified by keys
    求多个集合的并集,返回并集元素
  11. conn.sdiff(keys, *args)
    Return the difference of sets specified by keys
    求多个集合的差集。
  12. conn.sinterstore(dest, keys, *args), conn.sunionstore(dest, keys, *args), conn.sdiff(dest, keys, *args)
    将求出来的这些个集合,储存到dest中。如user:1集合与user:2集合
    dest的命名可以为user:1_2:inter,user:1_2:union,user:1_2:diff
  • 使用场景
    标签
    比如用户感兴趣的标签,鞋帽,手表,电子等等。再比如某标签下的用户,电子标签下有user1,user2,user3。
    (1)通过sadd向标签内添加用户,或者给用户添加标签。
    用户拥有的标签:user:1:tags
    标签内的用户:tag:1:users
    (2)通过srem删除用户标签或标签内用户,这两个操作最好放到一个事务中,以免信息不对等
    (3)通过sinter,sunion,sdiff对用户的标签进行分析
    随机抽取
    使用spop或者srandmember从某集合随机去除一组数,用来抽奖。spop是不放回抽样,srandmember是放回抽样

6. redis的Pipeline机制

redis执行一条命令的基本流程为:
(1)发送命令
(2)命令队列
(3)执行命令
(4)返回结果

一次流程可称之为Round Trip Time (TTP),如果每一次的操作都单独请求redis服务器,这个往返时间将会变的很长,显然与redis的高吞吐原则背道而驰。redis提供了pipeline(流水线)机制,将一组命令操作打包一次性发送给redis服务器,返回一组结果。

pipeline = conn.pipeline(transaction=True,shard_hint=None)
# 此处的pipeline可以调用conn链接实例下的方法,如set,scan,sadd,等等
pipeline.set('pipe_test1', 'hello')
pipeline.sadd('pipe_test2', '1', '2')
res = pipeline.excute()
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值