- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
在电商或服务平台中,缓存的使用是提高系统性能和响应速度的关键。然而,缓存穿透是一个常见的性能瓶颈问题,尤其是在查询不存在的数据时,系统会直接访问数据库,这不仅影响性能,还可能造成数据库负担过重。为了有效解决这个问题,我们提出了一种结合 布隆过滤器、空值缓存 和 分布式锁 的缓存穿透防护方案。以下是该方案的工作流程.
用户首先发起对优惠券模板信息的请求。该请求包括一个优惠券模板ID,系统需要根据该ID返回相应的优惠券信息.
系统首先会在 Redis缓存 中查询是否已经缓存了相关的优惠券信息。Redis 是一个高效的缓存系统,通常可以极大地提高查询速度。如果缓存中存在相应的模板信息,系统直接返回给用户,查询过程结束.
如果 Redis 缓存中没有找到对应的优惠券模板信息,系统会进一步通过 布隆过滤器 检查该模板ID是否有效。布隆过滤器是一种空间效率极高的数据结构,用来快速判断某个元素是否在集合中.
在布隆过滤器判断模板ID有效的情况下,系统继续检查 Redis 缓存中是否存在空值缓存。空值缓存是指对于某些查询,数据库返回了“空”结果(例如优惠券模板ID不存在于数据库中),为了避免重复查询数据库,这类空结果会被缓存一段时间.
为了防止多个请求同时查询数据库,造成数据库压力过大,或者多个线程同时执行相同查询操作,系统使用了 分布式锁 来确保在同一时间只有一个请求会访问数据库查询数据.
如果分布式锁可用,系统获取锁,并进行以下步骤:
如果分布式锁不可用,表示其他请求正在进行相同的数据库查询操作,系统会等待锁释放或返回错误信息.
public CouponTemplateQueryRespDTO getCouponTemplate(CouponTemplateQueryReqDTO requestParam) {
// 查询 Redis 缓存中是否存在优惠券模板信息
String cacheKey = String.format(RedisConstants.COUPON_TEMPLATE_KEY, requestParam.getTemplateId());
Map<Object, Object> cacheMap = stringRedisTemplate.opsForHash().entries(cacheKey);
// 如果缓存存在直接返回,否则通过布隆过滤器、空值缓存以及分布式锁查询数据库
if (MapUtil.isEmpty(cacheMap)) {
// 判断布隆过滤器是否存在指定模板 ID,不存在则返回错误
if (!bloomFilter.contains(requestParam.getTemplateId())) {
throw new ClientException("Coupon template does not exist");
}
// 查询 Redis 缓存中是否存在空值信息,如果存在则直接返回
String nullCacheKey = String.format(RedisConstants.COUPON_TEMPLATE_NULL_KEY, requestParam.getTemplateId());
Boolean isNullCached = stringRedisTemplate.hasKey(nullCacheKey);
if (isNullCached) {
throw new ClientException("Coupon template does not exist");
}
// 获取分布式锁
RLock lock = redissonClient.getLock(String.format(RedisConstants.LOCK_COUPON_TEMPLATE_KEY, requestParam.getTemplateId()));
lock.lock();
try {
// 双重检查空值缓存
isNullCached = stringRedisTemplate.hasKey(nullCacheKey);
if (isNullCached) {
throw new ClientException("Coupon template does not exist");
}
// 使用双重检查锁避免并发查询数据库
cacheMap = stringRedisTemplate.opsForHash().entries(cacheKey);
if (MapUtil.isEmpty(cacheMap)) {
LambdaQueryWrapper<CouponTemplate> queryWrapper = Wrappers.lambdaQuery(CouponTemplate.class)
.eq(CouponTemplate::getShopId, Long.parseLong(requestParam.getShopId()))
.eq(CouponTemplate::getId, Long.parseLong(requestParam.getTemplateId()))
.eq(CouponTemplate::getStatus, TemplateStatusEnum.ACTIVE.getStatus());
CouponTemplate couponTemplate = couponTemplateMapper.selectOne(queryWrapper);
// 如果模板不存在或已过期,设置空值缓存并抛出异常
if (couponTemplate == null) {
stringRedisTemplate.opsForValue().set(nullCacheKey, "", 30, TimeUnit.MINUTES);
throw new ClientException("Coupon template does not exist or has expired");
}
// 将数据库记录序列化并存入 Redis 缓存
CouponTemplateQueryRespDTO responseDTO = BeanUtil.toBean(couponTemplate, CouponTemplateQueryRespDTO.class);
Map<String, Object> responseMap = BeanUtil.beanToMap(responseDTO, false, true);
Map<String, String> cacheData = responseMap.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue() != null ? entry.getValue().toString() : ""
));
// 使用 Lua 脚本将数据存入 Redis 并设置过期时间
String luaScript = "redis.call('HMSET', KEYS[1], unpack(ARGV, 1, #ARGV - 1)) " +
"redis.call('EXPIREAT', KEYS[1], ARGV[#ARGV])";
List<String> keys = Collections.singletonList(cacheKey);
List<String> args = new ArrayList<>(cacheData.size() * 2 + 1);
cacheData.forEach((key, value) -> {
args.add(key);
args.add(value);
});
// 设置优惠券活动的过期时间
args.add(String.valueOf(couponTemplate.getEndTime().getTime() / 1000));
// 执行 Lua 脚本
stringRedisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
keys,
args.toArray()
);
cacheMap = cacheData.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
} finally {
lock.unlock();
}
}
// 返回从缓存中获取的数据
return BeanUtil.mapToBean(cacheMap, CouponTemplateQueryRespDTO.class, false, CopyOptions.create());
}
最后此篇关于缓存穿透防护方案设计的文章就讲到这里了,如果你想了解更多关于缓存穿透防护方案设计的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我的印象是 header guards 解决了重新定义的问题。我收到链接器错误,指出 .obj 文件中存在重新定义。这是我包含的 header ,问题在于所有全局声明的重新定义。 #ifndef DI
我正在 Microsoft Azure 中运行企业级应用程序。我想知道 Microsoft Azure 中 DDOS 投影的建议是什么。该文档明确指出该平台受到 DDOS 保护,但没有提供更多详细信息
我正在用 laravel sanctum 进行测试,但这里有一些问题.. 我正在创建管理员守卫。 当我将中间件更改为 auth:sanctum_admin .. 它应该只能由管理员访问,但在这里我可以
我有一个带有延迟加载模块的 Angular 4.3.6 应用程序。这是部分根路由器: const routes: Routes = [ { path: '', redirectTo: 'fleet
我有 Vaadin UI 的后端。我想保护后端数据库免受XSS攻击。对于 Vaadin UI 的 XSS 防护,您有何建议? 最佳答案 Vaadin 内置了 XSS 保护。所有组件都正确转义/编码 h
我正在尝试提高我的应用程序的安全性。每当我从用户(无论是通过 POST 还是 GET)接收到应该是整数的数据时,我都会适本地对其进行验证。但通常数据是 VARCHAR,有时可以包含 HTML。 在这种
我有一个具有此功能的服务,它会在 token 有效或无效时返回 true 或 false loggedIn() { return this.http.get('http://localhost:300
我们的应用程序中有几条路线,用户不应通过直接在浏览器的地址栏中输入网址来导航这些路线。 相反,当我们使用 router.navigate() 通过我们的应用程序逻辑以编程方式导航时,我们只想让它们可访
我通常在 .NET 中编写我的 SQL sql.Append("SELECT id, code, email FROM mytable WHERE variable = @variable "); 然
我需要同时将多行(1000 行)插入到 SQL Server 数据库中。我认为最好的方法是使用 SqlBulkCopy 但我不确定如何参数化插入查询以防止 SQL 注入(inject)。 你能帮我一下
Laravel 5.7 PHP 7.2.10 目前我可以使用 Web 和 api 防护中的任何一个,是否有任何方法可以同时允许两者,以便 Web 应用程序和 api 能够协同工作。 类似的东西 ret
Guard 总是因为这个问题而困扰我: .../gems/bundler-1.6.2/lib/bundler/runtime.rb:34:in `block in setup': You have a
我正在尝试通过 gmail api 从 Google 获取邮件 在收到 token 后对 Google_Client 进行身份验证时出现此错误 fatal error :未捕获异常“Google_Au
我是一名优秀的程序员,十分优秀!