《ASP.NET Core 微服务实战》-- 读书笔记(第11章)

第 11 章 开发实时应用和服务

在本章,我们将讨论“实时”的准确含义,以及在大部分消费者看来应该属于这一范畴的应用类型

接着,我们将探讨 WebSocket,并分析为什么传统的 WebSocket 与云环境完全不相适应,最后我们将构建一个实时应用的示例,用于展示向一个事件溯源系统添加实时消息的强大功能

实时应用的定义

我认为,实时系统的定义可以稍微宽泛一点,只要是事件的接收与处理过程之间只有少许延迟,或者完全没有延迟都可以认为是实时系统

下面是真正的实时系统中区分出非实时系统的几个特点:

  • 应用收集输入数据后,在生成输出前,有明显的等待

  • 应用只按照固定间隔或者基于某种按计划或随机触发的外部信号生成输出

实时系统有一个真正常见的迹象和特征,即当相关方关注的事件发生时,它们会收到推送通知,而不是由相关方以挂起等待或者间隔查询的方式来检查新状态

云环境中的 WebSocket

WebSocket 协议

WebSocket 协议始于 2008 年,它定义了浏览器和服务器之间建立持久的双向 Socket 连接的标准

这让服务器向运行于浏览器中的 Web 应用发送数据称为可能,期间不需要由 Web 应用执行“轮询”

在底层实现中,浏览器向服务器请求连接进行升级

握手完成后,浏览器和服务器将切换为单独的二进制 TCP 连接,以实现双向通信

部署模式

假如所有服务器都运行在亚马逊云的弹性计算服务环境中

当虚拟机被托管在云基础设施中时,它们就可能随时被搬移、销毁并重建

这原本是一件好事,旨在让应用近乎不受限制地伸缩

不过,这也意味着这种“实时” WebSocket 连接可能被切断或者严重延迟,并在不知不觉中失去响应

此处的解决方案通常是将对 WebSocket 的使用独立出去--把管理 WebSocket 连接和数据传输工作转移到应用的代码之外的位置

简单地说,相比于在自己的应用中管理 WebSocket,我们应该选用一种基于云的消息服务,让更专业的人来完成这项工作

使用云消息服务

我们的应用需要拥有实时通信的能力

我们希望微服务能够向客户端推送数据,但客户端无法建立到微服务的持续 TCP 连接

我们还希望能够使用相同类似的消息机制向后端服务发送消息

为让微服务遵循云原生特性、保留可伸缩的能力,并在云环境中自由地搬移,我们需要挑选一种消息服务,把一定的实时通信能力提取到进程之外

下面列举一些厂商,他们提供的云消息服务有的是独立产品,有的则是大型服务套件中的一部分:

  • Apigee (API 网关与实时消息通信)

  • PubNub (实时消息通信与活跃度监控)

  • Pusher(实时消息通信活跃度监控)

  • Kaazing(实时消息通信)

  • Mashery(API 网关与实时消息通信)

  • Google (Google 云消息通信)

  • ASP.NET SinglR (Azure 托管的实时消息通信服务)

  • Amazon (简单通知服务)

无论选择哪种机制,我们都应该投入一定的时间让代码与具体的消息服务相隔离,从而在更换服务商时,不至于产生太大的影响

开发位置接近监控服务

现在,我们要做的就是开发一个每当后端系统检测到接近事件时,就能够实时更新的监视器

我们可以生成一张地图,在上面绘出两个团队成员的位置,当系统检测到他们相互接近时,就让他们的头像跳动,或者生成一个动画

这些团队成员的移动设备可能还会在同一时刻收到通知

创建接近监控服务

我们的示例监控服务将包含一系列不同的组件

首先,我们需要消费由第 6 章编写的服务生成并放入队列的 ProximityDetectedEvent 事件

此后,我们要提取事件中的原始信息,调用团队服务以获取可供用户读取识别的信息

获取这些补充信息后,最后要在实时消息系统上发出一条消息

GitHub链接:https://github.com/microservices-aspnetcore/es-proximitymonitor

以下是我们接近监控服务背后的上层协调逻辑

using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StatlerWaldorfCorp.ProximityMonitor.Queues;
using StatlerWaldorfCorp.ProximityMonitor.Realtime;
using StatlerWaldorfCorp.ProximityMonitor.TeamService;namespace StatlerWaldorfCorp.ProximityMonitor.Events
{public class ProximityDetectedEventProcessor : IEventProcessor{private ILogger logger;private IRealtimePublisher publisher;private IEventSubscriber subscriber;private PubnubOptions pubnubOptions;public ProximityDetectedEventProcessor(ILogger<ProximityDetectedEventProcessor> logger,IRealtimePublisher publisher,IEventSubscriber subscriber,ITeamServiceClient teamClient,IOptions<PubnubOptions> pubnubOptions){this.logger = logger;this.pubnubOptions = pubnubOptions.Value;this.publisher = publisher;this.subscriber = subscriber;logger.LogInformation("Created Proximity Event Processor.");subscriber.ProximityDetectedEventReceived += (pde) => {Team t = teamClient.GetTeam(pde.TeamID);Member sourceMember = teamClient.GetMember(pde.TeamID, pde.SourceMemberID);Member targetMember = teamClient.GetMember(pde.TeamID, pde.TargetMemberID);ProximityDetectedRealtimeEvent outEvent = new ProximityDetectedRealtimeEvent{TargetMemberID = pde.TargetMemberID,SourceMemberID = pde.SourceMemberID,DetectionTime = pde.DetectionTime,SourceMemberLocation = pde.SourceMemberLocation,TargetMemberLocation = pde.TargetMemberLocation,MemberDistance = pde.MemberDistance,TeamID = pde.TeamID,TeamName = t.Name,SourceMemberName = $"{sourceMember.FirstName} {sourceMember.LastName}",TargetMemberName = $"{targetMember.FirstName} {targetMember.LastName}"};publisher.Publish(this.pubnubOptions.ProximityEventChannel, outEvent.toJson());};}public void Start(){subscriber.Subscribe();}public void Stop(){subscriber.Unsubscribe();}}
}

在这个代码清单中,首先要注意的是从 DI 向构造函数注入的一连串依赖:

  • 日志记录工具

  • 实时事件发布器

  • 事件订阅器

  • 团队服务客户端

  • PubNub 选项

创建实时事件发布器类实现类

using Microsoft.Extensions.Logging;
using PubnubApi;namespace StatlerWaldorfCorp.ProximityMonitor.Realtime
{public class PubnubRealtimePublisher : IRealtimePublisher{private ILogger logger;private Pubnub pubnubClient;public PubnubRealtimePublisher(ILogger<PubnubRealtimePublisher> logger,Pubnub pubnubClient){logger.LogInformation("Realtime Publisher (Pubnub) Created.");this.logger = logger;this.pubnubClient = pubnubClient;}public void Validate(){pubnubClient.Time().Async(new PNTimeResultExt((result, status) => {if (status.Error) {logger.LogError($"Unable to connect to Pubnub {status.ErrorData.Information}");throw status.ErrorData.Throwable;} else {logger.LogInformation("Pubnub connection established.");}}));}public void Publish(string channelName, string message){pubnubClient.Publish().Channel(channelName).Message(message).Async(new PNPublishResultExt((result, status) => {if (status.Error) {logger.LogError($"Failed to publish on channel {channelName}: {status.ErrorData.Information}");} else {logger.LogInformation($"Published message on channel {channelName}, {status.AffectedChannels.Count} affected channels, code: {status.StatusCode}");}}));}}
}

注入实时通信类

在 Startup 类中配置 DI 来提供 PubNub 客户端和其他相关类

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StatlerWaldorfCorp.ProximityMonitor.Queues;
using StatlerWaldorfCorp.ProximityMonitor.Realtime;
using RabbitMQ.Client.Events;
using StatlerWaldorfCorp.ProximityMonitor.Events;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using StatlerWaldorfCorp.ProximityMonitor.TeamService;namespace StatlerWaldorfCorp.ProximityMonitor
{public class Startup{public Startup(IHostingEnvironment env, ILoggerFactory loggerFactory){loggerFactory.AddConsole();loggerFactory.AddDebug();var builder = new ConfigurationBuilder().SetBasePath(env.ContentRootPath).AddJsonFile("appsettings.json", optional: false, reloadOnChange: false).AddEnvironmentVariables();Configuration = builder.Build();}public IConfigurationRoot Configuration { get; }public void ConfigureServices(IServiceCollection services){services.AddMvc();services.AddOptions();services.Configure<QueueOptions>(Configuration.GetSection("QueueOptions"));services.Configure<PubnubOptions>(Configuration.GetSection("PubnubOptions"));services.Configure<TeamServiceOptions>(Configuration.GetSection("teamservice"));services.Configure<AMQPOptions>(Configuration.GetSection("amqp"));services.AddTransient(typeof(IConnectionFactory), typeof(AMQPConnectionFactory));services.AddTransient(typeof(EventingBasicConsumer), typeof(RabbitMQEventingConsumer));services.AddSingleton(typeof(IEventSubscriber), typeof(RabbitMQEventSubscriber));services.AddSingleton(typeof(IEventProcessor), typeof(ProximityDetectedEventProcessor));services.AddTransient(typeof(ITeamServiceClient),typeof(HttpTeamServiceClient));services.AddRealtimeService();services.AddSingleton(typeof(IRealtimePublisher), typeof(PubnubRealtimePublisher));}// Singletons are lazy instantiation.. so if we don't ask for an instance during startup,// they'll never get used.public void Configure(IApplicationBuilder app,IHostingEnvironment env,ILoggerFactory loggerFactory,IEventProcessor eventProcessor,IOptions<PubnubOptions> pubnubOptions,IRealtimePublisher realtimePublisher){realtimePublisher.Validate();realtimePublisher.Publish(pubnubOptions.Value.StartupChannel, "{'hello': 'world'}");eventProcessor.Start();app.UseMvc();}}
}

我们尝试为类提供预先创建好的 PubNub API 实例

为整洁地实现这一功能,并继续以注入方式获取配置信息,包括 API 密钥,我们需要向 DI 中注册一个工厂

工厂类的职责是向外提供装配完成的 PubNub 实例

using System;
using Microsoft.Extensions.Options;
using PubnubApi;
using System.Linq;
using Microsoft.Extensions.Logging;namespace StatlerWaldorfCorp.ProximityMonitor.Realtime
{public class PubnubFactory{private PNConfiguration pnConfiguration;private ILogger logger;public PubnubFactory(IOptions<PubnubOptions> pubnubOptions,ILogger<PubnubFactory> logger){this.logger = logger;pnConfiguration = new PNConfiguration();pnConfiguration.PublishKey = pubnubOptions.Value.PublishKey;pnConfiguration.SubscribeKey = pubnubOptions.Value.SubscribeKey;pnConfiguration.Secure = false;logger.LogInformation($"Pubnub Factory using publish key {pnConfiguration.PublishKey}");}public Pubnub CreateInstance(){return new Pubnub(pnConfiguration);}}
}

将工厂注册到 DI 时使用的扩展方法机制

using System;
using Microsoft.Extensions.DependencyInjection;
using PubnubApi;namespace StatlerWaldorfCorp.ProximityMonitor.Realtime
{public static class RealtimeServiceCollectionExtensions{public static IServiceCollection AddRealtimeService(this IServiceCollection services){services.AddTransient<PubnubFactory>();return AddInternal(services, p => p.GetRequiredService<PubnubFactory>(), ServiceLifetime.Singleton);}private static IServiceCollection AddInternal(this IServiceCollection collection,Func<IServiceProvider, PubnubFactory> factoryProvider,ServiceLifetime lifetime){Func<IServiceProvider, object> factoryFunc = provider =>{var factory = factoryProvider(provider);return factory.CreateInstance();};var descriptor = new ServiceDescriptor(typeof(Pubnub), factoryFunc, lifetime);collection.Add(descriptor);return collection;}}
}

上面代码的关键功能是创建了一个 lambda 函数,接收 IServiceProvider 作为输入,并返回一个对象作为输出

它正是我们注册工厂时向服务描述对象中传入的工厂方法

汇总所有设计

要立即查看效果,从而确保一切工作正常,我们可模拟由第 6 章的服务输出的信息

只需要手动向 proximitydetected 队列中放入表示 ProximityDetectedEvent 对象的 JSON 字符串

在这个过程中,如果我们的监控服务处于运行之中、订阅了队列,而且团队服务处于运行之中、拥有正确的数据,那么接近监控服务将取出事件、补充必要的数据,并通过 PubNub 发送一个实时事件

利用 PubNub 调试控制台,我们可以立即看到这一处理过程生成的输出

为实时接近监控服务创建界面

为简化工作,同时掩盖我缺乏艺术细胞的真相,我将用一个不包含图形元素的简单 HTML 页面,它不需要托管在专门的 Web 服务器上

它实时地监听接近事件,并将携带的信息动态添加到新的 div 元素中

realtimetest.html

<html><head><title>RT page sample</title><script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.4.0.js"></script><script>var pubnub = new PubNub({subscribeKey: "yoursubkey",publishKey: "yourprivatekey",ssl: true});pubnub.addListener({message: function(m) {// handle messagevar channelName = m.channel; // The channel for which the message belongsvar channelGroup = m.subscription; // The channel group or wildcard subscription match (if exists)var pubTT = m.timetoken; // Publish timetokenvar msg = JSON.parse(m.message); // The Payloadconsole.log("New Message!!", msg);var newDiv = document.createElement('div')var newStr = "** (" + msg.TeamName + ") " + msg.SourceMemberName + " moved within " + msg.MemberDistance + "km of " + msg.TargetMemberName;newDiv.innerHTML = newStrvar oldDiv = document.getElementById('chatLog')oldDiv.appendChild(newDiv)},presence: function(p) {// handle presencevar action = p.action; // Can be join, leave, state-change or timeoutvar channelName = p.channel; // The channel for which the message belongsvar occupancy = p.occupancy; // No. of users connected with the channelvar state = p.state; // User Statevar channelGroup = p.subscription; //  The channel group or wildcard subscription match (if exists)var publishTime = p.timestamp; // Publish timetokenvar timetoken = p.timetoken;  // Current timetokenvar uuid = p.uuid; // UUIDs of users who are connected with the channel},status: function(s) {// handle status}});console.log("Subscribing..");pubnub.subscribe({channels: ['proximityevents']});</script></head><body><h1>Proximity Monitor</h1><p>Proximity Events listed below.</p><div id="chatLog"></div></body>
</html>

值得指出的是,这个文件并不需要托管在服务器上

在任何浏览器中打开,其中的 JavaScript 都可以运行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311862.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

word List 14

word List 14 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

python编程模式是什么_python 开发的三种运行模式详细介绍

Python 三种运行模式 Python作为一门脚本语言&#xff0c;使用的范围很广。有的同学用来算法开发&#xff0c;有的用来验证逻辑&#xff0c;还有的作为胶水语言&#xff0c;用它来粘合整个系统的流程。不管怎么说&#xff0c;怎么使用python既取决于你自己的业务场景&#xff0…

重磅!K8S 1.18版本将内置支持SideCar容器。

作者&#xff1a;justmine头条号&#xff1a;大数据与云原生微信公众号&#xff1a;大数据与云原生创作不易&#xff0c;在满足创作共用版权协议的基础上可以转载&#xff0c;但请以超链接形式注明出处。为了方便阅读&#xff0c;微信公众号已按分类排版&#xff0c;后续的文章…

word List 15

word List 15 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

.NET Core 如何验证信用卡卡号

_点击上方蓝字关注“汪宇杰博客”导语最近在家闲的蛋疼需要写点文章。正好我本人在金融科技公司工作&#xff0c;对信用卡业务略有了解。我们看看如何在 .NET Core 里验证一个信用卡的卡号是否合法。信用卡卡号组成首先&#xff0c;信用卡的卡号一般为16位&#xff0c;也有少许…

数据结构---关键路径

数据结构—关键路径 原理&#xff1a;参考趣学数据结构 代码&#xff1a; #include<stdio.h> #include<stdlib.h> #include "stack.h" #define typeNode int //每个头结点的标识数据类型 #define N 100 //最大结点数 int degree[N];//结点入度数,通过…

SkyWalking学习笔记(Window环境 本地环境)

基于 Windows 环境使用 SkyAPM-dotnet 来介绍一下 SkyWalking&#xff0c; SkyAPM-dotnet 是 SkyWalking 的 .NET Agent环境要求JDK8Elasticsearch8080,9200,10800,11800,12800 端口不被占用Elasticsearch安装Elasticsearch下载安装 参考官方教程.Elasticsearch下载安装官方教程…

word List16

word List16 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

python二分法求方程的根_Python查找函数f(x)=0根的解决方法

线性代数分享方程f(x)0的根 函数F(x)0的重根与F(x)0的根有什么关系&#xff1f;有些人一旦错过了&#xff0c;就是一辈子不再主动联系&#xff0c;不愿打扰你的生活&#xff0c;连偶尔的寒暄都没有&#xff0c;成长就是这样的&#xff0c;不断的告别&#xff0c;不断的遇见。 请…

word List 17

word List 17 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

pb 如何导出csv_如何计算指数温度?

指数温度的高低与价格高低没有必然的联系&#xff0c;指数温度的高低反映的是在历史中低于指数当前估值出现的概率。温度越低说明在历史上低于当前估值的概率越小&#xff0c;指数的价值越被低估&#xff0c;上涨的概率越大&#xff1b;温度越高说明在历史上低于当前估值的概率…

算法问题---两艘船是否有最大承载量

算法问题—两艘船是否有最大承载量 代码&#xff1a; 栈代码&#xff1a; #pragma once #include<stdio.h> #define maxSize 100 typedef struct stack {int * base;int * top; }stack; bool init(stack & Stack) {//栈的初始化Stack.base new int[maxSize];if (…

python标准库time_Python 标准库之时间篇

写在之前 大家好&#xff0c;这是首发在我公众号「Python空间」的第 69 篇文章&#xff0c;欢迎关注&#xff0c;期待和你的交流。 在昨天的文章&#xff08;Python 标准库之日期&#xff09;中我们学习了 Python 标准库中「日期 & 时间」中的「日期」&#xff0c;本来想昨…

《ASP.NET Core 微服务实战》-- 读书笔记(第12章)

第 12 章 设计汇总微服务开发并不是要学习 C#、Java 或者 Go 编程--而是要学习如何开发应用以适应并充分利用弹性伸缩环境的优势&#xff0c;它们对托管环境没有偏好&#xff0c;并能瞬间启停换句话说&#xff0c;我们要学习如何开发云原生应用识别并解决反模式我们既然已经学习…

[蓝桥杯2017初赛]纸牌三角形-枚举permutation+数论

题目描述 A,2,3,4,5,6,7,8,9 共9张纸牌排成一个正三角形&#xff08;A按1计算&#xff09;。要求每个边的和相等。 下图就是一种排法这样的排法可能会有很多。 如果考虑旋转、镜像后相同的算同一种&#xff0c;一共有多少种不同的排法呢&#xff1f; 输出 输出一个整数表示答…

算法----最大承载量下的最大价值问题

算法----最大承载量下的最大价值问题 代码&#xff1a; 栈代码&#xff1a;&#xff08;存储哪些是需要的价值物&#xff09; #pragma once #include<stdio.h> #define maxSize 100 typedef struct stack {int * base;int * top; }stack; bool init(stack & Stack…

oracle多条件分组统计_多条件统计,就必须用Ifs系列函数,绝对的高能!

数据统计&#xff0c;我们并不陌生&#xff0c;但是在实际的工作或应用中&#xff0c;数据统计都是附加条件的&#xff0c;而且大多情况下是“多条件”的&#xff0c;此时&#xff0c;我们必须掌握“Ifs”系列函数。一、多条件判断&#xff1a;Ifs函数。功能&#xff1a;判断指…

AcWing 895. 最长上升子序列

给定一个长度为N的数列&#xff0c;求数值严格单调递增的子序列的长度最长是多少。 输入格式 第一行包含整数N。 第二行包含N个整数&#xff0c;表示完整序列。 输出格式 输出一个整数&#xff0c;表示最大长度。 数据范围 1≤N≤1000&#xff0c; −10^9≤数列中的数≤ 10…

word List18

word List18 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

AcWing 1015. 摘花生

Hello Kitty想摘点花生送给她喜欢的米老鼠。 她来到一片有网格状道路的矩形花生地(如下图)&#xff0c;从西北角进去&#xff0c;东南角出来。 地里每个道路的交叉点上都有种着一株花生苗&#xff0c;上面有若干颗花生&#xff0c;经过一株花生苗就能摘走该它上面所有的花生。…