Redis核心数据结构与分布式锁实现详解

Redis核心数据结构与分布式锁实现详解
个人名片

🎓作者简介:java领域优质创作者
🌐个人主页码农阿豪
📞工作室:新空间代码工作室(提供各种软件服务)
💌个人邮箱:[[email protected]]
📱个人微信:15279484656
🌐个人导航网站:www.forff.top
💡座右铭:总有人要赢。为什么不能是我呢?
  • 专栏导航:
码农阿豪系列专栏导航
面试专栏:收集了java相关高频面试题,面试实战总结🍻🎉🖥️
Spring5系列专栏:整理了Spring5重要知识点与实战演练,有案例可直接使用🚀🔧💻
Redis专栏:Redis从零到一学习分享,经验总结,案例实战💐📝💡
全栈系列专栏:海纳百川有容乃大,可能你想要的东西里面都有🤸🌱🚀

目录

Redis核心数据结构与分布式锁实现详解

一、Redis简介与数据结构概述

Redis(Remote Dictionary Server)是一个开源的、基于内存的高性能键值存储系统,它支持多种数据结构,并提供了丰富的操作命令。由于其出色的性能和丰富的功能,Redis被广泛应用于缓存、消息队列、会话存储、排行榜等场景。

Redis之所以如此受欢迎,主要得益于以下几个特点:

  1. 内存存储:数据主要存储在内存中,读写速度极快
  2. 数据结构丰富:不仅支持简单的字符串,还支持列表、集合、哈希等复杂结构
  3. 持久化支持:可以将内存数据定期保存到磁盘
  4. 高可用性:支持主从复制和集群模式
  5. 原子性操作:所有操作都是原子性的

二、Redis常用数据结构详解

1. 字符串(String)

字符串是Redis最基本的数据类型,可以存储文本、数字或二进制数据,最大能存储512MB。

常用命令:

SET key value [EX seconds][PX milliseconds][NX|XX] GET key INCR key DECR key APPEND key value STRLEN key 

应用场景:

  • 缓存HTML片段或页面
  • 计数器
  • 存储用户会话信息

示例代码:

import redis r = redis.Redis(host='localhost', port=6379, db=0)# 设置和获取字符串 r.set('username','john_doe')print(r.get('username'))# 输出: b'john_doe'# 计数器 r.set('page_views',0) r.incr('page_views')print(r.get('page_views'))# 输出: b'1'

2. 哈希(Hash)

哈希是字段和值的映射表,特别适合存储对象。

常用命令:

HSET key field value HGET key field HGETALL key HDEL key field HKEYS key HVALS key 

应用场景:

  • 存储用户信息(姓名、年龄、地址等)
  • 存储产品属性

示例代码:

# 存储用户信息 r.hset('user:1000','name','Alice') r.hset('user:1000','age',25) r.hset('user:1000','email','[email protected]')# 获取所有字段print(r.hgetall('user:1000'))# 输出: {b'name': b'Alice', b'age': b'25', b'email': b'[email protected]'}# 获取单个字段print(r.hget('user:1000','name'))# 输出: b'Alice'

3. 列表(List)

列表是简单的字符串列表,按照插入顺序排序,可以从头部或尾部添加元素。

常用命令:

LPUSH key value [value ...] RPUSH key value [value ...] LPOP key RPOP key LRANGE key start stop LLEN key 

应用场景:

  • 消息队列
  • 最新消息排行
  • 记录用户最近的操作

示例代码:

# 作为消息队列使用 r.lpush('task_queue','task1') r.lpush('task_queue','task2') r.lpush('task_queue','task3')# 获取队列长度print(r.llen('task_queue'))# 输出: 3# 消费消息 task = r.rpop('task_queue')print(task)# 输出: b'task1'

4. 集合(Set)

集合是字符串的无序集合,不允许重复元素。

常用命令:

SADD key member [member ...] SREM key member [member ...] SMEMBERS key SISMEMBER key member SCARD key SINTER key [key ...]# 交集 SUNION key [key ...]# 并集 SDIFF key [key ...]# 差集

应用场景:

  • 标签系统
  • 好友关系
  • 唯一IP统计

示例代码:

# 标签系统 r.sadd('article:123:tags','python','programming','database')# 检查标签是否存在print(r.sismember('article:123:tags','python'))# 输出: True# 获取所有标签print(r.smembers('article:123:tags'))# 输出: {b'python', b'database', b'programming'}

5. 有序集合(Sorted Set)

有序集合类似于集合,但每个元素都关联一个分数(score),元素按分数排序。

常用命令:

ZADD key score member [score member ...] ZRANGE key start stop [WITHSCORES] ZREVRANGE key start stop [WITHSCORES] ZRANK key member ZSCORE key member ZCARD key 

应用场景:

  • 排行榜
  • 带权重的队列
  • 范围查找

示例代码:

# 排行榜 r.zadd('game_scores',{'Alice':1500,'Bob':1200,'Charlie':1800})# 获取前三名 top_players = r.zrevrange('game_scores',0,2, withscores=True)print(top_players)# 输出: [(b'Charlie', 1800.0), (b'Alice', 1500.0), (b'Bob', 1200.0)]# 获取某个玩家的排名(从0开始)print(r.zrevrank('game_scores','Alice'))# 输出: 1

6. 其他数据结构

Redis还支持一些特殊的数据结构:

HyperLogLog
用于基数统计,提供不精确的去重计数方案。

Geo
用于存储地理位置信息,支持半径查询等操作。

Bitmaps
通过位操作实现一些特殊功能,如用户签到统计。

三、Redis实现分布式锁

1. 分布式锁的概念与需求

在分布式系统中,当多个进程或服务需要互斥地访问共享资源时,就需要分布式锁。分布式锁需要满足以下几个基本要求:

  1. 互斥性:同一时刻只有一个客户端能持有锁
  2. 避免死锁:即使持有锁的客户端崩溃,锁也能被释放
  3. 容错性:只要大部分Redis节点正常运行,客户端就能获取和释放锁
  4. 自旋等待:获取不到锁的客户端应该能等待并重试

2. 基于Redis的分布式锁实现方案

方案一:使用SETNX命令(基本实现)
defacquire_lock(conn, lock_name, acquire_timeout=10): identifier =str(uuid.uuid4()) lock_key =f'lock:{lock_name}' end = time.time()+ acquire_timeout while time.time()< end:if conn.setnx(lock_key, identifier):return identifier time.sleep(0.001)returnFalsedefrelease_lock(conn, lock_name, identifier): lock_key =f'lock:{lock_name}'with conn.pipeline()as pipe:whileTrue:try: pipe.watch(lock_key)if pipe.get(lock_key)== identifier.encode(): pipe.multi() pipe.delete(lock_key) pipe.execute()returnTrue pipe.unwatch()breakexcept redis.exceptions.WatchError:passreturnFalse

问题:

  • 如果客户端在获取锁后崩溃,锁永远不会被释放
  • 没有重入机制
  • 锁的获取和释放不是原子操作
方案二:使用SET命令带过期时间(改进版)
defacquire_lock(conn, lock_name, acquire_timeout=10, lock_timeout=10): identifier =str(uuid.uuid4()) lock_key =f'lock:{lock_name}' lock_timeout =int(lock_timeout) end = time.time()+ acquire_timeout while time.time()< end:if conn.set(lock_key, identifier, ex=lock_timeout, nx=True):return identifier time.sleep(0.001)returnFalsedefrelease_lock(conn, lock_name, identifier): lock_key =f'lock:{lock_name}' script =""" if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """ result = conn.eval(script,1, lock_key, identifier)returnbool(result)

改进点:

  1. 使用SET命令的NX和EX参数替代SETNX,实现原子性设置值和过期时间
  2. 使用Lua脚本保证释放锁的原子性
  3. 添加了锁的超时机制,防止死锁
方案三:Redlock算法(集群环境)

在Redis集群环境下,为了确保锁的高可用性,可以使用Redlock算法:

  1. 获取当前时间(毫秒)
  2. 依次尝试从N个独立的Redis实例获取锁
  3. 计算获取锁花费的总时间,如果小于锁的过期时间,并且从大多数(N/2+1)实例获取成功,则获取锁成功
  4. 锁的实际有效时间 = 初始有效时间 - 获取锁花费的时间
  5. 如果获取锁失败,则向所有实例发送释放锁命令

Python实现示例:

import random import time defacquire_redlock(conns, lock_name, ttl, retry_count=3, retry_delay=0.2): quorum =len(conns)//2+1 identifier =str(uuid.uuid4())for _ inrange(retry_count): start_time = time.time()*1000 acquired =0for conn in conns:try:if conn.set(lock_name, identifier, nx=True, px=ttl): acquired +=1except redis.RedisError:continue elapsed = time.time()*1000- start_time if acquired >= quorum and elapsed < ttl:return{'validity': ttl - elapsed,'resource': lock_name,'identifier': identifier }# 释放已经获取的锁for conn in conns:try: conn.eval(""" if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """,1, lock_name, identifier)except redis.RedisError:pass time.sleep(retry_delay *(1+ random.random()))returnFalsedefrelease_redlock(conns, lock_info):ifnot lock_info:returnfor conn in conns:try: conn.eval(""" if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """,1, lock_info['resource'], lock_info['identifier'])except redis.RedisError:pass

3. 分布式锁的最佳实践

  1. 设置合理的超时时间:既不能太短导致任务未完成锁就释放,也不能太长导致系统长时间阻塞
  2. 使用唯一标识:确保只有锁的持有者才能释放锁
  3. 避免长时间持有锁:尽量减少锁的持有时间
  4. 考虑锁的重入:如果需要,可以实现可重入锁
  5. 处理网络分区:设计容错机制,处理网络不稳定的情况
  6. 监控和告警:监控锁的获取失败率和持有时间

4. 分布式锁的常见问题与解决方案

问题1:锁提前释放

  • 原因:业务处理时间超过锁的超时时间
  • 解决方案:设置合理的超时时间;实现锁续约机制

问题2:时钟漂移

  • 原因:不同机器时钟不一致
  • 解决方案:尽量使用时间增量而非绝对时间;使用NTP同步时钟

问题3:锁被误释放

  • 原因:其他客户端误删了锁
  • 解决方案:使用唯一标识验证锁的持有者

问题4:性能瓶颈

  • 原因:锁竞争激烈
  • 解决方案:细化锁粒度;使用读写锁;考虑无锁设计

四、总结

Redis作为一个高性能的内存数据库,提供了丰富的数据结构,可以满足各种场景下的需求。从简单的字符串到复杂的排序集合,每种数据结构都有其特定的应用场景和优势。

在分布式系统中,Redis也是实现分布式锁的理想选择。通过合理的命令组合和Lua脚本,可以实现安全可靠的分布式锁。对于更复杂的集群环境,Redlock算法提供了更高的可用性保证。

然而,分布式锁的实现需要考虑诸多细节,包括超时设置、锁续约、错误处理等。在实际应用中,也可以考虑使用现成的库如Redlock-py等,它们已经处理了大部分边缘情况。

最后,值得注意的是,分布式锁虽然强大,但并非所有场景都需要。在设计系统时,应该首先考虑是否可以通过无锁设计或其他并发控制机制来解决问题,只有在真正需要互斥访问时才使用分布式锁。

Read more

Linux:初始网络(上)

之前我们学习了linux的系统部分,从现在开始,我们将进入linux网络部分的学习,在正式开始学习网络之前,我们需要先了解一下什么网络,他是源自什么的,怎么发展的,以及协议的浅层意思,话不多说,现在开始 ⽹络基础概念 ⽹络发展 独⽴模式: 计算机之间相互独⽴ 在以前,计算机都是独立的,所以为了合作,只能A完成A的部分,然后将类似磁盘的存储数据的东西交给B,B再完成B的部分,然后教给C,C完成自己的部分,可想而知,这样效率缓慢 ⽹络互联: 多台计算机连接在⼀起, 完成数据共享 为了解决这种事情,有人提议将ABC三台电脑连接到一个服务器上,这样A完成之后只需要把数据给服务器,B只需要拉下服务器的数据就可以,不需要人跑来跑去送数据,这就是网络互联 局域⽹LAN: 计算机数量更多了, 通过交换机和路由器连接在⼀起 越来越多计算机这样,于是为了更好的联系,出现了交换机连接 ⼴域⽹WAN: 将远隔千⾥的计算机都连在⼀起 当局域网多了,于是他们又互相链接,

By Ne0inhk
Linux 进程间通信之命名管道(FIFO):跨进程通信的实用方案

Linux 进程间通信之命名管道(FIFO):跨进程通信的实用方案

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 命名管道核心概念:什么是 FIFO? * 1.1 命名管道的定义 * 1.2 命名管道的核心特性 * 1.3 命名管道和匿名管道的区别与联系 * 二. 命名管道的创建方式 * 2.1 命令行创建(mkfifo 命令) * 2.2 代码创建(mkfifo 函数) * 三. 命名管道的打开规则(关键!) * 四. 命名管道实战案例 * 4.1 案例 1:命名管道实现文件拷贝 * 4.1.

By Ne0inhk
Flutter 组件 ansi_styles 的鸿蒙化适配实战 - 驾驭极致终端交互艺术、实现 OpenHarmony 开发链路、日志系统与控制台的工业级色彩分级方案

Flutter 组件 ansi_styles 的鸿蒙化适配实战 - 驾驭极致终端交互艺术、实现 OpenHarmony 开发链路、日志系统与控制台的工业级色彩分级方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 ansi_styles 的鸿蒙化适配实战 - 驾驭极致终端交互艺术、实现 OpenHarmony 开发链路、日志系统与控制台的工业级色彩分级方案 前言 在鸿蒙(OpenHarmony)生态的底座开发、高性能服务端侧逻辑构建、或者是对命令行交互(CLI)有极其严苛要求的自动化工程流水线中。“终端日志的可视化分级与视觉重心引导维度”是衡量整个底层调试链路效能的最终质量门禁。面对包含数万行内核日志、海量网络请求报文、甚至是 0308 批次重型打包过程产生的满屏文字流。如果仅仅依靠终端中苍白的一串 White 和 Black 或者是毫无温标感的 txt 控制台。不仅会导致在定位历史回退(Regression)时让开发工程师如同在字符废墟中盲人摸象。更会因为缺乏大局观的报错优先级呈现。令技术高层在跨终端指挥调度时陷入严重的信息盲区。 我们需要一种“色彩生动、警示分明”的终端资产汇报艺术。 ansi_styles 是一套专注于无缝整合全球公认顶级

By Ne0inhk
Flutter 组件 vietqr_gen 适配鸿蒙 HarmonyOS 实战:标准聚合支付,构建金融级二维码生成与跨境支付治理架构

Flutter 组件 vietqr_gen 适配鸿蒙 HarmonyOS 实战:标准聚合支付,构建金融级二维码生成与跨境支付治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 vietqr_gen 适配鸿蒙 HarmonyOS 实战:标准聚合支付,构建金融级二维码生成与跨境支付治理架构 前言 在鸿蒙(OpenHarmony)生态迈向全场景商业化、涉及跨境数字化金融、智能收银终端及分布式聚合支付的背景下,如何生成符合国际 EMVCo 标准且具备高可靠校验机制的支付二维码,已成为决定金融类应用“交易确定性”的核心环节。在鸿蒙设备这类强调内核级安全防护与高精度金融计算的环境下,如果应用依然依赖简单的字符串拼接来构造具有复杂 TLV(Tag-Length-Value)结构的支付密令,由于由于字节统计误差或 CRC 校验逻辑漏洞,极易由于由于扫码解析失败导致资金结算链路的中断。 我们需要一种能够自动化 TLV 封装、支持标准银行目录映射且具备高精度 CRC16 校验的金融级生成方案。 vietqr_gen 为 Flutter 开发者引入了标准化的聚合支付二维码生成协议。它不仅支持对收款账号、金额及备注的结构化打包,更

By Ne0inhk