我们有一个注册类型系统,一旦完成就会发送确认电子邮件。该系统在几分钟内就有大约3000个注册,我们发现了一个错误。如果用户A在用户B注册后几毫秒注册,用户A将通过电子邮件获得用户B的详细信息。我们设法解决了这个问题,并将其缩小到这段代码,从缓存中获取电子邮件模板,并在占位符上进行字符串替换。
private string ProcessEmailBody(MyRegistrationModel registration)
{
var content = CacheHelper.GetContent("REGISTRATIONEMAIL");
if (content != null)
{
content.Text = context.Text.Replace("@@FULL_NAME@@", registration.FullName);
return content.Text;
}
else return null;
}
CacheHelper.GetContent()
方法是静态的,我修复了这个" bug"通过这样做:
private string ProcessEmailBody(MyRegistrationModel registration)
{
var content = CacheHelper.GetContent("REGISTRATIONEMAIL");
if (content != null)
{
string body = content.Text;
body = body.Replace("@@FULL_NAME@@", registration.FullName);
return body;
}
else return null;
}
我不能为我的生活找出解决问题的原因。任何人都可以对此有所了解吗?
编辑:这是我的GetContent()方法(我知道签名与上述不同,我很简短)
public static Content GetContent(string key, int partnerSiteId, int? version, IContentRepository contentRepository, out string cacheKey)
{
cacheKey = string.Format("{0}_{1}_{2}", key, partnerSiteId, version);
var content = CacheManager.Get(cacheKey, () => contentRepository.GetContent(key, partnerSiteId, version), WebConfig.GetCacheDuration(CacheProfile.Short));
return content;
}
private static DataCache _Cache = null; // DataCache is from AppFabric (Microsoft.ApplicationServer.Caching)
public static T Get<T>(string objectKey, Func<T> reloadItemExpresion, TimeSpan cacheDuration) where T : class
{
if (_Cache == null)
{
if (reloadItemExpresion != null)
{
return reloadItemExpresion.Invoke();
}
return null;
}
object cachedObject = null;
try
{
cachedObject = _Cache.Get(objectKey);
}
catch (Exception ex)
{
if (ex is FileNotFoundException)
{
_Cache.Remove(objectKey);
}
}
if (cachedObject != null)
{
return cachedObject as T;
}
if (reloadItemExpresion != null && cacheDuration > TimeSpan.Zero)
{
T item = reloadItemExpresion.Invoke();
if (item != null)
{
Insert(item, objectKey, cacheDuration);
}
return item;
}
return null;
}
contentRepository.GetContent
转到数据库并获取实际内容。
答案 0 :(得分:3)
第一次将"@@FULL_NAME@@"
中的context.Text
标记替换为第一个用户的详细信息。一旦你这样做,它再也不会回到"@@FULL_NAME@@"
,所以每个人都会得到这个家伙的详细信息,直到你的缓存被重置。您应该避免修改从缓存中获取的对象:
private string ProcessEmailBody(MyRegistrationModel registration) {
var content = CacheHelper.GetContent("REGISTRATIONEMAIL");
return content != null ? content.Replace("@@FULL_NAME@@", registration.FullName) : null;
}
答案 1 :(得分:2)
问题是你的content
对象是访问它的每个人之间的共享对象。通过使用第一种方法content.Text = context.Text.Replace()
,您将为同时访问它的每个人改变文本。
在第二种方法中,您不会改变共享对象中的文本,因此每个人都会同时获取自己的文本。为避免将来出现此问题,您应该考虑将content.Text
属性设置为只读给消费者(通过允许仅在构造函数中设置文本或仅提供具有只读访问权限的接口)。因此,即使在编译时也能避免这个错误。
答案 2 :(得分:1)
如果不了解您的CacheHelper方法是如何工作的,或者content
是什么类型,很难说。但似乎不是返回一个字符串,而是通过引用返回某种Content对象。因此,如果两个线程同时运行,则两者都可能使用CacheHelper返回的相同Content对象。
假设每次调用CacheHelper都不负责创建一个全新的内容模板,那么原始代码就会出错,因为每个Replace调用都会更改TEMPLATE,而不是从中派生的字符串。
我猜测这段代码也会有效:
string body = content.Text.Replace("@@FULL_NAME@@", registration.FullName);
return body;
重要的一点是没有将内容的Text读入局部变量,它不会替换内容的Text属性,这显然是共享的。