1.Redis介绍
1.什么是中间件?
中间件(Middleware) 是一种软件组件,它位于客户端和服务器端应用程序之间,为它们提供通信和管理的桥梁
常见的中间件类型有:
-
Web服务器中间件:如 Nginx、Apache。用于处理HTTP请求、负载均衡等。
-
数据库中间件:用于数据库读写分离、分库分表。如 MyCat、ShardingSphere。
-
消息队列中间件:用于异步通信、解耦。像 RabbitMQ、Kafka。
-
缓存中间件:比如 Redis,用于加速数据访问。
-
RPC中间件:用于跨服务调用,比如 gRPC、Thrift。
2.NoSQL
1.NoSQL vs SQL 比较
特性 | NoSQL | SQL(RDBMS) |
---|---|---|
数据模型 | 非关系型 | 关系型(表格) |
扩展方式 | 水平扩展 | 垂直扩展 |
事务支持 | 有限(部分支持) | ACID完整支持 |
查询语言 | 无标准,各产品不同 | 标准SQL |
一致性 | 通常最终一致 | 强一致 |
灵活性 | 高 | 低(需预定义模式) |
2.NoSQL特点
- 没有固定查询语言
- CAP原则(最终一致性)
- 高性能、高可用、高扩展
1.CAP 原则与 BASE 理论
CAP 原则(Consistency、Availability、Partition Tolerance)是分布式系统设计中的一个重要理论,指出在分布式系统中,一致性(C)、可用性(A)和分区容错性(P)三者无法同时完全满足,最多只能同时满足其中的两个。
-
一致性(Consistency):所有节点在同一时间看到相同的数据。
-
可用性(Availability):每个请求都能在合理时间内返回结果。
-
分区容错性(Partition Tolerance):系统在部分网络分区故障时仍能运行。
在分布式系统中,分区容错性通常是必须满足的,因此系统设计者往往需要在一致性和可用性之间做出权衡。
BASE 理论(Basically Available, Soft state, Eventually consistent)是对 CAP 原则中牺牲一致性以换取可用性的补充。它强调:
-
基本可用(Basically Available):系统在部分故障时仍能提供服务。
-
软状态(Soft state):数据的最终状态可能需要时间来同步。
-
最终一致性(Eventually consistent):系统会在一定时间内达到一致状态。
2.异地多活的核心特点
-
多地部署:在不同地理位置部署多个数据中心,每个数据中心都具备独立提供服务的能力。
-
数据同步:各数据中心之间实时同步数据,确保数据一致性和可用性。
-
故障切换:当某个数据中心发生故障时,系统可以自动切换到其他正常的数据中心,用户几乎感受不到中断。
异地多活本质上是一种 AP(可用性 + 分区容错性)方案。为了实现多地部署和高可用性,系统通常会牺牲部分一致性,采用最终一致性模型。
3.NoSQL 数据库的主要类型
1. 键值存储(Key-Value)
-
特点:最简单的NoSQL模型,数据作为键值对存储
-
代表产品:Redis, DynamoDB, Riak
-
适用场景:缓存、会话管理、配置存储
2. 文档型数据库(Document)
-
特点:数据以文档形式存储(如JSON, BSON)
-
代表产品:MongoDB, CouchDB
-
适用场景:内容管理系统、用户配置文件、产品目录
3. 列族存储(Column-family)
-
特点:数据按列族存储,适合大规模数据集
-
代表产品:Cassandra, HBase
-
适用场景:日志分析、时间序列数据、大数据应用
4. 图数据库(Graph)
-
特点:专注于实体间的关系
-
代表产品:Neo4j, ArangoDB
-
适用场景:社交网络、推荐系统、欺诈检测
3.Redis 是什么?
Redis 是一个开源的内存数据/键值数据库(存储系统),提供多种语言API,属于 NoSQL 数据库的一种。既可以当作缓存,也可以当作数据库,还能做消息队列。它的数据都是保存在内存中的,因此访问速度非常快。
1.Redis 的常见用途:
-
高速缓存(如商品信息、用户信息)来加速访问。
-
分布式锁(用来控制多个服务对资源的访问)。
-
消息队列(用 list 或 pub/sub 实现)。
-
计数器(用 INCR 之类的命令可以轻松实现)。
2.Redis 优点:
-
快!(因为数据在内存中)
-
支持丰富的数据结构
-
单线程模型但性能超高
-
支持持久化(数据可以保存到磁盘)
2.Redis安装
3.Redis使用
1.Redis使用教程
一、启动 Redis 服务
1. 使用 systemd
启动 Redis
如果你的系统使用 systemd
(如 Ubuntu 18.04 及以上版本),可以通过以下命令启动 Redis 服务:
sudo systemctl start redis-server
2. 手动启动 Redis
如果你没有使用 systemd
,或者希望手动启动 Redis,可以直接运行以下命令:
redis-server
二、检查 Redis 是否运行
1. 通过 systemctl
检查状态
如果你使用 systemd
,可以通过以下命令检查 Redis 服务的状态:
sudo systemctl status redis-server
2. 通过 redis-cli
检查
你也可以通过 Redis 的命令行工具 redis-cli
来检查 Redis 是否正常运行:
redis-cli ping
PONG
三、停止 Redis 服务
1. 使用 systemd
停止 Redis
如果你使用 systemd
,可以通过以下命令停止 Redis 服务:
sudo systemctl stop redis-server
2. 手动停止 Redis
如果你手动启动了 Redis,可以通过以下命令停止它:
redis-cli shutdown
如果 Redis 没有正常响应 shutdown
命令,你可以通过以下命令强制停止 Redis 服务:
pkill redis-server
四、基本操作
1. 连接到 Redis
你可以通过 redis-cli
连接到 Redis 服务器:
redis-cli
这将打开 Redis 的交互式命令行界面。
2. 设置和获取键值对
在 redis-cli
中,你可以执行以下命令来设置和获取键值对:
SET mykey "Hello, Redis!"
GET mykey
3. 查看所有键
你可以使用 KEYS
命令查看当前数据库中的所有键:
plaintext复制
KEYS *
4. 删除键
你可以使用 DEL
命令删除一个键:
DEL mykey
在 Redis 中,可以使用 FLUSHALL
命令来删除所有键
5. 其他常用命令
redis默认16个数据库 sudo vim /etc/redis/redis.conf database 16
-
select 3 #切换
-
查看当前数据库中的键数量:
DBSIZE
-
清空当前数据库:
FLUSHDB
-
清空所有数据库:
FLUSHALL
- 设置键的过期时间
EXPIRE mykey 30
- 获取键的剩余过期时间
TTL mykey
type key
五、配置 Redis
1. 编辑配置文件
Redis 的配置文件通常位于 /etc/redis/redis.conf
。你可以使用文本编辑器打开并修改它:
bash复制
sudo nano /etc/redis/redis.conf
2. 常用配置项
-
绑定地址:默认情况下,Redis 绑定到
127.0.0.1
,只允许本地访问。如果你想允许远程访问,可以将bind
设置为0.0.0.0
:bind 0.0.0.0
-
端口号:默认端口号是
6379
,你可以通过以下指令修改:port 6380
-
密码保护:为了安全起见,你可以设置密码:
requirepass your_password
-
持久化:Redis 支持多种持久化方式,如 RDB(快照)和 AOF(追加文件)。你可以选择适合你的持久化方式:
save 900 1 save 300 10 save 60 10000 appendonly yes
3. 重新加载配置
修改配置文件后,你可以通过以下命令重新加载配置:
sudo systemctl reload redis-server
或者手动重启 Redis 服务:
sudo systemctl restart redis-serve
2.redis-benchmark
redis-benchmark
是 Redis 自带的一款性能测试工具,用于模拟多个客户端同时发送请求,以评估 Redis 服务器在不同负载下的性能。以下是关于如何使用 redis-benchmark
的详细指南:
基本用法
运行以下命令即可启动默认的性能测试:
bash复制
redis-benchmark
默认情况下,该命令会使用 50 个并发连接,发送 100,000 个请求到本地 Redis 服务器(127.0.0.1:6379
)。
常用参数
-
-h <hostname>
:指定 Redis 服务器的主机地址,默认为127.0.0.1
。 -
-p <port>
:指定 Redis 服务器的端口号,默认为6379
。 -
-c <clients>
:指定并发连接数,默认为 50。 -
-n <requests>
:指定总请求数,默认为 100,000。 -
-t <tests>
:指定要测试的命令列表,例如set,get
,默认测试所有命令。 -
-d <size>
:指定 SET 和 GET 命令的数据大小(以字节为单位),默认为 2 字节。 -
-q
:安静模式,仅显示每秒请求数。 -
-r <keyspacelen>
:使用随机键进行 SET、GET 和 INCR 操作。 -
-P <numreq>
:启用管道功能,指定每个连接发送的请求数。
每次写入三个字节
只有一台服务器处理请求:单机性能
所有请求在22毫秒处理
4.Redis基础知识
redis是单线程的,基于内存操作,CPU不是Redis瓶颈,瓶颈是内存和网络带宽
1.Redis 如何管理内存
-
内存分配:
-
Redis 使用动态内存分配机制,根据数据的实际需求分配内存。例如,字符串数据会根据其大小动态分配内存。
-
-
LRU 算法:
-
Redis 使用 LRU(最近最少使用)算法来管理内存。当内存不足时,Redis 会优先淘汰最近最少使用的键值对,以确保热点数据常驻内存。
-
-
内存回收:
-
Redis 采用惰性删除和定期删除机制来回收内存。惰性删除是指在访问键时才检查键是否过期并删除,定期删除则是周期性地扫描数据库,删除过期键。
-
-
内存碎片整理:
-
Redis 会定期进行内存碎片整理,将散落的内存块合并为连续的内存空间,以减少内存碎片的影响。
-
2.Redis 如何处理大数据
-
数据分片和集群:
-
当单个 Redis 实例的内存不足以存储所有数据时,可以使用 Redis 集群。通过将数据分片到多个节点,每个节点只存储部分数据,从而减轻单个节点的内存压力。
-
-
异步操作:
-
Redis 提供了一些异步操作机制,例如
unlink
命令可以异步释放内存,避免在删除大键时阻塞主线程。
-
-
持久化策略:
-
Redis 提供了多种持久化策略,如 RDB(快照)和 AOF(追加文件)。选择合适的持久化方式可以在保证数据安全的同时,减少内存占用。
-
3.为什么单线程还能高效?
-
纯内存操作:无磁盘 I/O 阻塞。
-
I/O 多路复用:使用
epoll
/kqueue
高效处理网络请求。 -
避免锁竞争:单线程无需考虑并发控制的开销。
4.Redis 支持的数据类型:
-
字符串(String):计数器
-
列表(List):堆栈、消息队列(两边插入、移除效率高,中间效率低)
-
集合(Set):set中的值不能重复,差集,交集、并集
-
有序集合(Sorted Set)
-
哈希表(Hash)
-
位图、HyperLogLog 等高级类型
5. Redis 的发布-订阅(Pub/Sub)模式
Redis 的发布-订阅(Pub/Sub)模式是一种消息传递机制,允许客户端订阅特定的频道,并接收发布到这些频道的消息。这种模式非常适合实时消息传递、事件驱动的架构和分布式系统中的通信。以下是关于 Redis Pub/Sub 模式的详细说明和使用方法。
基本概念
-
发布者(Publisher):向特定频道发送消息的客户端。
-
订阅者(Subscriber):监听特定频道并接收消息的客户端。
-
频道(Channel):消息发布的目标,类似于消息队列中的主题。
-
模式(Pattern):允许订阅者订阅符合特定模式的多个频道。
命令
1. SUBSCRIBE channel1 [channel2 ...]
订阅一个或多个频道。
-
redis-cli SUBSCRIBE channel1
2. PUBLISH channel message
向指定频道发送消息。
-
redis-cli PUBLISH channel1 "Hello, Redis!"
3. UNSUBSCRIBE [channel1 [channel2 ...]]
取消订阅一个或多个频道。如果不指定频道,则取消所有订阅。
-
redis-cli UNSUBSCRIBE channel1
6.hredis
hiredis
是一个 C 语言客户端库,用于与 Redis 服务器进行交互。它支持 Redis 的大部分命令,包括字符串、哈希、列表、集合、排序集合等数据结构的操作。hiredis
是一个高性能、简洁且易于使用的库,特别适用于 C/C++ 项目中进行 Redis 客户端开发。
1.安装
git clone https://github.com/redis/hiredis.git
cd hiredis
make
make install
2.使用
1. 初始化 Redis 客户端
-
使用
redisConnect
创建两个独立的同步上下文(redisContext
):-
一个用于发布消息(
publish_context
)。 -
一个用于订阅消息(
subscribe_context
)。
-
-
发布和订阅操作不能使用同一个上下文,因为它们的工作方式不同。
2. 发布消息
-
使用
redisCommand
发送PUBLISH
命令,将消息发布到指定的频道。
3. 订阅消息
-
使用
redisCommand
发送SUBSCRIBE
命令,订阅指定的频道。 -
在独立线程中使用
redisGetReply
接收消息。 -
消息以数组形式返回,包含频道名称和消息内容。
4. 取消订阅
-
使用
redisCommand
发送UNSUBSCRIBE
命令,取消订阅指定的频道。
5. 消息回调
-
定义一个回调函数,用于处理接收到的消息。
-
将消息传递给业务逻辑层。
6. 清理资源
-
使用
redisFree
释放redisContext
资源。
Redis.h
#ifndef REDIS_H
#define REDIS_H
#include <hiredis/hiredis.h>
#include <functional>
#include <thread>
#include <string>
#include <atomic>
class Redis {
public:
Redis();
~Redis();
// 连接到 Redis 服务器
bool connect();
// 向指定频道发布消息
bool publish(int channel, const std::string& message);
// 订阅指定频道
bool subscribe(int channel);
// 取消订阅指定频道
bool unsubscribe(int channel);
// 异步接收订阅消息
void start_message_listener();
// 停止接收消息
void stop_message_listener();
// 初始化消息回调函数
void init_notify_handler(std::function<void(int, std::string)> fn);
private:
redisContext* _publish_context; // 发布消息的上下文
redisContext* _subscribe_context; // 订阅消息的上下文
std::function<void(int, std::string)> _notify_message_handler; // 消息回调函数
std::atomic<bool> _listener_running; // 标志消息监听是否运行
std::thread _listener_thread; // 用于异步监听消息的线程
};
#endif // REDIS_H
Redis.cpp
cpp复制
#include "Redis.h"
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
Redis::Redis() : _publish_context(nullptr), _subscribe_context(nullptr), _listener_running(false) {}
Redis::~Redis() {
if (_publish_context) {
redisFree(_publish_context);
}
if (_subscribe_context) {
redisFree(_subscribe_context);
}
if (_listener_thread.joinable()) {
_listener_thread.join(); // 确保线程退出
}
}
bool Redis::connect() {
// 创建发布上下文
_publish_context = redisConnect("127.0.0.1", 6379);
if (_publish_context == nullptr || _publish_context->err) {
if (_publish_context) {
std::cerr << "Publish context connection error: " << _publish_context->errstr << std::endl;
redisFree(_publish_context);
_publish_context = nullptr;
}
return false;
}
// 创建订阅上下文
_subscribe_context = redisConnect("127.0.0.1", 6379);
if (_subscribe_context == nullptr || _subscribe_context->err) {
if (_subscribe_context) {
std::cerr << "Subscribe context connection error: " << _subscribe_context->errstr << std::endl;
redisFree(_subscribe_context);
_subscribe_context = nullptr;
}
return false;
}
return true;
}
bool Redis::publish(int channel, const std::string& message) {
if (!_publish_context) {
std::cerr << "Publish context is not connected." << std::endl;
return false;
}
// 发送 PUBLISH 命令
redisReply* reply = (redisReply*)redisCommand(_publish_context, "PUBLISH %d %s", channel, message.c_str());
if (!reply) {
std::cerr << "Failed to publish message." << std::endl;
return false;
}
freeReplyObject(reply); // 释放回复对象
return true;
}
bool Redis::subscribe(int channel) {
if (!_subscribe_context) {
std::cerr << "Subscribe context is not connected." << std::endl;
return false;
}
// 发送 SUBSCRIBE 命令
redisReply* reply = (redisReply*)redisCommand(_subscribe_context, "SUBSCRIBE %d", channel);
if (!reply) {
std::cerr << "Failed to subscribe to channel." << std::endl;
return false;
}
freeReplyObject(reply); // 释放回复对象
return true;
}
bool Redis::unsubscribe(int channel) {
if (!_subscribe_context) {
std::cerr << "Subscribe context is not connected." << std::endl;
return false;
}
// 发送 UNSUBSCRIBE 命令
redisReply* reply = (redisReply*)redisCommand(_subscribe_context, "UNSUBSCRIBE %d", channel);
if (!reply) {
std::cerr << "Failed to unsubscribe from channel." << std::endl;
return false;
}
freeReplyObject(reply); // 释放回复对象
return true;
}
void Redis::start_message_listener() {
_listener_running.store(true); // 启动消息接收标志
// 启动接收消息的线程
_listener_thread = std::thread([this]() {
while (_listener_running.load()) {
redisReply* reply = NULL;
int status = redisGetReply(_subscribe_context, (void**)&reply);
// 检查状态码
if (status != REDIS_OK) {
std::cerr << "Failed to receive message. Status: " << status << std::endl;
break;
}
// 消息格式为数组,包含频道名称和消息内容
if (reply->type == REDIS_REPLY_ARRAY && reply->elements == 3) {
int channel = atoi(reply->element[1]->str); // 频道编号
std::string message = reply->element[2]->str; // 消息内容
// 调用回调函数处理消息
if (_notify_message_handler) {
_notify_message_handler(channel, message);
}
}
freeReplyObject(reply); // 释放回复对象
}
});
}
void Redis::stop_message_listener() {
_listener_running.store(false); // 停止消息接收
if (_listener_thread.joinable()) {
_listener_thread.join(); // 等待线程结束
}
}
void Redis::init_notify_handler(std::function<void(int, std::string)> fn) {
_notify_message_handler = fn;
}
main.cpp
cpp复制
#include "Redis.h"
#include <iostream>
#include <thread>
#include <chrono>
// 消息回调函数
void message_handler(int channel, std::string message) {
std::cout << "Received message on channel " << channel << ": " << message << std::endl;
}
int main() {
Redis redis;
if (!redis.connect()) {
std::cerr << "Failed to connect to Redis server." << std::endl;
return 1;
}
// 初始化消息回调
redis.init_notify_handler(message_handler);
// 订阅频道 1
if (!redis.subscribe(1)) {
std::cerr << "Failed to subscribe to channel 1." << std::endl;
return 1;
}
// 启动消息监听线程
redis.start_message_listener();
// 等待 1 秒,确保订阅完成
std::this_thread::sleep_for(std::chrono::seconds(1));
// 向频道 1 发布消息
if (!redis.publish(1, "Hello, Redis!")) {
std::cerr << "Failed to publish message." << std::endl;
}
// 等待 5 秒后取消订阅并退出
std::this_thread::sleep_for(std::chrono::seconds(5));
if (!redis.unsubscribe(1)) {
std::cerr << "Failed to unsubscribe from channel 1." << std::endl;
}
// 停止监听消息
redis.stop_message_listener();
return 0;
}
1.redisContext
redisContext
用于表示与 Redis 服务器的连接。它包含了连接的所有必要信息,例如套接字文件描述符、错误信息等。通过 redisContext
,你可以发送命令到 Redis 服务器并接收响应。
1. 创建连接
使用 redisConnect
或 redisConnectWithTimeout
创建一个 redisContext
。
redisContext* c = redisConnect("127.0.0.1", 6379);
if (c == NULL || c->err) {
// 处理错误
printf("Connection error: %s\n", c->errstr);
redisFree(c);
return 1;
}
2. 发送命令
通过 redisCommand
或 redisCommandArgv
向 Redis 发送命令。
redisReply* reply = (redisReply*)redisCommand(c, "PING");
if (reply) {
printf("PING: %s\n", reply->str);
freeReplyObject(reply);
}
3. 接收响应
命令的响应会存储在 redisReply
结构体中,你可以根据需要处理响应。
if (reply->type == REDIS_REPLY_STRING) {
printf("Response: %s\n", reply->str);
}
4. 关闭连接
使用 redisFree
释放 redisContext
。
redisFree(c);
2. redisCommand
redisCommand
是一个高级函数,用于发送 Redis 命令并同步接收响应。它封装了命令的发送和响应的接收过程,非常适合简单的场景。
redisCommand
的底层实现
redisCommand
实际上是通过以下步骤实现的:
-
使用
redisAppendCommand
将命令发送到 Redis 服务器。 -
使用
redisGetReply
接收响应。
redisReply* redisCommand(redisContext *c, const char *format, ...);
3. redisGetReply
redisGetReply
是一个低级函数,用于从 Redis 服务器接收响应。它通常与 redisAppendCommand
配合使用,适用于需要高性能或异步操作的场景。
4.什么是 std::atomic<bool>
?
std::atomic<bool>
是一个模板类 std::atomic
的特化版本,专门用于布尔类型。它提供了以下特性:
-
原子性:对
std::atomic<bool>
的读写操作是原子性的,不会被其他线程中断。 -
线程安全:不需要额外的锁来保证线程安全。
-
高效:原子操作通常比互斥锁(
std::mutex
)更高效。
1. 定义变量
cpp复制
std::atomic<bool> _listener_running;
-
定义一个
std::atomic<bool>
类型的变量_listener_running
。
2. 初始化变量
cpp复制
_listener_running = false; // 或者使用 _listener_running.store(false);
-
使用赋值操作或
store
方法初始化变量。
3. 读取变量
cpp复制
bool running = _listener_running.load(); // 或者直接使用 _listener_running
-
使用
load
方法或直接访问变量来读取值。
4. 修改变量
_listener_running = true; // 或者使用 _listener_running.store(true);
-
使用赋值操作或
store
方法修改变量的值。
3.CMake
add_executable(redis redis.cpp main.cpp)
target_include_directories(redis PRIVATE ${CMAKE_SOURCE_DIR}/include)
# 查找 hiredis 库
find_library(HIREDIS_LIB hiredis REQUIRED)
# 链接 hiredis 库
target_link_libraries(redis ${HIREDIS_LIB})