如何实现页面广告随时上下线、过期自动下线及到时自动上线

戳上方蓝字 “程序猿杂货铺” 关注我!

你的关注意义重大!

640?wx_fmt=jpeg

阅读文本大概需要 9 分钟。

原文链接:https://juejin.im/post/5c7e4907f265da2dcf62a77c

作者:walkinger

背景引入

最近需要实现一个功能,关于页面广告自动配置的,如支付宝的支付完成页。这篇随笔是记录对这个需求从分析到实现以及优化的过程,以免以后忘记。

需求描述

某些页面需要配置广告或活动宣传图,广告或活动需满足随时上下线、过期自动下线及到时自动上线。

如:现在时间 2019-2-22 16:16:13,要在支付完成页面配置领奖活动,活动要在 2019-3-10 00:00:00 准时上线,在 2019-3-30 23:59:59 结束活动。

所以要的效果是,在活动上线前的任意时刻配置完活动后,页面到时间自动上线这个活动。 也可能会是其他的多个活动或广告,每个页面广告的个数可变,不同上下线时间可不同,其他页面也需要实现这样的功能,页面与页面之间的活动不一定一样。

需求分析

需求简单的几句话,那么我们来具体的分析一下。

提取关键词
  • 广告或活动宣传图

  • 随时上下线、过期自动下线及到时自动上线

  • 每个页面广告的个数可变

  • 不同广告上下线时间可不同

  • 页面与页面之间的活动不一定一样

数据库分析

1、【广告或活动宣传图】

要为不同页面设置不同的广告,有的页面广告可能一样,也就是广告会复用,所以要有广告表

2、【每个页面广告的个数可变】【不同广告上下线时间可不同】【页面与页面之间的活动不一定一样】

页面可配置多个广告,所有要有页面配置表,以及广告和页面的关系表,即页面广告表

页面配置表主要配置页面的广告个数,实现【每个页面广告的个数可变】,页面广告表主要配置页面的每个广告上下线时间,实现【不同广告上下线时间可不同】

简单分析后得出如下表结构:广告表 adv,页面配置表 pageconfig,页面广告表 pageadv

640?wx_fmt=other

思考

这些页面配置的广告在一段时间内是不会变的,如果页面请求次数较多,广告查询次数就会很频繁,对数据库造成不必要的压力。所以可以引入缓存,降低数据库请求次数,缓解数据库压力。这里使用的 Redis。

何时入缓存?

可以选择在服务启动时异步把已在上下线时间区间内的广告先加载至缓存,或选择在请求时取缓存,缓存内没有时再查库然后放缓存。缓存时间视情况而定。

这里选择的是,项目启动时异步把符合条件的页面广告配置信息存入 Redis,那些还没到指定时间的先不放Redis,等到访问页面加载广告时,先查 Redis,若无则按条件(>=nowtime)查库,查到后存 Redis。

在接口中拿到广告配置信息后,判断当前时间是否在配置的时间区间内,由于一个页面配置多个广告,不同广告时间也不同,所以要迭代,把符合的返回,有过期的就做标记,然后把整个页面的配置信息在 Redis 里删除。 (或者不选择在启动时加载,就在用户请求时加入缓存,但是下面的第 1 步的方法在刷新加载时会用到,故不能删)

具体实现

第1步、项目启动时先把页面广告配置信息存入Redis

a、查询所有 pageId

 
 
  1. SELECT pageId FROM page_config page_adv WHERE nowtime <= endtime AND GROUP BY pageId

两个表内连接,得List,得到的都是配置的有广告的并且广告还没过期的pageId。

b、查询pegeId对应的广告图片及跳转链接

 
 
  1. SELECT 字段名 FROM page_adv adv WHERE begintime <= nowtime <= endtime AND pageId = {#pageId}

然后把查到的配置信息 List(为空时不做操作),以 pageId 为 KEY 放入缓存。

第2步、给前端写接口查询页面广告

按标准的控制层,业务层,数据访问层写,第一步中的逻辑就是在业务层完成的。

控制层:

控制层接参 pageId,调用业务层查询对应页面配置的广告信息,判空,直接返回状态码 0,即无广告前端不展示。

不为空就根据业务逻辑处理数据(如 img 的 URL 加域名),然后返回状态码 1,前端展示广告。 这里控制层还可以加逻辑,迭代广告 list,把当前时间在广告起始时间内的返回,不在的不返回,并且只要有一个广告过期,就把这个页面的广告 list 缓存清掉。这个逻辑是把过期的清掉。

业务层:

先取缓存,没有再查库判断不为空(本页面配置的有广告),放入缓存( pageId 为 KEY ),然后返回。

数据访问层:

SQL:

 
 
  1. SELECT 字段名 FROM page_config adv page_adv

    WHERE begintime <= nowtime <= endtime AND pageId = #{pageId}

三表联查,根据 pageId 查询当前页面配置的广告活动信息(已在广告活动时间内)

第 3 步、刷新加载

为什么使用刷新加载?

因为有这样的场景:给页面A配置了一个广告(当前时间在广告的起始时间内),那么这个页面的广告已经在缓存里了,假如此时A页面要新加一个广告,在后台配置后如果不做其他操作,这个广告不会显示(假设缓存时间较长,为一天),因为库更新了,缓存没有同步更新。

解决方案

使用 Redis 的发布订阅机制实现缓存的刷新加载,使新配置的广告及时能够显示。 刷新加载的回调方法即第 1 步中的方法。

进一步优化

想一想,目前的实现存在什么问题?

存在的问题

假如有页面需要配置广告,但是还没有配(前端已经开发完上线,每次都会调接口查广告信息),那么数据库肯定查不到,缓存也没有。如果这个页面访问量很大,那么缓存没命中就查库,这样对库的压力就会很大,这就是缓存穿透,请求上来了很容易击垮数据库。那怎么办呢?

解决方案

当页面没有配置广告时,在缓存存标志,查询时先看标志,在决定是否往下走。

具体方案

这时,上面的第 1 步就要改了。

1、首先改第 1 步的步骤 a 的 SQL,把所有的 pageId 都查询出来。

使用左连接

 
 
  1. SELECT pageId FROM page_config LEFT JOIN page_adv ON ... GROUP BY pageId

或者干脆查 page_config

 
 
  1. SELECT pageId FROM page_config

目的是把已在 pageconfig 表中配置,但关系表中 pageadv 未配置广告的 pageId 也查出来,这样才能给未配置广告的 pageId 在缓存里放标志

2、第 1 步的步骤 b 的 SQL 改为

 
 
  1. SELECT 字段名 FROM page_adv adv WHERE nowtime <= endtime AND pageId = {#pageId}

然后把查到的配置信息放入缓存之前判断【为空时的不做操作】改为【为空时存入一个标志】假如这个标志 KEY 为 pageId+"EMPTYFLAG",value为" DATABASEIS_NULL"

为什么只判断小于结束时间

因为如果该页面配置的广告开始时间大于当前时间,那么这个是查不到的,会被处理为 DATABASEISNULL,如果在这个标志还没失效之前就到了配置的开始时间了,那么这个广告不会被展示。所有要让未到开始时间的也放入缓存,然后让控制层去判断在不在时间区间。

3、所以要在第 2 步也修改一下

在业务层里取缓存中的广告列表之前,先从缓存取 pageId+"EMPTYFLAG" 的 value 判断为 "DATABASEIS_NULL" 直接返回空,这样就能解决缓存穿透的问题了。

继续修改第 2 步的业务层,查库的SQL同样要改:

 
 
  1. SELECT 字段名 FROM page_config adv page_adv

  2. WHERE nowtime <= endtime AND pageId = #{pageId}

然后判断为空的话,同上面的黄字那样处理。

4、最后,第 3 步的刷新加载调的是第 1 步的方法,不用改。

当然这个缓存穿透的优化方案只是其中一种。

还可以这样:

  1. 控制层拦截:根据 pageId 查询 page_adv 表,查不到说明没配置,直接返回。

  2. page_config 表增加字段,表示当前页面已经配置的广告个数,默认 0,每配置一个该字段加 1,把大于 0 的 pageId 缓存起来,调接口时前判断在不在缓存里。

总结:

实现这个功能并不是太难,主要用到了 Redis 的缓存技术,Redis 发布订阅机制,关键就是细节的把控,以及缓存穿透的处理。

推荐阅读

学 Guava 发现:不可变特性与防御性编程

我是一个平庸的程序员

并不是所有的 Github 写在简历上都加分

640?wx_fmt=png

加微信 进【技术交流群】

640?wx_fmt=jpeg


扫码关注 获取更多精彩

640?wx_fmt=png


新的一周从你的一个好看开始
640?wx_fmt=gif
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值