C# 什么';对数据库进行线程安全写缓存的最佳模式是什么?
我有一个方法,可以被多个线程调用,将数据写入数据库。为了减少数据库流量,我缓存数据并将其批量写入 现在我想知道,有没有更好的(例如无锁模式)可以使用 下面是一个例子,我现在是如何做到这一点的C# 什么';对数据库进行线程安全写缓存的最佳模式是什么?,c#,multithreading,caching,design-patterns,C#,Multithreading,Caching,Design Patterns,我有一个方法,可以被多个线程调用,将数据写入数据库。为了减少数据库流量,我缓存数据并将其批量写入 现在我想知道,有没有更好的(例如无锁模式)可以使用 下面是一个例子,我现在是如何做到这一点的 public class WriteToDatabase : IWriter, IDisposable { public WriteToDatabase(PLCProtocolServiceConfig currentConfig) {
public class WriteToDatabase : IWriter, IDisposable
{
public WriteToDatabase(PLCProtocolServiceConfig currentConfig)
{
writeTimer = new System.Threading.Timer(Writer);
writeTimer.Change((int)currentConfig.WriteToDatabaseTimer.TotalMilliseconds, Timeout.Infinite);
this.currentConfig = currentConfig;
}
private System.Threading.Timer writeTimer;
private List<PlcProtocolDTO> writeChache = new List<PlcProtocolDTO>();
private readonly PLCProtocolServiceConfig currentConfig;
private bool disposed;
public void Write(PlcProtocolDTO row)
{
lock (this)
{
writeChache.Add(row);
}
}
private void Writer(object state)
{
List<PlcProtocolDTO> oldCachce = null;
lock (this)
{
if (writeChache.Count > 0)
{
oldCachce = writeChache;
writeChache = new List<PlcProtocolDTO>();
}
}
if (oldCachce != null)
{
using (var s = VisuDL.CreateSession())
{
s.Insert(oldCachce);
}
}
if (!this.disposed)
writeTimer.Change((int)currentConfig.WriteToDatabaseTimer.TotalMilliseconds, Timeout.Infinite);
}
public void Dispose()
{
this.disposed = true;
writeTimer.Dispose();
Writer(null);
}
}
公共类WriteToDatabase:IWriter,IDisposable
{
公共WriteToDatabase(PLCProtocolServiceConfig currentConfig)
{
writeTimer=新系统.Threading.Timer(Writer);
writeTimer.Change((int)currentConfig.WriteToDatabaseTimer.TotalMillistics,Timeout.Infinite);
this.currentConfig=currentConfig;
}
专用System.Threading.Timer writeTimer;
私有列表writeCache=新列表();
私有只读PLCProtocolServiceConfig currentConfig;
私人住宅;
公共无效写入(PlcProtocolDTO行)
{
锁(这个)
{
writeCache.Add(行);
}
}
私有无效写入程序(对象状态)
{
列表oldcache=null;
锁(这个)
{
如果(writeCache.Count>0)
{
oldcache=写缓存;
WriteCache=新列表();
}
}
如果(oldcache!=null)
{
使用(var s=VisuDL.CreateSession())
{
s、 插入(oldcache);
}
}
如果(!this.disposed)
writeTimer.Change((int)currentConfig.WriteToDatabaseTimer.TotalMillistics,Timeout.Infinite);
}
公共空间处置()
{
这是真的;
writeTimer.Dispose();
编写器(空);
}
}
不必使用可变的
列表
并使用锁对其进行保护,您可以使用,并且不再担心列表在错误的时间被错误的线程变异的可能性。使用它,传递数据的快照既便宜又容易,因为在创建数据副本时不需要阻止写入程序(也可能是读卡器)。不可变集合本身就是快照
虽然你不必担心收藏的内容,但你仍然需要担心它的参考。这是因为更新不可变集合意味着用新集合替换对旧集合的引用。您不想让多个线程以无法控制的方式交换引用,因此仍然需要某种同步。您仍然可以使用lock
s,但是通过使用互锁操作可以很容易地避免完全锁定。下面的示例使用方便的方法,允许在一行中执行原子更新和交换:
private ImmutableList<PlcProtocolDTO> writeCache
= ImmutableList<PlcProtocolDTO>.Empty;
public void Write(PlcProtocolDTO row)
{
ImmutableInterlocked.Update(ref writeCache, x => x.Add(row));
}
private void Writer(object state)
{
IList<PlcProtocolDTO> oldCache = Interlocked.Exchange(
ref writeCache, ImmutableList<PlcProtocolDTO>.Empty);
using (var s = VisuDL.CreateSession())
s.Insert(oldCache);
}
private void Dump()
{
foreach (var row in Volatile.Read(ref writeCache))
Console.WriteLine(row);
}
私有不可变列表写入缓存
=不可变列表。为空;
公共无效写入(PlcProtocolDTO行)
{
ImmutableInterlocated.Update(ref writeCache,x=>x.Add(行));
}
私有无效写入程序(对象状态)
{
IList oldCache=Interlocked.Exchange(
ref writeCache,ImmutableList.Empty);
使用(var s=VisuDL.CreateSession())
s、 插入(oldCache);
}
私有无效转储()
{
foreach(Volatile.Read(ref writeCache)中的变量行)
控制台写入线(世界其他地区);
}
以下是ImmutableInterlocated.Update
方法的说明:
通过指定的转换函数,使用乐观锁定事务语义就地变异值。为了赢得乐观锁定竞赛,需要多次重试转换
此方法可用于更新任何类型的引用类型变量。它的使用可能会随着的出现而增加,默认情况下是不可变的,并且打算这样使用。基于计时器的代码存在一些问题
- 即使在新版本的代码中,重新启动或关闭时仍有可能丢失写操作。
方法没有等待完成当前可能正在进行的最后一次计时器回调。 由于计时器回调在线程池线程(后台线程)上运行,因此当主线程退出时,它们将被中止Dispose
- 批处理的大小没有限制,当您达到底层存储api的限制时,这将被打破 (例如,sql数据库对查询长度和使用的参数数量有限制)
- 因为您正在执行i/o,所以实现可能应该是异步的
- 这将在负载下表现不佳。 特别是当负载持续增加时,批处理会变得更大,因此执行速度会变慢, 一个较慢的批处理执行反过来将给下一个额外的时间来积累项目,使他们更慢,等等。。。 最终,要么编写批处理失败(如果达到sql限制,要么查询超时),要么应用程序内存不足。 要处理高负载,您实际上只有两种选择,即应用背压(即减慢生产者)或删除写入
- 如果数据库能够处理,您可能希望允许有限数量的并发写入程序
字段中存在竞争条件,这可能导致disposed
writeTimer.Change中出现
ObjectDisposedException