zoukankan      html  css  js  c++  java
  • .Net Core 缓存方式(二)DistributedSqlServerCache实现(2)

    .Net Core 缓存方式(二)DistributedSqlServerCache实现(2)

    DistributedSqlServerCache 是什么

    DistributedSqlServerCache是使用 SQL Server database 实现分布式缓存

    使用方式

    • Startup.ConfigureServices
    services.AddDistributedSqlServerCache(options =>
    {
        options.ConnectionString = 
            _config["DistCache_ConnectionString"];
        options.SchemaName = "dbo";
        options.TableName = "TestCache";
    });
    

    源码以及实现

    • SqlServerCachingServicesExtensions
        /// <summary>
        /// Extension methods for setting up Microsoft SQL Server distributed cache services in an <see cref="IServiceCollection" />.
        /// </summary>
        public static class SqlServerCachingServicesExtensions
        {
            /// <summary>
            /// Adds Microsoft SQL Server distributed caching services to the specified <see cref="IServiceCollection" />.
            /// </summary>
            /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
            /// <param name="setupAction">An <see cref="Action{SqlServerCacheOptions}"/> to configure the provided <see cref="SqlServerCacheOptions"/>.</param>
            /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
            public static IServiceCollection AddDistributedSqlServerCache(this IServiceCollection services, Action<SqlServerCacheOptions> setupAction)
            {
                if (services == null)
                {
                    throw new ArgumentNullException(nameof(services));
                }
    
                if (setupAction == null)
                {
                    throw new ArgumentNullException(nameof(setupAction));
                }
    
                services.AddOptions();
                AddSqlServerCacheServices(services);
                services.Configure(setupAction);
    
                return services;
            }
    
            // to enable unit testing
            internal static void AddSqlServerCacheServices(IServiceCollection services)
            {
                services.Add(ServiceDescriptor.Singleton<IDistributedCache, SqlServerCache>());
            }
        }
    
    • SqlServerCache
    // Licensed to the .NET Foundation under one or more agreements.
    // The .NET Foundation licenses this file to you under the MIT license.
    // See the LICENSE file in the project root for more information.
    
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Extensions.Caching.Distributed;
    using Microsoft.Extensions.Internal;
    using Microsoft.Extensions.Options;
    
    namespace Microsoft.Extensions.Caching.SqlServer
    {
        /// <summary>
        /// Distributed cache implementation using Microsoft SQL Server database.
        /// </summary>
        public class SqlServerCache : IDistributedCache
        {
            private static readonly TimeSpan MinimumExpiredItemsDeletionInterval = TimeSpan.FromMinutes(5);
            private static readonly TimeSpan DefaultExpiredItemsDeletionInterval = TimeSpan.FromMinutes(30);
    
            private readonly IDatabaseOperations _dbOperations;
            private readonly ISystemClock _systemClock;
            private readonly TimeSpan _expiredItemsDeletionInterval;
            private DateTimeOffset _lastExpirationScan;
            private readonly Action _deleteExpiredCachedItemsDelegate;
            private readonly TimeSpan _defaultSlidingExpiration;
    
            public SqlServerCache(IOptions<SqlServerCacheOptions> options)
            {
                var cacheOptions = options.Value;
    
                if (string.IsNullOrEmpty(cacheOptions.ConnectionString))
                {
                    throw new ArgumentException(
                        $"{nameof(SqlServerCacheOptions.ConnectionString)} cannot be empty or null.");
                }
                if (string.IsNullOrEmpty(cacheOptions.SchemaName))
                {
                    throw new ArgumentException(
                        $"{nameof(SqlServerCacheOptions.SchemaName)} cannot be empty or null.");
                }
                if (string.IsNullOrEmpty(cacheOptions.TableName))
                {
                    throw new ArgumentException(
                        $"{nameof(SqlServerCacheOptions.TableName)} cannot be empty or null.");
                }
                if (cacheOptions.ExpiredItemsDeletionInterval.HasValue &&
                    cacheOptions.ExpiredItemsDeletionInterval.Value < MinimumExpiredItemsDeletionInterval)
                {
                    throw new ArgumentException(
                        $"{nameof(SqlServerCacheOptions.ExpiredItemsDeletionInterval)} cannot be less than the minimum " +
                        $"value of {MinimumExpiredItemsDeletionInterval.TotalMinutes} minutes.");
                }
                if (cacheOptions.DefaultSlidingExpiration <= TimeSpan.Zero)
                {
                    throw new ArgumentOutOfRangeException(
                        nameof(cacheOptions.DefaultSlidingExpiration),
                        cacheOptions.DefaultSlidingExpiration,
                        "The sliding expiration value must be positive.");
                }
    
                _systemClock = cacheOptions.SystemClock ?? new SystemClock();
                _expiredItemsDeletionInterval =
                    cacheOptions.ExpiredItemsDeletionInterval ?? DefaultExpiredItemsDeletionInterval;
                _deleteExpiredCachedItemsDelegate = DeleteExpiredCacheItems;
                _defaultSlidingExpiration = cacheOptions.DefaultSlidingExpiration;
    
                // SqlClient library on Mono doesn't have support for DateTimeOffset and also
                // it doesn't have support for apis like GetFieldValue, GetFieldValueAsync etc.
                // So we detect the platform to perform things differently for Mono vs. non-Mono platforms.
                if (PlatformHelper.IsMono)
                {
                    _dbOperations = new MonoDatabaseOperations(
                        cacheOptions.ConnectionString,
                        cacheOptions.SchemaName,
                        cacheOptions.TableName,
                        _systemClock);
                }
                else
                {
                    _dbOperations = new DatabaseOperations(
                        cacheOptions.ConnectionString,
                        cacheOptions.SchemaName,
                        cacheOptions.TableName,
                        _systemClock);
                }
            }
    
            public byte[] Get(string key)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                var value = _dbOperations.GetCacheItem(key);
    
                ScanForExpiredItemsIfRequired();
    
                return value;
            }
    
            public async Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken))
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                token.ThrowIfCancellationRequested();
    
                var value = await _dbOperations.GetCacheItemAsync(key, token).ConfigureAwait(false);
    
                ScanForExpiredItemsIfRequired();
    
                return value;
            }
    
            public void Refresh(string key)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                _dbOperations.RefreshCacheItem(key);
    
                ScanForExpiredItemsIfRequired();
            }
    
            public async Task RefreshAsync(string key, CancellationToken token = default(CancellationToken))
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                token.ThrowIfCancellationRequested();
    
                await _dbOperations.RefreshCacheItemAsync(key, token).ConfigureAwait(false);
    
                ScanForExpiredItemsIfRequired();
            }
    
            public void Remove(string key)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                _dbOperations.DeleteCacheItem(key);
    
                ScanForExpiredItemsIfRequired();
            }
    
            public async Task RemoveAsync(string key, CancellationToken token = default(CancellationToken))
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                token.ThrowIfCancellationRequested();
    
                await _dbOperations.DeleteCacheItemAsync(key, token).ConfigureAwait(false);
    
                ScanForExpiredItemsIfRequired();
            }
    
            public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }
    
                if (options == null)
                {
                    throw new ArgumentNullException(nameof(options));
                }
    
                GetOptions(ref options);
    
                _dbOperations.SetCacheItem(key, value, options);
    
                ScanForExpiredItemsIfRequired();
            }
    
            public async Task SetAsync(
                string key,
                byte[] value,
                DistributedCacheEntryOptions options,
                CancellationToken token = default(CancellationToken))
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }
    
                if (options == null)
                {
                    throw new ArgumentNullException(nameof(options));
                }
    
                token.ThrowIfCancellationRequested();
    
                GetOptions(ref options);
    
                await _dbOperations.SetCacheItemAsync(key, value, options, token).ConfigureAwait(false);
    
                ScanForExpiredItemsIfRequired();
            }
    
            // Called by multiple actions to see how long it's been since we last checked for expired items.
            // If sufficient time has elapsed then a scan is initiated on a background task.
            private void ScanForExpiredItemsIfRequired()
            {
                var utcNow = _systemClock.UtcNow;
                // TODO: Multiple threads could trigger this scan which leads to multiple calls to database.
                if ((utcNow - _lastExpirationScan) > _expiredItemsDeletionInterval)
                {
                    _lastExpirationScan = utcNow;
                    Task.Run(_deleteExpiredCachedItemsDelegate);
                }
            }
    
            private void DeleteExpiredCacheItems()
            {
                _dbOperations.DeleteExpiredCacheItems();
            }
    
            private void GetOptions(ref DistributedCacheEntryOptions options)
            {
                if (!options.AbsoluteExpiration.HasValue
                    && !options.AbsoluteExpirationRelativeToNow.HasValue
                    && !options.SlidingExpiration.HasValue)
                {
                    options = new DistributedCacheEntryOptions()
                    {
                        SlidingExpiration = _defaultSlidingExpiration
                    };
                }
            }
        }
    }
    
    
    • IDatabaseOperations
        internal interface IDatabaseOperations
        {
            byte[] GetCacheItem(string key);
    
            Task<byte[]> GetCacheItemAsync(string key, CancellationToken token = default(CancellationToken));
    
            void RefreshCacheItem(string key);
    
            Task RefreshCacheItemAsync(string key, CancellationToken token = default(CancellationToken));
    
            void DeleteCacheItem(string key);
    
            Task DeleteCacheItemAsync(string key, CancellationToken token = default(CancellationToken));
    
            void SetCacheItem(string key, byte[] value, DistributedCacheEntryOptions options);
    
            Task SetCacheItemAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken));
    
            void DeleteExpiredCacheItems();
        }
    
    • SqlQueries sql 语句
        internal class SqlQueries
        {
            private const string TableInfoFormat =
                "SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE " +
                "FROM INFORMATION_SCHEMA.TABLES " +
                "WHERE TABLE_SCHEMA = '{0}' " +
                "AND TABLE_NAME = '{1}'";
    
            private const string UpdateCacheItemFormat =
            "UPDATE {0} " +
            "SET ExpiresAtTime = " +
                "(CASE " +
                    "WHEN DATEDIFF(SECOND, @UtcNow, AbsoluteExpiration) <= SlidingExpirationInSeconds " +
                    "THEN AbsoluteExpiration " +
                    "ELSE " +
                    "DATEADD(SECOND, SlidingExpirationInSeconds, @UtcNow) " +
                "END) " +
            "WHERE Id = @Id " +
            "AND @UtcNow <= ExpiresAtTime " +
            "AND SlidingExpirationInSeconds IS NOT NULL " +
            "AND (AbsoluteExpiration IS NULL OR AbsoluteExpiration <> ExpiresAtTime) ;";
    
            private const string GetCacheItemFormat =
                "SELECT Id, ExpiresAtTime, SlidingExpirationInSeconds, AbsoluteExpiration, Value " +
                "FROM {0} WHERE Id = @Id AND @UtcNow <= ExpiresAtTime;";
    
            private const string SetCacheItemFormat =
                "DECLARE @ExpiresAtTime DATETIMEOFFSET; " +
                "SET @ExpiresAtTime = " +
                "(CASE " +
                        "WHEN (@SlidingExpirationInSeconds IS NUll) " +
                        "THEN @AbsoluteExpiration " +
                        "ELSE " +
                        "DATEADD(SECOND, Convert(bigint, @SlidingExpirationInSeconds), @UtcNow) " +
                "END);" +
                "UPDATE {0} SET Value = @Value, ExpiresAtTime = @ExpiresAtTime," +
                "SlidingExpirationInSeconds = @SlidingExpirationInSeconds, AbsoluteExpiration = @AbsoluteExpiration " +
                "WHERE Id = @Id " +
                "IF (@@ROWCOUNT = 0) " +
                "BEGIN " +
                    "INSERT INTO {0} " +
                    "(Id, Value, ExpiresAtTime, SlidingExpirationInSeconds, AbsoluteExpiration) " +
                    "VALUES (@Id, @Value, @ExpiresAtTime, @SlidingExpirationInSeconds, @AbsoluteExpiration); " +
                "END ";
    
            private const string DeleteCacheItemFormat = "DELETE FROM {0} WHERE Id = @Id";
    
            public const string DeleteExpiredCacheItemsFormat = "DELETE FROM {0} WHERE @UtcNow > ExpiresAtTime";
    
            public SqlQueries(string schemaName, string tableName)
            {
                var tableNameWithSchema = string.Format(
                    "{0}.{1}", DelimitIdentifier(schemaName), DelimitIdentifier(tableName));
    
                // when retrieving an item, we do an UPDATE first and then a SELECT
                GetCacheItem = string.Format(UpdateCacheItemFormat + GetCacheItemFormat, tableNameWithSchema);
                GetCacheItemWithoutValue = string.Format(UpdateCacheItemFormat, tableNameWithSchema);
                DeleteCacheItem = string.Format(DeleteCacheItemFormat, tableNameWithSchema);
                DeleteExpiredCacheItems = string.Format(DeleteExpiredCacheItemsFormat, tableNameWithSchema);
                SetCacheItem = string.Format(SetCacheItemFormat, tableNameWithSchema);
                TableInfo = string.Format(TableInfoFormat, EscapeLiteral(schemaName), EscapeLiteral(tableName));
            }
    
            public string TableInfo { get; }
    
            public string GetCacheItem { get; }
    
            public string GetCacheItemWithoutValue { get; }
    
            public string SetCacheItem { get; }
    
            public string DeleteCacheItem { get; }
    
            public string DeleteExpiredCacheItems { get; }
    
            // From EF's SqlServerQuerySqlGenerator
            private string DelimitIdentifier(string identifier)
            {
                return "[" + identifier.Replace("]", "]]") + "]";
            }
    
            private string EscapeLiteral(string literal)
            {
                return literal.Replace("'", "''");
            }
        }
    
                // SqlClient library on Mono doesn't have support for DateTimeOffset and also
                // it doesn't have support for apis like GetFieldValue, GetFieldValueAsync etc.
                // So we detect the platform to perform things differently for Mono vs. non-Mono platforms.
                if (PlatformHelper.IsMono)
                {
                    _dbOperations = new MonoDatabaseOperations(
                        cacheOptions.ConnectionString,
                        cacheOptions.SchemaName,
                        cacheOptions.TableName,
                        _systemClock);
                }
                else
                {
                    _dbOperations = new DatabaseOperations(
                        cacheOptions.ConnectionString,
                        cacheOptions.SchemaName,
                        cacheOptions.TableName,
                        _systemClock);
                }
    
  • 相关阅读:
    POJ 1724 ROADS【最短路/搜索/DP】
    UVA 12716 GCD XOR【异或】
    UVA 10375 Choose and divide【唯一分解定理】
    UVA 12169 Disgruntled Judge【扩展欧几里德】
    UVA 11582 Colossal Fibonacci Numbers!【数学】
    011.progit笔记---git变基rebase
    010.progit笔记---git多个远程分支
    009.progit笔记---git单个远程分支
    008.progit笔记---git分支
    007.progit笔记---git别名
  • 原文地址:https://www.cnblogs.com/WNpursue/p/13498437.html
Copyright © 2011-2022 走看看