背景
最近在搞量化,其中一个刚需就是:策略触发之后给我的个人微信发消息。发私人消息这个功能(后文统一用 「Msg Server」 称呼),我用一种取巧的方式实现了,但是发一条消息需要 5~8s 的时间,还不能优化。这就带来了一个很大的问题:
量化交易里,时间就是金钱,必须争分夺秒。但是策略都是用 Python 写的,基本上都是同步操作,如果因为一个发消息的功能,耽误这么长时间,那黄花菜都凉了。
当然也有一些 Python 的异步调用方法,但是一是比较麻烦,而是我写的那个发私人消息的服务不支持并发。在折腾了一番后,终于找到了一个性价比比较高的解决方案:
阿里云的 云消息队列 RabbitMQ 版,选它的原因很简单,太便宜了,一个队列大概 ¥0.2/月,这不随便用嘛。
RabbitMQ 服务实现
原理简介
原理很简单,Msg Server 我也是用阿里云的「函数计算 FC」实现的,现在把它的触发方式仅设为「消息队列 RabbitMQ 版」即可。
这样我们在发消息的时候,只需要往 MQ 里发一条消息就结束了。调用 Msg Server 的工作,就交给 MQ 慢慢串行调用就可以了。而往 MQ 里发一条消息的操作,也就需要 100~300ms,这个速度对于我来说,可以接受了。
调用 RabbitMQ
所以整个调用流程应该是:
HTTP Request => Rabbit MQ Server => Msg Server
MQ 调用 Msg 这一步就不展开了,这个是吃饭的东西,不方便多说,但是 HTTP 调用 MQ 这步可以拿出来说说。
官方文档 只给出了 Java 的调用方式,但是安装环境太费劲了,还是 Python 或者 Nodejs 方便。于是尝试问了下 ChatGPT,果然被我试出来了,Python 用第三方库 pika 即可,这里就不放上来了。放上我用的 Nodejs 版本:
const amqp = require('amqplib');
async function sendMessage(options) {
const { username, password, host, vhost, queue, routeKey, exchange, message } = options;
try {
const url = `amqp://${username}:${password}@${host}/${vhost}`;
const connection = await amqp.connect(url);
const channel = await connection.createChannel();
await channel.assertQueue(queue, { durable: true });
channel.publish(exchange, routeKey, Buffer.from(message));
await channel.close();
await connection.close();
return `[Success] Sent '${message}' to exchange ${exchange} with routing key '${routeKey}'`;
} catch (error) {
console.error('Error:', error);
return `[Fail] Sent '${message}' to exchange ${exchange} with routing key '${routeKey}'`;
}
}
// USAGE:
// {
// "vhost": "xxx",
// "queue": "xxx",
// "exchange": "xxx",
// "routeKey": "1",
// "message": "xxx"
// }
可以看到需要传入的参数还是很多的,一眼看上去有点懵。其实不用怕,跟着 官方文档 一步一步走,自然就知道这些参数是什么,应该填什么了。
提示:
关于
routeKey
参数,在配置 exchange 和 queue 绑定的时候,如果没有手动填Routing Key
,那么默认值是1
,我在这里小卡了一下,特此提醒。
封装成 Server
接下来用阿里云函数计算 FC 封装成 API 就可以了,代码如下:
'use strict';
const amqp = require('amqplib');
const { USERNAME, PASSWORD, HOST } = process.env;
exports.handler = async (event, context, callback) => {
try {
const eventObj = JSON.parse(event);
const payload = getPayload(eventObj);
console.log(payload);
const { vhost, queue, routeKey, exchange, message } = payload;
const msg = await sendMessage({
username: USERNAME,
password: PASSWORD,
host: HOST,
vhost,
queue,
routeKey,
exchange,
message,
});
callback(null, {
statusCode: 200,
body: msg,
});
} catch (e) {
console.log(e);
callback(e, null);
}
};
async function sendMessage(options) {
// code
}
const getPayload = (eventObj) => {
// http
if (Object.prototype.hasOwnProperty.call(eventObj, 'requestContext')) {
if (Object.prototype.hasOwnProperty.call(eventObj, 'body')) {
let objStr = eventObj.body;
if (eventObj.isBase64Encoded) {
objStr = Buffer.from(objStr, 'base64').toString('utf-8');
}
return JSON.parse(objStr);
}
}
// normal
return eventObj;
};
注意:
username
、password
、host
这几个固定的值,写死在环境变量里就可以了,既方便又安全;- 这里封装的
getPayload
可以保证无论是 HTTP 调用还是其它云产品的调用都能够正常处理,算是个小技巧。
总结
这次的内容就这么多,最有价值的实际上是那个给个人微信发消息的实现,想知道得话可得交学费,哈哈。
经过这次的折腾,笔者对于阿里云的 Serverless 服务运用的已经非常熟练的,函数计算 FC、云消息队列 RabbitMQ 版、对象存储 OSS、云工作流这几个产品,可以玩出特别多的花活。
比如每天定时给我发送股票打新信息,每天自动下载可转债数据等等。
最关键是省钱啊,每个月往多了用也用不了 10 块钱,真香~~