.NET Core 和 ASP.NET Core 因其设计简洁、轻量级、开源且可在 Windows 和 Linux 系统上运行而日益普及。因此,许多现有应用程序也正从 .NET Framework 迁移到 .NET Core。几乎所有新应用程序都使用 .NET Core 开发。
许多 .NET Core 应用程序都具有高流量的特点,服务于数百万用户并处理大量交易。因此,这些应用程序对您的业务有着巨大的影响,至关重要。
通常需要可扩展性的 .NET Core 应用程序是服务器应用程序,它们必须快速处理大量事务并实现极快的响应速度。这些应用程序大多面向客户,这意味着它们需要处理客户请求。如果它们无法快速响应客户请求,将会给企业造成巨大的损失,包括收入损失和客户流失。
以下 .NET Core 应用程序需要具备可扩展性:
有趣的是,上面提到的所有应用程序都具有非常可扩展的应用程序级架构。 它们中的每一个都允许您通过添加更多服务器、VM 或容器实例以及负载均衡器来随着事务负载的增长而线性扩展。
但是,尽管应用层架构具有很强的可扩展性,.NET Core 服务器应用程序目前仍面临着严重的扩展性瓶颈。这些瓶颈出现在以下几个方面:
对于所有高流量的 .NET Core 应用程序而言,最大的瓶颈在于其应用程序数据库。目前大多数应用程序仍然使用 SQL Server 或 Oracle 等关系型数据库。随着应用程序事务负载的增加,这些数据库很快就会成为可扩展性的瓶颈。无论是在虚拟机上使用 SQL Server 还是在 Azure SQL 数据库上,情况都是如此。
这是因为关系型数据库无法像 NoSQL 数据库那样进行逻辑分区,而是固定在一个物理位置;即使是列级分区也与真正的 NoSQL 式分区截然不同。因此,关系型数据库无法像 NoSQL 数据库那样通过添加更多数据库服务器来扩展其事务处理能力。
例如,虽然随着事务负载的增长,您的应用层可以轻松拥有 10、20、30 或更多的应用服务器,但您的数据库层根本无法以同样的方式增长。
由于这一切,您的关系数据库成为您存储在其中的任何数据(应用程序数据或其他数据)的性能瓶颈。
SQL Server 引入了内存优化以增加每秒的事务数。 Oracle 还提供了他们自己的 In-Memory 表版本。
虽然内存优化带来了性能改进,但它们并没有解决线性可扩展性的核心问题。 In-Memory 表通常用于只读数据,为了扩展只读事务容量,您需要在更高端的机器上添加更多 SQL Server 实例。
In-Memory 表对数据的大小也有限制; 您不能将大表放在内存中,因为必须将整个表放在内存中。 并且它们到其他 SQL Server 实例的复制只能复制到其他 In-Memory 表而不是适当的数据库。
总而言之,SQL Server 和 Oracle 数据库中的这些内存优化无法完全满足 .NET Core 应用程序的可扩展性需求。
NoSQL数据库之所以流行,原因之一在于它们能够基于哈希算法和其他算法对数据进行合理的分区。这解决了SQL Server和Oracle等关系型数据库在事务处理能力方面面临的诸多可扩展性问题。
但是,NoSQL 数据库并非解决这些数据库瓶颈的理想方案,原因有以下几点:
上述所有问题的解决方案是使用 In-Memory Distributed Cache,如 NCache 在您的 .NET Core 应用程序部署中。 NCache 是一个开源的分布式缓存,适用于 .NET 和 .NET Core,速度极快且可线性扩展。您可以将其视为一个分布式内存数据存储。内存存储使其速度极快,而分布式特性使其可线性扩展。
NCache 是线性可扩展的,因为它构建了一个低成本缓存服务器的 TCP 集群(与您的 Web 应用程序服务器相同的配置,但具有更多内存)并将所有这些服务器的内存和 CPU 资源汇集到一个逻辑容量中。 NCache 然后允许您随着事务负载的增长在运行时将缓存服务器添加到此集群。 而且,由于 NCache 全部数据都在内存中运行,速度超快,可提供亚毫秒级的响应时间,这是关系型数据库甚至 NoSQL 数据库都无法实现的。
除了提供线性可扩展性之外,分布式缓存 NCache 智能地复制数据,因此您的性能不会受到影响,同时在任何缓存服务器出现故障的情况下实现数据可靠性。
NCache 可通过以下方式扩展您的 .NET Core 应用程序:
.NET Core 应用程序面临的最大瓶颈是“应用程序数据库”。 NCache 与 NoSQL 数据库不同, NCache 不会要求您停止使用现有的关系数据库。 您可以继续使用 SQL Server、Azure SQL 数据库、Oracle 等作为您的数据库,并且仍然可以通过使用实现线性可伸缩性 NCache 在您的关系数据库之上。 这是因为 NCache 消除了所有关系数据库的可扩展性瓶颈,因为与您的数据库不同, NCache 实际上是线性可扩展的。
应用程序数据缓存使您能够消除数据库瓶颈。 NCache 允许您缓存应用程序数据并减少那些昂贵的数据库访问。 您可以预期将 80-90% 的数据库流量转移到 NCache. 这减少了数据库的压力,并允许它更快地执行并处理更大的事务负载而不会减慢速度。
应用程序数据缓存意味着您缓存从关系数据库获得的任何应用程序数据。 这通常采用域对象(也称为实体)的形式。 这是一个如何使用分布式缓存的示例 NCache 用于应用程序数据缓存。
Customer Load(string custId)
{
ICache cache = CacheManager.GetCache("myCache");
string key = "Customer:CustomerID:" + custId;
Customer cust = cache.Get<Customer>(key);
if (cust == null)
{
// Item not in cache so load from db
LoadCustomerFromDb(cust);
// Add item to cache for future reference
cache.Add(key, cust);
}
return cust;
}
图 3:使用内存中分布式缓存进行应用数据缓存
另一个潜在的瓶颈是将 ASP.NET Core 会话存储在 SQL Server 或独立的 MemoryCache 中。这两种方案在性能和可扩展性方面都存在很大的局限性。SQL Server 存储并不适合 ASP.NET Core 会话,而且很快就会像应用程序数据一样成为性能瓶颈。
NCache 是存储 ASP.NET Core 会话的绝佳位置,因为它比其他存储选项速度更快、可扩展性更强。 NCache 速度更快,因为它基于内存,并提供键值对接口,其中值是一个“对象”,而 ASP.NET Core 会话就是一个对象。此外,它具有可扩展性,因为它是一个分布式缓存。
而且, NCache 它还通过其丰富的缓存拓扑结构智能地复制 ASP.NET Core 会话,因此即使缓存服务器发生故障,也不会丢失会话数据。这种复制是必要的,因为 NCache 提供内存存储,而内存是违反存储的。
NCache 还可以加快 ASP.NET Core Session 的序列化速度,这是在将其存储到进程外之前所必需的。 NCache 它利用动态紧凑序列化功能来实现这一点,该功能比常规的 .NET 和 .NET Core 序列化快 10 倍。您无需更改任何代码即可使用此功能。
您可以使用 NCache 可以通过两种方式将其作为 ASP.NET Core 会话存储。
以下示例展示了如何配置 ASP.NET Core 应用程序以使用 NCache 会话提供者:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Specify NCache as the session provider
services.AddNCacheSession(Configuration.GetSection("NCacheSettings"));
...
}
public void Configure(IApplicationBuilder app, ...)
{
// select NCache session provider for ASP.NET Core
app.UseNCacheSession();
...
}
}
图 4:插件 NCache 作为 ASP.NET Core 会话
ASP.NET Core 应用程序通常包含动态内容,但有时会遇到这样的情况:某些页面的内容或响应在多次请求中保持不变。然而,每次请求到来时,这些页面仍然需要执行。这不仅给 Web 服务器资源带来不必要的负担,也给应用程序的各个层级造成压力。最终,这会导致性能瓶颈,并限制应用程序的可扩展性。
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Turn on ASP.NET Core Response Cache with IDistributedCache
services.AddResponseCaching();
// Select NCache as IDistributedCache provider
services.AddNCacheDistributedCache(Configuration.GetSection("NCacheSettings"));
...
}
}
图 5:插件 NCache 作为 ASP.NET Core 响应缓存中间件
为了解决页面响应不变时重复执行页面带来的开销,ASP.NET Core 提供了一种名为 ASP.NET 响应缓存中间件的页面响应缓存机制。 NCache 由于 ASP.NET Core 中实现了 IDistributedCache 接口,因此您可以无缝地插入它。 NCache 作为您的 ASP.NET Core 响应缓存中间件。
因此,您可以使用 NCache 将 ASP.NET Core 页面响应缓存一段时间,以便下次使用相同参数调用同一页面时,可以直接返回缓存的响应,而无需重新执行整个页面。以下是配置方法的代码示例。 NCache 作为您的 ASP.NET Core 响应缓存中间件。
如果您的 ASP.NET Core 应用程序是实时 Web 应用程序,那么它很可能使用了 ASP.NET Core SignalR 来实现这种实时性。实时 Web 应用程序能够提供从服务器到客户端的高频更新。此类应用程序的示例包括游戏、拍卖、投票、社交网络等。
如果您的 ASP.NET Core 应用程序运行在负载均衡的多服务器环境中,则必须使用 ASP.NET Core SignalR Backplane 提供程序才能在多个 Web 服务器之间共享事件。而且,此 Backplane 必须具有可扩展性。否则,您的 ASP.NET Core SignalR 应用程序将开始面临性能瓶颈。
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Specify NCache as the ASP.NET Core SignalR Backplane
services.AddSignalR().AddNCache(ncacheOptions =>
{ ncacheOptions.CacheName = "myPartitionedCache"; });
...
}
public void Configure(IApplicationBuilder app, ...)
{
// Use SignalR in ASP.NET Core
app.UseSignalR(config => { config.MapHub<MessageHub>("/messages"); });
...
}
}
图 6:插件 NCache 作为 ASP.NET Core SignalR 背板提供程序
NCache 已实现 ASP.NET Core SignalR Backplane 提供程序。 NCache's ASP.NET Core SignalR Backplane 提供程序使用发布/订阅消息传递功能 NCache 由于完全在内存中运行,因此速度极快。这使得您的 ASP.NET Core SignalR 应用程序能够加快 SignalR 事件在所有 Web 服务器之间以及最终到客户端的传播速度。
并且,这使您的实时 Web 应用程序在向客户端提供这些频繁更新时更具响应性。 而且,您可以不断增加客户端数量并添加更多 Web 服务器,而不必担心任何性能瓶颈。
如果您的 .NET Core 应用程序需要使用发布/订阅消息传递或事件,那么它很可能使用的是并非完全内存化的发布/订阅消息传递平台,而是将所有消息存储在磁盘上。因此,如果您的应用程序事务处理量非常高,这很容易成为性能瓶颈。
NCache 还提供了超快的 Pub/Sub 消息传递,因为它完全是内存中的。 而且,它将所有消息复制到另一个 NCache 服务器以确保在任何一台服务器宕机的情况下不会丢失数据。
因此,如果你的 .NET Core 应用程序使用 NCache 作为其 Pub/Sub 消息传递平台,它将体验到超快的性能和线性可扩展性,因为 NCache 本身是线性可扩展的。
下面是如何使用 Pub/Sub 消息传递的示例,由 NCache 在您的 .NET Core 应用程序中。
private void PublishMessage (string topicName)
{
ITopic topic = _cache.MessagingService.GetTopic(topicName);
Order order = Order.GenerateOrder<Order>();
// Publish message containing "order" with expiry
Message message = new Message(order, new TimeSpan(0, 0, 15));
topic.Publish(message, DeliveryOption.All, true);
}
private ITopicSubscription SubscribeMessage (string topicName)
{
ITopic topic = _cache.MessagingService.GetTopic(topicName);
// Subscribes to the topic. Message delivered to MessageReceivedCallback
return topic.CreateSubscription(MessageReceivedCallback);
}
static void MessageReceivedCallback(object sender, MessageEventArgs args) { ... }
图 7:在 .NET Core 应用程序中使用发布/订阅消息传递
.NET Core 应用程序必须消除的最大可扩展性瓶颈在于应用程序数据库。通过应用程序数据缓存,应用程序可以实现高性能和线性可扩展性。原因很简单:大多数 .NET Core 应用程序都需要与数据库进行大量数据交互。
当谈到应用程序数据缓存时,人们最大的恐惧是缓存变得陈旧,这意味着它包含已由另一个用户或另一个应用程序在数据库中更改的旧版本数据。
这种对缓存过时的恐惧是如此强烈,以至于大多数人只缓存只读或静态数据(参考数据)。 但是,这些只读数据仅占查找表和其他参考数据形式的总数据的 20%。 数据库中的大部分数据都是事务性的,包括客户、帐户、活动等。而且,如果您不缓存这些事务性数据,那么您就不会从缓存中充分受益。
因此,如果您可以缓存所有类型的数据,而不必担心缓存变得陈旧,那么缓存的真正好处就来了。 NCache 提供了许多功能来解决这个问题。
保持缓存最新的最有效方法是始终使其与数据库保持同步。 NCache 允许您对各种数据库执行此操作,如下所示:
当您将缓存与 SQL Server 同步时,您会问 NCache 将自己注册为 SQL Server 的客户端,然后发出 SqlDependency 调用以及基于 SQL 查询的数据集。 然后,当 SQL Server 看到此数据集中的任何更改时,它会通知 NCache 关于它
private static void CreateSqlDependency (Product product)
{
string connectionString = "Data Source=localhost;Database=northwind;...";
// SQL stmt on which the SQL Dependency is created in SQL Server
string sqlStmt = "SELECT ProductID, ProductName, QuantityPerUnit, UnitPrice " +
"FROM dbo.PRODUCTS WHERE ProductID = " + product.Id;
CacheDependency sqlDependency = new SqlCacheDependency(connectionString, sqlStmt);
CacheItem cacheItem = new CacheItem(product) { Dependency = sqlDependency };
string key = "Product:ProductId:" + product.Id; ;
cache.Add(key, cacheItem);
}
图 8:使用 SqlDependency 与 SQL Server 同步缓存
然后, NCache 从缓存中删除这个项目,所以下次应用程序需要它时,它必须从数据库中获取最新的副本。 如果您使用的是通读处理程序(见下文),那么 NCache 还可以为您自动重新加载数据库中的最新副本。 下面是一个示例,说明如何使用 SqlDependency 将缓存与 SQL Server 同步。
Read-through Cache 是一种缓存,它能够通过调用您开发并提供给缓存的 Readthrough Handler 从数据库中读取数据。 类似地,直写缓存能够通过调用您开发并提供给缓存的直写处理程序将数据更改写入数据库。 Write-behind Cache 与 Write-through 相同,只是数据库更新是异步完成的。
读取穿透、写入穿透和写入后置为您的 .NET Core 应用程序带来诸多好处,包括:
下面是一个示例,说明如何使用 Read-through with NCache.
// Read through handler for SQL Server
public class SqlReadThruProvider : Runtime.DatasourceProviders.IReadThruProvider
{
public void Init(IDictionary parameters, string cacheId) {}
public void Dispose() {}
// Get object from the database/data-source based on the key
public ProviderCacheItem LoadFromSource(string key) {}
// Bulk-Get objects from the database/data-source based on the keys
public IDictionary<string, ProviderCacheItem> LoadFromSource(ICollection<string> keys)
{}
}
图 9:使用 Read-through Handler NCache
一旦您对缓存所有数据感到满意,您就可以开始将大量数据放入分布式缓存中。 在这里,您可以开始面临如何快速轻松地找到您的数据的另一个特殊问题。 由于大多数分布式缓存都是键值存储,因此仅通过键来跟踪所有数据变得非常困难。
这是哪里 NCache 为您提供多种快速从缓存中查找数据的方法。 示例包括:
下面是一个示例,说明如何使用基于 LINQ 的查询 NCache.
// Search the cache based on object attributes by using LINQ
IQueryable>Product< products = new NCacheQuery<Product>(_cache);
var result = from product in products
where product.Id > 10
select product;
if (result != null)
{
foreach (Product p in result1)
{
// Process each “product” fetched from the database
Console.WriteLine("ProductID : " + p.Id);
}
}
图 10:使用 LINQ 查询 NCache
高流量的 .NET Core 应用程序不能承受宕机,尤其是在高峰时段。对于这类应用程序,一个优秀的内存分布式缓存(例如 InMemory Distributed Cache)能够实现三个非常重要的架构目标。 NCache 满足。
让我在下面解释每一个。
NCache 提供一个客户端缓存,它是一个非常靠近您的应用程序的本地缓存。 它可以是 InProc(意味着它驻留在您的应用程序进程中)或本地 OutProc。 无论哪种方式,它都可以非常快速地访问此应用服务器上的应用程序此时需要的缓存数据子集。 客户端缓存同时与缓存层保持同步,因此缓存层中其他用户或应用程序更改的任何数据都会立即传播到客户端缓存。 客户端允许您拥有 InProc 速度,同时仍然是非常可扩展的缓存层的一部分。
最重要的架构目标之一 NCache 是通过其缓存拓扑实现具有数据可靠性的线性可扩展性。 这里有一些 NCache 缓存拓扑 这有助于实现这两个目标。
最重要的架构目标之一 NCache 就是实现高可用和缓存弹性。 它通过以下架构功能做到这一点: