17、利用ActiveMessaging连接消息队列及嵌入C和C++代码

利用ActiveMessaging连接消息队列及嵌入C和C++代码

1. 利用ActiveMessaging连接消息队列

1.1 问题描述

公司大部分基础设施基于异步消息传递,其中一个关键组件是中央订单处理程序。需要构建一个Rails应用程序,通过向中央订单处理程序发送消息来下订单。订单将存储在本地数据库中,应用程序将监听订单处理程序发出的订单状态消息,以便前端能提供良好的用户体验并跟踪订单的当前状态。

1.2 所需准备

  • 执行创建消息基础设施的所有安装步骤。
  • 从Rails应用程序的根目录安装ActiveMessaging插件:
mschmidt> script/plugin install \
> http://activemessaging.googlecode.com/svn/trunk/plugins/\
> activemessaging

1.3 解决方案

1.3.1 构建订单模型

在数据库中构建订单模型,代码如下:

create_table :orders do |t|
  t.column :customer, :string
  t.column :product, :string
  t.column :quantity, :int
  t.column :status, :string, :default => 'OPEN'
  t.timestamps
end

该模型存储客户姓名、订单状态以及所订购产品的名称和数量。

1.3.2 编辑配置文件
  • broker.yml :定义消息代理的所有连接参数,使用ActiveMQ和STOMP协议,配置如下:
development:
  adapter: stomp
  login: ""
  passcode: ""
  host: localhost
  port: 61613
  reliable: true
  reconnectDelay: 5
  • messaging.rb :定义要使用的所有消息队列的符号名称:
ActiveMessaging::Gateway.define do |s|
  s.destination :order, '/queue/orders.input'
  s.destination :order_status, '/queue/orders.status'
end
1.3.3 创建订单控制器
require 'activemessaging/processor'

class OrderController < ApplicationController
  include ActiveMessaging::MessageSender
  publishes_to :order

  def add
    order = Order.new(params[:order])
    if request.post? and order.save
      flash.now[:notice] = 'Order has been submitted.'
      publish :order, order.to_xml
      redirect_to :action => 'show_status', :id => order.id
    end
  end

  def show_status
    @order = Order.find(params[:id])
  end
end

该控制器将新订单的XML表示发送到订单处理程序。

1.3.4 添加处理器

使用以下命令生成处理器:

mschmidt> ruby script/generate processor OrderStatus

处理器代码如下:

require 'rexml/document'

class OrderStatusProcessor < ApplicationProcessor
  subscribes_to :order_status

  def on_message(message)
    doc = REXML::Document.new(message)
    order_id = doc.root.attributes['id']
    order_status = doc.root.text
    order = Order.find(order_id)
    order.status = order_status
    order.save
    logger.debug "Status of order #{order_id} is #{order_status}."
  end
end

该处理器监听订单状态队列,解析消息并更新数据库中的订单状态。

1.3.5 启动轮询守护进程
mschmidt> ruby script/poller run
1.3.6 订单处理程序模拟器
require 'stomp'
require 'rexml/document'

class OrderHandler
  attr_accessor :user, :password, :host, :port

  def initialize
    @user, @password = '', ''
    @host, @port = 'localhost', 61613
  end

  def handle_orders(in_queue, out_queue)
    connection = Stomp::Connection.open @user, @password, @host, @port
    connection.subscribe in_queue, { :ack => 'client' }
    puts "Waiting for messages in #{in_queue}."
    while true
      message = connection.receive
      body = message.body
      message_id = message.headers['message-id']
      puts "Got a message: #{body} (#{message_id})"
      order_status = get_order_status(body)
      options = { 'persistent' => 'false' }
      connection.send out_queue, order_status, options
      connection.ack message_id
    end
    connection.disconnect
  end

  private

  def get_order_status(body)
    doc = REXML::Document.new(body)
    order_id = doc.root.attributes['id']
    "<order-status id='#{order_id}'>SHIPPED</order-status>"
  end
end

order_handler = OrderHandler.new
order_handler.handle_orders(
  '/queue/orders.input',
  '/queue/orders.status'
)

该模拟器接收订单消息,处理订单并发送订单状态消息。

1.4 系统架构

graph LR
    A[Rails App] -->|orders.input| B[Message Broker]
    B -->|orders.input| C[Order Handler]
    C -->|orders.status| B
    B -->|orders.status| D[AM Poller]
    D -->|invoke action| A

2. 嵌入C和C++代码

2.1 问题描述

公司有很多遗留的C或C++代码,需要将其集成到新程序中,并且担心Ruby有时可能无法满足所有性能需求。需要学习如何将C/C++代码直接嵌入到Ruby程序中。

2.2 所需准备

安装ruby-inline gem:

$ gem install RubyInline

注意,ruby-inline在Microsoft Windows系统上运行效果不佳。

2.3 解决方案

2.3.1 阶乘示例
  • 纯Ruby实现
class FactorialTest
  def factorial(n)
    result = 1
    n.downto(2) { |x| result *= x }
    result
  end
end
  • C实现
require 'inline'

class FactorialTest
  inline(:C) do |builder|
    builder.c <<-CSOURCE
      long factorial_c(const int n) {
        long result = 1, i = n;
        while (i >= 2)
          result *= i--;
        return result;
      }
    CSOURCE
  end
end
2.3.2 性能测试
require 'benchmark'
label_width = 6
test = FactorialTest.new
Benchmark.bm(label_width) do |x|
  x.report('ruby: ') { 1.upto(10_000_000) { test.factorial(12) } }
  x.report('C: ') { 1.upto(10_000_000) { test.factorial_c(12) } }
end

测试结果表明,C函数比Ruby函数快约二十倍,但C版本存在溢出问题。

2.3.3 多字节整数解码示例
  • Ruby实现
class MultiByteIntegerReader
  def self.get_multibyte_integer(raw_data)
    multi_int = 0
    while raw_data[0][7] == 1
      multi_int = (multi_int << 7) | (raw_data.shift & 0x7f)
    end
    (multi_int << 7) | raw_data.shift
  end
end
  • C实现
require 'inline'

class MultiByteIntegerReader
  inline do |builder|
    builder.c_singleton <<-CSOURCE
      VALUE get_multibyte_integer_c(VALUE raw_data) {
        unsigned int multi_int = 0;
        unsigned int current_value = FIX2INT(rb_ary_shift(raw_data));
        while ((current_value & 0x80)) {
          multi_int = (multi_int << 7) | (current_value & 0x7f);
          current_value = FIX2INT(rb_ary_shift(raw_data));
        }
        return INT2FIX((multi_int << 7) | current_value);
      }
    CSOURCE
  end
end
2.3.4 性能测试
require 'benchmark'
label_width = 6
runs = 10_000_000
Benchmark.bm(label_width) do |x|
  x.report('ruby: ') do
    1.upto(runs) do
      MultiByteIntegerReader.get_multibyte_integer([0x81, 0x06])
    end
  end
  x.report('C: ') do
    1.upto(runs) do
      MultiByteIntegerReader.get_multibyte_integer_c([0x81, 0x06])
    end
  end
end

测试结果表明,C版本比Ruby版本快约四倍。

2.3.5 嵌入C++代码示例
require 'inline'

class MessagePrinter
  inline(:C) do |builder|
    builder.include '<iostream>'
    builder.include '<string>'
    builder.add_compile_flags '-x c++', '-lstdc++'
    builder.c <<-CSOURCE
      void shout_message(int i, char* message) {
        std::string upperCase = std::string(message);
        std::transform(
          upperCase.begin(), upperCase.end(),
          upperCase.begin(), ::toupper
        );
        while (i-- > 0)
          std::cout << upperCase << std::endl;
      }
    CSOURCE
  end
end

MessagePrinter.new.shout_message 3, 'Hello, world!'

运行该程序将输出:

HELLO, WORLD!
HELLO, WORLD!
HELLO, WORLD!

但使用Ruby Inline结合Ruby和C++时会变得复杂,若需要更紧密的集成,可以考虑使用Rice。

2.4 性能测试结果分析

测试内容 Ruby 版本耗时 C 版本耗时 速度对比
阶乘计算(10000000 次,n = 12) 44.665101s 2.577188s C 约快 20 倍
多字节整数解码(10000000 次,[0x81, 0x06]) 28.059626s 6.494582s C 约快 4 倍

从上述表格可以清晰地看到,C 和 C++ 代码在性能上相较于 Ruby 有明显的优势。在阶乘计算中,C 函数的执行速度比 Ruby 函数快了约二十倍;在多字节整数解码测试中,C 版本也比 Ruby 版本快了约四倍。不过,C 版本也存在一些问题,比如在阶乘计算中,由于 C 语言本身的特性,容易出现溢出错误,而 Ruby 会自动处理大整数,避免了此类问题。

2.5 代码执行流程分析

阶乘计算代码流程
graph LR
    A[开始] --> B[选择计算方式: Ruby 或 C]
    B -->|Ruby| C[初始化结果为 1]
    B -->|C| D[初始化结果为 1,i 为输入值]
    C --> E[从输入值向下到 2 循环]
    D --> F[判断 i 是否大于等于 2]
    E --> G[结果乘以当前循环值]
    F -->|是| H[结果乘以 i,i 减 1]
    F -->|否| I[返回结果]
    G --> E
    H --> F
    E -->|循环结束| I
多字节整数解码代码流程
graph LR
    A[开始] --> B[选择计算方式: Ruby 或 C]
    B -->|Ruby| C[初始化 multi_int 为 0]
    B -->|C| D[初始化 multi_int 为 0,获取数组首个元素并转换为整数]
    C --> E[判断数组首个元素最高位是否为 1]
    D --> F[判断当前值最高位是否为 1]
    E -->|是| G[multi_int 左移 7 位并与当前元素低 7 位按位或,移除数组首个元素]
    F -->|是| H[multi_int 左移 7 位并与当前值低 7 位按位或,获取数组下一个元素并转换为整数]
    E -->|否| I[multi_int 左移 7 位并与数组首个元素按位或,移除数组首个元素,返回结果]
    F -->|否| J[multi_int 左移 7 位并与当前值按位或,返回结果]
    G --> E
    H --> F

2.6 总结

通过上述内容,我们学习了如何利用 ActiveMessaging 连接消息队列以及将 C 和 C++ 代码嵌入到 Ruby 程序中。在消息队列连接方面,我们详细介绍了从构建订单模型、编辑配置文件、创建控制器、添加处理器到启动轮询守护进程以及使用订单处理程序模拟器的完整流程,通过系统架构图可以清晰地看到各个组件之间的交互关系。在嵌入 C 和 C++ 代码方面,我们通过阶乘计算、多字节整数解码等示例展示了如何使用 Ruby Inline 库将 C 和 C++ 代码集成到 Ruby 程序中,并通过性能测试对比了 Ruby 和 C/C++ 代码的执行效率。虽然 C 和 C++ 代码在性能上有明显优势,但也存在一些问题,如溢出错误等。同时,使用 Ruby Inline 结合 Ruby 和 C++ 时会变得复杂,对于更紧密的集成需求,可以考虑使用 Rice。这些技术可以帮助我们在需要提高性能或集成遗留代码时,灵活地选择合适的解决方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值