Redis 基础

Redis 基础

  • Redis 没有建库建表的操作,默认会自动建16个库,切换库操作: select (0-15)
  • redis特性
    • redis 与其他 key-value 缓存产品有以下三个特点:
    • redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
    • redis 不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
    • redis支持数据的备份,即master-slave模式的数据备份
  • redis 优势
    • 性能极高-redis能读的速度是110000次/s,写的速度是81000次/s
    • 丰富的数据类型-redis支持二进制案例的Strings,Lists,Hashes,Sets 及 Orderd Sets 数据类型操作
    • 原子 - Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行
    • 丰富的特性-Redis还支持 publish/subscribe,通知,key过期等特性
  • 推荐阅读

Redis 的数据类型

  • string 字符串
    • set --- 设置键值,存在即修改不存在即创建
    • setex --- 设置具有有效期的键值对,秒为单位
    • mset --- 一次设置多个键值对
    • append --- 对指定的键后面追加字符 append key new_value
    • get --- 获取键 key 的值,不存在就返回 nil
    • mget --- 同时获取多个键的值value
    • incr --- 计数加1
    • decr --- 计数减1
    • incrby --- 计数加 n
  • keys 键的操作命令
    • keys [pattern] --- keys * 获取所有键 key 的值 value
    • exists --- 判断键 key 是否存在,存在为 1 不存在为 0
    • type --- 查看键对应 value 的类型
    • del --- 删除键及对应的值
    • expire --- 设置key的过期时间,秒为单位
    • getrange --- 截取字符串 getrange key start end 负数表示从字符串最后开始计数
    • ttl --- 查看键的有效时间
  • hash 哈希(相当于一个类中有多个键值对;值的类型为string)
    • hset --- 设置某个键中属性和值(一个键中的多个属性和值)
    • hmset --- 设置一个键中的多个属性和值
    • hkeys --- 获取指定键所有属性
    • hget --- 获取某个key的一个属性所对应的值
    • hmget --- 获取多个字段
    • hvals --- 获取某个hash键的属性所对应的所有值
    • hdel --- 删除整个hash键的属性(对应值会同时删掉)
  • list 列表(有序的,相当于数组,列表元素类型string,按照插入顺序排序)
    • lpush --- 在列表左侧插入数据

    • rpush --- 在列表右侧插入数据

    • linsert --- 在指定元素的前(before)或后(after)插入新元素

    • lrange --- 查看,返回列表里指定范围内的元素 lrange key start stop 索引从左侧开始第一个元素下标为0,负数是从尾部 -1开始

    • lset --- 设置指定索引位置的元素值 lset key index value

    • lrem --- 删除指定元素(也可以删除重复元素的其中几个 lrem aa 2 b表示aa列表中从头开始查找b删除两个,负数为从后开始计数)

    • ltrim --- 截取修建,根据范围删除数据 ltrim key start stop [start stop]区间内的元素全部删除

    • lpop --- 从左侧删除元素

    • rpop --- 从右侧删除元素

  • set 集合(无序集合,集合顺序和插入顺序无关,string类型,元素具有唯一性,不重复,没有修改操作)
    • sadd --- 添加元素

    • smembers --- 获取键的所有元素

    • srem --- 删除指定键里面的值

    • sismember --- 判断是否包含

  • zset 有序集合(sorted set,有序集合,string类型,元素具有唯一性,不重复,每个元素都有一个double类型的score表示权重,没有修改操作)
    • zadd --- 向集合中增加权重和对应元素值
    • zrange --- 遍历元素(按分数从小到大)
    • zrevrange ---反向遍历元素(从大到小)
    • zrangebyscore --- 根据权重取值;zrangebyscore key min max 返回 score(权重 值)在 min 和 max之间的成员
    • zscore --- 查询集合中某个元素的权重
    • zrem --- 删除集合中指定元素
    • zremrangebyscore --- 根据集合中的权重值范围删除元素
    • zincrby --- 元素的分数计数加n
  • python 编程操作redis

    • 下载库 pip3 install redis
通过init创建对象,指定参数host、port与指定的服务器和端⼝连接,host默认为localhost,port默认为6379,db默认为0
示例代码:
from redis import *

if __name__ == '__main__':
    try:
        # decode_responses='utf8' 对获取到的值进行解码
        # 建立连接:host port db 可以不写,默认是本地连接
        src = StrictRedis(host='localhost',port=6379, db=0 ,decode_responses='utf8')
        # 执行操作
        res = src.set('n', '黑马')
        print("设置成功与否", res)
        n = src.get('n')
        print("获取的值", n)
        a = src.delete("n")
        print("删除个数", a)

    except Exception as e:
        print(e)
  • 解决:命令行查看中文乱码
# 在命令行查看某个键的中文值不乱码的方法:
[luojie@11:47:38]redis-cli --raw
127.0.0.1:6379> get name
黑马
127.0.0.1:6379> keys *
name
Redis 持久化

RDB做镜像全量持久化,AOF做增量持久化。因为RDB会很耗时,不够实用,在停机的时候会导致大量丢失数据,所以需要AOF来配合使用。在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令实现完整恢复重启之前的状态

持久化的两种方式

Redis 持久化是其高可用中比较重要的一个环节,因为 Redis数据在内存的特性,持久化是必要的,Redis 的持久化有两种方式:

  • RDB:RDB 持久化机制,是对 Redis 中的数据执行周期性的持久化

  • AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,因为这个模式是只追加的方式,所有没有任何磁盘寻址的开销,所有很快,有点像Mysql中的binlog日志

    上述两种方式都可以把Redis内存中的数据持久化到磁盘上,然后再将这些数据备份到别的地方去,RDB更适合做冷备AOF更适合做热备

注:两种机制全部开启的时候,Redis在重启的时候会默认使用AOF去重新构建数据,因为AOF的数据是比RDB更完整

持久化两种方式的优缺点

RDB

  • 优点:以快照的方式生成多个数据文件,每个数据文件都代表了某一时刻Redis里面的数据,这种方式,更适合做冷备,设置定时任务来执行。RDB对Redis的性能影响非常小,是因为在同步数据的时候它只是fork了一个子进程去做持久化,而且在数据恢复是速度也比AOF来的快
  • 缺点:RDB都是快照文件,都是默认五分钟甚至更久的时间才去生成一次,这意味着你这次同步到下一次同步这中间5分钟的时间很可能会丢失。AOF则最多丢一秒的数据,数据完整性高下立判。还有RDB在生成数据快照时,如果文件很大就会很耗时。

AOF

  • 优点:AOF是一秒一次去通过后台的线程fsync操作,最多会丢失1秒的数据。AOF在对日志文件进行操作的时候是以append-only的方式去写的,它只是追加的方式写数据,自然就少了很多磁盘寻址的开销,写入性能惊人,文件也不容易破损。AOF的日志是通过一个非常可读的方式记录的,这样的特性适合做灾难性数据误删除的紧急恢复。
  • 缺点:一样的数据,AOF文件比RDB还要大。随着时间的流逝,AOF文件会越来越大。AOF开启后,Redis支持写的QPS会比RDB支持写的要低,它不是每秒都要去异步刷新一次日志fsync

两种备份方式如何选择

两种都要,如果要进行数据恢复时,用RDB进行第一时间的快速恢复,然后用AOF做数据补全,冷备热备一起使用

Redis主从中的数据如何同步
  • 当启动一台slave的时候,他会发送一个psync命令个master,如果是这个slave第一次连接到master,他会触发一个全量复制。master就会启动一个线程,生成RDB快照,还会把新的写请求都缓存在内存中,RDB文件生成后,master会将这个RDB发送给slave的,slave拿到之后做到的第一件事就是写进本地的磁盘,然后加载进内存,然后master会把内存里面缓存的那些新命令都发给slave;数据传输过程中断网了怎么办?传输过程中出现连接断开会自动重连,并且把连接后缺少的数据补上
Redis 集群中,有卡槽的可以直接存取数据,没有卡槽的使用重定向存取数据
Redis的过期删除策略
  • 定时删除:每个设置过期时间的key都创建一个定时器,到过期时间会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源进行计时和处理过期数据,从而影响缓存的响应时间和吞吐量
  • 定期删除(默认策略):每隔一定时间100s,扫描数据库中一部分设置了有效期的key,并清除其中已过期的key。该策略是另外两种的折中方案。通过调整定时扫描的时间间隔和每次扫面的限定耗时,可以在不同情况下使用CPU和内存资源达到最优的平衡效果
  • 惰性删除(默认策略):只有当访问一个key时,才会判断该key是否已过期,过期则清除(返回nil)。该策略可以最大化节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清楚,占用大量内存
  • 如果定期没能删除过期的数据key就会用到内存淘汰机制!:所谓内存淘汰机制,是指在Redis允许使用的内存达到上限时,如何淘汰已有数据及处理新的写入需求。Redis提供多种缓存淘汰策略,最常用的LRU和LFU
    • LRU(Least recently used 最后使用时间策略):LRU算法根据数据的历史访问记录来进行淘汰数据,优先淘汰最近没有使用过的数据
      • 基本思路
        • 新数据插入到列表头部
        • 每当缓存命中(访问),则将数据移动到列表头部
        • 当列表满的时候,将列表尾部的数据丢弃
      • 存在问题:单独按照最后使用时间进行数据淘汰,可能会将一些使用频繁的数据删除,可以调整淘汰时间来删除数据
    • LFU(Least Frequently Used 最少使用次数策略)
      • redis4.x 后支持LFU策略
      • 优先淘汰使用率最低的数据
      • 考虑到新添加的数据往往使用次数要低于旧数据,LFU还实现了定期衰减机制
      • LFU的缺点:
        • 需要每条数据维护一个使用计数
        • 还需要定期衰减

Redis的淘汰策略

  • allkeys-lfu: 当内存不足以容纳新写入数据时,在键空间中,优先移除使用次数最少的key。
  • (常用)volatile-lfu: 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,优先移除使用次数最少的key。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,优先移除最近没有使用过的key。
  • (常用)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,优先移除最近没有使用过的key。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
思考:如何将2000w条mysql数据,在redis中只存储20w条热点数据?
  • 设置淘汰策略volatile-lfu
  • 计算20w条数据的大致内存大小,设置成上限
  • 2000w条数据都去过redis,到达内存上限后不是热点的数据就会被删除,剩下的就是热点数据
Redis分布式锁
  • 乐观锁:watch命令监听数据,若在事务执行中发生变化就停止,进入下一次循环直到监听的数据没有发生变化

  • 悲观锁:使用setnx命令来抢锁,抢到锁的就可以执行数据操作,在用expire给锁加一个过期时间防止忘记释放,在最后还是要删除锁防止死锁

    • 悲观锁问题:设置锁和设置有效期不符合原子性;当抢到锁之后到设置过期时间之前出现进程以为carsh或者重启服务了,就会出现死锁

    • 解决方案:setnx命令替换成set命令,符合原子性,同时设置

    • set 命令:

      set key value [EX seconds] [PX milliseconds] [NX|XX]
      EX seconds:设置失效时长,单位秒
      PX milliseconds:设置失效时长,单位毫秒
      NX:key不存在时设置value,成功返回OK,失败返回(nil)
      XX:key存在时设置value,成功返回OK,失败返回(nil)
      
    # 需求:使用redis 悲观锁实现秒杀功能(防止超卖)
    
    from redis import StrictRedis, WatchError
    
    # 1、创建客户端
    redis_client = StrictRedis(password=4988, decode_responses=True)
    # 2、构建锁
    key = 'order:lock'
    
    while True:
        try:
            # 3、抢锁,成功返回True反之返回False
            # Redis的 setnx 命令是当 key 不存在时设置key,但不能设置过期时长。可用set命令代替
            # lock = redis_client.setnx(key, 1)
    
            lock = redis_client.set(name=key, value=1, ex=5,  nx=True)
            # print(lock)
    
            # 防止忘记删除锁资源,给锁资源添加过期时长 -- 防止出现死锁
            # redis_client.expire(key, 5)
    
            # 4、读取库存数量
            count = redis_client.get('count')
    
            # 争夺到锁资源后允许对redis进行操作
            if lock:
                if int(count) > 0:
                    redis_client.decr('count')
                    print("下单成功")
                else:  # 无库存
                    print("商品已售罄")
                    redis_client.delete(key)
                    break
    
        except WatchError:
            print("数据被修改,正在重试")
            continue
    
    
Redis 缓存更新、缓存雪崩、穿透和击穿
  • 缓存更新:mysql和redis是两个独立的系统,当两个用户并发更新同一个数据时,可能因为网络等延迟问题,出现mysql和redis的数据不一致的问题

    • 解决方案1: 悲观锁(redis-setnx)使用消息队列顺序执行
      • 缺点:并发能力差,将并发操作变成串行执行
    • **解决方案2: **先更新写入mysql的同时删除redis缓存,下一次请求redis缓存时会进行数据回填,此时数据一致
      • 主要用于数据对象(少的数据进行直接删除)
      • 数据集合可以考虑更新缓存(集合的数据量大查询成本高,不易直接删除,只能采用更新部分数据)
  • 缓存穿透:缓存只是为了缓解数据库压力而添加的一层保护层,当从缓存中查询不到需要的数据时就会到数据库中去查。当频繁访问缓存中没有的数据就会导致缓存穿透

    • 解决方案1对于数据库中不存在的数据,也对其在缓存中设置默认值Null,试请求不会到达DB层

      • 为避免占用资源,一般会设置较短的过期时间
    • 解决方案2:可以设置一些过滤规则

      • 布隆过滤器(bloomfilter),在应用服务前,将所有可能的值录入过滤器,如果不包含直接返回None,会有误杀概率

      • 布隆过滤器(bloomfilter)的缺点:

        • 存在误判:可以在 bloom filter中存储的是黑名单,可以建立一个白名单来存储容易误判的元素
        • 删除困难
      • 代码示例:在请求redis之前就进行验证

        import pybloomfilter
        
        
        def get_user_info(user_id):
            # 创建过滤器对象
            # 参数:布隆过滤器容器大小,误差值,文件名
            filter = pybloomfilter.BloomFilter(10000, 0.001, "world bloom")
        
            # 添加数据
            id_list = [i for i in range(1, 100)]
            # filter.update(('bj', 'sh', 'ss'))
            filter.update(id_list)
            # 判断是否包含
            if user_id not in filter:
                print('不包含此id')
                return None
        
            print('包含')
        
        
        
  • 缓存雪崩:如果大量的key设置同一过期时间,导致大量key过期,而正在此时有大量客户涌入,轻则出现redis卡顿,重则出现缓存雪崩

    • 解决方法1:在key的过期时间后加上一个随机值,使得过期时间分散一些
    • 解决方法2:采用多级缓存,不同级别缓存设置的超时时间不同,即使某个级别缓存都过期,也有其他级别缓存兜底

redis 的消息通知机制-- 实现订单倒计时

![redis 消息通知](/Users/luojie/Desktop/笔记(备)/截图/redis 消息通知.png)

![redis 消息通知 订单倒计时方案](/Users/luojie/Desktop/笔记(备)/截图/redis 消息通知 订单倒计时方案.png)

注意:在开启消息通知时,需要修改配置文件允许键的通知,官网没有该命令

redis键空间通知

posted @ 2025-07-10 10:37  星河霓虹  阅读(13)  评论(0)    收藏  举报