前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis知多少?(2)

Redis知多少?(2)

作者头像
测试蔡坨坨
发布2024-07-24 14:18:09
720
发布2024-07-24 14:18:09
举报
文章被收录于专栏:蔡坨坨的测试笔记

Redis为什么这么快?

主要有三方面原因:

  1. 存储方式
  2. 优秀的线程模型和IO模型
  3. 高效的数据

存储方式

Redis的存储是基于内存的,直接访问内存的速度是远远大于访问磁盘的速度的。

一般情况下,计算机访问一次SSD磁盘的时间大概是50~150微秒;如果是传统的硬盘,需要的时间更长,大概是1~10毫秒;而访问一次内存的时间大概是120纳秒。因此,可见访问的速度差了快一千倍左右。

优秀的线程模型和IO模型

Redis使用单个主线程来执行命令,不需要进行线程切换,避免了上下文切换带来的性能开销,大大提高了Redis的运行效率和响应速度。

Redis采用了I/O多路复用技术,实现了单个线程同时处理多个客户端连接的能力,从而提高Redis的并发能力。

不过,Redis并不是一直都是单线程的,从4.0开始,Redis引入了Unlink这类命令,用于异步执行删除等操作,还有在6.0之后,Redis为了进一步提升I/O的性能,引入了多线程机制,利用多线程机制并发处理网络请求,从而减少Redis由于网络I/O等待造成的影响。

高效的数据结构

Redis本身提供了丰富的数据结构,比如:String、Hash、Zset等,这些数据结构大多操作的时间复杂度都是O(1)。

Redis集群是什么?

简单来说,Redis集群就是通过多台机器分担单台机器上的压力。

当单机Redis缓存的数据量太大,请求量也高,这个时候,就可以采用Redis集群(Redis Cluster)的方案。

Redis集群会将数据分片存储到多台Redis上,多个Redis实例都可进行读写操作(每个分片内部还是有主从结构,目的是为了提高集群的可用性)。

集群内每个节点都会保存集群的完整拓扑信息,包括每个节点的ID、IP地址、端口、负责的哈希槽范围等,它们直接通过Gossip协议保持通信,会周期性地发送PING和PONG消息,交换集群信息,使得集群信息得以同步。

Redis集群分片原理

Redis集群会将数据分散到16384(2^14)个哈希槽中,集群中的每个节点负责一定范围的哈希槽。

每个节点会拥有一部分的槽位,然后对应的键值会根据其本身的Key,映射到一个哈希槽中:

  • 根据键值的Key,按照CRC16算法计算一个16bit的值,然后将16bit的值对16384进行取余运算,最后得到一个对应的哈希槽编号。
  • 根据每个节点分配的哈希槽区间,对应编号的数据落在对应的区间上,就能找到对应的分片实例。

Redis客户端可以访问集群中任意一台实例,正常情况下这个实例包含这个数据。

但如果槽被转移了,客户端还未来得及更新槽的信息,当前实例没有这个数据,则返回MOVED响应给客户端,将其重定向到对应的实例。

为什么哈希槽节点的数目是16384?

  • 首先是消息大小的考虑,正常的心跳包需要带上节点完整配置数据,心跳还是比较频繁的,所以需考虑数据包的大小,如果使用16384数据包只要2k,如果使用65535则需要8k。 实际上槽位信息使用一个长度为16384位的数组来表示,节点拥有哪个槽位,就将对应位置的数据信息设置为1,否则为0。
  • 集群规模的考虑,集群不太可能会扩展超过1000个节点,16384够用且使得每个分片下的槽位又不会太少。
Redis如何实现分布式锁?

如果是基于Redis来实现分布式锁,则需要利用SET EX NX命令 + lua脚本。

加锁:

代码语言:javascript
复制
SET lock_key unique_value EX expire_time NX

解锁(使用lua脚本):

代码语言:javascript
复制
if redis.call("GET",KEYS[1]) == ARGV[1] then
  return redis.call("DEL",KEYS[1])
else
  return 0
end
  • lock_key:就是锁的key键。
  • unique_value:是客户端生成的唯一标识,为了防止被别的客户端给释放了。 假设没有这个唯一值: 此时客户端B就会一脸懵逼,我还在执行呢,锁怎么就被别人释放了?? 所以每个 客户端/每个线程 加锁时,需要设置一个唯一标识,比如uuid,防止锁被别的客户端误释放。 因为需要先判断锁的值和唯一标识是否一致,一致后再删除释放锁,这里就涉及到两步操作,所以需要使用lua脚本才能保证原子性,这也是为什么释放锁要使用lua脚本的原因。
    1. 客户端A加锁成功,然后执行业务逻辑,但执行的时间超过了锁的过期时间。
    2. 此时锁已经过期被释放了,客户端B加锁成功。
    3. 客户端B执行业务逻辑。
    4. 客户端A执行完了,执行释放锁逻辑,即删除锁。
  • 锁需要有过期机制,假设某个客户端加了锁之后宕机了,锁没有设置过期机制,会使得其他客户端都无法抢到锁。 EX expire_time就是设置锁的过期,单位是秒;还有PX也是过期时间,单位是毫秒。
  • 在2.6.12版本之前只有SETNX,即SET if Not eXists,它表示如果key已存在,则什么都不会做,返回0,如果不存在则会设置它的值,返回1。 那个时候,SETNX和过期时间的设置就无法保证原子性,如果客户端在发送完SETNX之后就宕机了,还没来得及设置过期时间,一样会导致锁不会被释放。 因此在2.6.12版本之后,优化了SET命令,使得可以执行SET EX PX。

扩展:如何进行幂等性设计?避免项目中出现多笔重复的交易订单

数据库唯一约束

可以将订单号作为数据库的主键或唯一索引,这样一来,数据库就会拒绝重复插入的情况,避免重复订单。

分布式锁

可以利用分布式锁来避免多个请求同时处理同一笔订单的情况,比如使用Redis来实现:

代码语言:javascript
复制
String lockKey = "order_lock_" + orderNo;
boolean isLocked = redisTemplate.opsForValue();

if (isLocked){
  try {
    // 处理订单逻辑
  } finally {
    redisTemplate.delete(lockKey);
  }
} else {
  // 已有请求在处理订单
}

PS:这里设置lockKey时使用"order_lock_" + orderNo这种以订单号的维度加锁,避免同笔订单多次插入的同时锁的粒度也足够细。

假设仅使用"order_lock"作为lockKey,那么下单方法的并发度就是1,严重影响性能,会导致请求阻塞引发系统崩溃。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-07-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 测试蔡坨坨 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Redis集群是什么?
  • Redis如何实现分布式锁?
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档