前置条件:

《Dapr运用》

《Dapr 运用之 Java gRPC 调用篇》

《Dapr 运用之集成 Asp.Net Core Grpc 调用篇》


  1. 搭建 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".
  2. 改造 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
  3. 使用 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"
  4. 创建 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!
  5. 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 的广播数据。

  6. 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 用于减少库存
  7. 启动 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的更多相关文章

  1. RabbitMQ与java、Spring结合实例详细讲解(转)

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文介绍了rabbitMq,提供了如何在Ubuntu下安装RabbitMQ 服务的方法. ...

  2. springboot集成rabbitmq(实战)

    RabbitMQ简介RabbitMQ使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现(AMQP的主要特征是面向消息.队列.路由.可靠性.安全).支持多种客户端,如:Python.Ru ...

  3. SpringBoot集成rabbitmq(二)

    前言 在使用rabbitmq时,我们可以通过消息持久化来解决服务器因异常崩溃而造成的消息丢失.除此之外,我们还会遇到一个问题,当消息生产者发消息发送出去后,消息到底有没有正确到达服务器呢?如果不进行特 ...

  4. RabbitMQ的Java API编程

    1.创建Maven工程,pom.xml引入依赖: <dependency> <groupId>com.rabbitmq</groupId> <artifact ...

  5. Spring Boot系列——7步集成RabbitMQ

    RabbitMQ是一种我们经常使用的消息中间件,通过RabbitMQ可以帮助我们实现异步.削峰的目的. 今天这篇,我们来看看Spring Boot是如何集成RabbitMQ,发送消息和消费消息的.同时 ...

  6. SpringBoot集成RabbitMQ消息队列搭建与ACK消息确认入门

    1.RabbitMQ介绍 RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性.扩展性.高可用性等方面表现不俗.Rabbi ...

  7. RabbitMQ与java、Spring结合实例详细讲解

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:本文介绍了rabbitMq,提供了如何在Ubuntu下安装RabbitMQ 服务的方法. ...

  8. 集成RabbitMQ做秒杀

    由于秒杀的并发量太大,所以仅仅使用缓存是不够的,还需要用到RabbitMQ. 这里推荐一款用于分库分表的中间件:mycat 解决超卖的问题(看第五章节): 秒杀接口优化: 实操: 然后把下载好的文件上 ...

  9. RabbitMQ(3) Java客户端使用

    RabbitMQ针对不同的开发语言(java,python,c/++,Go等等),提供了丰富对客户端,方便使用.就Java而言,可供使用的客户端有RabbitMQ Java client. Rabbi ...

随机推荐

  1. Date、DateFormat、Calendar、Math、System

    Date(基本已过时了,被Calendar替换) 构造方法(有两个) Date(); Date(long l);long类型的毫秒值 常用方法(其他方法都已被Calendar替换) getTime() ...

  2. STL初探

    关于STL的一些东西 感言: 学C++不学STL函数库的人可能都是... 有点问题 头文件<algorithm>的一些东西 sort,快排: 这是个初学者必需掌握的东西,及其好用,因为方( ...

  3. java连接mysql数据库8.0以上版本过程中遇到的坑

    来自:https://blog.csdn.net/u013276277/article/details/80255468 首先,我居然不能用navicat客户端连接上mysql8.0数据库报1251错 ...

  4. 常见运算符_python

    一.算数运算符a=2b=5c=8#加减乘除print(a+b,a-b,a*b,a/b)#取模,也就是余数print(b%a)#取整除print(b//a)#幂(次方)print(b**a) 二.比较运 ...

  5. 2019HDU多校第一场1001 BLANK (DP)(HDU6578)

    2019HDU多校第一场1001 BLANK (DP) 题意:构造一个长度为n(n<=10)的序列,其中的值域为{0,1,2,3}存在m个限制条件,表示为 l r x意义为[L,R]区间里最多能 ...

  6. Java实现JSONObject对象与Json字符串互相转换

    Java实现JSONObject对象与Json字符串互相转换 JSONObject 转 JSON 字符串 Java代码: JSONObject jsonObject = new JSONObject( ...

  7. JAVA中fail-fast机制

    在JDK的Collection中我们时常会看到类似于这样的话: 例如,ArrayList: 注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证.快速失 ...

  8. AcWing 854. Floyd求最短路 多源 邻接矩阵

    //不存在负权回路 //边权可能为负数 #include <cstring> #include <iostream> #include <algorithm> us ...

  9. honoka和格点三角形(牛客寒假训练营day1)

    可以把面积为1的好三角形分成两类分开统计:两条边和两个坐标轴平行:只有一条边和某个坐标轴平行. 对于第一种情况,一定是1*2或者2*1的形式,一个1*2的矩形中含有4个不同的三角形.总数是4*((n- ...

  10. 【Python】如何处理Excel中的数据

    我们平时在做自动化测试的时候,可能会涉及到从表格中去读取或者存储数据,我们除了可以使用openpyxl来操作excel,当然也可以利用pandas来完成,这篇随笔只是我在学习过程中的简单记录,其他的功 ...