可扩展微服务架构:QR 解析微服务实现
1. 库部分实现
1.1 添加导入和常量
首先,在
src/lib.rs
文件中添加必要的导入:
pub mod queue_actor;
use actix::{Message, SystemRunner};
use failure::Error;
use futures::Future;
use lapin::channel::{Channel, QueueDeclareOptions};
use lapin::client::{Client, ConnectionOptions};
use lapin::error::Error as LapinError;
use lapin::queue::Queue;
use lapin::types::FieldTable;
use serde_derive::{Deserialize, Serialize};
use tokio::net::TcpStream;
同时,定义两个队列的名称常量:
pub const REQUESTS: &str = "requests";
pub const RESPONSES: &str = "responses";
1.2 实现
spawn_client
函数
该函数用于创建一个连接到 RabbitMQ 的客户端并生成一个通道:
pub fn spawn_client(sys: &mut SystemRunner) -> Result<Channel<TcpStream>, Error> {
let addr = "127.0.0.1:5672".parse().unwrap();
let fut = TcpStream::connect(&addr)
.map_err(Error::from)
.and_then(|stream| {
let options = ConnectionOptions::default();
Client::connect(stream, options).from_err::<Error>()
});
let (client, heartbeat) = sys.block_on(fut)?;
actix::spawn(heartbeat.map_err(drop));
let channel = sys.block_on(client.create_channel())?;
Ok(channel)
}
其执行步骤如下:
1. 使用
TcpStream::connect
方法从常量地址创建一个
TcpStream
。
2. 使用
Client::connect
方法创建一个连接到 RabbitMQ 的客户端。
3. 使用
SystemRunner
的
block_on
方法立即执行
Future
,得到客户端和心跳实例。
4. 使用
actix::spawn
方法启动心跳任务。
5. 调用客户端的
create_channel
方法创建一个通道。
1.3 实现
ensure_queue
方法
该方法用于在 RabbitMQ 中创建一个队列:
pub fn ensure_queue(
chan: &Channel<TcpStream>,
name: &str,
) -> impl Future<Item = Queue, Error = LapinError> {
let opts = QueueDeclareOptions {
auto_delete: true,
..Default::default()
};
let table = FieldTable::new();
chan.queue_declare(name, opts, table)
}
这里将
auto_delete
字段设置为
true
,以便在应用程序结束时删除创建的队列,适用于测试目的。
1.4 定义请求和响应类型
请求类型
QrRequest
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct QrRequest {
pub image: Vec<u8>,
}
impl Message for QrRequest {
type Result = ();
}
响应类型
QrResponse
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum QrResponse {
Succeed(String),
Failed(String),
}
impl From<Result<String, Error>> for QrResponse {
fn from(res: Result<String, Error>) -> Self {
match res {
Ok(data) => QrResponse::Succeed(data),
Err(err) => QrResponse::Failed(err.to_string()),
}
}
}
impl Message for QrResponse {
type Result = ();
}
2. 实现工作者(Worker)
2.1 引入依赖
use actix::System;
use failure::{format_err, Error};
use image::GenericImageView;
use log::debug;
use queens_rock::Scanner;
use rabbit_actix::queue_actor::{QueueActor, QueueHandler, TaskId};
use rabbit_actix::{QrRequest, QrResponse, REQUESTS, RESPONSES};
2.2 实现
WorkerHandler
struct WokerHandler {}
impl QueueHandler for WokerHandler {
type Incoming = QrRequest;
type Outgoing = QrResponse;
fn incoming(&self) -> &str {
REQUESTS
}
fn outgoing(&self) -> &str {
RESPONSES
}
fn handle(
&self,
_: &TaskId,
incoming: Self::Incoming,
) -> Result<Option<Self::Outgoing>, Error> {
debug!("In: {:?}", incoming);
let outgoing = self.scan(&incoming.image).into();
debug!("Out: {:?}", outgoing);
Ok(Some(outgoing))
}
}
2.3 实现
scan
方法
impl WokerHandler {
fn scan(&self, data: &[u8]) -> Result<String, Error> {
let image = image::load_from_memory(data)?;
let luma = image.to_luma().into_vec();
let scanner = Scanner::new(
luma.as_ref(),
image.width() as usize,
image.height() as usize,
);
scanner
.scan()
.extract(0)
.ok_or_else(|| format_err!("can't extract"))
.and_then(|code| code.decode().map_err(|_| format_err!("can't decode")))
.and_then(|data| {
data.try_string()
.map_err(|_| format_err!("can't convert to a string"))
})
}
}
2.4 实现
main
函数
fn main() -> Result<(), Error> {
env_logger::init();
let mut sys = System::new("rabbit-actix-worker");
let _ = QueueActor::new(WokerHandler {}, &mut sys)?;
let _ = sys.run();
Ok(())
}
工作者工作流程
graph TD;
A[接收请求队列消息] --> B[解码为 QR 图像];
B --> C[转换为字符串];
C --> D[生成响应];
D --> E[发送到响应队列];
3. 实现服务器(Server)
3.1 引入依赖
use actix::{Addr, System};
use actix_web::dev::Payload;
use actix_web::error::MultipartError;
use actix_web::http::{self, header, StatusCode};
use actix_web::multipart::MultipartItem;
use actix_web::{
middleware, server, App, Error as WebError, HttpMessage, HttpRequest, HttpResponse,
};
use askama::Template;
use chrono::{DateTime, Utc};
use failure::Error;
use futures::{future, Future, Stream};
use indexmap::IndexMap;
use log::debug;
use rabbit_actix::queue_actor::{QueueActor, QueueHandler, SendMessage, TaskId};
use rabbit_actix::{QrRequest, QrResponse, REQUESTS, RESPONSES};
use std::fmt;
use std::sync::{Arc, Mutex};
3.2 定义共享状态
type SharedTasks = Arc<Mutex<IndexMap<String, Record>>>;
#[derive(Clone)]
struct Record {
task_id: TaskId,
timestamp: DateTime<Utc>,
status: Status,
}
#[derive(Clone)]
enum Status {
InProgress,
Done(QrResponse),
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Status::InProgress => write!(f, "in progress"),
Status::Done(resp) => match resp {
QrResponse::Succeed(data) => write!(f, "done: {}", data),
QrResponse::Failed(err) => write!(f, "failed: {}", err),
},
}
}
}
#[derive(Clone)]
struct State {
tasks: SharedTasks,
addr: Addr<QueueActor<ServerHandler>>,
}
3.3 实现
ServerHandler
struct ServerHandler {
tasks: SharedTasks,
}
impl QueueHandler for ServerHandler {
type Incoming = QrResponse;
type Outgoing = QrRequest;
fn incoming(&self) -> &str {
RESPONSES
}
fn outgoing(&self) -> &str {
REQUESTS
}
fn handle(
&self,
id: &TaskId,
incoming: Self::Incoming,
) -> Result<Option<Self::Outgoing>, Error> {
self.tasks.lock().unwrap().get_mut(id).map(move |rec| {
rec.status = Status::Done(incoming);
});
Ok(None)
}
}
3.4 实现请求处理程序
索引处理程序
fn index_handler(_: &HttpRequest<State>) -> HttpResponse {
HttpResponse::Ok().body("QR Parsing Microservice")
}
任务处理程序
fn tasks_handler(req: HttpRequest<State>) -> impl Future<Item = HttpResponse, Error = WebError> {
let tasks: Vec<_> = req
.state()
.tasks
.lock()
.unwrap()
.values()
.cloned()
.collect();
let tmpl = Tasks { tasks };
future::ok(HttpResponse::Ok().body(tmpl.render().unwrap()))
}
#[derive(Template)]
#[template(path = "tasks.html")]
struct Tasks {
tasks: Vec<Record>,
}
上传处理程序
fn upload_handler(req: HttpRequest<State>) -> impl Future<Item = HttpResponse, Error = WebError> {
req.multipart()
.map(handle_multipart_item)
.flatten()
.into_future()
.and_then(|(bytes, stream)| {
if let Some(bytes) = bytes {
Ok(bytes)
} else {
Err((MultipartError::Incomplete, stream))
}
})
.map_err(|(err, _)| WebError::from(err))
.and_then(move |image| {
debug!("Image: {:?}", image);
let request = QrRequest { image };
req.state()
.addr
.send(SendMessage(request))
.from_err()
.map(move |task_id| {
let record = Record {
task_id: task_id.clone(),
timestamp: Utc::now(),
status: Status::InProgress,
};
req.state().tasks.lock().unwrap().insert(task_id, record);
req
})
})
.map(|req| {
HttpResponse::build_from(&req)
.status(StatusCode::FOUND)
.header(header::LOCATION, "/tasks")
.finish()
})
}
pub fn handle_multipart_item(
item: MultipartItem<Payload>,
) -> Box<Stream<Item = Vec<u8>, Error = MultipartError>> {
match item {
MultipartItem::Field(field) => {
Box::new(field.concat2().map(|bytes| bytes.to_vec()).into_stream())
}
MultipartItem::Nested(mp) => Box::new(mp.map(handle_multipart_item).flatten()),
}
}
3.5 实现
main
函数
fn main() -> Result<(), Error> {
env_logger::init();
let mut sys = System::new("rabbit-actix-server");
let tasks = Arc::new(Mutex::new(IndexMap::new()));
let addr = QueueActor::new(
ServerHandler {
tasks: tasks.clone(),
},
&mut sys,
)?;
let state = State {
tasks: tasks.clone(),
addr,
};
server::new(move || {
App::with_state(state.clone())
.middleware(middleware::Logger::default())
.resource("/", |r| r.f(index_handler))
.resource("/task", |r| {
r.method(http::Method::POST).with_async(upload_handler);
})
.resource("/tasks", |r| r.method(http::Method::GET).with_async(tasks_handler))
})
.bind("127.0.0.1:8080")
.unwrap()
.start();
let _ = sys.run();
Ok(())
}
服务器工作流程
graph TD;
A[接收 HTTP 请求] --> B[处理上传文件];
B --> C[生成请求消息];
C --> D[发送到请求队列];
E[接收响应队列消息] --> F[更新任务状态];
4. 测试
4.1 启动 RabbitMQ 容器
确保已经启动了一个 RabbitMQ 实例的容器。
4.2 编译项目
使用
cargo build
命令编译所有部分。
4.3 启动服务器和工作者
启动服务器实例:
RUST_LOG=rabbit_actix_server=debug ./target/debug/rabbit-actix-server
启动工作者实例:
RUST_LOG=rabbit_actix_worker=debug ./target/debug/rabbit-actix-worker
4.4 查看队列信息
使用以下命令查看队列信息:
docker exec -it test-rabbit rabbitmqctl list_queues
使用以下命令查看所有连接的消费者:
docker exec -it test-rabbit rabbitmqctl list_consumers
4.5 测试应用
打开浏览器,访问
http://localhost:8080/tasks
,上传一个 QR 码图片,查看任务状态和解析结果。
5. 总结
通过以上步骤,我们实现了一个基于 RabbitMQ 的可扩展微服务架构,包括工作者和服务器。工作者负责消费请求队列中的消息并解析 QR 码,服务器负责处理 HTTP 请求和更新任务状态。这种架构具有良好的可扩展性和灵活性,可以根据需要添加更多的工作者来处理更多的请求。
同时,我们也实现了测试步骤,确保整个系统的正常运行。在实际应用中,可以根据具体需求对代码进行进一步的优化和扩展。
6. 技术点分析
6.1 异步编程与 Future
在整个实现过程中,大量使用了
Future
来处理异步操作。例如,在
spawn_client
函数中,
TcpStream::connect
和
Client::connect
方法都返回
Future
对象。通过
and_then
等组合器,可以将多个异步操作串联起来,形成一个异步操作链。
let fut = TcpStream::connect(&addr)
.map_err(Error::from)
.and_then(|stream| {
let options = ConnectionOptions::default();
Client::connect(stream, options).from_err::<Error>()
});
这种异步编程模型可以提高系统的并发性能,避免阻塞线程,使得系统能够同时处理多个请求。
6.2 消息队列 RabbitMQ
RabbitMQ 作为消息中间件,在整个架构中起到了关键作用。工作者和服务器通过 RabbitMQ 进行消息传递,实现了松耦合的通信。具体来说,服务器将请求消息发送到
requests
队列,工作者从该队列中消费消息并处理,然后将响应消息发送到
responses
队列,服务器再从该队列中接收响应消息。
6.3 状态管理
在服务器端,使用
Arc<Mutex<IndexMap<String, Record>>>
来管理共享状态。
Arc
用于实现多线程之间的共享所有权,
Mutex
用于保证线程安全,
IndexMap
用于存储任务的相关信息。
type SharedTasks = Arc<Mutex<IndexMap<String, Record>>>;
通过这种方式,服务器可以跟踪每个任务的状态,确保任务信息的一致性。
6.4 序列化与反序列化
使用
serde_derive
库实现了请求和响应类型的序列化和反序列化。
QrRequest
和
QrResponse
结构体都使用了
#[derive(Deserialize, Serialize)]
宏,使得它们可以方便地在网络中传输。
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct QrRequest {
pub image: Vec<u8>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum QrResponse {
Succeed(String),
Failed(String),
}
7. 优化建议
7.1 错误处理优化
在当前实现中,错误处理主要使用了
failure
库。可以进一步细化错误类型,提供更详细的错误信息,方便调试和维护。例如,在
scan
方法中,可以针对不同的错误情况返回不同的错误类型。
7.2 性能优化
- 缓存机制 :对于一些频繁使用的数据或计算结果,可以考虑添加缓存机制,减少重复计算。
- 并发优化 :可以增加工作者的数量,提高系统的并发处理能力。同时,合理调整 RabbitMQ 的配置,如队列的预取计数,以提高消息处理效率。
7.3 安全性优化
- 身份验证和授权 :在 RabbitMQ 中添加身份验证和授权机制,确保只有授权的用户和应用程序可以访问队列。
- 数据加密 :对于敏感数据,如 QR 码图片,在传输和存储过程中进行加密处理。
8. 总结与展望
8.1 总结
本文详细介绍了一个基于 RabbitMQ 的可扩展微服务架构的实现,包括库部分的实现、工作者和服务器的开发,以及测试步骤。通过使用异步编程、消息队列和状态管理等技术,实现了一个高效、灵活的 QR 解析微服务。
8.2 展望
- 功能扩展 :可以添加更多的功能,如支持多种格式的二维码解析、提供更详细的任务统计信息等。
- 分布式部署 :将工作者和服务器部署到多个节点上,实现分布式处理,进一步提高系统的可扩展性和容错性。
技术对比表格
| 技术点 | 优点 | 缺点 |
|---|---|---|
| 异步编程(Future) | 提高并发性能,避免阻塞线程 | 代码复杂度较高,调试困难 |
| RabbitMQ | 松耦合通信,支持多种消息模式 | 增加了系统的复杂度和运维成本 |
| 状态管理(Arc + Mutex) | 保证线程安全,方便跟踪任务状态 | 可能存在锁竞争问题,影响性能 |
| 序列化与反序列化(serde) | 方便数据传输和存储 | 增加了一定的开销 |
优化建议列表
- 错误处理优化 :细化错误类型,提供详细错误信息。
- 性能优化 :添加缓存机制,增加工作者数量,调整 RabbitMQ 配置。
- 安全性优化 :添加身份验证和授权,对敏感数据进行加密。
未来扩展流程图
graph LR;
A[现有系统] --> B[功能扩展];
A --> C[分布式部署];
B --> B1[支持多种格式二维码解析];
B --> B2[提供详细任务统计信息];
C --> C1[多节点部署工作者];
C --> C2[多节点部署服务器];
通过以上的分析和优化建议,我们可以进一步完善这个可扩展微服务架构,使其在实际应用中发挥更大的作用。
基于RabbitMQ的QR解析微服务实现
超级会员免费看
712

被折叠的 条评论
为什么被折叠?



