返回首页 | 新开户送体验金的娱乐城

合作共赢、快速高效、优质的网站建设提供商

更多精品源码-尽在织梦模板-www.moke8.com

网站开发运用SQLite做本地数据缓存的考虑

时间:2017-10-11 编辑:admin

由于SQLite的列并不能直接存储完好的一个目标,需求将这个目标进行序列化之后 再进行存储,由于多了一些额定的操作,比较MemoryCache就耗费了多一点的时刻,

比方现在有一个Product类(有id,name两个字段)的实例obj,要存储这个实例,需求先对其进行序列化,转成一个JSON字符串后再进行存储。当然在读取的时分也就需求进行反序列化的操作才能够。

为了便利新开户送体验金的娱乐城缓存的接入,一致了一下缓存的进口,便于后边的运用。

/// summary 
/// Cache entry.
/// /summary 
public class CacheEntry
 /// summary 
 /// Initializes a new instance of the see cref= T:SQLiteCachingDemo.Caching.CacheEntry / class.
 /// /summary 
 /// param name= cacheKey Cache key. /param 
 /// param name= cacheValue Cache value. /param 
 /// param name= absoluteExpirationRelativeToNow Absolute expiration relative to now. /param 
 /// param name= isRemoveExpiratedAfterSetNewCachingItem If set to c true /c is remove expirated after set new caching item. /param 
 public CacheEntry(string cacheKey,
 object cacheValue,
 TimeSpan absoluteExpirationRelativeToNow,
 bool isRemoveExpiratedAfterSetNewCachingItem = true)
 if (string.IsNullOrWhiteSpace(cacheKey))
 throw new ArgumentNullException(nameof(cacheKey));
 if (cacheValue == null)
 throw new ArgumentNullException(nameof(cacheValue));
 if (absoluteExpirationRelativeToNow = TimeSpan.Zero)
 throw new ArgumentOutOfRangeException(
 nameof(AbsoluteExpirationRelativeToNow),
 absoluteExpirationRelativeToNow,
 The relative expiration value must be positive. 
 this.CacheKey = cacheKey;
 this.CacheValue = cacheValue;
 this.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow;
 this.IsRemoveExpiratedAfterSetNewCachingItem = isRemoveExpiratedAfterSetNewCachingItem;
 /// summary 
 /// Gets the cache key.
 /// /summary 
 /// value The cache key. /value 
 public string CacheKey { get; private set; }
 /// summary 
 /// Gets the cache value.
 /// /summary 
 /// value The cache value. /value 
 public object CacheValue { get; private set; }
 /// summary 
 /// Gets the absolute expiration relative to now.
 /// /summary 
 /// value The absolute expiration relative to now. /value 
 public TimeSpan AbsoluteExpirationRelativeToNow { get; private set; }
 /// summary 
 /// Gets a value indicating whether this see cref= T:SQLiteCachingDemo.Caching.CacheEntry / is remove
 /// expirated after set new caching item.
 /// /summary 
 /// value c true /c if is remove expirated after set new caching item; otherwise, c false /c . /value 
 public bool IsRemoveExpiratedAfterSetNewCachingItem { get; private set; }
 /// summary 
 /// Gets the serialize cache value.
 /// /summary 
 /// value The serialize cache value. /value 
 public string SerializeCacheValue
 if (this.CacheValue == null)
 throw new ArgumentNullException(nameof(this.CacheValue));
 else
 return JsonConvert.SerializeObject(this.CacheValue);
}

在缓存进口中,需求留意的是:

AbsoluteExpirationRelativeToNow , 缓存的过期时刻是相关于其时时刻(格林威治时刻)的肯定过期时刻。 IsRemoveExpiratedAfterSetNewCachingItem , 这个特点是用于处理是否在刺进新缓存时移除去一切过期的缓存项,这个在默许情况下是敞开的,防止有些操作要比较快的呼应,所以要能够将这个选项封闭掉,让其他缓存刺进操作去触发。 SerializeCacheValue , 序列化后的缓存目标,主要是用在刺进缓存项中,一致存储方法,也削减要刺进时需求进行多一步的有些序列化操作。 缓存进口的特点都是通过结构函数来进行初始化的。

然后是缓存接口的规划,这个都是比较常见的一些做法。

/// summary 
/// Caching Interface.
/// /summary 
public interface ICaching
 /// summary 
 /// Sets the async.
 /// /summary 
 /// returns The async. /returns 
 /// param name= cacheEntry Cache entry. /param 
 Task SetAsync(CacheEntry cacheEntry);
 /// summary 
 /// Gets the async.
 /// /summary 
 /// returns The async. /returns 
 /// param name= cacheKey Cache key. /param 
 Task object GetAsync(string cacheKey); 
 /// summary 
 /// Removes the async.
 /// /summary 
 /// returns The async. /returns 
 /// param name= cacheKey Cache key. /param 
 Task RemoveAsync(string cacheKey); 
 /// summary 
 /// Flushs all expiration async.
 /// /summary 
 /// returns The all expiration async. /returns 
 Task FlushAllExpirationAsync();
}

由于都是数据库的操作,防止不必要的资源糟蹋,就把接口都规划成异步的了。这儿只要增删查的操作,没有更新的操作。

最终就是怎么完结的问题了。完结上凭借了Dapper来完结相应的数据库操作,平常是Dapper混搭其他ORM来用的。

想想不弄那么杂乱,就只用Dapper来处理就OK了。

/// summary 
/// SQLite caching.
/// /summary 
public class SQLiteCaching : ICaching
 /// summary 
 /// The connection string of SQLite database.
 /// /summary 
 private readonly string connStr = $ Data Source ={Path.Combine(Directory.GetCurrentDirectory(), localcaching.sqlite )} 
 /// summary 
 /// The tick to time stamp.
 /// /summary 
 private readonly int TickToTimeStamp = 10000000;
 /// summary 
 /// Flush all expirated caching items.
 /// /summary 
 /// returns /returns 
 public async Task FlushAllExpirationAsync()
 using (var conn = new SqliteConnection(connStr))
 var sql = DELETE FROM [caching] WHERE [expiration] STRFTIME( %s , now ) 
 await conn.ExecuteAsync(sql);
 /// summary 
 /// Get caching item by cache key.
 /// /summary 
 /// returns /returns 
 /// param name= cacheKey Cache key. /param 
 public async Task object GetAsync(string cacheKey)
 using (var conn = new SqliteConnection(connStr))
 var sql = @ SELECT [cachevalue]
 FROM [caching]
 WHERE [cachekey] = @cachekey AND [expiration] STRFTIME( %s , now ) 
 var res = await conn.ExecuteScalarAsync(sql, new
 cachekey = cacheKey
 // deserialize object .
 return res == null ? null : JsonConvert.DeserializeObject(res.ToString());
 /// summary 
 /// Remove caching item by cache key.
 /// /summary 
 /// returns /returns 
 /// param name= cacheKey Cache key. /param 
 public async Task RemoveAsync(string cacheKey)
 using (var conn = new SqliteConnection(connStr))
 var sql = DELETE FROM [caching] WHERE [cachekey] = @cachekey 
 await conn.ExecuteAsync(sql , new 
 cachekey = cacheKey
 /// summary 
 /// Set caching item.
 /// /summary 
 /// returns /returns 
 /// param name= cacheEntry Cache entry. /param 
 public async Task SetAsync(CacheEntry cacheEntry)
 using (var conn = new SqliteConnection(connStr))
 //1. Delete the old caching item at first .
 var deleteSql = DELETE FROM [caching] WHERE [cachekey] = @cachekey 
 await conn.ExecuteAsync(deleteSql, new
 cachekey = cacheEntry.CacheKey
 //2. Insert a new caching item with specify cache key.
 var insertSql = @ INSERT INTO [caching](cachekey,cachevalue,expiration)
 VALUES(@cachekey,@cachevalue,@expiration) 
 await conn.ExecuteAsync(insertSql, new
 cachekey = cacheEntry.CacheKey,
 cachevalue = cacheEntry.SerializeCacheValue,
 expiration = await GetCurrentUnixTimestamp(cacheEntry.AbsoluteExpirationRelativeToNow)
 if(cacheEntry.IsRemoveExpiratedAfterSetNewCachingItem)
 // remove all expirated caching item when new caching item was set .
 await FlushAllExpirationAsync(); 
 /// summary 
 /// Get the current unix timestamp.
 /// /summary 
 /// returns The current unix timestamp. /returns 
 /// param name= absoluteExpiration Absolute expiration. /param 
 private async Task long GetCurrentUnixTimestamp(TimeSpan absoluteExpiration)
 using (var conn = new SqliteConnection(connStr))
 var sql = SELECT STRFTIME( %s , now ) 
 var res = await conn.ExecuteScalarAsync(sql);
 //get current utc timestamp and plus absolute expiration 
 return long.Parse(res.ToString()) + (absoluteExpiration.Ticks / TickToTimeStamp);
}

这儿需求留意下面几个:

SQLite并没有严厉意义上的时刻类型,所以在这儿用了时刻戳来处理缓存过期的问题。 运用SQLite内置函数 STRFTIME('%s','now') 来获取时刻戳相关的数据,这个函数获取的是格林威治时刻,一切的操作都是以这个时刻为基准。 在刺进一条缓存数据的时分,会先履行一次删去操作,防止主键抵触的问题。 读取的时分就做了一次反序列化操作,简化调用操作。 TickToTimeStamp , 这个是过期时刻转化成时刻戳的变换单位。

最终的话,天然就是怎么运用的问题了。

首先是在IServiceCollection中注册一下

service.AddSingleton ICaching,SQLiteCaching 

然后在控制器的结构函数中进行注入。

private readonly ICaching _caching;
public HomeController(ICaching caching)
 this._caching = caching;
}

刺进缓存时,需求先实例化一个CacheEntry目标,依据这个目标来进行相应的处理。

var obj = new Product()
 Id = 123 ,
 Name = Product123 
var cacheEntry = new CacheEntry( mykey , obj, TimeSpan.FromSeconds(3600));
await _caching.SetAsync(cacheEntry);

从缓存中读取数据时,主张是用dynamic去接纳,由于其时没有考虑泛型的处理。

dynamic product = await _caching.GetAsync( mykey 
var id = product.Id;
var name = product.Name;

从缓存中移除缓存项的两个操作如下所示。

//移除指定键的缓存项
await _caching.RemoveAsync( mykey 
//移除一切过期的缓存项
await _caching.FlushAllExpirationAsync();

通过在Mac book Pro上简略的测验,从几十万数据中并行读取1000条到10000条记载也都能够在零点几ms中完结。

这个在高读写比的体系中应该是比较有优势的。

可是并行的刺进就相对要慢不少了,并行的刺进一万条记载,直接就数据库死锁了。1000条还牵强能在20000ms搞定!

这个是由SQLite自身所支撑的并发性导致的,别的刺进缓存数据时都会开一个数据库的衔接,这也是比较耗时的,所以这儿能够考虑做一下后续的优化。

移除一切过期的缓存项能够在一两百ms内搞定。

当然,还应该在不同的机器上进行更多的模仿测验,这样得到的作用比较真实可信。

SQLite做本地缓存有它自己的优势,也有它的下风。

优势:

无需网络衔接 读取数据快

下风:

高一点并发的时分就有可能over了 读写都需求进行序列化操作

虽然并发高的时分能够会有问题,可是在进入使用服务器的前已经是通过一层负载均衡的分流了,所以这儿理论上对中小型使用影响不会太大。

别的关于缓存的滑动过期时刻,文中并没有完结,能够在这个基础上进行弥补修正,然后使其能支撑滑动过期。

本文演示Demo


浏览:

网站建设

流程

    网站建设流程