利用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。这些技术可以帮助我们在需要提高性能或集成遗留代码时,灵活地选择合适的解决方案。
超级会员免费看
13

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



