前置条件:

《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. vue全局组件与局部组件

    <!DOCTYPE html> <html> <head> <title></title> </head> <script ...

  2. NEON的vsub方法溢出

    关于NEON的vsub方法的溢出,结果如下: vsub会产生溢出,根据数据bit表示规律,可知溢出结果和理论正确结果形成互补,比如249-(-7)=256 使用类vreinterpretq_s16_u ...

  3. 【算法学习记录-排序题】【PAT A1062】Talent and Virtue

    About 900 years ago, a Chinese philosopher Sima Guang wrote a history book in which he talked about ...

  4. 新的存储网站,和存储单元dropbox

    新的存储网站,和存储单元dropbox 待办 https://www.dropbox.com/home google 登陆 google邮箱 密码 521google 但是免费存储量只有2G goog ...

  5. AAC DRC

    DRC feature 在AAC decoder是optional.DRC info是在编码在fill element. Program reference level是进行DRC的一个参考值.是原始 ...

  6. HTML5学习(1)简介

    HTML5是HTML最新的修订版本,2014年10月由万维网联盟(W3C)完成标准制定. HTML5的设计目的是为了在移动设备上支持多媒体. HTML5 简单易学. 什么是 HTML5? HTML5 ...

  7. python夜记

    关于多行字符串(multi-line strings)的表现: Python列表是基于0索引的.(zero-indexed). 晌午起床来嘞,再来些笔记: Treasures 1: 列表方法rever ...

  8. hdu 6214 Smallest Minimum Cut(最小割的最少边数)

    题目大意是给一张网络,网络可能存在不同边集的最小割,求出拥有最少边集的最小割,最少的边是多少条? 思路:题目很好理解,就是找一个边集最少的最小割,一个方法是在建图的时候把边的容量处理成C *(E+1 ...

  9. 【做题笔记】[NOIOJ,非NOIp原题]装箱问题

    题意:给定一些矩形,面积分别是 \(1\times 1,2\times 2,3\times 3,4\times 4,5\times 5,6\times 6\).您现在知道了这些矩形的个数 \(a,b, ...

  10. MyBatis(8)——联表多对一的处理

    xml说明: <!--column不做限制,可以为任意表的字段,而property须为type 定义的pojo属性--> <resultMap id="唯一的标识" ...