0
点赞
收藏
分享

微信扫一扫

企业级自定义表单引擎解决方案(十)--缓存设计2

爱动漫建模 2022-02-10 阅读 91

Python微信订餐小程序课程视频

https://edu.csdn.net/course/detail/36074

Python实战量化交易理财系统

https://edu.csdn.net/course/detail/35475
  新年伊始,万物皆生机,然冠未去,美帝相向,于华夏之子,吾辈当自强。

这篇文章接上一篇文章,主要介绍缓存的代码实现

后端本地缓存

之前介绍的将自定义表单数据全部存储到应用程序内存中,任何自定义表单数据更新之后,都刷新内存缓存,分布式部署涉及到缓存同步刷新问题。

  • 全局本地缓存容器设计
  1. 用线程安全的字典ConcurrentDictionary CacheDict,存储每一个数据对象集合,比如视图集合、表单集合等,每一次数据变更都清除具体的一个字典项数据
  2. 绝大多数时间都是读取缓存内容,因此这里上的读写锁,读写每一项缓存时,都上自己的读锁,锁的集合存储在ConcurrentDictionary CacheReaderWriterLockDict变量中,Key与CacheDict的Key相同。
  3. 当检测到缓存通知服务断开时,会将本地所有缓存清空,直接读取原始数据库,用bool IsEnabledLocalCache变量控制。
  4. 当读取缓存时,发现本地缓存没有数据,则调用具体加载数据委托方法,本地没有数据读取时,需要加锁,防止缓存穿透。

具体代码如下:

?

| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 | /// /// 本地缓存容器``/// public class LocalCacheContainer``{``private static ConcurrentDictionary<``string``, object``> CacheDict;``private static ConcurrentDictionary<``string``, ReaderWriterLock> CacheReaderWriterLockDict; static LocalCacheContainer()``{``CacheDict = new ConcurrentDictionary<``string``, object``>();``CacheReaderWriterLockDict = new ConcurrentDictionary<``string``, ReaderWriterLock>();``} public static bool IsEnabledLocalCache { get``; private set``; } = true``; /// /// 缓存通知断开时调用``/// /// 是否启用缓存``internal static void SetLocalCacheIsEnabled(``bool isEnabled)``{``IsEnabledLocalCache = isEnabled;``if``(!isEnabled)``{``ClearAllCache();``}``} public static object Get(``string key, Func<``string``, object``> factory)``{``var readerWriterLock = GetReadWriteLock(key);``readerWriterLock.AcquireReaderLock(5000); try``{``//return CacheDict.GetOrAdd(key, factory); // 缓存穿透?``if (CacheDict.ContainsKey(key))``{``return CacheDict.GetOrAdd(key, factory);``}``else``{``lock (``string``.Intern(key))``{``return CacheDict.GetOrAdd(key, factory);``}``}``}``finally``{``readerWriterLock.ReleaseReaderLock();``}``} internal static void ClearCache(``string key)``{``var readerWriterLock = GetReadWriteLock(key);``readerWriterLock.AcquireWriterLock(5000); try``{``object objRemove;``CacheDict.TryRemove(key, out objRemove);``}``finally``{``readerWriterLock.ReleaseReaderLock();``}``} // 清楚所有缓存信息``private static void ClearAllCache()``{``CacheDict.Clear();``CacheReaderWriterLockDict.Clear();``} private static ReaderWriterLock GetReadWriteLock(``string key)``{``return CacheReaderWriterLockDict.GetOrAdd(key, k =>``{``return new ReaderWriterLock();``});``}``} |

缓存变更处理

  1. 主要分为缓存变更通知与接收缓存变更处理,缓存变更只需要通知哪一个Key过期即可。
  2. 接收缓存变更处理比较简单,接收到缓存变更之后,将内存容器中对应的字典项删除即可。
  3. 缓存通知定义为接口,如果是单应用部署,直接调用删除本地缓存服务即可,如果是分布式部署,也会调用删除本地缓存数据,通知发送分布式通知到其他自定义表单应用服务器,其他自定义表单应用服务器接收到缓存变更通知时,删除本地缓存数据。
  • ReceiveCacheNotice代码

?

| 1234567891011121314151617181920 | public static class ReceiveCacheNotice``{``public static void ReceiveClearCache(``string key)``{``LocalCacheContainer.ClearCache(key);``} public static void ReceiveClearCaches(List<``string``> keys)``{``foreach``(``var key in keys)``{``LocalCacheContainer.ClearCache(key);``}``} public static void SetLocalCacheIsEnabled(``bool isEnabled)``{``LocalCacheContainer.SetLocalCacheIsEnabled(isEnabled);``}``} |

  • ICacheSendNotice及本地通知LocalCacheSendNotice代码

?

| 123456789101112131415161718192021222324252627282930313233 | /// /// 设计时实体变更通知缓存``/// public interface ICacheSendNotice``{``/// /// 发送缓存变更``/// /// 缓存Key``void SendClearCache(``string key); /// /// 发送缓存多个变更``/// /// 缓存Key集合``void SendClearCaches(List<``string``> keys);``} /// /// 本地缓存容器通知服务``/// public class LocalCacheSendNotice : ICacheSendNotice``{``public void SendClearCache(``string key)``{``ReceiveCacheNotice.ReceiveClearCache(key);``} public void SendClearCaches(List<``string``> keys)``{``ReceiveCacheNotice.ReceiveClearCaches(keys);``}``} |

  • 分布式缓存发布订阅Redis实现,主要是用StackExchange.Redis组件实现,代码没有太多的逻辑,阅读代码即可。

?

| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 | /// /// Redis缓存容器通知服务``/// public class RedisCacheSendNotice : ICacheSendNotice``{``private readonly SpriteConfig _callHttpConfig;``private readonly IDistributedCache _distributedCache;``private readonly ISubscriber _subscriber; public RedisCacheSendNotice(IDistributedCache distributedCache, IOptions callHttpConfig)``{``_distributedCache = distributedCache;``_callHttpConfig = callHttpConfig.Value;``var spriteRedisCache = _distributedCache as SpriteRedisCache;``spriteRedisCache.RedisDatabase.Multiplexer.ConnectionFailed += Multiplexer_ConnectionFailed;``spriteRedisCache.RedisDatabase.Multiplexer.ConnectionRestored += Multiplexer_ConnectionRestored;``_subscriber = spriteRedisCache.RedisDatabase.Multiplexer.GetSubscriber(); if (_callHttpConfig.RemoteReceivePreKey != null``)``{``foreach (``var remoteReceivePreKey in _callHttpConfig.RemoteReceivePreKey)``{``_subscriber.Subscribe(remoteReceivePreKey, (channel, message) =>``{``ReceiveCacheNotice.ReceiveClearCache(message);``}); _subscriber.Subscribe($``"{remoteReceivePreKey}s"``, (channel, message) =>``{``List<``string``> keys = JsonConvert.DeserializeObjectstring>>(message);```ReceiveCacheNotice.ReceiveClearCaches(keys);});}}}` `private` `void` `Multiplexer_ConnectionRestored(objectsender, StackExchange.Redis.ConnectionFailedEventArgs e){ReceiveCacheNotice.SetLocalCacheIsEnabled(true);}` `private` `void` `Multiplexer_ConnectionFailed(objectsender, StackExchange.Redis.ConnectionFailedEventArgs e){ReceiveCacheNotice.SetLocalCacheIsEnabled(false);}` `public` `void` `SendClearCache(stringkey){ReceiveCacheNotice.ReceiveClearCache(key);if` `(_callHttpConfig.RemoteNoticePreKey !=` `null){if(_callHttpConfig.RemoteNoticePreKey.Any(r => key.StartsWith(KaTeX parse error: Expected 'EOF', got '}' at position 72: …[0], key);``}``}̲``}` `public` `…"{groupKeyList.Key}s", JsonConvert.SerializeObject(groupKeyList.ToList()));}}}}``}` |

  • 具体缓存代码实现举例(以表单为例)

?

| 123456789101112131415161718192021222324252627 | public class SpriteFormLocalCache : LocalCache``{``public override string CacheKey => CommonConsts.SpriteFormCacheKey; public override Dictionary GetAllDict(``string applicationCode)``{``if (!LocalCacheContainer.IsEnabledLocalCache) // 如果缓存通知服务不可以,直接读取数据库``{``return _serviceProvider.DoDapperService(DefaultDbConfig, (unitOfWork) =>``{``return GetSpriteFormVueDtos(applicationCode, unitOfWork);``});``}``else``{``// 读取本地缓存内容,如果本地缓存没有数据,读取数据库数据,并写入本地缓存容器``return (Dictionary)LocalCacheContainer.Get($``"{CommonConsts.SpriteFormCachePreKey}-{applicationCode}_{CacheKey}"``, key =>``{``return _serviceProvider.DoDapperService(DefaultDbConfig, (unitOfWork) =>``{``return GetSpriteFormVueDtos(applicationCode, unitOfWork);``});``});``}``}``......``} |

  • 前端缓存主要是用IndexDb实现,前端代码暂时没开源,阅读一下即可

?

| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 | import Dexie from 'dexie'``import { SpriteRumtimeApi } from '@/sprite/api/spriteform' const db = new Dexie(``'formDb'``)``db.version(1).stores({``form:id})` `db.version(1).stores({``view: `id})db.version(1).stores({frameworkCache: `id```})` `db.version(1).stores({dict: id```}) window.spriteDb = db db.menuFormRelationInfo = {}``const createMenuFormRelations = function (routeName, applicationCode, relationInfos) {``if (!db.menuFormRelationInfo.hasOwnProperty(routeName)) {``db.menuFormRelationInfo[routeName] = {}``db.menuFormRelationInfo[routeName].applicationCode = applicationCode``db.menuFormRelationInfo[routeName].relationInfos = relationInfos``} else {``relationInfos.forEach(relationInfo => {``if (!db.menuFormRelationInfo[routeName].relationInfos.find(r => r.relationType === relationInfo.relationType && r.id === relationInfo.id && r.version === relationInfo.version)) {``db.menuFormRelationInfo[routeName].relationInfos.push(relationInfo)``} });``}``} /**``* 递归获取表单或视图关联表单视图版本信息``* @param {guid} objId 表单或视图Id``* @param {int} relationType 1=表单,2=视图``* @param {obj} relationInfos 表单和视图版本信息``*/``const findRelationConfigs = async function (objId, relationType, relationInfos) {``if (!relationInfos) {``relationInfos = []``}``console.log(relationType)``var findData = relationType === 1 ? await db.form.get(objId) : await db.view.get(objId)``if (findData && relationInfos.findIndex(r => r.id === findData.id) < 0) {``relationInfos.push({ relationType: relationType, id: findData.id, version: findData.version })``}``if (findData && findData.relationInfos && findData.relationInfos.length > 0) {``for (``var i = 0; i < findData.relationInfos.length; i++) {``await findRelationConfigs(findData.relationInfos[i].id, findData.relationInfos[i].relationType, relationInfos)``}``}``console.log(``'relationInfos'``)``console.log(relationInfos)``return relationInfos``} db.getFormData = async function (routeName, formId, fromMenu, applicationCode) {``var formData = await db.form.get(formId)``var dictFrameworkCache = await db.frameworkCache.get(``'dict'``)``console.log(``"getFormData"``)``if (!formData) {``var resultData = await SpriteRumtimeApi.simpleform({ id: formId, applicationCode: applicationCode })``var menuFormrelationInfos = []``if (resultData && resultData) {``for (``var i = 0; i < resultData.formDatas.length; i++) {``await db.form.put(resultData.formDatas[i])``menuFormrelationInfos.push({relationType: 1, id: resultData.formDatas[i].id, version: resultData.formDatas[i].version})``}``for (``var j = 0; j < resultData.viewDatas.length; j++) {``await db.view.put(resultData.viewDatas[j])``menuFormrelationInfos.push({relationType: 2, id: resultData.viewDatas[j].id, version: resultData.viewDatas[j].version})``}``}``if (resultData && resultData.dictVersion && resultData.dicts) {``await db.frameworkCache.put({ id: 'dict'``, version: resultData.dictVersion })``await db.dict.clear()``await db.dict.bulkAdd(resultData.dicts)``}``createMenuFormRelations(routeName, applicationCode, menuFormrelationInfos)``formData = await db.form.get(formId)``} else { // 从indexdb找到数据,如果从菜单进入,需要调用接口,判断版本号信息``if (fromMenu) {``delete db.menuFormRelationInfo[routeName]``var relationInfos = await findRelationConfigs(formId, 1, [])``var relationParams = { applicationCode: applicationCode, formId: formId, relationInfos: relationInfos, dictVersion: dictFrameworkCache?.version }``var checkResult = await SpriteRumtimeApi.checkversions(relationParams)``if ((checkResult && checkResult.formDatas && checkResult.formDatas.length > 0) || (checkResult && checkResult.viewDatas && checkResult.viewDatas.length > 0)) {``relationInfos = []``}``if (checkResult && checkResult.formDatas && checkResult.formDatas.length > 0) {``for (``var i2 = 0; i2 < checkResult.formDatas.length; i2++) {``await db.form.put(checkResult.formDatas[i2])``relationInfos.push({relationType: 1, id: checkResult.formDatas[i2].id, version: checkResult.formDatas[i2].version})``}``}``if (checkResult && checkResult.viewDatas && checkResult.viewDatas.length > 0) {``for (``var j2 = 0; j2 < checkResult.viewDatas.length; j2++) {``await db.view.put(checkResult.viewDatas[j2])``relationInfos.push({relationType: 2, id: checkResult.viewDatas[j2].id, version: checkResult.viewDatas[j2].version})``}``}``if (checkResult && checkResult.dictVersion && checkResult.dicts) {``await db.frameworkCache.put({ id: 'dict'``, version: checkResult.dictVersion })``await db.dict.clear()``await db.dict.bulkAdd(checkResult.dicts)``}``createMenuFormRelations(routeName, applicationCode, relationInfos)``formData = await db.form.get(formId)``}``}``return formData``} |

开源地址:https://gitee.com/kuangqifu/sprite

体验地址:http://47.108.141.193:8031(首次加载可能有点慢,用的阿里云最差的服务器)

自定义表单文章地址:https://blog.csdn.net/spritekuang/

流程引擎文章地址:https://blog.csdn.net/spritekuang/category/834975.html(采用WWF开发,已过时,已改用Elsa实现,https://blog.csdn.net/spritekuang/p/14970992.html )

Github地址:https://github.com/kuangqifu/CK.Sprite.Job

举报

相关推荐

0 条评论