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

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
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
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A(Rails App):::process -->|orders.input| B(Message Broker):::process
    B -->|orders.input| C(Order Handler):::process
    C -->|orders.status| B
    B -->|orders.status| D(AM Poller):::process
    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

测试结果如下:
| 方法 | 用户时间 | 系统时间 | 总时间 | 实际时间 |
| ---- | ---- | ---- | ---- | ---- |
| ruby: | 44.490000 | 0.150000 | 44.640000 | 44.665101 |
| C: | 2.570000 | 0.010000 | 2.580000 | 2.577188 |

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

测试结果如下:
| 方法 | 用户时间 | 系统时间 | 总时间 | 实际时间 |
| ---- | ---- | ---- | ---- | ---- |
| ruby: | 27.990000 | 0.060000 | 28.050000 | 28.059626 |
| C: | 6.490000 | 0.000000 | 6.490000 | 6.494582 |

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!'

2.4 嵌入 C 和 C++ 代码的注意事项

  • 数据类型转换 :Ruby Inline 会自动进行一些基本数据类型的转换,例如将整数转换为 Fixnum 实例,将 String 对象转换为 char*。但对于复杂的数据类型,如 C++ 的 std::string,没有特定的类型转换。
  • 性能问题 :虽然 C 和 C++ 代码通常比 Ruby 代码快,但在实际应用中,需要考虑编译和数据转换的开销。例如,在阶乘示例中,C 函数比 Ruby 函数快约二十倍;在多字节整数解码示例中,C 版本比 Ruby 版本快约四倍。
  • 错误处理 :C 和 C++ 代码可能会出现溢出等错误,而 Ruby 会自动处理一些错误情况。例如,在阶乘计算中,C 版本可能会因为整数溢出而返回错误结果,而 Ruby 会自动将 Fixnum 对象转换为 Bignum 对象。

2.5 更紧密的集成

如果需要更紧密地集成 Ruby 和 C++,可以考虑使用 Rice。Rice 是一个围绕 Ruby 的 C API 的 C++ 包装器,它可以让你更轻松地将 C++ 代码与 Ruby 的内部结构结合起来。

3. 总结

3.1 技术要点回顾

  • ActiveMessaging 连接消息队列 :通过 ActiveMessaging 插件,可以方便地在 Rails 应用程序中连接消息队列,实现异步消息传递。主要步骤包括安装插件、配置消息代理和队列、创建订单模型和控制器、添加处理器以及启动轮询器。
  • 嵌入 C 和 C++ 代码 :使用 ruby-inline gem 可以将 C 和 C++ 代码嵌入到 Ruby 程序中,提高性能。但需要注意数据类型转换、性能开销和错误处理等问题。

3.2 应用场景

  • 消息队列 :适用于需要异步处理任务的场景,如订单处理、数据同步等。通过消息队列,可以提高系统的性能和可扩展性。
  • 嵌入 C 和 C++ 代码 :适用于对性能要求较高的场景,如计算密集型任务、处理大量数据等。通过嵌入 C 和 C++ 代码,可以充分利用其高性能的特点。

3.3 未来展望

随着技术的不断发展,Ruby 和相关技术也在不断进步。例如,Ruby 1.9 版本通过将程序编译为字节码来提高性能。未来,可能会有更多的工具和方法来简化消息队列的使用和 C/C++ 代码的嵌入,进一步提高开发效率和系统性能。

3.4 操作步骤总结

3.4.1 ActiveMessaging 连接消息队列操作步骤
  1. 安装 ActiveMessaging 插件:
mschmidt> script/plugin install \
> http://activemessaging.googlecode.com/svn/trunk/plugins/\
> activemessaging
  1. 配置消息代理(broker.yml):
development:
  adapter: stomp
  login: ""
  passcode: ""
  host: localhost
  port: 61613
  reliable: true
  reconnectDelay: 5
  1. 配置消息队列(messaging.rb):
ActiveMessaging::Gateway.define do |s|
  s.destination :order, '/queue/orders.input'
  s.destination :order_status, '/queue/orders.status'
end
  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. 创建订单控制器:
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
  1. 添加处理器:
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. 启动轮询器:
mschmidt> ruby script/poller run
  1. 启动订单处理程序模拟器:
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'
)
3.4.2 嵌入 C 和 C++ 代码操作步骤
  1. 安装 ruby-inline gem:
$ gem install RubyInline
  1. 嵌入 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
  1. 嵌入 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!'

3.5 流程图总结

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    A(开始):::process --> B{选择技术}:::process
    B -->|ActiveMessaging| C(安装插件):::process
    B -->|嵌入 C/C++| D(安装 ruby-inline):::process
    C --> E(配置消息代理和队列):::process
    E --> F(创建订单模型和控制器):::process
    F --> G(添加处理器):::process
    G --> H(启动轮询器):::process
    H --> I(启动订单处理程序模拟器):::process
    D --> J(嵌入 C/C++ 代码):::process
    J --> K(测试性能):::process
    I --> L(结束):::process
    K --> L

通过以上的总结和操作步骤,我们可以更好地理解和应用 ActiveMessaging 连接消息队列和嵌入 C 和 C++ 代码的技术,在实际项目中提高系统的性能和可扩展性。

在Spring Boot项目中集成MQTT(Message Queuing Telemetry Transport)通信通常涉及以下几个步骤: 1. 添加依赖:首先,需要在项目的pom.xml或build.gradle文件中添加MQTT客户端库的依赖。对于Maven,可以使用`org.springframework.boot:spring-boot-starter-messaging`,它包含了对RabbitMQStomp的支持,包括MQTT的实现,比如`org.eclipse.paho:org.eclipse.paho.client.mqttv3`。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-messaging</artifactId> </dependency> ``` 2. 配置MQTT连接:在application.properties或application.yml文件中配置MQTT服务器的相关信息,如主机名、端口、用户名密码等。 ```properties spring.activemessaging.broker-url=mqtt://localhost:1883 spring.activemessaging.broker-username=your_username spring.activemessaging.broker-password=your_password ``` 3. 创建消息发送者接收者:创建一个实现了`org.springframework.messaging.MessageConverter`接口的类,用于序列化反序列化消息。然后创建消费者(Subscriber),订阅主题并处理接收到的消息。如果是生产者(Publisher),则创建发布消息到特定主题的方法。 4. 注解支持:如果使用Spring Messaging API,可以使用`@MessageMapping``@SendTo`注解映射MQTT的消息目的地。 5. 启动服务:启动Spring Boot应用后,MQTT客户端会自动连接到服务器,并按照配置开始监听发送消息。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.config.annotation.EnableMessageBroker; import org.springframework.messaging.simp.config.MessageBrokerRegistry; @Configuration @EnableMessageBroker public class MqttConfig { @Autowired public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/queue"); config.setApplicationDestinationPrefixes("/app"); } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值