redis和缓存服务

redis 连接

Redis是一个快速、高性能的开源键值对数据库,可以用于缓存、消息队列、持久化等多种场景。它支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,提供了丰富的命令集合,可以实现诸如缓存、计数器、分布式锁、消息队列等功能。Redis的特点包括:

  1. 快速:Redis的内存存储和高效的I/O操作使得它的响应速度非常快。

  2. 持久化:Redis支持RDB和AOF两种持久化方式,可以将内存中的数据写入磁盘,防止数据丢失。

  3. 多样的数据结构:Redis支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,适用于不同的场景。

  4. 原子性:Redis的命令是原子性的,保证多个命令之间的操作是原子的。

  5. 分布式:Redis支持分布式部署,可以将数据分布在多个节点上,提高性能和可用性。

Redis在互联网应用中被广泛应用,如缓存、计数器、排行榜、实时消息等。

import redis
​
# 连接Redis服务器
r = redis.Redis(host='localhost', port=6379, db=0)
​
# 设置键值对
r.set('name', 'Alice')
​
# 获取键对应的值
name = r.get('name')
print(name.decode('utf-8'))  # 需要将bytes类型的返回值解码为字符串

redis 使用

  1. 连接Redis服务器

要使用Redis,首先需要连接到Redis服务器。可以使用redis模块的Redis类来创建一个Redis客户端对象,然后通过它与Redis服务器进行交互。示例代码如下:

import redis
​
# 连接Redis服务器
r = redis.Redis(host='localhost', port=6379, db=0)
  1. 设置键值对

可以使用set()方法设置一个键值对。示例代码如下:

# 设置键值对 
r.set('name', 'Alice')
  1. 获取键对应的值

可以使用get()方法获取一个键对应的值。示例代码如下:

​
# 获取键对应的值
name = r.get('name')
print(name)

注意,获取到的值是一个bytes类型的对象,需要使用decode()方法将其解码为字符串。

  1. 设置过期时间

可以使用expire()方法设置一个键的过期时间。示例代码如下:

​
# 设置过期时间
r.expire('name', 60)

以上代码将键name的过期时间设置为60秒。

  1. 删除键

可以使用delete()方法删除一个键。示例代码如下:

pythonCopy code
# 删除键
r.delete('name')
  1. 其他操作

Redis还支持其他各种操作,例如:

  • 判断键是否存在:exists()

  • 获取键的类型:type()

  • 自增或自减键的值:incr()decr()

  • 获取哈希表中指定键的值:hget()

  • 设置或获取列表中的元素:lset()lindex()

  • 添加或删除集合中的元素:sadd()srem()

  • 添加或删除有序集合中的元素:zadd()zrem()

缓存使用方式

缓存使用比较常见的场景是在web 应用程序中,当用户登录的时候,为了实现用户追踪有两种方式,一种是记录session,在浏览器存储session id,通过session id找到服务器中存储的session数据。这种方式的一个弊端就在于,当并发量比较大的时候,服务器就会产生很大的压力导致性能问题。这时候就可以引入缓存解决这个问题,使用空间换时间,将session都储存在缓存中,释放服务器压力。另一种方式是使用token,用户登录成功后,服务器会发送token给到客户端,浏览器会将token存储在本地local storage或者cookie。当客户端发送其他请求的时候,会带着token,当token过期或者没有token发送请求的时候,会自动将用户重定向到登录页面。常用的token有JWT(json web token)。

JWT的优缺点

使用JWT的优点非常明显,包括:

  1. 更容易实现水平扩展,因为令牌保存在浏览器中,服务器不需要做状态管理。

  2. 更容易防范CSRF攻击,因为在请求头中添加localStoragesessionStorage中的token必须靠JavaScript代码完成,而不是自动添加到请求头中的。

  3. 可以防伪造和篡改,因为JWT有签名,伪造和篡改的令牌无法通过签名验证,会被认定是无效的令牌。

当然,任何技术不可能只有优点没有缺点,JWT也有诸多缺点,大家需要在使用的时候引起注意,具体包括:

  1. 可能会遭受到XSS攻击(跨站脚本攻击),通过注入恶意脚本执行JavaScript代码获取到用户令牌。

  2. 在令牌过期之前,无法作废已经颁发的令牌,要解决这个问题,还需要额外的中间层和代码来辅助。

  3. JWT是用户的身份令牌,一旦泄露,任何人都可以获得该用户的所有权限。为了降低令牌被盗用后产生的风险,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应通过其他方式再次对用户进行认证,例如短信验证码等。

缓存常见问题

缓存数据的更新

在使用缓存时,一个必须搞清楚的问题就是,当数据改变时,如何更新缓存中的数据。通常更新缓存有如下几种套路,分别是:

  1. Cache Aside Pattern

  2. Read/Write Through Pattern

  3. Write Behind Caching Pattern

第1种方式的具体做法就是,当数据更新时,先更新数据库,再删除缓存。注意,不能够使用先更新数据库再更新缓存的方式,也不能够使用先删除缓存再更新数据库的方式,大家可以自己想一想为什么(考虑一下有并发的读操作和写操作的场景)。当然,先更新数据库再删除缓存的做法在理论上也存在风险,但是发生问题的概率是极低的,所以不少的项目都使用了这种方式。

第1种方式相当于编写业务代码的开发者要自己负责对两套存储系统(缓存和关系型数据库)的操作,代码写起来非常的繁琐。第2种方式的主旨是将后端的存储系统变成一套代码,对缓存的维护封装在这套代码中。其中,Read Through指在查询操作中更新缓存,也就是说,当缓存失效的时候,由缓存服务自己负责对数据的加载,从而对应用方是透明的;而Write Through是指在更新数据时,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由缓存服务自己更新数据库(同步更新)。刚才我们说过,如果自己对项目中的Redis操作再做一次封装,就可以实现“Read Through”和“Write Through”模式,这样做虽然会增加工作量,但无疑是一件“一劳永逸”且“功在千秋”的事情。

第3种方式是在更新数据的时候,只更新缓存,不更新数据库,而缓存服务这边会异步的批量更新数据库。这种做法会大幅度提升性能,但代价是牺牲数据的强一致性。第3种方式的实现逻辑比较复杂,因为他需要追踪有哪数据是被更新了的,然后再批量的刷新到持久层上。

缓存穿透

缓存是为了缓解数据库压力而添加的一个中间层,如果恶意的访问者频繁的访问缓存中没有的数据,那么缓存就失去了存在的意义,瞬间所有请求的压力都落在了数据库上,这样会导致数据库承载着巨大的压力甚至连接异常,类似于分布式拒绝服务攻击(DDoS)的做法。解决缓存穿透的一个办法是约定如果查询返回为空值,把这个空值也缓存起来,但是需要为这个空值的缓存设置一个较短的超时时间,毕竟缓存这样的值就是对缓存空间的浪费。另一个解决缓存穿透的办法是使用布隆过滤器,具体的做法大家可以自行了解。

缓存击穿

在实际的项目中,可能存在某个缓存的key某个时间点过期,但恰好在这个时间点对有对该key的大量的并发请求过来,这些请求没有从缓存中找到key对应的数据,就会直接从数据库中获取数据并写回到缓存,这个时候大并发的请求可能会瞬间把数据库压垮,这种现象称为缓存击穿。比较常见的解决缓存击穿的办法是使用互斥锁,简单的说就是在缓存失效的时候,不是立即去数据库加载数据,而是先设置互斥锁(例如:Redis中的setnx),只有设置互斥锁的操作成功的请求,才能执行查询从数据库中加载数据并写入缓存,其他设置互斥锁失败的请求,可以先执行一个短暂的休眠,然后尝试重新从缓存中获取数据,如果缓存还没有数据,则重复刚才的设置互斥锁的操作,大致的参考代码如下所示。

data = redis_cli.get(key)
while not data:
    if redis_cli.setnx('mutex', 'x'):
        redis.expire('mutex', timeout)
        data = db.query(...)
        redis.set(key, data)
        redis.delete('mutex')
    else:
        time.sleep(0.1)
        data = redis_cli.get(key)

缓存雪崩

缓存雪崩是指在将数据放入缓存时采用了相同的过期时间,这样就导致缓存在某一时刻同时失效,请求全部转发到数据库,导致数据库瞬时压力过大而崩溃。解决缓存雪崩问题的方法也比较简单,可以在既定的缓存过期时间上加一个随机时间,这样可以从一定程度上避免不同的key在同一时间集体失效。还有一种办法就是使用多级缓存,每一级缓存的过期时间都不一样,这样的话即便某个级别的缓存集体失效,但是其他级别的缓存还能够提供数据,避免所有的请求都落到数据库上。

Summary

所以在我们设置缓存的时候,要注意1. 解决缓存穿透的一个办法是约定如果查询返回为空值,把这个空值也缓存起来,但是需要为这个空值的缓存设置一个较短的超时时间,毕竟缓存这样的值就是对缓存空间的浪费。另一个解决缓存穿透的办法是使用布隆过滤器。2. 查询redis中没有的数据的时候,返回空值,并且保存设置过期时间。3. 最好设置多级缓存,或者在每个缓存过期时间中加上随机时间,保证缓存过期时间不相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值