HybridCache in What's new doc (#32593)

pull/32586/head
Tom Dykstra 2024-05-20 10:39:51 -07:00 committed by GitHub
parent 43ffb067e6
commit 6eca4d1274
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 139 additions and 3 deletions

View File

@ -4,14 +4,14 @@ author: rick-anderson
description: Learn about the new features in ASP.NET Core 9.0.
ms.author: riande
ms.custom: mvc
ms.date: 04/17/2024
ms.date: 05/17/2024
uid: aspnetcore-9
---
# What's new in ASP.NET Core 9.0
This article highlights the most significant changes in ASP.NET Core 9.0 with links to relevant documentation.
This article has been updated for .NET 9 Preview 3.
This article has been updated for .NET 9 Preview 4.
<!-- New content should be added to ~/aspnetcore-9/includes/newFeatureName.md files. This will help prevent merge conflicts in this file. -->
@ -21,7 +21,6 @@ This section describes new features for Blazor.
[!INCLUDE[](~/release-notes/aspnetcore-9/includes/blazor.md)]
## SignalR
This section describes new features for SignalR.
@ -46,6 +45,8 @@ This section describes new features for authentication and authorization.
The following sections describe miscellaneous new features.
[!INCLUDE[](~/release-notes/aspnetcore-9/includes/hybrid-cache.md)]
[!INCLUDE[](~/release-notes/aspnetcore-9/includes/endpoint-metadata.md)]
[!INCLUDE[](~/release-notes/aspnetcore-9/includes/debugger.md)]

View File

@ -0,0 +1,135 @@
---
ms.topic: include
author: mgravell
ms.author: marcgravell
ms.date: 05/17/2024
---
### New `HybridCache` library
The [`HybridCache`](https://source.dot.net/#Microsoft.Extensions.Caching.Hybrid/Runtime/HybridCache.cs,8c0fe94693d1ac8d) API bridges some gaps in the existing <xref:Microsoft.Extensions.Caching.Distributed.IDistributedCache> and <xref:Microsoft.Extensions.Caching.Memory.IMemoryCache> APIs. It also adds new capabilities, such as:
* **"Stampede" protection** to prevent parallel fetches of the same work.
* Configurable serialization.
`HybridCache` is designed to be a drop-in replacement for existing `IDistributedCache` and `IMemoryCache` usage, and it provides a simple API for adding new caching code. It provides a unified API for both in-process and out-of-process caching.
To see how the `HybridCache` API is simplified, compare it to code that uses `IDistributedCache`. Here's an example of what using `IDistributedCache` looks like:
```csharp
public class SomeService(IDistributedCache cache)
{
public async Task<SomeInformation> GetSomeInformationAsync
(string name, int id, CancellationToken token = default)
{
var key = $"someinfo:{name}:{id}"; // Unique key for this combination.
var bytes = await cache.GetAsync(key, token); // Try to get from cache.
SomeInformation info;
if (bytes is null)
{
// Cache miss; get the data from the real source.
info = await SomeExpensiveOperationAsync(name, id, token);
// Serialize and cache it.
bytes = SomeSerializer.Serialize(info);
await cache.SetAsync(key, bytes, token);
}
else
{
// Cache hit; deserialize it.
info = SomeSerializer.Deserialize<SomeInformation>(bytes);
}
return info;
}
// This is the work we're trying to cache.
private async Task<SomeInformation> SomeExpensiveOperationAsync(string name, int id,
CancellationToken token = default)
{ /* ... */ }
}
```
That's a lot of work to get right each time, including things like serialization. And in the cache miss scenario, you could end up with multiple concurrent threads, all getting a cache miss, all fetching the underlying data, all serializing it, and all sending that data to the cache.
To simplify and improve this code with `HybridCache`, we first need to add the new library `Microsoft.Extensions.Caching.Hybrid`:
``` xml
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0" />
```
Register the `HybridCache` service, like you would register an `IDistributedCache` implementation:
```csharp
services.AddHybridCache(); // Not shown: optional configuration API.
```
Now most caching concerns can be offloaded to `HybridCache`:
```csharp
public class SomeService(HybridCache cache)
{
public async Task<SomeInformation> GetSomeInformationAsync
(string name, int id, CancellationToken token = default)
{
return await cache.GetOrCreateAsync(
$"someinfo:{name}:{id}", // Unique key for this combination.
async cancel => await SomeExpensiveOperationAsync(name, id, cancel),
token: token
);
}
}
```
We provide a concrete implementation of the `HybridCache` abstract class via dependency injection, but it's intended that developers can provide custom implementations of the API. The `HybridCache` implementation deals with everything related to caching, including concurrent operation handling. The `cancel` token here represents the combined cancellation of *all* concurrent callers&mdash;not just the cancellation of the caller we can see (that is, `token`).
High throughput scenarios can be further optimized by using the `TState` pattern, to avoid some overhead from captured variables and per-instance callbacks:
```csharp
public class SomeService(HybridCache cache)
{
public async Task<SomeInformation> GetSomeInformationAsync(string name, int id, CancellationToken token = default)
{
return await cache.GetOrCreateAsync(
$"someinfo:{name}:{id}", // unique key for this combination
(name, id), // all of the state we need for the final call, if needed
static async (state, token) =>
await SomeExpensiveOperationAsync(state.name, state.id, token),
token: token
);
}
}
```
`HybridCache` uses the configured `IDistributedCache` implementation, if any, for secondary out-of-process caching, for example, using
Redis. But even without an `IDistributedCache`, the `HybridCache` service will still provide in-process caching and "stampede" protection.
#### A note on object reuse
In typical existing code that uses `IDistributedCache`, every retrieval of an object from the cache results in deserialization. This behavior means that each concurrent caller gets a separate instance of the object, which cannot interact with other instances. The result is thread safety, as there's no risk of concurrent modifications to the same object instance.
Because a lot of `HybridCache` usage will be adapted from existing `IDistributedCache` code, `HybridCache` preserves this behavior by default to avoid introducing concurrency bugs. However, a given use case is inherently thread-safe:
* If the types being cached are immutable.
* If the code doesn't modify them.
In such cases, inform `HybridCache` that it's safe to reuse instances by:
* Marking the type as `sealed`. The `sealed` keyword in C# means that the class cannot be inherited.
* Applying the `[ImmutableObject(true)]` attribute to it. Yhe `[ImmutableObject(true)]` attribute indicates that the object's state cannot be changed after it's created.
By reusing instances, `HybridCache` can reduce the overhead of CPU and object allocations associated with per-call deserialization. This can lead to performance improvements in scenarios where the cached objects are large or accessed frequently.
#### Other `HybridCache` features
Like `IDistributedCache`, `HybridCache` supports removal by key with a `RemoveKeyAsync` method.
`HybridCache` also provides optional APIs for `IDistributedCache` implementations, to avoid `byte[]` allocations. This feature is implemented
by the preview versions of the `Microsoft.Extensions.Caching.StackExchangeRedis` and `Microsoft.Extensions.Caching.SqlServer` packages.
Serialization is configured as part of registering the service, with support for type-specific and generalized serializers via the
`WithSerializer` and `.WithSerializerFactory` methods, chained from the `AddHybridCache` call. By default, the library
handles `string` and `byte[]` internally, and uses `System.Text.Json` for everything else, but you can use protobuf, xml, or anything
else.
`HybridCache` supports older .NET runtimes, down to .NET Framework 4.7.2 and .NET Standard 2.0.
For more information about `HybridCache`, including planned features, see GitHub issue [dotnet/aspnetcore #54647](https://github.com/dotnet/aspnetcore/issues/54647).