【问题标题】:Possibility of updating data in real-time on a client可以在客户端实时更新数据
【发布时间】:2019-04-18 04:20:16
【问题描述】:

我有以下场景,我想知道它是否可能/可行。如果这被认为是一个过于“宽泛”的问题,我深表歉意,但我认为 SO 是提出这个问题的最佳场所。

假设我有一个网站,我想向最终用户显示一个图表。出于本示例的目的,假设我们要向他们显示过去一小时内的“每个类别的销售额”。数据将显示在图表中,运行查询的 SQL 可能是这样的:

SELECT SUM(revenue) FROM sales 
WHERE timestamp > NOW() - INTERVAL 1 HOUR
GROUP BY category

据我所知,为最终用户更新数据的一般方法有两种:

  1. 以一定的时间间隔执行某种轮询(或类似技术)以从查询中重新获取数据。但是,这可能会变得非常昂贵,具体取决于查询的复杂性/持续时间以及同时连接的人数。
  2. 第二种方法是将所有数据存储在内存中并将更新直接推送到该内存存储(可以是客户端或服务器端,我们可以随时向最终用户发送 ws 请求有数据更新。这方面的一个例子是使用 https://github.com/jpmorganchase/perspective 之类的东西。

然后我的问题是,当数据太大而无法存储在内存中时,是否有可能进行实时数据更新(我在示例 2 中描述的情况)。我认为答案是“不”,但也许我错过了一些方法来做到这一点。例如,假设我在 BigQuery 中存储了 1TB 的数据,并且我正在通过购买新产品对其进行流式更新——有没有一种方法可以将更新推送到最终客户端,而不必每次都重新运行查询想要获得更新?是否有任何其他技术可以用于这种情况/有用?

同样,我不认为这是可能的,但我想看看在查询的数据集上尽可能近实时地向终端客户端显示什么是可能的。

【问题讨论】:

  • 嗨 David542,也许像 Google PubSub 这样的东西可以工作? IE。每次购买新产品时,包含购买相关信息的“消息”通过消息发布客户端发布,与正在更新的数据库并行。消息是通过订阅者接收的,订阅者处理它们并将来自最后(例如)小时的消息的信息编译成有用且可在图表上呈现的格式。我对数据库一无所知,仅供参考!更多信息:cloud.google.com/pubsub/docs/overview
  • @Paul -- 对,我们目前正在使用它,但我的问题是如何用它更新最终用户的图表?
  • webpush - tools.ietf.org/html/draft-ietf-webpush-protocol-10 怎么样?您可以在数据库中完成一个触发器,该触发器将填充一个您将拥有客户端订阅的表,这会将更改推送到客户端。
  • @tukan 这将如何与 bigquery 或其他数据库这样的东西一起工作?
  • Cloud Firestore 怎么样?您已经在使用 pub/sub,因此您可以创建一个云函数来处理来自 pub/sub 的消息以更新您的 Cloud Firestore 数据库。

标签: websocket google-cloud-platform google-bigquery analytics real-time


【解决方案1】:

如果您的数据在每个客户端上都是唯一的、大且实时变化的,那么使用任何数据库或缓存作为交换都没有任何好处。您必须直接发送数据更新。

如果您不能直接将数据从执行数据库更新的进程推送到客户端,您可能可以通过消息代理将数据从执行更新的进程传递到执行推送的进程(我将使用 Rabbitmq举个例子)。

此设置的最佳配置是 topic model,其中主题是客户端 ID 或密钥,并为该主题为每个连接的客户端设置一个侦听器 - 或者,为所有客户端设置一个侦听器,但动态注册/取消注册主题.

让 websocket 处理程序监听其客户端的主题。设置更新数据库的过程以将更新流式传输到客户端的主题 ID。代理将丢弃所有未发送到已连接客户端的更新,从而使侦听器端的负载更易于管理。

无需任何存储或轮询,此解决方案具有低延迟。即使有 1000 个同时存在的客户,我也怀疑代理会耗尽内存。

【讨论】:

  • 感谢您的回答。嗯......除了将“每 5 秒查询一次”从客户端推送到服务器之外,上述操作还能完成什么?无论哪种方式,您仍然会每 5 秒查询一次...
  • @David542 每个客户端每 5s 查询一次与每 5s total 查询一次非常不同。我提出的解决方案实现了后者。
  • 不,不一定。无论哪种方式,我们都会向我们的 api 发出请求,对查询进行校验,如果相同,则返回缓存查询,否则,查询我们的数据存储并返回新的查询结果。所以从这个意义上说,如果客户端之间有 100 个不同的查询(无论是 100 个客户端还是 10,000 个),每 5 秒查询一次将产生每秒 20 个查询。还是我在您的回答中遗漏了什么?
  • @David542 您的原始帖子缺少您的数据每个客户都是唯一的。我从示例查询中假设它不是。显然,如果不运行 100 个唯一查询,我无法从 100 个唯一数据集生成 100 个唯一结果。我会用更相关的东西重写答案。
【解决方案2】:

由于您对此选项感兴趣,我决定将评论扩展到答案。我将采用 SQL ServerC# 组件 - sqltabledependency。你可以看看它是否符合你的需要。

  1. 您将创建一个临时表,在其中放置 sales 表中的任何更改,例如sales_data_watch(您也可以在此处使用示例中的预计算聚合)。

  2. 您将创建一个每小时作业来监控sales 表中的更改并在sales_data_watch 上执行插入/更新

  3. 您将连接到 sales_data_watch 的 C# sqltabledependency(注意:取自示例以适合您的表格)

    public class SaleData
    {
       public int revenue{ get; set; }
    }
    
    public class Program
    {
     private static string _con = "data source=.; initial catalog=MyDB; integrated security=True";
    
     public static void Main()
     {
      // The mapper object is used to map model properties 
      // that do not have a corresponding table column name.
      // In case all properties of your model have same name 
      // of table columns, you can avoid to use the mapper.
      var mapper = new ModelToTableMapper<SaleData>();
      mapper.AddMapping(s => s.revenue, "Aggregated revenue");
    
      // Here - as second parameter - we pass table name: 
      // this is necessary only if the model name is different from table name 
      // (in our case we have Sale vs Sales). 
      // If needed, you can also specifiy schema name.
      using (var dep = new SqlTableDependency<SaleData>(_con, "sales_data_watch", mapper: mapper));
      {
       dep.OnChanged += Changed;
       dep.Start();
    
       Console.WriteLine("Press a key to exit");
       Console.ReadKey();
    
       dep.Stop();
      } 
     }
    
     public static void Changed(object sender, RecordChangedEventArgs<SaleData> e)
     {
      var changedEntity = e.Entity;
    
      Console.WriteLine("DML operation: " + e.ChangeType);
      Console.WriteLine("Revenue: " + changedEntity.Revenue);
     }
    }
    
  4. 在所有通知分发后,您可以在之后执行truncate table sales_data_watch(如果您不希望表格变得太大,最终会减慢整个过程。

这仅使用 sql server 和 C# 组件。还有其他可能更好的选择,例如:Detect record table change with MVC, SignalR, jQuery and SqlTableDependency 以不同的方式进行操作。这将取决于您的偏好。

编辑Building real time charts with Angular 5, Google Charts, SignalR Core, .NET Core 2, Entity Framework Core 2 and SqlTable dependency 的完整示例链接(此链接是三个页面的第一页)。在页面顶部,您可以看到实时 google 的仪表图。所有学分都转到anthonygiretti。您可以在github 下载示例项目。

使用的技术

数据库

Sql Server, localDb with Visual Studio 2017 是正确的,可以正常工作

前端技术

Angular 5
Google Charts
Visual Studio Code
SignalR Client

后端技术

.NET Core 2
SignalR Core
EntityFramework Core
EntityFramework Core for Sql Server
SqlTableDependency

首先是安装所需的组件 - 服务代理、SQL 表、Angular-CLI、Angular 5 项目、SignalR 客户端(已安装 VS 2017、.Net Core 2 SDK) - 链接相同 part1

接下来是后端设置 - part2

为了使它工作,这个项目包含:

  • EntityFramework Core 的 DbContext (GaugesContext.cs)
  • 用于广播数据的 SignalR 集线器 (GaugeHub.cs)
  • 包含要发送的强类型数据的模型 (Gauge.cs)
  • 使用实体框架及其接口(GaugeRepository.cs 和 IGaugeRepository.cs)公开的存储库
  • 使用 SqlTableDependency 及其接口(GaugeDatabaseSubscription.cs 和 IDatabaseSubscription)对 Gauge sql 表的订阅
  • 扩展 IServiceCollection (AddDbContextFactory.cs) 和 IApplicationBuilder (UseSqlTableDependency.cs) 的两个扩展方法 还有 Startup.cs 和 Program.cs

最后一部分是设置前端 - part3

我们有:

  • 包含仪表图组件(gaugeschart.component.html 和 gaugeschart.component.ts)的文件夹
  • 包含仪表图表服务和 Google 图表基础服务(google-gauges-chart.service.ts 和 google-charts.base.service.ts)的文件夹
  • 包含环境文件的文件夹
  • 包含仪表图 (gauge.ts) 的强类型模型的文件夹
  • 最后在 src 文件夹的根目录下的默认文件组件和模块(应用组件文件和应用模块文件)

在下一步中,您应该对其进行测试,以查看更改数据时数据是否正确投影到图表中。

【讨论】:

  • 不过,这是为了检测“记录”更改,这不是问题。问题是用户正在查看应用了 sql 查询的图表——任何“记录”更改只会有帮助,因为它们告诉客户端“再次运行 sql 查询”,这又回到了场景 1。
  • @David542 sqltabledependency 将信息推送到客户端,无需重新加载。正确设置它取决于您。我试图创建一个最小的展示示例,您必须在其中选择推送的通知。我通过链接在编辑中包含了一个完整的示例。您可以在其中看到实时图表的完整设置,该图表会根据数据更改进行更新。
【解决方案3】:

我认为问题可能源于客户图表及其设计要求的问题。

“最近一小时的销售额”图表既缺乏信息又难以更新。

随着“最近时间”的进行(下午 1:05 变为下午 1:06),更新需要扣除销售额并增加新的销售额。

此外,这些信息可能看起来令人兴奋,但它提供的信息很少,营销部门可以用来提高销售量(即,应该在什么时间添加更多广告)。

我会考虑 24 小时图,或 12 小时图除以实际小时数。

这可以简化更新并可能提供更多有用的指标。​​

这样,对图表的更新始终是累加的,因此不需要内存数据存储(并且信息更具可操作性)。

例如,每笔新销售都可以发布到"new_sale" 频道。公布的销售数据可能包括它的确切时间。

这将允许订阅的客户在图表中的正确时间添加新的销售,而无需调用额外的数据库调用,也不需要内存中的数据存储。

【讨论】:

    猜你喜欢
    • 2018-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多