当前位置: 首页 > news >正文

Redis学习笔记

文章目录

  • 前言
  • 1. Redis有哪些实际应用?
  • 2. Redis的工作原理是什么?
    • 2.1 基于内存的数据存储
    • 2.2 数据结构与操作命令
    • 2.3 持久化机制
    • 2.4 单线程架构
    • 2.5 哨兵和集群机制
  • 3. Redis的使用实例


前言

Redis是一种开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。Redis提供数据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,地理空间索引和流。Redis具有内置的复制[集群],Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性[磁盘],并通过Redis Sentinel和Redis Cluster自动分区提供了高可用性【集群】。

1. Redis有哪些实际应用?

缓存系统

  • Redis通过提供超高速的数据访问,常用于缓存网站内容,减轻后端数据库的负担,提高响应速度。它可以缓存网页、数据库查询结果和常用对象。例如,网站可以将热门文章的内容缓存在Redis中,以快速向用户展示,避免每次访问都查询数据库。

会话存储

  • 在Web应用中,Redis被用来存储用户会话信息。它能够快速读写用户的会话数据,支持大量并发的访问。例如,在线购物平台可将用户的登录信息和购物车状态存储在Redis中,实现快速会话恢复。

消息队列

  • Redis的发布/订阅模式和列表数据结构使其成为实现消息队列的理想选择。它能够在生产者和消费者之间高效地传递消息。例如,一个应用可以用Redis来处理发送邮件的任务队列,确保邮件按顺序发送。

实时分析

  • Redis的速度使其非常适合于需要实时分析的应用场景,如计数器、实时监控和事件分析等。例如,一个网站可能使用Redis来追踪每个页面的实时访问次数。

排行榜和计数器

  • 利用Redis的有序集合,可以有效地实现排行榜系统。它允许快速更新和检索排名信息。例如,游戏应用可以用Redis来维护玩家的分数排行榜,实时更新玩家排名。

地理空间数据处理

  • Redis提供了地理空间索引功能,可以快速进行地理位置存储和查询。这适用于需要处理地理位置信息的服务。例如,一个位置基础的服务可以用Redis来追踪和查询附近的兴趣点或用户。

2. Redis的工作原理是什么?

2.1 基于内存的数据存储

Redis是一个内存中的数据结构存储系统,意味着它使用计算机的主内存(RAM)来存储所有的数据。这种内存优先的设计使得Redis能够提供极高的性能,因为内存的数据访问速度远远超过了传统硬盘存储。

由于存储在内存中,Redis能够以微秒级别的延迟对数据进行读写操作,这对于需要快速响应的应用来说至关重要,如缓存系统、实时分析平台和高频交易系统等。然而,内存资源相对有限且价格较高,因此Redis也提供了数据驱动的逐出策略(如LRU—最近最少使用算法)和精细的内存管理功能,确保有效利用可用内存。

2.2 数据结构与操作命令

Redis提供了丰富的数据结构,每种结构都有专门的操作命令:

  • 字符串(String):最基本的数据类型,用于存储文本或二进制数据。常用命令包括 GET、SET 用于存取数据,INCR、DECR 用于原子性递增或递减操作。

  • 哈希(Hash):用于存储对象,由字段和字段值组成的映射表。HGET、HSET 用于获取和设置字段值,HGETALL 用来获取哈希表的所有字段和值。

  • 列表(List):有序集合,支持双端插入和删除操作。LPUSH、RPUSH 用于从左端或右端插入元素,LPOP、RPOP 用于从两端弹出元素,LRANGE 用于获取列表片段。

  • 集合(Set):无序且元素唯一的集合。SADD、SREM 用于添加或移除元素,SMEMBERS 用于获取所有元素,SINTER、SUNION 和 SDIFF 用于集合运算。

  • 有序集合(Sorted Set):类似 Set,但每个元素关联一个分数值,按分数有序排列。ZADD 添加元素,ZRANGE 按照分数范围查询元素,ZREM 删除元素。

Redis还支持位操作、地理空间索引、HyperLogLogs等高级数据结构,通过一系列特定的命令进行操作,使得Redis能够应用于广泛的场景中。

2.3 持久化机制

为了保证内存中的数据在断电或故障时不会丢失,Redis提供了两种主要的持久化机制:RDB(Redis Database)和AOF(Append Only File)。

  • RDB:通过创建数据集的时间点快照来实现持久化。这是通过周期性地执行一个称为“BGSAVE”的操作来完成的,它会产生一个包含了Redis在某一时刻所有数据的二进制文件。RDB文件是一个压缩的二进制文件,可以用来在需要的时候,快速恢复整个数据集。它适合于数据备份和灾难恢复,但在发生故障后,自上次快照以来的所有数据都有可能丢失。

  • AOF:记录每一个写操作命令到一个日志文件中,命令以追加的方式保存。AOF文件以纯文本格式存储,提供了更好的数据安全性,因为它可以配置为每次写操作后同步到磁盘,或者每秒同步一次。在Redis重启时,AOF文件中的命令会被重新执行,以重建内存中的数据状态。通过AOF恢复数据通常比RDB更慢,但可以更频繁地记录数据状态,减少数据丢失的可能性。

2.4 单线程架构

Redis的单线程架构指的是它的核心数据操作是由一个单一的线程来执行的。这种设计带来了简单性和效率,因为避免了多线程上下文切换的开销,并简化了并发控制,因为不需要考虑数据在多个线程间的同步问题。尽管Redis处理命令的主循环是单线程的,它还是能利用IO多路复用技术来同时处理多个客户端的请求。此外,对于某些耗时操作,如持久化和部分网络IO处理,Redis会使用后台线程来避免阻塞主线程,确保了服务的响应性。单线程架构使得Redis可以高效地处理大量的请求,同时保持操作的原子性和一致性。

2.5 哨兵和集群机制

为了实现高可用性和水平扩展,Redis提供了哨兵(Sentinel)机制和集群(Cluster)模式。

Redis哨兵(Sentinel)是一个高可用性解决方案。哨兵系统可以监测Redis主从服务器的健康状态,自动执行故障转移,选举新的主服务器,并通知应用程序新主服务器的地址。哨兵还负责通知管理员,发送警报,并执行自定义脚本响应各种事件。

Redis集群(Cluster)提供了一个数据分区(sharding)和自动管理的环境,支持在多个节点间进行数据共享。它能够在节点间自动分配数据,并在节点故障时提供自动的故障转移功能。集群通过分片来提高数据库的可扩展性,并能在不中断服务的情况下,动态地添加或移除节点。

3. Redis的使用实例

在Ubuntu系统中,Redis作为集群服务器之间的消息队列使用
在开发过程中遇到一个Bug:
向Redis PUBLISH发布消息不成功,导致无法跨服务器通信
原因为:
两个客户端C1和C2分别登录在两台服务器S1和S2上,服务器通过redis作为消息中间件进行消息通信。当客户端C1和C2通信时,C1先发送消息到S1上,S1向redis上名字为C2的channel通道发布消息,S2服务器从C2的channel上接收消息后,再把消息转发给C2客户端,注意hiredis提供的发送redis命令行接口函数redisCommand,在发送发布-订阅命令时,需要在不同的上下文Context环境中进行,不能够在同一Context下进行

为了方便使用,把redis.conf文件中改成无密码登录。

使用库为hiredis

hpp文件:

#ifndef REDIS_H
#define REDIS_H#include <hiredis/hiredis.h>
#include <thread>
#include <functional>
using namespace std;class Redis
{
public:Redis();~Redis();// 连接redis服务器 bool connect();// 向redis指定的通道channel发布消息bool publish(int channel, string message);// 向redis指定的通道subscribe订阅消息bool subscribe(int channel);// 向redis指定的通道unsubscribe取消订阅消息bool unsubscribe(int channel);// 在独立线程中接收订阅通道中的消息void observer_channel_message();// 初始化向业务层上报通道消息的回调对象void init_notify_handler(function<void(int, string)> fn);private:// hiredis同步上下文对象,负责publish消息redisContext *_publish_context;// hiredis同步上下文对象,负责subscribe消息redisContext *_subcribe_context;// 回调操作,收到订阅的消息,给service层上报function<void(int, string)> _notify_message_handler;
};#endif

cpp文件:

#include "redis.hpp"
#include <iostream>
using namespace std;Redis::Redis(): _publish_context(nullptr), _subcribe_context(nullptr)
{
}Redis::~Redis()
{if (_publish_context != nullptr){redisFree(_publish_context);}if (_subcribe_context != nullptr){redisFree(_subcribe_context);}
}bool Redis::connect()
{// 负责publish发布消息的上下文连接_publish_context = redisConnect("127.0.0.1", 6379);if (nullptr == _publish_context){cerr << "connect redis failed!" << endl;return false;}// 负责subscribe订阅消息的上下文连接_subcribe_context = redisConnect("127.0.0.1", 6379);if (nullptr == _subcribe_context){cerr << "connect redis failed!" << endl;return false;}// 在单独的线程中,监听通道上的事件,有消息给业务层进行上报thread t([&]() {observer_channel_message();});t.detach();cout << "connect redis-server success!" << endl;return true;
}// 向redis指定的通道channel发布消息
bool Redis::publish(int channel, string message)
{redisReply *reply = (redisReply *)redisCommand(_publish_context, "PUBLISH %d %s", channel, message.c_str());if (nullptr == reply){cerr << "publish command failed!" << endl;return false;}freeReplyObject(reply);return true;
}// 向redis指定的通道subscribe订阅消息
bool Redis::subscribe(int channel)
{// SUBSCRIBE命令本身会造成线程阻塞等待通道里面发生消息,这里只做订阅通道,不接收通道消息// 通道消息的接收专门在observer_channel_message函数中的独立线程中进行// 只负责发送命令,不阻塞接收redis server响应消息,否则和notifyMsg线程抢占响应资源if (REDIS_ERR == redisAppendCommand(this->_subcribe_context, "SUBSCRIBE %d", channel)){cerr << "subscribe command failed!" << endl;return false;}// redisBufferWrite可以循环发送缓冲区,直到缓冲区数据发送完毕(done被置为1)int done = 0;while (!done){if (REDIS_ERR == redisBufferWrite(this->_subcribe_context, &done)){cerr << "subscribe command failed!" << endl;return false;}}// redisGetReplyreturn true;
}// 向redis指定的通道unsubscribe取消订阅消息
bool Redis::unsubscribe(int channel)
{if (REDIS_ERR == redisAppendCommand(this->_subcribe_context, "UNSUBSCRIBE %d", channel)){cerr << "unsubscribe command failed!" << endl;return false;}// redisBufferWrite可以循环发送缓冲区,直到缓冲区数据发送完毕(done被置为1)int done = 0;while (!done){if (REDIS_ERR == redisBufferWrite(this->_subcribe_context, &done)){cerr << "unsubscribe command failed!" << endl;return false;}}return true;
}// 在独立线程中接收订阅通道中的消息
void Redis::observer_channel_message()
{redisReply *reply = nullptr;while (REDIS_OK == redisGetReply(this->_subcribe_context, (void **)&reply)){// 订阅收到的消息是一个带三元素的数组if (reply != nullptr && reply->element[2] != nullptr && reply->element[2]->str != nullptr){// 给业务层上报通道上发生的消息_notify_message_handler(atoi(reply->element[1]->str) , reply->element[2]->str);}freeReplyObject(reply);}cerr << ">>>>>>>>>>>>> observer_channel_message quit <<<<<<<<<<<<<" << endl;
}void Redis::init_notify_handler(function<void(int,string)> fn)
{this->_notify_message_handler = fn;
}
http://www.xdnf.cn/news/427933.html

相关文章:

  • SAP Business One(B1)打开自定义对象报错【Failed to initialize document numbering:】
  • 大模型核心运行机制
  • 玩转ChatGPT:DeepSeek实战(统一所在地格式)
  • 基于STM32、HAL库的TDA7719TR音频接口芯片驱动程序设计
  • RK3568移植鸿蒙系统openharmony-5.1.0-release
  • 【愚公系列】《Manus极简入门》036-物联网系统架构师:“万物互联师”
  • 数据结构基础--蓝桥杯备考
  • 在Flutter上如何实现按钮的拖拽效果
  • Ceph 集群常用管理命令
  • esp32硬件支持AT指令
  • 什么类型的网站适合用WAF?Web应用防火墙的适用场景解析
  • Python(1) 做一个随机数的游戏
  • MySQL索引底层数据结构与算法
  • Vue 2 和 Vue 3的比较(二、语法差异)
  • Excel的详细使用指南
  • Mac修改hosts文件方法
  • Linux文件编程——标准库函数fopen、fread、fwrite等函数
  • Confusion2(Python反序列化+JWT)
  • MySQL——八、SQL优化
  • 【deekseek】P2P通信路由过程
  • 测试报告--博客系统
  • --openssl-legacy-provider is not allowed in NODE_OPTIONS 报错的处理方式
  • 栈与乘积 / 栈
  • rk3576--- HDMI CEC唤醒
  • TCP核心机制
  • 机器学习第八讲:向量/矩阵 → 数据表格的数学表达,如Excel表格转数字阵列
  • 已情感分析入门学习大模型-初级篇
  • MCP-RAG 服务器:完整设置和使用指南
  • Java 集合与 MyBatis 动态 SQL 实战教程
  • 普通项目与 FreeRTOS 项目的异同