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.Redis三种常见的缓存模式

  • 旁路模式(cache side pattern)
  • 读写穿透模式(read/write through pattern)
  • 异步写入模式(write behind pattern)

1.旁路模式

  • 写:更新db->删除cache
    在这里插入图片描述

  • 读:查cache

    • 存在直接还回
    • 不存在则查db,将db数据直接返还给客户端,然后再更新给cache
      在这里插入图片描述
  • 应用场景:使用最多的模式,适用于对数据一致性要求不高,读多写少的场景,减轻读的压力,例如新闻资讯类系统

2.读写穿透模式

  • 写:查cache

    • 存在更新cache,更新db
    • 不存在更新db
      在这里插入图片描述
  • 读:查 cache

    • 存在直接还回
    • 不存在则查询db,将db数据更新给cache,然后通过cache返还给客户端
  • 应用场景:适用于对数据一致性要求高的场景,电子商务系统金融交易系统
    在这里插入图片描述

3.异步写入模式

  • 写:更新缓存后直接响应,之后将缓存中的数据异步写入数据库中
  • 读:和旁路缓存模式一样
  • 应用场景:使用于对性能要求较高,数据一致性要求不高的场景,写多读少的模式,减轻系统写入读的影响,日志处理系统,大数据处理平台

旁路模式的缺陷

  • 首次请求的数据一定不存在cache中
    • 可以提前将热点数据存入到缓存中
  • 写操作较多的场景,缓存会被频繁删除,影响缓存命中率
    • 添加分布式锁保证同时更新缓存和数据库
    • 对缓存添加较短的过期时间

旁路模式写入操作时为什么不先删除缓存再更新数据库

  • 保证数据一致性,即使存在数据不一致但概率相对较低

9.Redis三种常见的问题

  • 缓存穿透
  • 缓存击穿
  • 缓存雪崩

1.缓存穿透

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

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

解决方案

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

2.缓存击穿

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

原因: 热点数据过期失效

解决方案:

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

3.缓存雪崩

  • 描述:缓存中有大量的数据同时过期导致在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.Java guide

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值