Redis协议详解及其异步应用
目录
- 一、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?fromshare=blogdetail&sharetype=blogdetail&sharerId=142876618&sharerefer=PC&sharesource=weixin_43925427&sharefrom=from_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 <string>int 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 << "Can't 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(快照)和AOF(Append-Only File)两种持久化机制。特别是在AOF持久化策略为
appendfsync=always时,Redis能够确保事务提交后数据被永久保存。 - 实际情况:在实际项目中,通常不会将AOF配置为
always,因为这会影响性能。
- 部分支持:Redis提供了RDB(快照)和AOF(Append-Only File)两种持久化机制。特别是在AOF持久化策略为
WATCH 命令
WATCH 命令用于监视一个或多个键,并在事务执行期间检测这些键是否被修改。如果被监视的键在事务执行前被修改,事务将被取消执行。这是一种乐观锁机制。
事务实现示例
示例 1:事务实现 zpop
假设需要从一个有序集合中弹出最高分的成员,并将其添加到另一个集合中,这是一个需要原子性操作的场景。
#include <hiredis/hiredis.h>
#include <iostream>
#include <string>int 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 << "Can't 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 <string>int 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 << "Can't 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持久化策略为
appendfsync=always时,Lua脚本的修改才具备持久性。否则,可能会在系统故障时丢失。
- 部分不满足:只有在AOF持久化策略为
基本命令
-
EVAL:执行一个Lua脚本。
EVAL script numkeys [key ...] [arg ...] -
EVALSHA:根据脚本的SHA1哈希值执行脚本。
EVALSHA sha1 numkeys [key ...] [arg ...] -
SCRIPT LOAD:将Lua脚本加载到Redis并返回其SHA1哈希值。
SCRIPT LOAD script -
SCRIPT EXISTS:检查脚本缓存中是否存在指定的SHA1哈希值的Lua脚本。
SCRIPT EXISTS sha1 [sha1 ...] -
SCRIPT FLUSH:清除所有脚本缓存。
SCRIPT FLUSH -
SCRIPT 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) 200
C++ 示例(使用 EVAL 和 EVALSHA)
以下示例展示了如何使用 hiredis 库在C++中加载并执行Lua脚本:
#include <hiredis/hiredis.h>
#include <iostream>
#include <string>int 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 << "Can't 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 message -
SUBSCRIBE:订阅一个或多个频道。
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 <chrono>int 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 << "Can't 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 <thread>int 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 << "Can't 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
相关文章:
Redis协议详解及其异步应用
目录 一、Redis Pipeline(管道)概述优点使用场景工作原理Pipeline 的基本操作步骤C 示例(使用 [hiredis](https://github.com/redis/hiredis) 库) 二、Redis 事务概述事务的前提事务特征(ACID 分析)WATCH 命…...
LeetCode213:打家劫舍II
题目链接:213. 打家劫舍 II - 力扣(LeetCode) 代码如下 class Solution { public:int rob(vector<int>& nums) {if(nums.size() 0) return 0;if(nums.size() 1) return nums[0];if(nums.size() 2) return max(nums[0…...
linux一二三章那些是重点呢
第一章 静态库动态库的区别 什么是库 库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接 拿来用的变量、函数或类。 如何制作 静态动态库 静态库: GCC 进行链接时,会把静态库中代码打…...
C语言中的程序入口:超越main函数的探索
在C语言中,尽管main函数是标准程序的默认入口点,但借助编译器特性和链接器选项,我们可以指定其他函数作为程序的入口。GCC编译器通过-e选项,允许我们将任何符合签名的函数作为程序的入口。这一特性可以用于特定的实验需求、特定系…...
《面试之MQ篇》
《面试之MQ篇》 1. 为什么要使用MQ 首先,面试官问的第一个问题或者说是逼问的一个问题:“为什么要使用MQ”其实面试官问这个问题就是想考察你MQ的特性,这个时候呢,我们必须要答出三点:解耦、异步、削峰。 1. 解耦 1. 传统系统…...
Git 分支操作-开发规范
一、背景 在实际开发中,一般在主分支的基础上单独创建一个新的分支进行开发,最后合并到master分支,而不是直接在master分支进行开发。 二、新建分支 1、初始状态,local为本地分支,remote为远程分支 2、单击 “Remot…...
JSONArray根据指定字段去重
JSONArray dataList new JSONArray();这儿省略dataList 加数据的过程 dataList new JSONArray(dataList.stream().distinct().collect(Collectors.toList())); Set<String> timestamps new HashSet<>();根据时间字段去重 dataList dataList.stream().map(obj -…...
线程有哪几种状态? 分别说明从一种状态到另一种状态转变有哪些方式?
在 Java 中,线程的生命周期管理通过不同的状态来跟踪。一个线程在其生命周期中可以处于多种状态,不同的状态之间会通过特定的事件发生转变。以下是 Java 线程的几种状态及其之间的转移方式: 1. 线程的状态 1.1 NEW(新建状态&…...
自注意力机制self-attention中的KV 缓存
在自注意力机制中,KV 缓存(Key-Value Caching)主要用于加速模型在推理阶段的计算,尤其是在处理长序列或者生成任务(如文本生成)时,这种缓存机制可以显著提高效率。 1. KV 缓存的背景 在 Trans…...
前端库--nanoid(轻量级的uuid)
文章目录 定义:生成方式:现实使用:NanoID 只有 108 个字节那么大NanoID更安全NanoID它既快速又紧凑 使用步骤1.安装nanoid包2.引入使用3.使用4.自定义字母 定义: UUID 是 通用唯一识别码(Universally Unique Identifierÿ…...
计算机基础-什么是网络端口?
网络端口可以想象成一个大型公寓楼的邮箱。每个公寓楼(这里指的是一个计算机或服务器)有很多个邮箱(即网络端口),每个邮箱都有一个独一无二的编号(端口号)。当一封信(网络数据包&…...
力扣动态规划基础版(斐波那契类型)
70. 爬楼梯https://leetcode.cn/problems/climbing-stairs/ 70.爬楼梯 方法一 动态规划 考虑转移方程和边界条件: f(x) f(x -1) f(x - 2);f(1) 1;f&…...
Java重修笔记 InetAddress 类和 Socket 类
InetAddress 类相关方法 1. 获取本机 InetAddress 对象:getLocalHost public static InetAddress getLocalHost() throws UnknownHostException 返回值:本地主机的名字和地址 异常:UnknownHostException - 如果本地主机名无法解析成地址 2…...
秋招突击——8/6——万得数据面试总结
文章目录 引言正文面经整理一1、讲一下java的多态,重载,重写的概念,区别2、说一下Java的数组,链表的结构,优缺点3、创建java线程的方式有哪些,具体说说4、创建线程池呢、每个参数的意义5、通过那几种方式保…...
STM32定时器
目录 STM32定时器概述 STM32基本定时器 基本定时器的功能 STM32基本定时器的寄存器 STM32通用定时器 STM32定时器HAL库函数 STM32定时器概述 从本质上讲定时器就是“数字电路”课程中学过的计数器(Counter),它像“闹钟”一样忠实地为处…...
第七课 Vue中的v-for遍历指令
Vue中的v-for遍历指令 v-for用于对象遍历(数组/JSON),渲染数据列表 基础示例: <div id"app"><ul><li v-for"val in arr">{{val}}</li></ul></div><script>new V…...
【NTN 卫星通信】卫星通信的专利
1 概述 好久没有看书了,最近买了本讲低轨卫星专利的书,也可以说是一个分析报告。推荐给喜欢的朋友。 2 书籍截图 图1 封面 图2 波音低轨卫星专利演进 图3 低轨卫星关键技术专利发展阶段 图4 第一页 3 参考文献 产业专利分析报告–低轨卫星通信技术...
vue3 element table 插槽外的数据更新,插槽内的数据未更新。
在使用element table组件时候,有时候需要对table内部的header插槽进行单独的列的数据操作,比如在列头增加一个筛选功能,对指定范围的值进行一个筛选,需要对input的值进行v-model的绑定,对绑定的值进行清空时候…...
飞凌嵌入式FET527N-C核心板已适配OpenHarmony4.1
近期,飞凌嵌入式为FET527N-C核心板适配了OpenHarmony4.1系统——进一步提升了核心板的兼容性、稳定性和安全性。 OpenHarmony4.1在应用开发方面展现了全新的开放能力,以更加清晰的逻辑和场景化视角提供给开发者丰富的API接口,应用开发能力得…...
CVPR 2024最佳论文候选-pixelSplat论文解读
目录 一、概述 二、相关工作 1、单场景下的视角合成 2、基于先验的三维重建和视图合成 3、多视图几何测量 三、3DGS的缺点 1、容易陷入最小值 2、需要大量输入图像 3、尺度模糊性 四、pixelSplat 1、解决尺度模糊性(深度信息生成) 2、编码器…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
