feat: 缓存调整

This commit is contained in:
钟喜旺 2024-07-31 18:07:10 +08:00
parent 010408985e
commit 8f8d7b2a44
10 changed files with 163 additions and 105 deletions

View File

@ -0,0 +1,9 @@
using SJZY.Track.Domain.Dtos.Forewarning;
namespace SJZY.Track.Application.Contract.Dtos.Forewarning;
public class ClearRulCacheEvent
{
public List<RuleAreaBaseInfo> Items { get; set; }
}

View File

@ -24,11 +24,19 @@ public interface IForewarningCacheService
/// 重置本地缓存 /// 重置本地缓存
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task Clear(); Task Clear(ClearRulCacheEvent @event);
/// <summary> /// <summary>
/// 清除redis 缓存 /// 清除redis 缓存
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task ClearRedis(); Task ClearRedisByAreaId(int areaId);
/// <summary>
/// 清除redis 缓存
/// </summary>
/// <param name="subSystemId">子系统id</param>
/// <param name="customerId">客户id</param>
/// <returns></returns>
Task ClearRedisBySystemIdAndCustomer(int subSystemId, int customerId);
} }

View File

@ -1,8 +1,11 @@
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using SJZY.Expand.ABP.Core; using SJZY.Expand.ABP.Core;
using SJZY.Track.Application.Contract.Dtos.Forewarning;
using SJZY.Track.Application.Contract.Events; using SJZY.Track.Application.Contract.Events;
using SJZY.Track.Application.Contract.Services; using SJZY.Track.Application.Contract.Services;
using SJZY.Track.Domain.Consts; using SJZY.Track.Domain.Consts;
using SJZY.Track.Domain.Dtos.Forewarning;
using SJZY.UserCenter.SharedModel.Dtos.PHP.User;
namespace SJZY.Track.Application.HostServices; namespace SJZY.Track.Application.HostServices;
@ -34,9 +37,10 @@ public class RedisHostService:IHostedService
{ {
// 暂时只处理一个事件,不做反序列化 // 暂时只处理一个事件,不做反序列化
//var instance = Newtonsoft.Json.JsonConvert.DeserializeObject<ModifyWarningEvent>(value as string); //var instance = Newtonsoft.Json.JsonConvert.DeserializeObject<ModifyWarningEvent>(value as string);
var instance = Newtonsoft.Json.JsonConvert.DeserializeObject<ClearRulCacheEvent>(value as string);
if(instance!=null)
_forewarningCacheService.Clear(instance);
} }
_forewarningCacheService.Clear();
} }
private string GetKey() private string GetKey()

View File

@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using FreeRedis; using FreeRedis;
using SJZY.Expand.ABP.Core; using SJZY.Expand.ABP.Core;
using SJZY.Expand.ABP.Core.PublicMethords; using SJZY.Expand.ABP.Core.PublicMethords;
@ -16,41 +17,31 @@ public class ForewarningCacheService: IForewarningCacheService, ISingletonDepend
readonly IKpiAreaRepository _kpiAreaRepository; readonly IKpiAreaRepository _kpiAreaRepository;
readonly IWarningRepository _warningRepository; readonly IWarningRepository _warningRepository;
const string GlobalKpiAreaLockKey = "KKpiAreaLockKey_GlobalLock";
private const string GlobalWaningLockKey = "GlobalWaningLockKey_GlobalLock"; private const string GlobalWaningLockKey = "GlobalWaningLockKey_GlobalLock";
private Dictionary<string, List<ForewarningRuleCacheEntry>> _rules = new(); private readonly ConcurrentDictionary<string, List<ForewarningRuleCacheEntry>> _rules = new();
private Dictionary<int,List<AreaCacheEntry>>? _areas = new(); private readonly ConcurrentDictionary<int, List<AreaCacheEntry>> _areas = new();
readonly object _lock = new object(); object _lock = new object();
public ForewarningCacheService(IKpiAreaRepository kpiAreaRepository, IWarningRepository warningRepository) public ForewarningCacheService(IKpiAreaRepository kpiAreaRepository, IWarningRepository warningRepository)
{ {
_kpiAreaRepository = kpiAreaRepository; _kpiAreaRepository = kpiAreaRepository;
_warningRepository = warningRepository; _warningRepository = warningRepository;
} }
string WarningKey(int subSystemId, int customerId) =>
$"{GlobalConst.TrackWarningKeyPrefix}:{subSystemId}:{customerId}";
string WarningKey(int subSystemId, int customerId) => $"{GlobalConst.TrackWarningKeyPrefix}:{subSystemId}:{customerId}"; string AreaKey(int areaId) => $"{GlobalConst.TrackAreaKeyPrefix}:{areaId}";
string WarningKey() => $"{GlobalConst.TrackWarningKeyPrefix}:all";
string AreaKey()
{
return $"{GlobalConst.TrackAreaKeyPrefix}";
}
// 30w => 1w => local
public async Task<List<ForewarningRuleCacheEntry>?> GetRulesAsync(int subSystemId, int customerId) public async Task<List<ForewarningRuleCacheEntry>?> GetRulesAsync(int subSystemId, int customerId)
{ {
var key = WarningKey(subSystemId, customerId); var key = WarningKey(subSystemId, customerId);
var rules = _rules.GetValueOrDefault(key); if( _rules.TryGetValue(key,out var rules))
if (rules == null)
{ {
return await GetRules(subSystemId, customerId); return rules;
} }
return rules; return await GetRules(subSystemId, customerId);
} }
private List<ForewarningRuleCacheEntry> WriteRules(List<WarningRuleCacheDto> rules) private List<ForewarningRuleCacheEntry> WriteRules(List<WarningRuleCacheDto> rules)
@ -74,7 +65,9 @@ public class ForewarningCacheService: IForewarningCacheService, ISingletonDepend
StartTrackStatusId = item.StartTrackStatusId, StartTrackStatusId = item.StartTrackStatusId,
EndTrackStatusId = item.EndTrackStatusId, EndTrackStatusId = item.EndTrackStatusId,
CargoType = item.CargoType, CargoType = item.CargoType,
Months = string.IsNullOrEmpty(item.Months)?new List<int>():item.Months.Split('/').Select(int.Parse).ToList(), Months = string.IsNullOrEmpty(item.Months)
? new List<int>()
: item.Months.Split('/').Select(int.Parse).ToList(),
IsTransit = item.IsTransit, IsTransit = item.IsTransit,
Areas = areaList, Areas = areaList,
ProductCode = item.ProductCode, ProductCode = item.ProductCode,
@ -89,27 +82,30 @@ public class ForewarningCacheService: IForewarningCacheService, ISingletonDepend
private async Task<List<ForewarningRuleCacheEntry>?> GetRules(int systemId, int customerId) private async Task<List<ForewarningRuleCacheEntry>?> GetRules(int systemId, int customerId)
{ {
var key = WarningKey(); var key = WarningKey(systemId, customerId);
var result = RedisHelper.Client.Get<List<WarningRuleCacheDto>>(key); var result = RedisHelper.Client.Get<List<WarningRuleCacheDto>>(key);
if (result != null) if (result != null)
{ {
// 非线程安全,需要加锁 // 非线程安全,需要加锁
lock (_lock) lock (_lock)
{ {
_rules = result.GroupBy(val => WarningKey(val.SystemId, val.CustomerId)) if (_rules.TryGetValue(key, out var ruleResult))
.ToDictionary(val => val.Key, val => WriteRules(val.ToList())); {
return ruleResult;
}
return _rules.GetValueOrDefault(WarningKey(systemId,customerId)); ruleResult = WriteRules(result);
_rules.TryAdd(key,ruleResult);
return ruleResult;
} }
} }
// 非线程安全,需要加锁 // 非线程安全,需要加锁
using (RedisHelper.Client.Lock(GlobalWaningLockKey, 20)) using (RedisHelper.Client.Lock(GlobalWaningLockKey, 20))
{ {
var ruleResult = _rules.GetValueOrDefault(WarningKey(systemId,customerId)); if (_rules.TryGetValue(key, out var ruleResult)) return ruleResult;
if (ruleResult == null)
{ var warnings = await _warningRepository.GetWarningRule(systemId, customerId);
var warnings = await _warningRepository.GetAllWarningRule();
if (warnings == null || warnings.Count == 0) if (warnings == null || warnings.Count == 0)
{ {
RedisHelper.Client.Set(key, new List<WarningRuleCacheDto>(), TimeSpan.FromSeconds(20)); RedisHelper.Client.Set(key, new List<WarningRuleCacheDto>(), TimeSpan.FromSeconds(20));
@ -117,36 +113,29 @@ public class ForewarningCacheService: IForewarningCacheService, ISingletonDepend
} }
RedisHelper.Client.Set(key, warnings); RedisHelper.Client.Set(key, warnings);
_rules = warnings.GroupBy(val => WarningKey(val.SystemId, val.CustomerId)) ruleResult = WriteRules(warnings);
.ToDictionary(val => val.Key, val => WriteRules(val.ToList())); _rules.TryAdd(key, ruleResult);
return _rules.GetValueOrDefault(WarningKey(systemId,customerId));
}
return ruleResult; return ruleResult;
} }
} }
// a b c del local cusomer 3 del redis cache pub del redis cache
public Task DeleteRulesAsync(int subSystemId, int customerId) public Task DeleteRulesAsync(int subSystemId, int customerId)
{ {
_rules.Remove(WarningKey(subSystemId, customerId)); _rules.Remove(WarningKey(subSystemId, customerId),out _);
return Task.CompletedTask; return Task.CompletedTask;
} }
private List<AreaCacheEntry>? GetAreaInfoByAreaId(int areaId) private List<AreaCacheEntry>? GetAreaInfoByAreaId(int areaId)
{ {
if (_areas == null || _areas.Count == 0) if (_areas.TryGetValue(areaId, out var areaInfos)) return areaInfos;
{
var areas = GetAreas();
if(areas==null || areas.Count==0) return null;
_areas = areas.GroupBy(x => x.AreaId)
.ToDictionary(x => x.Key, x => x.Select(KpiAreaInfoToEntry).ToList());
}
return _areas.GetValueOrDefault(areaId); var areas = GetAreas(areaId);
if (areas == null || areas.Count == 0) return null;
areaInfos = areas.Select(KpiAreaInfoToEntry).ToList();
_areas.TryAdd(areaId, areaInfos);
return areaInfos;
} }
AreaCacheEntry KpiAreaInfoToEntry(KpiAreaInfo model) AreaCacheEntry KpiAreaInfoToEntry(KpiAreaInfo model)
@ -162,21 +151,19 @@ public class ForewarningCacheService: IForewarningCacheService, ISingletonDepend
} }
/// <summary> /// <summary>
/// 获取所有区域 /// 获取区域
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private List<KpiAreaInfo>? GetAreas() private List<KpiAreaInfo>? GetAreas(int areaId)
{ {
var key = AreaKey(); var key = AreaKey(areaId);
var result = RedisHelper.Client.Get<List<KpiAreaInfo>>(key); var result = RedisHelper.Client.Get<List<KpiAreaInfo>>(key);
if (result == null) if (result == null)
{
using (RedisHelper.Client.Lock(GlobalKpiAreaLockKey, 20))
{ {
result = RedisHelper.Client.Get<List<KpiAreaInfo>>(key); result = RedisHelper.Client.Get<List<KpiAreaInfo>>(key);
if (result != null) return new List<KpiAreaInfo>(); if (result != null) return new List<KpiAreaInfo>();
var kpiArea = _kpiAreaRepository.GetAllAreaInfos(); var kpiArea = _kpiAreaRepository.GetAreaInfosByAreaId(areaId);
if (kpiArea == null) if (kpiArea == null)
{ {
RedisHelper.Client.Set(key, new List<KpiAreaInfo>(), TimeSpan.FromSeconds(20)); RedisHelper.Client.Set(key, new List<KpiAreaInfo>(), TimeSpan.FromSeconds(20));
@ -186,23 +173,52 @@ public class ForewarningCacheService: IForewarningCacheService, ISingletonDepend
RedisHelper.Client.Set(key, kpiArea); RedisHelper.Client.Set(key, kpiArea);
return kpiArea; return kpiArea;
} }
}
return result; return result;
} }
public Task Clear() public Task Clear(ClearRulCacheEvent @event)
{ {
_rules?.Clear(); foreach (var item in @event.Items)
_areas?.Clear(); {
DeleteRulesAsync(item.SystemId, item.CustomerId);
}
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task ClearRedis() public async Task ClearRedisBySystemIdAndCustomer(int subSystemId,int customerId)
{ {
RedisHelper.Client.Del(WarningKey()); await RedisHelper.Client.DelAsync(WarningKey(subSystemId, customerId));
RedisHelper.Client.Del(AreaKey()); var eventModel = new ClearRulCacheEvent
return Task.CompletedTask; {
Items = new List<RuleAreaBaseInfo>()
{
new RuleAreaBaseInfo(subSystemId, customerId)
}
};
await PublishEvent(eventModel);
} }
public async Task ClearRedisByAreaId(int areaId)
{
var baseInfos = await _warningRepository.GetWarningRuleByAreaId(areaId);
if(baseInfos==null||baseInfos.Count==0) return;
baseInfos = baseInfos.DistinctBy(val => WarningKey(val.SystemId, val.CustomerId)).ToList();
var willDelKeys = baseInfos.Select(val => WarningKey(val.SystemId, val.CustomerId)).ToArray();
await RedisHelper.Client.DelAsync(willDelKeys);
await RedisHelper.Client.DelAsync(AreaKey(areaId));
var eventModel = new ClearRulCacheEvent
{
Items = baseInfos
};
await PublishEvent(eventModel);
}
private async Task PublishEvent(ClearRulCacheEvent @event)
{
await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, @event.ToJson());
}
} }

View File

@ -42,8 +42,7 @@ public class KpiAreaService : ApplicationService
{ {
var ruleAreaEntity = ObjectMapper.Map<AddRuleAreaInfoRequest, RuleAreaInfoEntity>(input); var ruleAreaEntity = ObjectMapper.Map<AddRuleAreaInfoRequest, RuleAreaInfoEntity>(input);
var ruleInfoId = await _ruleAreaRepository.AddAreaInfoAsync(ruleAreaEntity); var ruleInfoId = await _ruleAreaRepository.AddAreaInfoAsync(ruleAreaEntity);
await _forewarningCacheService.ClearRedis(); await _forewarningCacheService.ClearRedisByAreaId(input.AreaId);
await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, "clear local cache");
return new UnifyResultDto(true, ruleInfoId, "添加成功"); return new UnifyResultDto(true, ruleInfoId, "添加成功");
} }
@ -56,8 +55,7 @@ public class KpiAreaService : ApplicationService
public async ValueTask<UnifyResultDto> DeleteKpiArea(int id) public async ValueTask<UnifyResultDto> DeleteKpiArea(int id)
{ {
await _ruleAreaRepository.DeleteArea(id); await _ruleAreaRepository.DeleteArea(id);
await _forewarningCacheService.ClearRedis(); await _forewarningCacheService.ClearRedisByAreaId(id);
await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, "clear local cache");
return new UnifyResultDto(true, "删除成功"); return new UnifyResultDto(true, "删除成功");
} }
@ -69,9 +67,10 @@ public class KpiAreaService : ApplicationService
[HttpDelete("KpiArea/DeleteKpiAreaInfo")] [HttpDelete("KpiArea/DeleteKpiAreaInfo")]
public async ValueTask<UnifyResultDto> DeleteKpiAreaInfo(int id) public async ValueTask<UnifyResultDto> DeleteKpiAreaInfo(int id)
{ {
var info=await _ruleAreaRepository.GetAreaInfoById(id);
if (info == null) return new UnifyResultDto(false,"未找到该区域信息");
await _ruleAreaRepository.DeleteAreaInfo(id); await _ruleAreaRepository.DeleteAreaInfo(id);
await _forewarningCacheService.ClearRedis(); await _forewarningCacheService.ClearRedisByAreaId(info.AreaId);
await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, "clear local cache");
return new UnifyResultDto(true, "删除成功"); return new UnifyResultDto(true, "删除成功");
} }
@ -113,7 +112,7 @@ public class KpiAreaService : ApplicationService
var ruleAreaEntity = ObjectMapper.Map(input, areaInfo); var ruleAreaEntity = ObjectMapper.Map(input, areaInfo);
await _ruleAreaRepository.UpdateAreaInfoAsync(ruleAreaEntity); await _ruleAreaRepository.UpdateAreaInfoAsync(ruleAreaEntity);
await _forewarningCacheService.ClearRedis(); await _forewarningCacheService.ClearRedisByAreaId(areaInfo.AreaId);
await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, "clear local cache"); await RedisHelper.Client.PublishAsync(GlobalConst.PublishNotifyChannelKey, "clear local cache");
return new UnifyResultDto(true, "更新成功"); return new UnifyResultDto(true, "更新成功");
} }

View File

@ -64,6 +64,13 @@ public interface IWarningRepository : IBasicRepository<WarningEntity>
/// 获取预警规则列表 /// 获取预警规则列表
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task<List<WarningRuleCacheDto>?> GetAllWarningRule(); Task<List<WarningRuleCacheDto>?> GetWarningRule(int systemId, int customerId);
/// <summary>
/// 获取预警规则基础信息
/// </summary>
/// <param name="areaId"></param>
/// <returns></returns>
Task<List<RuleAreaBaseInfo>?> GetWarningRuleByAreaId(int areaId);
} }

View File

@ -64,5 +64,5 @@ public interface IKpiAreaRepository : IBasicRepository<RuleAreaEntity>
/// 获取所有区域信息 /// 获取所有区域信息
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
List<KpiAreaInfo>? GetAllAreaInfos(); List<KpiAreaInfo>? GetAreaInfosByAreaId(int areaId);
} }

View File

@ -0,0 +1,3 @@
namespace SJZY.Track.Domain.Dtos.Forewarning;
public record RuleAreaBaseInfo(int SystemId, int CustomerId);

View File

@ -94,17 +94,19 @@ public class KpiAreaRepository : BasicRepository<RuleAreaEntity>, IKpiAreaReposi
/// 获取所有区域信息 /// 获取所有区域信息
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public List<KpiAreaInfo> GetAllAreaInfos() public List<KpiAreaInfo> GetAreaInfosByAreaId(int areaId)
{ {
return Context.Queryable<RuleAreaInfoEntity>() return Context.Queryable<RuleAreaInfoEntity>()
.Select((x)=>new KpiAreaInfo .InnerJoin<RuleAreaEntity>((r,i)=>r.AreaId==i.Id)
.Where((r,i)=>r.AreaId==areaId)
.Select((r,i)=>new KpiAreaInfo
{ {
AreaId = x.AreaId, AreaId = r.AreaId,
Country = x.CountryCode, Country = r.CountryCode,
LeftPostcode = x.LeftPostcode, LeftPostcode = r.LeftPostcode,
City = x.City, City = r.City,
Province = x.Province, Province = r.Province,
RightPostcode = x.RightPostcode RightPostcode = r.RightPostcode
}) })
.ToList(); .ToList();
} }

View File

@ -110,11 +110,11 @@ public class WarningRepository : BasicRepository<WarningEntity>, IWarningReposit
return warning; return warning;
} }
public Task<List<WarningRuleCacheDto>?> GetAllWarningRule() public Task<List<WarningRuleCacheDto>?> GetWarningRule(int systemId, int customerId)
{ {
return Context.Queryable<WarningRuleEntity>() return Context.Queryable<WarningRuleEntity>()
.InnerJoin<WarningEntity>((a, b) => a.WarningId == b.Id) .InnerJoin<WarningEntity>((a, b) => a.WarningId == b.Id)
.Where((a,b)=>b.IsEnable) .Where((a,b)=>b.IsEnable && b.SubSystemId == systemId && b.CustomerId == customerId)
.Select((a,b)=>new WarningRuleCacheDto .Select((a,b)=>new WarningRuleCacheDto
{ {
Id = a.Id, Id = a.Id,
@ -135,4 +135,14 @@ public class WarningRepository : BasicRepository<WarningEntity>, IWarningReposit
}) })
.ToListAsync(); .ToListAsync();
} }
public Task<List<RuleAreaBaseInfo>?> GetWarningRuleByAreaId(int areaId)
{
return Context.Queryable<WarningRuleEntity>()
.InnerJoin<WarningEntity>((a, b) => a.WarningId == b.Id)
.Where((a,b)=>b.IsEnable && a.AreaId==areaId)
.Select((a,b)=>new RuleAreaBaseInfo(b.CustomerId,b.SubSystemId))
.ToListAsync();
}
} }