《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…

递归实现排列型枚举

把 1∼n 这 n 个整数排成一行后随机打乱顺序&#xff0c;输出所有可能的次序。 输入格式 一个整数 n。 输出格式 按照从小到大的顺序输出所有方案&#xff0c;每行 1 个。 首先&#xff0c;同一行相邻两个数用一个空格隔开。 其次&#xff0c;对于两个不同的行&#xff0c;…

【在路上5】实时计算助力派件管控

签收率系统试运营以来&#xff0c;每天算出高额罚款&#xff0c;虽然没有真正执行&#xff0c;但也挺吓人的。并且&#xff0c;完完全全的把全网人员积极性提升起来了。总部网络管理中心每天给出各省区前一天的签收率报表&#xff0c;来了个排名和点名&#xff1b;各省区管理中…

数据结构---B-(B)、B+的总结

数据结构—B-&#xff08;B&#xff09;、B的总结 原理&#xff1a;参考趣学数据结构 m阶B-树规则(有序的&#xff08;左子树的元素值<根节点的元素值<右子树的元素值&#xff09;、平衡的&#xff08;每个结点的左右子树的高度差<1&#xff09;、多路的&#xff08…

dfs巩固训练

按顺序从上往下刷即可&#xff01;&#xff01;&#xff01; 知识点: 关于环形的数组&#xff0c;前移动和后移动可能会溢出下标。解决方法是&#xff0c;转移后的坐标公式为 &#xff08;原坐标改变量数组长度&#xff09;%数组长度 易错点: 写搜索时候&#xff0c;有时候会…

python适用范围_Python应用范围总结概览

Python就是万金油&#xff01; Python&#xff08;派森&#xff09;&#xff0c;它是一个简单的、解释型的、交互式的、可移植的、面向对象的超高级语言。这就是对Python语言的最简单的描述。 Python有一个交互式的开发环境&#xff0c;因为Python是解释运行&#xff0c;这大大…

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

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

广义表的学习(原理和代码)

广义表的学习&#xff08;原理和代码&#xff09; 参考链接&#xff1a; https://blog.csdn.net/it_is_me_a/article/details/99870530

bfs巩固训练

按顺序从上往下刷即可&#xff01;&#xff01;&#xff01; 知识点: 关于环形的数组&#xff0c;前移动和后移动可能会溢出下标。解决方法是&#xff0c;转移后的坐标公式为 &#xff08;原坐标改变量数组长度&#xff09;%数组长度 易错点: 写搜索时候&#xff0c;有时候会…

python输出文本 去掉引号_Python可以在文本文件中读取时从字符串中删除双引号吗?...

我有一些这样的文本文件&#xff0c;有50​​00行&#xff1a; 5.6 4.5 6.8 "6.5" (new line) 5.4 8.3 1.2 "9.3" (new line) 所以最后一个术语是双引号之间的数字。 我想做的是使用Python(如果可能的话)将四列分配给双变量。但主要的问题是最后一个术语&a…

word List 15

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

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

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

python统计文件中每个单词出现的次数_python统计文本中每个单词出现的次数

.python统计文本中每个单词出现的次数&#xff1a; #codingutf-8 __author__ ‘zcg‘ import collections import os with open(‘abc.txt‘) as file1:#打开文本文件 str1file1.read().split(‘ ‘)#将文章按照空格划分开 print "原文本:\n %s"% str1 print "…

AcWing 1101. 献给阿尔吉侬的花束

阿尔吉侬是一只聪明又慵懒的小白鼠&#xff0c;它最擅长的就是走各种各样的迷宫。 今天它要挑战一个非常大的迷宫&#xff0c;研究员们为了鼓励阿尔吉侬尽快到达终点&#xff0c;就在终点放了一块阿尔吉侬最喜欢的奶酪。 现在研究员们想知道&#xff0c;如果阿尔吉侬足够聪明…

数据结构---关键路径

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

Kubernetes AIOps解决方案商 Carbon Relay获6300万美元A轮融资

每10家企业中就有4家报告说他们正在使用Kubernetes&#xff0c;Kubernetes是开放源代码容器编排框架&#xff0c;用于在生产环境中自动化应用程序的部署&#xff0c;扩展和管理。但是&#xff0c;诸如管理复杂性&#xff0c;部署时间和无法预料的错误之类的挑战可能会降低生产率…

python结束线程_2018-01-02 如何优雅地终止python线程

前言 零 我们知道&#xff0c;在python里面要终止一个线程&#xff0c;常规的做法就是设置/检查 --->标志或者锁方式来实现的。 这种方式好不好呢&#xff1f; 应该是不大好的&#xff01;因为在所有的程序语言里面&#xff0c;突然地终止一个线程&#xff0c;这无论如何都…

AcWing 1113. 红与黑

有一间长方形的房子&#xff0c;地上铺了红色、黑色两种颜色的正方形瓷砖。 你站在其中一块黑色的瓷砖上&#xff0c;只能向相邻&#xff08;上下左右四个方向&#xff09;的黑色瓷砖移动。 请写一个程序&#xff0c;计算你总共能够到达多少块黑色的瓷砖。 输入格式 输入包括…

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

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