最近须要实现一个功能,关于页面广告自动配置的,如支付宝的支付完成页。这篇随笔是记录对这个需求从分析到实现以及优化的过程,以避免之后忘记。前端
某些页面须要配置广告或活动宣传图,广告或活动需知足随时上下线、过时自动下线及到时自动上线。数据库
如:如今时间2019-2-22 16:16:13,要在支付完成页面配置领奖活动,活动要在2019-3-10 00:00:00准时上线,在2019-3-30 23:59:59结束活动。缓存
因此要的效果是,在活动上线前的任意时刻配置完活动后,页面到时间自动上线这个活动。 也可能会是其余的多个活动或广告,每一个页面广告的个数可变,不一样上下线时间可不一样,其余页面也须要实现这样的功能,页面与页面之间的活动不必定同样。bash
需求简单的几句话,那么咱们来具体的分析一下。网络
一、【广告或活动宣传图】异步
要为不一样页面设置不一样的广告,有的页面广告可能同样,也就是广告会复用,全部要有广告表。优化
二、【每一个页面广告的个数可变】【不一样广告上下线时间可不一样】【页面与页面之间的活动不必定同样】spa
页面可配置多个广告,全部要有页面配置表,以及广告和页面的关系表,即页面广告表。code
页面配置表主要配置页面的广告个数,实现【每一个页面广告的个数可变】,页面广告表主要配置页面的每一个广告上下线时间,实现【不一样广告上下线时间可不一样】cdn
简单分析后得出以下表结构:广告表adv,页面配置表page_config,页面广告表page_adv
这些页面配置的广告在一段时间内是不会变的,若是页面请求次数较多,广告查询次数就会很频繁,对数据库形成没必要要的压力。因此能够引入缓存,下降数据库请求次数,缓解数据库压力。这里使用的Redis。
什么时候入缓存?
能够选择在服务启动时异步把已在上下线时间区间内的广告先加载至缓存,或选择在请求时取缓存,缓存内没有时再查库而后放缓存。缓存时间视状况而定。
这里选择的是,项目启动时异步把符合条件的页面广告配置信息存入Redis,那些还没到指定时间的先不放Redis,等到访问页面加载广告时,先查Redis,若无则按条件(>=nowtime)查库,查到后存Redis。
在接口中拿到广告配置信息后,判断当前时间是否在配置的时间区间内,因为一个页面配置多个广告,不一样广告时间也不一样,因此要迭代,把符合的返回,有过时的就作标记,而后把整个页面的配置信息在Redis里删除。 (或者不选择在启动时加载,就在用户请求时加入缓存,可是下面的第1步的方法在刷新加载时会用到,故不能删)
a、查询全部pageId
SELECT pageId FROM page_config page_adv WHERE nowtime<=endtime AND GROUP BY pageId
复制代码
两个表内链接,得List<pageId>,获得的都是配置的有广告的而且广告还没过时的pageId。
b、查询pegeId对应的广告图片及跳转连接
SELECT 字段名 FROM page_adv adv WHERE begintime<=nowtime<=endtime AND pageId={#pageId}
复制代码
而后把查到的配置信息List<adv>(为空时不作操做),以pageId为KEY放入缓存。
按标准的控制层,业务层,数据访问层写,第一步中的逻辑就是在业务层完成的。
控制层:
控制层接参pageId,调用业务层查询对应页面配置的广告信息,判空,直接返回状态码0,即无广告前端不展现。
不为空就根据业务逻辑处理数据(如img的URL加域名),而后返回状态码1,前端展现广告。 这里控制层还能够加逻辑,迭代广告list,把当前时间在广告起始时间内的返回,不在的不返回,而且只要有一个广告过时,就把这个页面的广告list缓存清掉。这个逻辑是把过时的清掉。
业务层:
先取缓存,没有再查库判断不为空(本页面配置的有广告),放入缓存(pageId为KEY),而后返回。
数据访问层:
SQL:
SELECT 字段名 FROM page_config adv page_adv WHERE begintime<=nowtime<=endtime AND pageId=#{pageId}
复制代码
三表联查,根据pageId查询当前页面配置的广告活动信息(已在广告活动时间内)
为何使用刷新加载?
由于有这样的场景:给页面A配置了一个广告(当前时间在广告的起始时间内),那么这个页面的广告已经在缓存里了,假如此时A页面要新加一个广告,在后台配置后若是不作其余操做,这个广告不会显示(假设缓存时间较长,为一天),由于库更新了,缓存没有同步更新。
解决方案
使用Redis的发布订阅机制实现缓存的刷新加载,使新配置的广告及时可以显示。 刷新加载的回调方法即第1步中的方法。
想想,目前的实现存在什么问题?
存在的问题
假若有页面须要配置广告,可是尚未配(前端已经开发完上线,每次都会调接口查广告信息),那么数据库确定查不到,缓存也没有。若是这个页面访问量很大,那么缓存没命中就查库,这样对库的压力就会很大,这就是缓存穿透,请求上来了很容易击垮数据库。那怎么办呢?
解决方案
当页面没有配置广告时,在缓存存标志,查询时先看标志,在决定是否往下走。
具体方案
这时,上面的第1步就要改了。
一、首先改第1步的步骤a的SQL,把全部的pageId都查询出来。
使用左链接
SELECT pageId FROM page_config LEFT JOIN page_adv ON ... GROUP BY pageId
复制代码
或者干脆查page_config
SELECT pageId FROM page_config
复制代码
目的是把已在page_config表中配置,但关系表中page_adv未配置广告的pageId也查出来,这样才能给未配置广告的pageId在缓存里放标志
二、第1步的步骤b的SQL改成
SELECT 字段名 FROM page_adv adv WHERE nowtime<=endtime AND pageId={#pageId}
复制代码
而后把查到的配置信息放入缓存以前判断【为空时的不作操做】改成【为空时存入一个标志】假如这个标志KEY为pageId+"_EMPTY_FLAG",value为"DB_IS_NULL"
为何只判断小于结束时间
由于若是该页面配置的广告开始时间大于当前时间,那么这个是查不到的,会被处理为DATABASE_IS_NULL,若是在这个标志还没失效以前就到了配置的开始时间了,那么这个广告不会被展现。全部要让未到开始时间的也放入缓存,而后让控制层去判断在不在时间区间。
三、因此要在第2步也修改一下
在业务层里取缓存中的广告列表以前,先从缓存取pageId+"EMPTY_FLAG"的value判断为"DB_IS_NULL"直接返回空,这样就能解决缓存穿透的问题了。
继续修改第2步的业务层,查库的SQL一样要改:
SELECT 字段名 FROM page_config adv page_adv WHERE nowtime<=endtime AND pageId=#{pageId}
复制代码
而后判断为空的话,同上面的黄字那样处理。
四、最后,第3步的刷新加载调的是第1步的方法,不用改。
固然这个缓存穿透的优化方案只是其中一种。还能够这样:
一、控制层拦截:根据pageId查询page_adv表,查不到说明没配置,直接返回。
二、page_config 表增长字段,表示当前页面已经配置的广告个数,默认0,每配置一个该字段加1,把大于0的pageId缓存起来,调接口时前判断在不在缓存里。
实现这个功能并非太难,主要用到了Redis的缓存技术,Redis发布订阅机制,关键就是细节的把控,以及缓存穿透的处理。
(封面图片来源于网络,侵权请联系删除)