4.Redis总结

1.为什么要使用缓存

  • 缓解关系数据库并发访问的压力:热点数据

  • 减少响应时间:内存IO速度比磁盘快(从数据库中读取数据一般十几毫秒,从内存中读取数据一般十几微妙)

  • 提升吞吐量:Redis等内存数据库单机就可以支撑很大的并发

在这里插入图片描述

2.常见的内存缓存工具有Redis和Memcached

在这里插入图片描述

3.简述Redis常用数据类型和使用场景

数据类型应用场景
String(字符串)计数器,分布式锁
List(链表)消息队列,任务队列
Hash(Hash表)用户信息
Set(集合)去重
ZSet(有序集合)排行榜

4.Redis内置实现

类型c底层实现
Stringint或者sds(simple dynamic string)
Listziplist或者double linked list双向链表功能
Hashziplist或者hashtable
Setintset或者hahstable
SortedSetskiplist跳跃表

注意

1.这些数据结构操作的时间和空间复杂度是多少,深入学习参考 <<Redis设计与实现>>

5.Redis持久化方式

持久化方式定义
RDB(Redis Database Backup file)将内存数据保存到磁盘的二进制文件dump.rdb中( 主从复制就是基于RDB持久化功能实现),速度更快,一般用作备份,也称之为基于快照的持久化
AOF(append Only File)每一个写命令追加到appendonly.aof中,可以最大程度的保证redis数据安全,类似于mysql的binlog
  • RDB

    dir /data/6379              #定义持久化文件存储位置
    dbfilename  dbmp.rdb        #rdb持久化文件
    
  • AOF

    appendonly yes
    appendfsync  always    总是修改类的操作
                 everysec   每秒做一次持久化
                 no     依赖于系统自带的缓存大小机制
    

6.Redis事务

  • 将多个请求打包,一次性,按序执行多个命令的机制
  • redis通过multi,exec,watch等命令实现事务功能
  • redis-py pipline=conn.pipeline(transaction=True)

7.redis如何实现分布式锁

背景:分布式系统在共享资源时为保证数据的一致性

  • 使用setnx(key,value,expire)实现加锁(互斥锁)
  • 锁的value值可以使用一个随机的uuid或者特定的命名
  • 释放锁的时候,通过uuid判断是否是该锁,是则执行delete释放锁

支持超时时间参数

import uuid
import math
import time


def acquire_lock_with_timeout(conn, lock_name, acquire_timeout=3, lock_timeout=2):
    """
    基于 Redis 实现的分布式锁
    
    :param conn: Redis 连接
    :param lock_name: 锁的名称
    :param acquire_timeout: 获取锁的超时时间,默认 3 秒
    :param lock_timeout: 锁的超时时间,默认 2 秒
    :return:
    """

    identifier = str(uuid.uuid4())
    lockname = f'lock:{lock_name}'
    lock_timeout = int(math.ceil(lock_timeout))

    end = time.time() + acquire_timeout

    while time.time() < end:
        # 如果不存在这个锁则加锁并设置过期时间,避免死锁
        if conn.set(lockname, identifier, ex=lock_timeout, nx=True):
            return identifier

        time.sleep(0.001)

    return False


def release_lock(conn, lock_name, identifier):
    """
    释放锁
    
    :param conn: Redis 连接
    :param lockname: 锁的名称
    :param identifier: 锁的标识
    :return:
    """
    unlock_script = """
    if redis.call("get",KEYS[1]) == ARGV[1] then
        return redis.call("del",KEYS[1])
    else
        return 0
    end
    """
    lockname = f'lock:{lock_name}'
    unlock = conn.register_script(unlock_script)
    result = unlock(keys=[lockname], args=[identifier])
    if result:
        return True
    else:
        return False

深入思考:如果Redis单个节点宕机,如何处理?还有其他业界的方案实现分布锁吗

参考:

1.Python 使用 Redis 实现分布式锁

8.常用的缓存读写策略

  • 旁路缓存模式(Cache Aside)

      • cache中读取数据,读取到就直接返回
      • cache中读取不到的话,就从DB读取返回
      • 再把数据写到cache
      • 先更新DB
      • 然后直接删除cache
    • 缺点

      • 首次请求的数据一定不在cache的问题
      • 写操作比较频繁的话导致cache中的数据会被频繁的删除
    • 思考

      • 如果在写数据的过程中,可以先删除cache,再更新DB吗
      • 如果先写BD,再删除cache就不会造成数据不一致了吗
  • 读写穿透(Read/Write Through)

    • 读:

      • 先从cache中读取数据,读取到直接返回。
      • cache中读取不到,则先从DB加载写入到cache后返回响应。
      • 先查cache,cache中不存在,直接更新DB
      • cache中存在,则先更新cache,然后cache服务自己更新DB(同时更新DBcache
  • 异步缓存写入(Write Behind Pattern)

    • 读:

      • 先从cache中读取数据,读取到直接返回。
      • cache中读取不到,则先从DB加载写入到cache后返回响应。
      • 先查cache,cache中不存在,不直接更新 db,而是改为异步批量的方式来更新 db
      • cache中存在,则先更新cache,不直接更新 db,而是改为异步批量的方式来更新 db

9.如何解决Redis缓存穿透问题

描述大量要访问的数据既不在Redis 缓存中,也不在数据库

**原因:**缓存和数据库中都没有要访问的数据

解决方案

  • 对于没查到返回为None的数据也缓存或者设置缺省
  • 对入口请求合法性检查
  • 布隆过滤器
  • 插入数据的时候删除相应缓存,或者设置较短的超时时间

10.如何解决Redis缓存击穿

描述: 某个访问非常频繁的热点数据,缓存中没有数据,访问该数据的大量请求,一下子都发送到数据库,导致数据库压力激增

原因: 热点数据过期失效

解决方案:

  • 不给热点数据加过期时间
  • 分布式锁:获取锁的线程从数据库拉数据更新缓存,其他线程等待
  • 异步后台更新:后台任务针对过期的key自动刷新

11.如何解决缓存雪崩问题

  • 描述:缓存中有大量的数据同时过期导致在Redis缓存无法处理,应用将大量请求发送到数据库层,导致数据库层的压力激增
  • 原因
    • 缓存中有大量数据同时过期,导致大量请求数据库
    • 实例宕机
  • 解决方案:
    • 多节缓存:不同级别的key设置不同的超时时间
    • key的超时时间随机设置,防止同时超时
    • 架构层:提升系统可用性.监控,报警完善

12.实现发布/订阅

订阅者

import time

from redis import StrictRedis

s = StrictRedis(host='', port=6379, password='')
sub = s.pubsub()
sub.subscribe('FM101', 'FM102')
sub.subscribe('*ython1', '2python*')  # 通配符不生效

while True:
    time.sleep(1)
    msg = sub.parse_response()
    print("msg:%s" % msg)

发布者

from redis import StrictRedis

r = StrictRedis(host='', port=6379, password='')
res1 = r.publish('FM101', 'hello 1')
res2 = r.publish('FM102', 'hello 2')
res3 = r.publish('python1', 'python1')
res4 = r.publish('2python2', 'python2')
print(res1)  # 返回订阅者数量
print(res2)  # 返回订阅者数量
print(res3)  # 返回订阅者数量
print(res4)  # 返回订阅者数量

13.Redis的python基本操作

from redis import StrictRedis

r = StrictRedis(host='', port=6379, password='')
r.ping()  # 运行测试命令
r.select(1)  # 切换数据库

# 1.String
# mset,mget
r.set('age', 23, ex=100, nx=True)
r.get('age')

# mset,mget
r.mset({'k3': 'v3', 'k4': 'v4'})
r.mget('k1', 'k2', 'k3')  # [None, None, b'v3']

# append
r.get('age')  # b'17'
a = r.append('age', 6)  # b'176'
a.decode('utf-8')  # '176

# 2.keys
# key
r.keys()  # [b'age', b'k4', b'k3']
r.keys('k*')  # 查看包含k的键
# exists
r.exists('age')  # 1:表示存在 0表示不存在
# type
r.type('age')  # b'string'
# delete
r.delete('k3', 'k4')  # 正数:表示删除的数量 0表示不存在
# expire
r.expire('k3', 6)  # 将k3这个键设置为6秒过期
# getrange

r.get('k4')  # b'v4'
r.getrange('k4', 0, 1)  # b'4'
# key
r.ttl('k4')  # 查看键k4的有效时间

# 3.hash
r.hset('person', 'name', 'jason')
r.hset('person', 'age', 18)
r.hget('person', 'age')
r.hgetall('person')
r.hvals('person')
r.hmset('person3', {'name3': 'jason', 'age3': 16})
r.delete('person1')
r.hdel('person3', 'name3')

# 4.list
r.lpush('a1', 'a', 'b', 'c')  # 在左侧插⼊数据
r.rpush('a1', 0, 1)  # 在右侧插⼊数据
r.lrange('a1', 0, -1)
r.lset('a1', 0, 'z')
r.lrem('a1', -2, 'b')  # 从a1列表右侧开始删除2个b

# set:集合没有修改操作
r.sadd('name', 'jason1', 'jason2')
r.smembers('name')
r.srem('name', 'json1')
r.srem('name')

# zset:有序集合没有修改操作,通过权重将元素从⼩到⼤排序
r.zadd('name2', {'jason1': 4, 'jason2': 5})
r.zrange('name2', 0, -1)
r.zrangebyscore('name2', 4, 5)
r.zscore('name2', 'jason1')
r.zremrangebyscore('name2', 4, 5)

# 管道
pipe = r.pipeline()
pipe.set('a1', '11')
pipe.set('a2', '12')
pipe.execute()
pipe.watch()  # 如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
pipe.multi()  # 标记一个事务块的开始
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值