Redis介绍及使用

1.Redis介绍

1.什么是中间件?

中间件(Middleware) 是一种软件组件,它位于客户端和服务器端应用程序之间,为它们提供通信和管理的桥梁

常见的中间件类型有:
  1. Web服务器中间件:如 Nginx、Apache。用于处理HTTP请求、负载均衡等。

  2. 数据库中间件:用于数据库读写分离、分库分表。如 MyCat、ShardingSphere。

  3. 消息队列中间件:用于异步通信、解耦。像 RabbitMQ、Kafka。

  4. 缓存中间件:比如 Redis,用于加速数据访问。

  5. RPC中间件:用于跨服务调用,比如 gRPC、Thrift。

2.NoSQL

1.NoSQL vs SQL 比较

特性NoSQLSQL(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 如何管理内存

  1. 内存分配

    • Redis 使用动态内存分配机制,根据数据的实际需求分配内存。例如,字符串数据会根据其大小动态分配内存。

  2. LRU 算法

    • Redis 使用 LRU(最近最少使用)算法来管理内存。当内存不足时,Redis 会优先淘汰最近最少使用的键值对,以确保热点数据常驻内存。

  3. 内存回收

    • Redis 采用惰性删除和定期删除机制来回收内存。惰性删除是指在访问键时才检查键是否过期并删除,定期删除则是周期性地扫描数据库,删除过期键。

  4. 内存碎片整理

    • Redis 会定期进行内存碎片整理,将散落的内存块合并为连续的内存空间,以减少内存碎片的影响。

2.Redis 如何处理大数据

  1. 数据分片和集群

    • 当单个 Redis 实例的内存不足以存储所有数据时,可以使用 Redis 集群。通过将数据分片到多个节点,每个节点只存储部分数据,从而减轻单个节点的内存压力。

  2. 异步操作

    • Redis 提供了一些异步操作机制,例如 unlink 命令可以异步释放内存,避免在删除大键时阻塞主线程。

  3. 持久化策略

    • Redis 提供了多种持久化策略,如 RDB(快照)和 AOF(追加文件)。选择合适的持久化方式可以在保证数据安全的同时,减少内存占用。

3.为什么单线程还能高效?

  1. 纯内存操作:无磁盘 I/O 阻塞。

  2. I/O 多路复用:使用 epoll/kqueue 高效处理网络请求。

  3. 避免锁竞争:单线程无需考虑并发控制的开销。

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. 创建连接

使用 redisConnectredisConnectWithTimeout 创建一个 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. 发送命令

通过 redisCommandredisCommandArgv 向 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 实际上是通过以下步骤实现的:

  1. 使用 redisAppendCommand 将命令发送到 Redis 服务器。

  2. 使用 redisGetReply 接收响应。

redisReply* redisCommand(redisContext *c, const char *format, ...);

3. redisGetReply

redisGetReply 是一个低级函数,用于从 Redis 服务器接收响应。它通常与 redisAppendCommand 配合使用,适用于需要高性能或异步操作的场景。

4.什么是 std::atomic<bool>

std::atomic<bool> 是一个模板类 std::atomic 的特化版本,专门用于布尔类型。它提供了以下特性:

  1. 原子性:对 std::atomic<bool> 的读写操作是原子性的,不会被其他线程中断。

  2. 线程安全:不需要额外的锁来保证线程安全。

  3. 高效:原子操作通常比互斥锁(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})

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值