using System;
using System.Text;
using System.Threading;
using NewLife.Log;
using NewLife.Threading;
using XCode.DataAccessLayer;
namespace XCode.Cache
{
/// <summary>实体缓存</summary>
/// <remarks>
/// 关于异步缓存,非常有用!
/// 第一次读取缓存的时候,同步从数据库读取,这样子手上有一份数据。
/// 以后更新,都开异步线程去读取,而当前马上返回,让大家继续用着旧数据,这么做性能非常好。
/// </remarks>
/// <typeparam name="TEntity">实体类型</typeparam>
public class EntityCache<TEntity> : CacheBase<TEntity>, IEntityCache where TEntity : Entity<TEntity>, new()
{
#region 基础属性
/// <summary>缓存过期时间</summary>
DateTime ExpiredTime { get; set; }
/// <summary>缓存更新次数</summary>
private Int64 Times { get; set; }
/// <summary>过期时间。单位是秒,默认60秒</summary>
public Int32 Expire { get; set; }
/// <summary>异步更新,默认打开</summary>
public Boolean Asynchronous { get; set; }
/// <summary>是否在使用缓存,在不触发缓存动作的情况下检查是否有使用缓存</summary>
internal Boolean Using { get; private set; }
/// <summary>当前获得锁的线程</summary>
private Int32 _thread = 0;
#endregion
#region 构造
/// <summary>实例化实体缓存</summary>
public EntityCache()
{
Expire = Setting.Current.Cache.EntityCacheExpire;
Asynchronous = true;
}
#endregion
#region 缓存核心
private EntityList<TEntity> _Entities = new EntityList<TEntity>();
/// <summary>实体集合。无数据返回空集合而不是null</summary>
public EntityList<TEntity> Entities
{
get
{
// 更新统计信息
XCache.CheckShowStatics(ref NextShow, ref Total, ShowStatics);
// 只要访问了实体缓存数据集合,就认为是使用了实体缓存,允许更新缓存数据期间向缓存集合添删数据
Using = true;
// 两种情况更新缓存:1,缓存过期;2,不允许空但是集合又是空
Boolean nodata = _Entities.Count == 0;
if (nodata || DateTime.Now >= ExpiredTime)
{
// 为了确保缓存数据有效可用,这里必须加锁,保证第一个线程更新拿到数据之前其它线程全部排队
// 即使打开了异步更新,首次读取数据也是同步
// 这里特别要注意,第一个线程取得锁以后,如果因为设计失误,导致重复进入缓存,这是设计错误
//!!! 所有加锁的地方都务必消息,同一个线程可以重入同一个锁
//if (_thread == Thread.CurrentThread.ManagedThreadId) throw new XCodeException("设计错误!当前线程正在获取缓存,在完成之前,本线程不应该使用实体缓存!");
// 同一个线程重入查询实体缓存时,直接返回已有缓存或者空,这符合一般设计逻辑
if (_thread == Thread.CurrentThread.ManagedThreadId) return _Entities;
lock (this)
{
_thread = Thread.CurrentThread.ManagedThreadId;
nodata = _Entities.Count == 0;
if (nodata || DateTime.Now >= ExpiredTime)
UpdateCache(nodata);
else
Interlocked.Increment(ref Shoot2);
_thread = 0;
}
}
else
Interlocked.Increment(ref Shoot1);
return _Entities;
}
}
private Func<EntityList<TEntity>> _FillListMethod;
/// <summary>填充数据的方法</summary>
public Func<EntityList<TEntity>> FillListMethod
{
get
{
if (_FillListMethod == null) _FillListMethod = Entity<TEntity>.FindAll;
return _FillListMethod;
}
set { _FillListMethod = value; }
}
#endregion
#region 缓存操作
void UpdateCache(Boolean nodata)
{
// 异步更新时,如果为空,表明首次,同步获取数据
// 有且仅有非首次且数据不为空时执行异步查询
if (Times > 0 && Asynchronous && !nodata)
{
// 这里直接计算有效期,避免每次判断缓存有效期时进行的时间相加而带来的性能损耗
// 设置时间放在获取缓存之前,让其它线程不要空等
ExpiredTime = DateTime.Now.AddSeconds(Expire);
Times++;
if (Debug)
{
var reason = Times == 1 ? "第一次" : (nodata ? "无缓存数据" : Expire + "秒过期");
DAL.WriteLog("异步更新实体缓存(第{2}次):{0} 原因:{1} {3}", typeof(TEntity).FullName, reason, Times, XTrace.GetCaller(3, 16));
}
ThreadPoolX.QueueUserWorkItem(FillWaper, Times);
}
else
{
Times++;
if (Debug)
{
var reason = Times == 1 ? "第一次" : (nodata ? "无缓存数据" : Expire + "秒过期");
DAL.WriteLog("更新实体缓存(第{2}次):{0} 原因:{1} {3}", typeof(TEntity).FullName, reason, Times, XTrace.GetCaller(3, 16));
}
FillWaper(Times);
// 这里直接计算有效期,避免每次判断缓存有效期时进行的时间相加而带来的性能损耗
// 设置时间放在获取缓存之后,避免缓存尚未拿到,其它线程拿到空数据
ExpiredTime = DateTime.Now.AddSeconds(Expire);
}
}
private void FillWaper(Object state)
{
_Entities = Invoke<Object, EntityList<TEntity>>(s => FillListMethod(), null);
if (Debug) DAL.WriteLog("完成更新缓存(第{1}次):{0}", typeof(TEntity).FullName, state);
}
/// <summary>清除缓存</summary>
public void Clear(String reason = null)
{
lock (this)
{
if (_Entities.Count > 0 && Debug) DAL.WriteLog("清空实体缓存:{0} 原因:{1}", typeof(TEntity).FullName, reason);
// 使用异步时,马上打开异步查询更新数据
if (Asynchronous && _Entities.Count > 0)
UpdateCache(false);
else
{
// 修改为最小,确保过期
ExpiredTime = DateTime.MinValue;
}
// 清空后,表示不使用缓存
Using = false;
}
}
private IEntityOperate Operate = Entity<TEntity>.Meta.Factory;
internal void Update(TEntity entity)
{
// 正在更新当前缓存,跳过
//if (!Using || _thread > 0 || _Entities == null) return;
if (!Using) return;
// 尽管用了事务保护,但是仍然可能有别的地方导致实体缓存更新,这点务必要注意
var fi = Operate.Unique;
var e = fi != null ? _Entities.Find(fi.Name, entity[fi.Name]) : null;
if (e != null)
{
//if (e != entity) e.CopyFrom(entity);
// 更新实体缓存时,不做拷贝,避免产生脏数据,如果恰巧又使用单对象缓存,那会导致自动保存
lock (_Entities)
{
_Entities.Remove(e);
}
}
//// 加入超级缓存的实体对象,需要标记来自数据库
//entity.MarkDb(true);
lock (_Entities)
{
_Entities.Add(entity);
}
}
#endregion
#region 统计
/// <summary>总次数</summary>
public Int32 Total;
/// <summary>第一次命中</summary>
public Int32 Shoot1;
/// <summary>第二次命中</summary>
public Int32 Shoot2;
/// <summary>下一次显示时间</summary>
public DateTime NextShow;
/// <summary>显示统计信息</summary>
public void ShowStatics()
{
if (Total > 0)
{
var sb = new StringBuilder();
sb.AppendFormat("实体缓存<{0,-20}>", typeof(TEntity).Name);
sb.AppendFormat("总次数{0,7:n0}", Total);
if (Shoot1 > 0) sb.AppendFormat(",命中{0,7:n0}({1,6:P02})", Shoot1, (Double)Shoot1 / Total);
if (Shoot2 > 0) sb.AppendFormat(",二级命中{0,3:n0}({1,6:P02})", Shoot2, (Double)Shoot2 / Total);
XTrace.WriteLine(sb.ToString());
}
}
#endregion
#region IEntityCache 成员
EntityList<IEntity> IEntityCache.Entities { get { return new EntityList<IEntity>(Entities); } }
/// <summary>根据指定项查找</summary>
/// <param name="name">属性名</param>
/// <param name="value">属性值</param>
/// <returns></returns>
public IEntity Find(string name, object value) { return Entities.Find(name, value); }
/// <summary>根据指定项查找</summary>
/// <param name="name">属性名</param>
/// <param name="value">属性值</param>
/// <returns></returns>
public EntityList<IEntity> FindAll(string name, object value) { return new EntityList<IEntity>(Entities.FindAll(name, value)); }
/// <summary>检索与指定谓词定义的条件匹配的所有元素。</summary>
/// <param name="match">条件</param>
/// <returns></returns>
public EntityList<IEntity> FindAll(Predicate<IEntity> match) { return new EntityList<IEntity>(Entities.FindAll(e => match(e))); }
#endregion
#region 辅助
internal EntityCache<TEntity> CopySettingFrom(EntityCache<TEntity> ec)
{
this.Expire = ec.Expire;
this.Asynchronous = ec.Asynchronous;
this.FillListMethod = ec.FillListMethod;
return this;
}
#endregion
}
}
|