28、可扩展微服务架构:QR 解析微服务实现

基于RabbitMQ的QR解析微服务实现

可扩展微服务架构: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) 方便数据传输和存储 增加了一定的开销

优化建议列表

  1. 错误处理优化 :细化错误类型,提供详细错误信息。
  2. 性能优化 :添加缓存机制,增加工作者数量,调整 RabbitMQ 配置。
  3. 安全性优化 :添加身份验证和授权,对敏感数据进行加密。

未来扩展流程图

graph LR;
    A[现有系统] --> B[功能扩展];
    A --> C[分布式部署];
    B --> B1[支持多种格式二维码解析];
    B --> B2[提供详细任务统计信息];
    C --> C1[多节点部署工作者];
    C --> C2[多节点部署服务器];

通过以上的分析和优化建议,我们可以进一步完善这个可扩展微服务架构,使其在实际应用中发挥更大的作用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值