技术选型 分布式缓存方案默认采用的是主流的缓存框架:Redis,即将缓存数据存储在另一台Redis服务器上。系统在使用缓存时,依赖的是缓存的接口,而非具体的实现; 分布式缓存在更新时不允许并发更新,防止缓存击穿,因此我们在Redis的基础上,采用了基于Redisson的分布式锁,在更新分布式缓存前必须先获得锁。缓存生命周期 更新机制被动刷新:基于Redis的数据驱逐策略,包括LRU和TTL等;主动刷新:业务数据驱动的数据更新。当业务侧有数据变更时,将会主动刷新分布式缓存。比如当秒杀品下线时,会发出相应的领域事件,而在领域事件的处理中就会刷新缓存。 分布式缓存在刷新的过程中,并不会主动刷新所有服务器上的本地缓存,本地缓存将遵循单机的刷新策略。这意味着,本地缓存可能会有秒级或毫秒级的滞后,对于数据一致性非绝对敏感的场景,这种短时间的延迟下的脏数据是可以接受的,它只是会对用户侧的展示有所影响,而不会影响到服务端的数据状态。分布式锁基于Redis实现分布式锁利用setnxex获取锁,并设置过期时间,保存线程标识释放锁时先判断线程标识是否与自己一致,一致则删除锁基于redis的分布式锁publicclassSimpleRedisLockimplementsILock{privateStringname;privateStringRedisTemplatestringRedisTemplate;publicSimpleRedisLock(Stringname,StringRedisTemplatestringRedisTemplate){this。namename;this。stringRedisTemplatestringRedisTemplate;}privatestaticfinalStringKEYPREFIXlock:;privatestaticfinalStringIDPREFIXUUID。randomUUID()。toString();锁paramtime锁的过期时间returnOverridepublicbooleantryLock(longtime){longidThread。currentThread()。getId();值用uuid线程id拼接StringvalueIDPREFIXid;自动拆箱有空指针问题BooleanaBooleanstringRedisTemplate。opsForValue()。setIfAbsent(KEYPREFIXname,value,time,TimeUnit。SECONDS);returnBoolean。TRUE。equals(aBoolean);}解锁OverridepublicvoidunLock(){StringvalueInRedisstringRedisTemplate。opsForValue()。get(KEYPREFIXname);longidThread。currentThread()。getId();值用uuid线程id拼接StringvalueIDPREFIXid;两者相同才释放锁,要先做判断再进行释放if(value。equals(valueInRedis)){stringRedisTemplate。delete(KEYPREFIXname);}}}基于Redison实现分布式锁 导包dependencygroupIdorg。redissongroupIdredissonartifactIdversion3。16。3versiondependency 配置文件ConfigurationpublicclassRedissonConfig{BeanpublicRedissonClientredissonClient(){ConfigconfignewConfig();config。useSingleServer()。setAddress(redis:localhost:6379)。setPassword(ezreal)。setDatabase(0);returnRedisson。create(config);}} 代码实现ComponentpublicclassRedissonLockServiceimplementsDistributedLockFactoryService{privatefinalLoggerloggerLoggerFactory。getLogger(RedissonLockService。class);ResourceprivateRedissonClientredissonClient;OverridepublicDistributedLockgetDistributedLock(Stringkey){RLockrLockredissonClient。getLock(key);returnnewDistributedLock(){OverridepublicbooleantryLock(longwaitTime,longleaseTime,TimeUnitunit)throwsInterruptedException{booleanisLockSuccessrLock。tryLock(waitTime,leaseTime,unit);logger。info({}getlockresult:{},key,isLockSuccess);returnisLockSuccess;}Overridepublicvoidlock(longleaseTime,TimeUnitunit){rLock。lock(leaseTime,unit);}Overridepublicvoidunlock(){if(isLocked()isHeldByCurrentThread()){rLock。unlock();}}OverridepublicbooleanisLocked(){returnrLock。isLocked();}OverridepublicbooleanisHeldByThread(longthreadId){returnrLock。isHeldByThread(threadId);}OverridepublicbooleanisHeldByCurrentThread(){returnrLock。isHeldByCurrentThread();}};}} 实现原理可重入:利用hash结构记录线程id和重入次数可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制超时续约::利用watchDog,每隔一段时间(releaseTime3),重置超时时间 分布式缓存基本逻辑 每次获取缓存的时候,先从本地缓存中获取,再从分布式缓存中获取。 若分布式缓存中不存在对应的值,则需要获取分布式锁,然后对分布式缓存数据进行更新若获取锁成功,则查询数据库,获取最新的数据放入缓存; 若数据库中的数据也为空,则也要存储入数据库,防止缓存穿透若获取锁失败,则直接返回,不要等待重新获取锁,客户端对这次请求进行静默处理;实现代码 缓存的接口 实现部分Redis数据结构的存储接口publicinterfaceDistributedCacheService{voidput(Stringkey,Stringvalue);voidput(Stringkey,Objectvalue);voidput(Stringkey,Objectvalue,longtimeout,TimeUnitunit);voidput(Stringkey,Objectvalue,longexpireTime);TTgetObject(Stringkey,ClassTtargetClass);StringgetString(Stringkey);TListTgetList(Stringkey,ClassTtargetClass);Booleandelete(Stringkey);BooleanhasKey(Stringkey);} 基本逻辑代码privateSeckillGoodCacheupdateDistributedSeckillGood(LongitemId){logger。info(更新远程缓存{},itemId);DistributedLockdistributedLockdistributedLockFactoryService。getDistributedLock(UPDATEITEMSCACHELOCKKEYitemId);try{booleantryLockdistributedLock。tryLock(1,5,TimeUnit。SECONDS);如果没有获得到锁,就返回重试if(!tryLock){returnnewSeckillGoodCache()。tryLater();}再次检查SeckillGoodCachedistributedSeckillCachedistributedCacheService。getObject(buildItemCacheKey(itemId),SeckillGoodCache。class);if(distributedSeckillCache!null){returndistributedSeckillCache;}查询数据库SeckillGoodseckillGoodseckillGoodMapper。selectById(itemId);SeckillGoodCacheseckillGoodCachenewSeckillGoodCache();if(seckillGoodnull){数据不存在也要返回也要存缓存防止缓存穿透seckillGoodCache。notExist();}else{seckillGoodCache。with(seckillGood)。setVersion(System。currentTimeMillis());}logger。info(itemCache远程缓存已更新{},itemId);distributedCacheService。put(buildItemCacheKey(itemId),JSON。toJSONString(seckillGoodCache));returnseckillGoodCache;}catch(InterruptedExceptione){logger。error(itemCache远程缓存更新失败{},itemId);returnnewSeckillGoodCache()。tryLater();}finally{distributedLock。unlock();}}