网站规划书500字,云呼叫中心系统 免费,南阳做网站多少电话,头条权重查询站长工具目录 一、Redis Pipeline#xff08;管道#xff09;概述优点使用场景工作原理Pipeline 的基本操作步骤C 示例#xff08;使用 [hiredis](https://github.com/redis/hiredis) 库#xff09; 二、Redis 事务概述事务的前提事务特征#xff08;ACID 分析#xff09;WATCH 命… 目录 一、Redis Pipeline管道概述优点使用场景工作原理Pipeline 的基本操作步骤C 示例使用 [hiredis](https://github.com/redis/hiredis) 库 二、Redis 事务概述事务的前提事务特征ACID 分析WATCH 命令示例 1事务实现 zpop示例 2事务实现加倍操作 三、Lua 脚本Lua 脚本的事务特性分析基本命令应用示例示例执行加倍操作 四、Redis 发布订阅Pub/Sub主要命令应用场景C 示例使用 [hiredis](https://github.com/redis/hiredis) 库发布者示例订阅者示例 5. Redis 异步方式移步[Redis异步实现解析](https://blog.csdn.net/weixin_43925427/article/details/142876618?fromshareblogdetailsharetypeblogdetailsharerId142876618sharereferPCsharesourceweixin_43925427sharefromfrom_link)六、Redis 的缺点七、总结Redis 的优势Redis 的局限性应用建议参考 一、Redis Pipeline管道
概述
Redis Pipeline管道 是一种客户端机制允许一次性发送多个Redis命令到服务器而无需等待每个命令的响应。通过管道可以显著减少网络延迟提高命令执行的吞吐量。
优点
减少网络延迟批量发送命令减少网络往返次数RTT。提高吞吐量一次性处理多个命令提升操作效率。
使用场景
批量插入或更新数据如批量存储用户信息或日志数据。批量读取数据如同时获取多个键的值。
工作原理 传统上每个Redis命令在客户端和服务器之间都需要一次网络往返通信。当需要执行大量命令时这种通信开销会成为性能瓶颈。使用Redis Pipeline可以将多个命令打包成一个网络请求一次性发送给服务器减少网络开销并更充分地利用服务器的处理能力。
Pipeline 的基本操作步骤
创建 Pipeline 对象在客户端中创建一个 Pipeline 对象用于存储要执行的多个命令。向 Pipeline 中添加命令使用 Pipeline 对象的方法如 pipeline.set(key, value)向其中添加要执行的Redis命令。可以添加任意多个命令。执行 Pipeline调用 Pipeline 对象的 execute() 方法将 Pipeline 中的所有命令一次性发送给Redis服务器执行。获取结果通过遍历 Pipeline 中的命令结果或使用 execute() 方法的返回值来获取执行结果。
C 示例使用 hiredis 库
以下示例展示了如何使用 hiredis 库在C中实现Redis Pipeline
#include hiredis/hiredis.h
#include iostream
#include vector
#include stringint main() {// 连接到Redis服务器redisContext *c redisConnect(127.0.0.1, 6379);if (c nullptr || c-err) {if (c) {std::cerr Connection error: c-errstr std::endl;redisFree(c);} else {std::cerr Cant allocate redis context std::endl;}return 1;}// 启动管道使用 MULTI 开启事务redisAppendCommand(c, MULTI);redisAppendCommand(c, SET key1 value1);redisAppendCommand(c, SET key2 value2);redisAppendCommand(c, SET key3 value3);redisAppendCommand(c, EXEC);// 读取响应for (int i 0; i 5; i) {redisReply *reply;if (redisGetReply(c, (void**)reply) REDIS_OK) {// 处理每个回复if (reply-type REDIS_REPLY_STATUS) {std::cout Reply i : reply-str std::endl;} else if (reply-type REDIS_REPLY_ARRAY) {std::cout Reply i : [;for (size_t j 0; j reply-elements; j) {if (reply-element[j]-str)std::cout reply-element[j]-str;elsestd::cout nil;if (j ! reply-elements -1) std::cout , ;}std::cout ] std::endl;} else {std::cout Reply i : (reply-str ? reply-str : ) std::endl;}freeReplyObject(reply);} else {std::cerr Failed to get reply std::endl;break;}}// 关闭连接redisFree(c);return 0;
}输出示例
Reply 0: OK
Reply 1: OK
Reply 2: OK
Reply 3: OK
Reply 4: [OK, OK, OK]解释
redisAppendCommand将多个命令包括事务命令 MULTI 和 EXEC添加到管道中。redisGetReply逐个获取命令的响应。处理回复根据回复类型处理并输出每个命令的响应。
注意在实际应用中可以根据需求添加更多命令到管道中以实现批量操作。 二、Redis 事务
概述
Redis 事务 允许客户端一次性执行一系列命令确保这些命令按顺序执行且没有其他客户端的干扰。事务通过以下命令实现
MULTI标记事务的开始。EXEC执行事务中的所有命令。DISCARD取消事务清空事务队列。WATCH监视一个或多个键如果这些键在事务执行前被修改事务将被中断。
事务的前提
在有并发连接的情况下不同连接异步执行命令可能会导致不可预期的冲突。Redis 是单线程的但在事务执行期间如果不加以控制仍可能出现数据不一致的问题。比如我们希望顺序执行命令1、2、3。但是如果Redis是请求回应模型若在命令1和命令2之间的空档期命令3插入执行那么最后的结果就会出错。
事务特征ACID 分析
ACID原子性、一致性、隔离性和持久性是关系型数据库管理系统确保事务正确执行的四个基本特性。以下分析Redis事务在ACID方面的支持情况 原子性Atomicity 部分支持Redis中的单个命令是原子性的。然而Redis事务通过 MULTI 和 EXEC 实现不支持回滚机制。如果在事务中执行多个命令其中一个命令失败之前的命令依然会执行无法回滚。 示例 MULTI
SET key1 value1
INCR key2
EXEC如果 INCR key2 对应的键类型不是整数INCR 命令会失败但 SET key1 value1 已经执行无法回滚。 一致性Consistency 部分支持Redis引擎本身不提供严格的一致性保证。例如在主从复制模式下当主节点出现故障时从节点可能无法立即更新导致数据的部分丢失。类型一致性 示例 SET count 1000
TYPE count // 返回 string
LPUSH count 2000 // 返回 (error) WRONGTYPE Operation against a key holding the wrong kind of value隔离性Isolation 支持Redis使用单线程模型一个客户端的命令在执行期间不会被其他客户端的命令中断因此天然具备隔离性。注意在多线程环境下临界资源仍需要加锁来确保数据一致性。 持久性Durability 部分支持Redis提供了RDB快照和AOFAppend-Only File两种持久化机制。特别是在AOF持久化策略为 appendfsyncalways 时Redis能够确保事务提交后数据被永久保存。实际情况在实际项目中通常不会将AOF配置为 always因为这会影响性能。
WATCH 命令
WATCH 命令用于监视一个或多个键并在事务执行期间检测这些键是否被修改。如果被监视的键在事务执行前被修改事务将被取消执行。这是一种乐观锁机制。
事务实现示例
示例 1事务实现 zpop
假设需要从一个有序集合中弹出最高分的成员并将其添加到另一个集合中这是一个需要原子性操作的场景。
#include hiredis/hiredis.h
#include iostream
#include stringint main() {// 连接到Redis服务器redisContext *c redisConnect(127.0.0.1, 6379);if (c nullptr || c-err) {if (c) {std::cerr Connection error: c-errstr std::endl;redisFree(c);} else {std::cerr Cant allocate redis context std::endl;}return 1;}// 监视zsetredisReply *watch_reply (redisReply*)redisCommand(c, WATCH myzset);freeReplyObject(watch_reply);// 获取zset的最高分成员redisReply *reply (redisReply*)redisCommand(c, ZREVRANGE myzset 0 0 WITHSCORES);if (reply-elements 0) {std::cout myzset is empty std::endl;freeReplyObject(reply);redisFree(c);return 0;}std::string member reply-element[0]-str;double score atof(reply-element[1]-str);freeReplyObject(reply);// 开始事务redisReply *multi_reply (redisReply*)redisCommand(c, MULTI);freeReplyObject(multi_reply);// 弹出成员redisAppendCommand(c, ZREM myzset %s, member.c_str());// 添加到另一个集合redisAppendCommand(c, ZADD anotherzset %f %s, score, member.c_str());// 执行事务redisReply *exec_reply;if (redisGetReply(c, (void**)exec_reply) REDIS_OK) {if (exec_reply-type REDIS_REPLY_ARRAY) {std::cout Transaction executed successfully std::endl;} else {std::cout Transaction failed std::endl;}freeReplyObject(exec_reply);} else {std::cerr EXEC failed std::endl;}redisFree(c);return 0;
}解释
WATCH监视 myzset如果在事务执行前 myzset 被其他客户端修改事务将被取消。ZREVRANGE获取 myzset 中分数最高的成员。MULTI开始事务。ZREM ZADD在事务中移除成员并添加到另一个集合。EXEC执行事务。如果在事务执行前 myzset 被修改EXEC 将返回 null表示事务被中断。
示例 2事务实现加倍操作
假设需要对某个键的值进行加倍操作这需要确保读取和写入的原子性。
#include hiredis/hiredis.h
#include iostream
#include stringint main() {// 连接到Redis服务器redisContext *c redisConnect(127.0.0.1, 6379);if (c nullptr || c-err) {if (c) {std::cerr Connection error: c-errstr std::endl;redisFree(c);} else {std::cerr Cant allocate redis context std::endl;}return 1;}std::string key counter;while (true) {// 监视键redisReply *watch_reply (redisReply*)redisCommand(c, WATCH %s, key.c_str());freeReplyObject(watch_reply);// 获取当前值redisReply *reply (redisReply*)redisCommand(c, GET %s, key.c_str());if (reply-type REDIS_REPLY_STRING) {int value atoi(reply-str);freeReplyObject(reply);// 开始事务redisReply *multi_reply (redisReply*)redisCommand(c, MULTI);freeReplyObject(multi_reply);// 设置新值redisAppendCommand(c, SET %s %d, key.c_str(), value * 2);// 执行事务redisReply *cmd_reply;if (redisGetReply(c, (void**)cmd_reply) REDIS_OK) {freeReplyObject(cmd_reply);redisReply *exec_reply;if (redisGetReply(c, (void**)exec_reply) REDIS_OK) {if (exec_reply-type REDIS_REPLY_ARRAY) {std::cout Counter doubled to value * 2 std::endl;freeReplyObject(exec_reply);break;} else {std::cout Transaction aborted std::endl;freeReplyObject(exec_reply);continue;}}}} else {freeReplyObject(reply);std::cerr Failed to get key std::endl;break;}}redisFree(c);return 0;
}解释
WATCH监视 counter 键确保在事务执行期间没有其他客户端修改该键。GET获取 counter 当前值。MULTI开始事务。SET在事务中设置新值为当前值的两倍。EXEC执行事务。如果 counter 在事务执行前被修改EXEC 将返回 null事务被取消循环继续尝试。 三、Lua 脚本
Lua 脚本的事务特性分析
Lua 脚本 是Redis提供的一种在服务器端执行复杂操作的机制。Lua脚本在Redis服务器上运行能够将多个命令组合成一个原子性操作减少网络往返次数。
ACID 特性分析 原子性Atomicity 部分满足Lua脚本通过一个命令将所有脚本中的语句一起执行但不具备回滚机制。如果脚本中某个命令执行失败之前成功的命令依然会生效。 一致性Consistency 部分不满足Lua脚本本身不具备严格的一致性。如果脚本执行过程中发生错误无法回滚已执行的命令可能导致数据不一致。 隔离性Isolation 满足Redis使用单线程模型Lua脚本作为单个数据包执行期间其他命令或脚本不会被打断确保隔离性。 持久性Durability 部分不满足只有在AOF持久化策略为 appendfsyncalways 时Lua脚本的修改才具备持久性。否则可能会在系统故障时丢失。
基本命令 EVAL执行一个Lua脚本。 EVAL script numkeys [key ...] [arg ...]EVALSHA根据脚本的SHA1哈希值执行脚本。 EVALSHA sha1 numkeys [key ...] [arg ...]SCRIPT LOAD将Lua脚本加载到Redis并返回其SHA1哈希值。 SCRIPT LOAD scriptSCRIPT EXISTS检查脚本缓存中是否存在指定的SHA1哈希值的Lua脚本。 SCRIPT EXISTS sha1 [sha1 ...]SCRIPT FLUSH清除所有脚本缓存。 SCRIPT FLUSHSCRIPT KILL强制停止正在运行的脚本如死循环。 SCRIPT KILL应用示例
示例执行加倍操作
测试使用
SET jack 100
EVAL local key KEYS[1]; local val redis.call(GET, key); if val then redis.call(SET, key, 2 * val); return 2 * val; end; return 0; 1 jack输出
(integer) 200实际使用 加载脚本到Redis SCRIPT LOAD local key KEYS[1]; local val redis.call(GET, key); if val then redis.call(SET, key, 2 * val); return 2 * val; end; return 0;输出 f76a2571acb0452ef1a0ba3b0bbd6c46a321cbe1执行缓存的脚本 EVALSHA f76a2571acb0452ef1a0ba3b0bbd6c46a321cbe1 1 jack输出 (integer) 200C 示例使用 EVAL 和 EVALSHA
以下示例展示了如何使用 hiredis 库在C中加载并执行Lua脚本
#include hiredis/hiredis.h
#include iostream
#include stringint main() {// 连接到Redis服务器redisContext *c redisConnect(127.0.0.1, 6379);if (c nullptr || c-err) {if (c) {std::cerr Connection error: c-errstr std::endl;redisFree(c);} else {std::cerr Cant allocate redis context std::endl;}return 1;}// Lua 脚本key3 key1 key2const char *script local v1 tonumber(redis.call(GET, KEYS[1])) \local v2 tonumber(redis.call(GET, KEYS[2])) \local sum v1 v2 \redis.call(SET, KEYS[3], sum) \return sum;// 加载脚本到RedisredisReply *reply (redisReply*)redisCommand(c, SCRIPT LOAD %s, script);std::string sha ;if (reply-type REDIS_REPLY_STRING) {sha reply-str;std::cout Script SHA1: sha std::endl;}freeReplyObject(reply);// 设置初始值redisCommand(c, SET key1 15);redisCommand(c, SET key2 25);// 使用 EVALSHA 执行脚本std::string evalsha_cmd EVALSHA sha 3 key1 key2 key3;reply (redisReply*)redisCommand(c, evalsha_cmd.c_str());if (reply-type REDIS_REPLY_INTEGER || reply-type REDIS_REPLY_STRING) {std::cout Sum via EVALSHA: (reply-type REDIS_REPLY_INTEGER ? std::to_string(reply-integer) : reply-str) std::endl;}freeReplyObject(reply);// 获取结果reply redisCommand(c, GET key3);if (reply-type REDIS_REPLY_STRING) {std::cout key3 reply-str std::endl;}freeReplyObject(reply);redisFree(c);return 0;
}输出示例
Script SHA1: f76a2571acb0452ef1a0ba3b0bbd6c46a321cbe1
Sum via EVALSHA: 40
key3 40解释
SCRIPT LOAD将Lua脚本加载到Redis并获取其SHA1哈希值。EVALSHA使用脚本的SHA1哈希值执行脚本实现 key3 key1 key2。GET获取执行结果 key3 的值。 四、Redis 发布订阅Pub/Sub
主要命令
为了支持消息的多播机制Redis的发布订阅Pub/Sub是一种消息传递模式允许消息发布者将消息发送到特定频道订阅者订阅这些频道以接收消息。消息不一定可达分布式消息队列stream的方式确保一定可达主要命令包括 PUBLISH发布消息到指定频道。 PUBLISH channel messageSUBSCRIBE订阅一个或多个频道。 SUBSCRIBE channel [channel ...]UNSUBSCRIBE取消订阅频道。 UNSUBSCRIBE channel [channel ...]PSUBSCRIBE按模式订阅一个或多个频道。 PSUBSCRIBE pattern [pattern ...]PUNSUBSCRIBE取消按模式订阅的频道。 PUNSUBSCRIBE pattern [pattern ...]示例命令
# 订阅频道
SUBSCRIBE news.game news.tech news.school# 订阅模式频道
PSUBSCRIBE news.*# 发布消息
PUBLISH news.game EDG wins S12 championship# 取消订阅频道
UNSUBSCRIBE news.game# 取消订阅模式频道
PUNSUBSCRIBE news.*客户端接收消息示例
当订阅者订阅了 news.game、news.tech 和 news.school 频道后发布者发布消息到 news.game 频道所有订阅该频道的客户端都会收到消息。
message news.game EDG wins S12 championship应用场景
发布订阅功能通常需要重新开启一个连接因为订阅连接会进入阻塞模式接收消息无法继续执行其他命令。因此实际项目中支持Pub/Sub时通常需要另开一条连接用于处理发布订阅。
常见应用场景
实时聊天系统用户通过频道发送和接收消息。实时通知系统事件通过频道通知相关服务。分布式系统的消息传递不同服务之间的通信。
C 示例使用 hiredis 库
发布者和订阅者通常需要在不同的连接上进行操作因为订阅连接会进入阻塞模式接收消息。
发布者示例
#include hiredis/hiredis.h
#include iostream
#include thread
#include chronoint main() {// 连接到Redis服务器redisContext *c redisConnect(127.0.0.1, 6379);if (c nullptr || c-err) {if (c) {std::cerr Connection error: c-errstr std::endl;redisFree(c);} else {std::cerr Cant allocate redis context std::endl;}return 1;}// 发布消息for (int i 1; i 5; i) {std::string message Hello std::to_string(i);redisReply *reply (redisReply*)redisCommand(c, PUBLISH mychannel %s, message.c_str());if (reply-type REDIS_REPLY_INTEGER) {std::cout Published message to reply-integer subscribers. std::endl;}freeReplyObject(reply);std::this_thread::sleep_for(std::chrono::seconds(1));}redisFree(c);return 0;
}输出示例
Published message to 1 subscribers.
Published message to 1 subscribers.
Published message to 1 subscribers.
Published message to 1 subscribers.
Published message to 1 subscribers.订阅者示例
#include hiredis/hiredis.h
#include iostream
#include threadint main() {// 连接到Redis服务器redisContext *c redisConnect(127.0.0.1, 6379);if (c nullptr || c-err) {if (c) {std::cerr Connection error: c-errstr std::endl;redisFree(c);} else {std::cerr Cant allocate redis context std::endl;}return 1;}// 订阅频道redisReply *reply (redisReply*)redisCommand(c, SUBSCRIBE mychannel);freeReplyObject(reply);// 持续接收消息while (redisGetReply(c, (void**)reply) REDIS_OK) {if (reply-type REDIS_REPLY_ARRAY reply-elements 3) {std::cout Received message from reply-element[1]-str : reply-element[2]-str std::endl;}freeReplyObject(reply);}redisFree(c);return 0;
}输出示例
Received message from mychannel: Hello 1
Received message from mychannel: Hello 2
Received message from mychannel: Hello 3
Received message from mychannel: Hello 4
Received message from mychannel: Hello 5注意订阅者和发布者通常需要在不同的进程或线程中运行因为订阅者连接会进入阻塞状态无法执行其他命令。 5. Redis 异步方式
移步Redis异步实现解析
六、Redis 的缺点
尽管Redis在许多场景下表现出色但也存在一些缺点和限制 内存限制 描述Redis是内存数据库所有数据存储在内存中。对于大数据量应用内存成本较高。影响高内存消耗限制了Redis在大规模数据存储上的应用。解决方案通过分片Sharding和使用更高效的数据结构来优化内存使用。 单线程模型 描述Redis的大部分命令是单线程执行的虽然通过IO多路复用实现高性能但在多核CPU上无法充分利用多线程优势。影响在CPU密集型操作或需要高并发处理时性能可能受限。解决方案通过集群部署分散负载到多个Redis实例。 数据持久化风险 描述虽然Redis支持RDB和AOF持久化但在极端情况下可能会丢失部分数据。影响数据的可靠性和持久性在某些应用场景下可能无法满足要求。解决方案合理配置持久化策略结合主从复制和高可用架构增强数据安全性。 缺乏复杂查询能力 描述Redis主要支持键值操作不适合需要复杂查询和关联的应用场景。影响对于需要复杂数据关系和查询的应用Redis无法直接满足需求。解决方案结合其他数据库如SQL数据库使用Redis用于缓存和快速访问。 有限的事务支持 描述Redis的事务不支持回滚机制无法像传统数据库那样处理复杂的事务逻辑。影响在需要严格事务控制的场景下Redis无法提供足够的支持。解决方案使用Lua脚本实现复杂的原子操作或结合其他数据库的事务功能。 数据结构局限 描述尽管Redis支持多种数据结构但在某些特定场景下可能不如专用数据库高效。影响对于特定类型的数据处理如图数据需要额外的实现工作。解决方案使用专用的数据库如图数据库来处理特定类型的数据。 安全性 描述Redis的默认配置安全性较低需额外配置才能满足生产环境的安全要求。影响未经配置的Redis实例可能容易受到未经授权的访问和攻击。解决方案配置密码认证、限制网络访问、启用TLS等安全措施。 缺乏多版本并发控制MVCC 描述Redis不支持复杂的并发控制机制可能导致竞争条件和数据一致性问题。影响在高并发环境下可能会出现数据冲突和不一致。解决方案使用 WATCH、Lua脚本等机制实现乐观锁或通过应用层控制并发。 七、总结
Redis 是一个高性能的内存数据库适用于多种场景如缓存、实时数据处理、消息队列等。通过深入了解其Pipeline管道、事务、Lua 脚本、发布订阅Pub/Sub和异步连接等功能开发者可以充分利用Redis的优势来优化应用性能。
Redis 的优势
高性能基于内存支持快速的数据读写。丰富的数据结构支持字符串、哈希、列表、集合、有序集合等多种数据类型。多样的功能支持事务、发布订阅、Lua脚本、持久化等功能。简单易用Redis命令直观易于学习和使用。
Redis 的局限性
内存消耗高对于大规模数据存储内存成本较高。事务支持有限缺乏回滚机制事务控制不如关系型数据库完善。安全性需额外配置默认配置不够安全需手动加强安全措施。单线程模型在某些高并发或CPU密集型场景下性能可能受限。
应用建议
在选择使用Redis时需综合考虑应用需求和Redis的特性 适合场景 高频访问的数据缓存。实时数据分析和处理。实现分布式锁和消息队列。会话存储和排行榜系统。 不适合场景 需要复杂事务控制的应用。大规模数据存储超过内存容量。需要复杂查询和数据关联的应用。
通过合理设计架构结合Redis的优势和其他数据库的功能可以构建高性能、可靠的应用系统。
参考
0voice · GitHub