Dapr Pub/Sub 集成 RabbitMQ 、Golang、Java、DotNet Core
前置条件:
《Dapr运用》
《Dapr 运用之 Java gRPC 调用篇》
《Dapr 运用之集成 Asp.Net Core Grpc 调用篇》
搭建 RabbitMQ
Docker 搭建 RabbitMQ 服务
docker run -d --hostname my-rabbit --name some-rabbit -p 5672:5672 -p 15672:15672 rabbitmq:3-management
创建 rabbiqmq.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: messagebus
spec:
type: pubsub.rabbitmq
metadata:
- name: host
value: "amqp://localhost:5672" # Required. Example: "rabbitmq.default.svc.cluster.local:5672"
- name: consumerID
value: "61415901178272324029" # Required. Any unique ID. Example: "myConsumerID"
- name: durable
value: "true" # Optional. Default: "false"
- name: deletedWhenUnused
value: "false" # Optional. Default: "false"
- name: autoAck
value: "false" # Optional. Default: "false"
- name: deliveryMode
value: "2" # Optional. Default: "0". Values between 0 - 2.
- name: requeueInFailure
value: "true" # Optional. Default: "false".
改造 StorageService.Api
目的:把 StorageService 从 Grpc 客户端改造为 Grpc 服务端,并 Sub Storage.Reduce 主题,完成减库存操作。
删除 Storage 中无用的代码 StorageController.cs
修改 Program.cs 中的 CreateHostBuilder 代码为
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Loopback, 5003, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
});
webBuilder.UseStartup<Startup>();
});
}
添加 DaprClientService
public sealed class DaprClientService : DaprClient.DaprClientBase
{
public override Task<GetTopicSubscriptionsEnvelope> GetTopicSubscriptions(Empty request, ServerCallContext context)
{
var topicSubscriptionsEnvelope = new GetTopicSubscriptionsEnvelope();
topicSubscriptionsEnvelope.Topics.Add("Storage.Reduce");
return Task.FromResult(topicSubscriptionsEnvelope);
}
}
Dapr 运行时将调用此方法获取 StorageServcie 关注的主题列表。
修改 Startup.cs
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services">Services.</param>
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddDbContextPool<StorageContext>(options => { options.UseMySql(Configuration.GetConnectionString("MysqlConnection")); });
}
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app">app.</param>
/// <param name="env">env.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseRouting(); app.UseEndpoints(endpoints =>
{
endpoints.MapSubscribeHandler();
endpoints.MapGrpcService<DaprClientService>();
});
}
复制 rabbimq.yaml 文件到 components 文件夹中,删除 redis_messagebus.yaml 文件
启动 StorageService 服务
dapr run --app-id storageService --app-port 5003 --protocol grpc dotnet run
使用 Java 开发一个 Order 服务端,Order 服务提供的功能为
- 下单
- 查看订单详情
- 获取订单列表
在当前上下文中着重处理的是下单功能,以及下单成功后 Java 服务端将发布一个事件到 Storage.Reduce 主题,即减少库存。
创建 CreateOrder.proto 文件
syntax = "proto3"; package daprexamples; option java_outer_classname = "CreateOrderProtos";
option java_package = "generate.protos"; service OrderService {
rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
rpc RetrieveOrder(RetrieveOrderRequest) returns(RetrieveOrderResponse);
rpc GetOrderList(GetOrderListRequest) returns(GetOrderListResponse);
} message CreateOrderRequest {
string ProductID = 1; //Product ID
int32 Amount=2; //Product Amount
string CustomerID=3; //Customer ID
} message CreateOrderResponse {
bool Succeed = 1; //Create Order Result,true:success,false:fail
} message RetrieveOrderRequest{
string OrderID=1;
} message RetrieveOrderResponse{
Order Order=1;
} message GetOrderListRequest{
string CustomerID=1;
} message GetOrderListResponse{
repeated Order Orders=1;
} message Order{
string ID=1;
string ProductID=2;
int32 Amount=3;
string CustomerID=4;
}
使用 protoc 生成 Java 代码
protoc -I=C:\Users\JR\DaprDemos\java\examples\src\main\protos\examples --java_out=C:\Users\JR\DaprDemos\java\examples\src\main\java C:\Users\JR\DaprDemos\java\examples\src\main\protos\examples\CreateOrder.proto
引用 MyBatis 做为 Mapper 工具
修改 HelloWorldService.java 文件,提取 GrpcHelloWorldDaprService.java 到单独的包中,在此文件中添加
createOrder()、getOrderList()、retrieveOrder()三个函数的实现复制 rabbimq.yaml 文件到 components 文件夹中,删除原有 redis_messagebus.yaml 文件
启动 OrderService 服务
dapr run --app-id OrderService --app-port 5000 --protocol grpc -- mvn exec:java -pl=examples -Dexec.mainClass=server.HelloWorldService -Dexec.args="-p 5000"
创建 Golang Grpc 客户端,该客户端需要完成创建订单 Grpc 调用,订单创建成功发布扣除库存事件
引用 CreateOrder.proto 文件,并生成 CreateOrder.pb.go 文件
如未安装 protoc-gen-gogo ,通过一下命令获取并安装
go get github.com/gogo/protobuf/gogoproto
安装 protoc-gen-gogo
go install github.com/gogo/protobuf/gogoproto
根据 proto 文件生成代码
protoc -I C:\Users\JR\DaprDemos\golang\shoppingCartForJava\protos\daprexamples C:\Users\JR\DaprDemos\golang\shoppingCartForJava\protos\daprexamples\CreateOrder.proto --go_out=plugins=grpc:C:\Users\JR\DaprDemos\golang\shoppingCartForJava\protos\daprexamples\
客户端代码,创建订单
... response, err := client.InvokeService(context.Background(), &pb.InvokeServiceEnvelope{
Id: "OrderService",
Data: createOrderRequestData,
Method: "createOrder",
})
if err != nil {
fmt.Println(err)
return
} ...
添加 DataToPublish.proto 文件,此文件作为事件发布数据结构
syntax = "proto3"; package daprexamples; option java_outer_classname = "DataToPublishProtos";
option java_package = "generate.protos"; message StorageReduceData {
string ProductID = 1;
int32 Amount=2;
}
生成 DataToPublish 代码
protoc -I C:\Users\JR\DaprDemos\golang\shoppingCartForJava\protos\daprexamples C:\Users\JR\DaprDemos\golang\shoppingCartForJava\protos\daprexamples\DataToPublish.proto --go_out=plugins=grpc:C:\Users\JR\DaprDemos\golang\shoppingCartForJava\protos\daprexamples\
修改 main.go 代码,根据 createOrder 结果判断是否要发布信息到消息队列
... createOrderResponse := &daprexamples.CreateOrderResponse{} if err := proto.Unmarshal(response.Data.Value, createOrderResponse); err != nil {
fmt.Println(err)
return
}
fmt.Println(createOrderResponse.Succeed) if !createOrderResponse.Succeed {
//下单失败
return
} storageReduceData := &daprexamples.StorageReduceData{
ProductID: createOrderRequest.ProductID,
Amount: createOrderRequest.Amount,
}
storageReduceDataData, err := jsoniter.ConfigFastest.Marshal(storageReduceData) //ptypes.MarshalAny(storageReduceData)
if err != nil {
fmt.Println(err)
return
} _, err = client.PublishEvent(context.Background(), &pb.PublishEventEnvelope{
Topic: "Storage.Reduce",
Data: &any.Any{Value: storageReduceDataData},
}) fmt.Println(storageReduceDataData) if err != nil {
fmt.Println(err)
} else {
fmt.Println("Published message!")
}
...
注意: 发送数据前,使用 jsoniter 转换数据为 json 字符串,原因是如果直接传输 Grpc 流,当前版本(0.3.x) Dapr runtime 打包数据时使用 Json 打包,解包使用 String ,导致数据不一致。
复制 rabbimq.yaml 文件到 components 文件夹,删除原有 redis_messagebus.yaml 文件
启动 golang Grpc 客户端
dapr run --app-id client go run main.go
输出
== APP == true
== APP == Published message!
RabbitMQ
在浏览器中输入
http://localhost:15672/,账号和密码均为 guest查看 Connections ,有3个连接
- 这个3个连接来自配置了 messagebus.yaml 组件的三个服务
查看 Exchanges
Name Type Features Message rate in Message rate out
(AMQP default) direct D
Storage.Reduce fanout D
amq.direct direct D
amq.fanout fanout D
...
着重看 Storage.Reduce ,可以看出 Dapr 运行时创建了一个 fanout 类型的 Exchange ,这表明该 Exhange 中的数据是广播的。
查看 Queues
Dapr 运行时创建了 storageService-Storage.Reduce ,该 Queue 绑定了 Storage.Reduce Exchange ,所以可以收到 Storage.Reduce 的广播数据。
DotNet Core StorageService.Api 改造以完成 Sub 事件
打开 DaprClientService.cs 文件,更改内容为
public sealed class DaprClientService : DaprClient.DaprClientBase
{
private readonly StorageContext _storageContext; public DaprClientService(StorageContext storageContext)
{
_storageContext = storageContext;
} public override Task<GetTopicSubscriptionsEnvelope> GetTopicSubscriptions(Empty request, ServerCallContext context)
{
var topicSubscriptionsEnvelope = new GetTopicSubscriptionsEnvelope();
topicSubscriptionsEnvelope.Topics.Add("Storage.Reduce");
return Task.FromResult(topicSubscriptionsEnvelope);
} public override async Task<Empty> OnTopicEvent(CloudEventEnvelope request, ServerCallContext context)
{
if (request.Topic.Equals("Storage.Reduce"))
{
StorageReduceData storageReduceData = StorageReduceData.Parser.ParseJson(request.Data.Value.ToStringUtf8());
Console.WriteLine("ProductID:" + storageReduceData.ProductID);
Console.WriteLine("Amount:" + storageReduceData.Amount);
await HandlerStorageReduce(storageReduceData);
}
return new Empty();
} private async Task HandlerStorageReduce(StorageReduceData storageReduceData)
{
Guid productID = Guid.Parse(storageReduceData.ProductID);
Storage storageFromDb = await _storageContext.Storage.FirstOrDefaultAsync(q => q.ProductID.Equals(productID));
if (storageFromDb == null)
{
return;
} if (storageFromDb.Amount < storageReduceData.Amount)
{
return;
} storageFromDb.Amount -= storageReduceData.Amount;
Console.WriteLine(storageFromDb.Amount);
await _storageContext.SaveChangesAsync();
}
说明
- 添加
GetTopicSubscriptions()将完成对主题的关注- 当应用停止时,RabbitMQ 中的 Queue 自动删除
- 添加
OnTopicEvent()重写,此方法将完成对 Sub 主题的事件处理
HandlerStorageReduce用于减少库存
- 添加
启动 DotNet Core StorageService.Api Grpc 服务,启动 Java OrderService Grpc 服务,启动 Go Grpc 客户端
DotNet Core
dapr run --app-id storageService --app-port 5003 --protocol grpc dotnet run
Java
dapr run --app-id OrderService --app-port 5000 --protocol grpc -- mvn exec:java -pl=examples -Dexec.mainClass=server.HelloWorldService -Dexec.args="-p 5000"
go
dapr run --app-id client go run main.go
go grpc 输出为
== APP == true
== APP == Published message!
查看 MySql Storage 数据库,对应产品库存减少 20
至此,通过 Dapr runtime 完成了 Go 和 Java 之间的 Grpc 调用,并通过 RabbitMQ 组件完成了 Pub/Sub
Dapr Pub/Sub 集成 RabbitMQ 、Golang、Java、DotNet Core的更多相关文章
- RabbitMQ与java、Spring结合实例详细讲解(转)
林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文介绍了rabbitMq,提供了如何在Ubuntu下安装RabbitMQ 服务的方法. ...
- springboot集成rabbitmq(实战)
RabbitMQ简介RabbitMQ使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现(AMQP的主要特征是面向消息.队列.路由.可靠性.安全).支持多种客户端,如:Python.Ru ...
- SpringBoot集成rabbitmq(二)
前言 在使用rabbitmq时,我们可以通过消息持久化来解决服务器因异常崩溃而造成的消息丢失.除此之外,我们还会遇到一个问题,当消息生产者发消息发送出去后,消息到底有没有正确到达服务器呢?如果不进行特 ...
- RabbitMQ的Java API编程
1.创建Maven工程,pom.xml引入依赖: <dependency> <groupId>com.rabbitmq</groupId> <artifact ...
- Spring Boot系列——7步集成RabbitMQ
RabbitMQ是一种我们经常使用的消息中间件,通过RabbitMQ可以帮助我们实现异步.削峰的目的. 今天这篇,我们来看看Spring Boot是如何集成RabbitMQ,发送消息和消费消息的.同时 ...
- SpringBoot集成RabbitMQ消息队列搭建与ACK消息确认入门
1.RabbitMQ介绍 RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性.扩展性.高可用性等方面表现不俗.Rabbi ...
- RabbitMQ与java、Spring结合实例详细讲解
林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文介绍了rabbitMq,提供了如何在Ubuntu下安装RabbitMQ 服务的方法. ...
- 集成RabbitMQ做秒杀
由于秒杀的并发量太大,所以仅仅使用缓存是不够的,还需要用到RabbitMQ. 这里推荐一款用于分库分表的中间件:mycat 解决超卖的问题(看第五章节): 秒杀接口优化: 实操: 然后把下载好的文件上 ...
- RabbitMQ(3) Java客户端使用
RabbitMQ针对不同的开发语言(java,python,c/++,Go等等),提供了丰富对客户端,方便使用.就Java而言,可供使用的客户端有RabbitMQ Java client. Rabbi ...
随机推荐
- c数据结构 -- 栈与队列
栈和队列 ·栈和队列是两种常用的.重要的数据结构 ·栈和队列是限定插入和删除只能在表的“端点”进行的线性表 栈 只能在队尾插入,只能在队尾删除 -- 后进后出 表尾称为栈顶:表头称为栈底 插入元素到栈 ...
- vs2019 scanf 解决 C4996问题
1. 首先选择项目 2. 然后选择最下面那行的 工程属性, 其后于此处 3. 添加上 :_CRT_SECURE_NO_WARNINGS 最后保存,使用 scanf 读取即无报错了
- 给html元素添加自定义属性,并且用jquery进行筛选
例如有多个div,想要实现类似radio的效果. <div name="teacher" selected="selected">tch1</ ...
- [Reversal 剧情设计] 第一章——不速之客
夜.草叶上渗出的晶莹水珠反射着清冷的月光,但随后就暗淡下来——一个身披黑色大衣的男人无声地经过,腰间的长剑隐约露出剑柄. 远处是一个静谧的小山村,少有外人来往,此时正值深夜,家家户户都熄灭了油灯,只有 ...
- Django初步皮毛
Django是一个用python编写的Web框架. #Django安装 pip install Django 然后是配置环境变量,将这目录添加到系统环境变量中: C:\Python33\Lib\sit ...
- Sliding Window POJ - 2823 单调队列模板题
Sliding Window POJ - 2823 单调队列模板题 题意 给出一个数列 并且给出一个数m 问每个连续的m中的最小\最大值是多少,并输出 思路 使用单调队列来写,拿最小值来举例 要求区间 ...
- Go_CSP并发模型
go语言的最大两个亮点,一个是goroutine,一个就是chan了.二者合体的典型应用CSP,基本就是大家认可的并行开发神器,简化了并行程序的开发难度,我们来看一下CSP. 11.1.CSP是什么 ...
- web布局相关
1.用table布局时,如果设置了table-layout:fixed或者对第一行的两个列进行了合并后导致后面的列宽度失效,这是可以使用 <colgroup> <col ...
- Python偶斐波那契数
斐波那契数列中的每一项都是前两项的和.由1和2开始生成的斐波那契数列前10项为 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, … 考虑该斐波那契数列中不超过四百万的项,求其中为 ...
- numpy (1.16.4) has the default value of allow_pickle as False.
My version of numpy (1.16.4) has the default value of allow_pickle as False. numpy版本是1.16.4,allow_pi ...