我制作了一个我认为没有意义的网站可以用一个宁静的架构来实现(至少不是与这个问题相关的部分),但它会导致一些种族问题跨多个共享数据库的服务器的条件。
我的网站有关于其他产品的用户的信息,因此它有一个用户表(不过我网站的用户)。用户有很多文件。
用户和文件由自动服务填充,而不是在网站上手动填充。服务将文件发布到服务器,服务器解析它们并从文件中获取用户名。如果用户名是新的,则会在表中创建新的用户行。然后它将文件返回给发出请求的服务。
我遇到的问题是当多个请求同时进入相关对象的竞争条件时,它会导致数据库中违反唯一索引的行为。
例如,用户名上有一个唯一键。如果来自同一用户的文件的自动服务的2个请求同时进入,则此代码可能会出现问题。
var myuser = db.users.FirstOrDefault(u => u.username == username);
if(myuser == null)
{
myuser = new user(username);
db.AddObject(user);
}
db.SaveChanges();
请求1将看到没有用户名为foo的用户,因此if条件返回true。请求2看到同样的事情,不知道请求1已经开始创建用户,并且当请求2尝试保存时,它违反了唯一密钥。
这个问题有共同的模式或解决方案吗?我知道如果服务器是RESTful,这不会有问题,但我认为服务改变它的请求方式真的可行,所以我喜欢尽可能保持不变。现在,它只是将文件发布到服务器,不知道该文件的用户是否已经存在,或者该文件是否已发布到服务器(它可能会多次发布)。如果它们尚不存在,则创建这些对象,如果它们存在,则更新项目列表。但就服务而言,它只是想知道有关该文件的某些信息,并不关心它是否已存在于我的数据库中。
我认为它过于缓慢,无法尝试通过请求创建用户,然后尝试通过请求创建文件,然后在另一个请求中请求有关该文件的信息。此外,该服务通过Parallel.ForEach一次运行多个请求,并且它在单个线程中运行它的速度太慢。
答案 0 :(得分:3)
首先是关注点的分离。如果您有填充数据的自动服务,则 服务(或其他中间件)应负责创建数据库记录。这不应该在运行时响应对您网站的请求而发生。
其次,如果你必须这样做,那就是锁的用途。对您网站的每个请求都在其自己的主题中运行。因此,如果多个线程需要访问相同的volatile资源(您的数据库),那么您需要建立乐观锁定,以便wins中的第一个线程和任何其他线程只能尝试与该表或行进行交互(取决于关于锁的类型)一旦第一个完成它的工作。
第三,这几乎是完全 RESTful架构试图解决的问题。您可以使用ETag对资源进行版本控制,因此任何POST过时资源的尝试都将返回HTTP错误(409 Conflict),指示客户端重新获取原始资源。