在信息暴炸的时代,为了在项目中提高数据加载效率,缓存技术是必不可以少的,缓存技术存在于应用场景的方方面面。从浏览器请求,到反向代理服务器,从进程内缓存到分布式缓存。其中缓存策略,算法也是层出不穷,下面要说的就是一套如何实现一套可以对后端服务器形成最小压力的架构。
一、缓存的解析
借一下上一篇文章中的图,其实现在问很多人怎么提高数据的访问速度和效率,很多人都能回答出做缓存处理,这句话可以说对也可以说不对,对是因为没错,缓存是可以提高效率,但并不是做了缓存处理他的并发量就一定能提升的上来,就以上图为例,如果说前端请求很高,达到了2000K,如果所有的数据加载在后端redis中做了缓存,如果这时所有用户请求全部从前端传到后端,服务器照样承受不了这么高并发,所以说只在后端提升了加载速度是没有用的,正确的做法是把缓存在到Nginx中而不是存到后台服务器,原因就是Nginx能承受的并发比tomcat要高。
有了这个思路后,架构就要换一下了,如上图,当第一次请求过来时走后台去后台Redis中查询,并存在Nginx下面的Redis,之后的所有请求就不用走后台查询,这样性能就提升上来了,这时还要考虑一个问题,那就是如果Nginx下的Redis本身没有缓存数据又怎么搞,那就要去Nginx缓存中去找,因为Nginx本身是带有缓存功能的,如果Nginx中存在要的缓存就直接从Nginx中返回给用户,这样一设计后可以几十万个请求就只有几十个落在了后台服务器上了。这种设计除了可以有效提高加载速度、降低后端服务负载之外,还可以防止缓存雪崩,这也就是电商中用的多级缓存架构体系。
二、应用
我们在看购物平台时有很多商品优先推荐展示,这些其实都是推广商品,并非真正意义上的热门商品,首页展示这些商品数据需要加载效率极高,并且商城首页访问频率也是极高,所以需要对首页数据做缓存处理,首先想到的就是Redis缓存。
接下来要作的东西就是设计推广商品的表结构及逻辑设计,其实这推广产品不一定只在首页出现,可能在别的地方也有,所以可以设计一张表存放这玩意,表结构如下
DROP TABLE IF EXISTS `ad_items`; CREATE TABLE `ad_items` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT NULL, `type` int(3) DEFAULT NULL COMMENT '分类,1首页推广,2列表页推广', `sku_id` varchar(60) DEFAULT NULL COMMENT '展示的产品', `sort` int(11) DEFAULT NULL COMMENT '排序', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=123 DEFAULT CHARSET=utf8;
建立实体类:
@Data @AllArgsConstructor @NoArgsConstructor //MyBatisPlus表映射注解 @TableName(value = "ad_items") public class AdItems implements Serializable{ @TableId(type = IdType.AUTO) private Integer id; private String name; private Integer type; private String skuId; private Integer sort; }
public interface AdItemsMapper extends BaseMapper<AdItems> { }
public interface SkuService extends IService<Sku> { /** * 根据推广产品分类ID查询Sku列表 * @param id * @return */ List<Sku> typeSkuItems(Integer id); }
@Service public class SKuServiceImpl extends ServiceImpl<SkuMapper, Sku> implements SkuService { @Resource private AdItemsMapper adItemsMapper; @Resource private SkuMapper skuMapper; @Override public List<Sku> typeSkuItems(Integer id) { //1.查询当前分类下的所有列表信息 QueryWrapper<AdItems> adItemsQueryWrapper = new QueryWrapper<AdItems>(); adItemsQueryWrapper.eq("type",id); List<AdItems> adItems = adItemsMapper.selectList(adItemsQueryWrapper); //2.根据推广列表查询产品列表信息 List<String> skuids = adItems.stream().map(adItem->adItem.getSkuId()).collect(Collectors.toList()); return skuids==null || skuids.size()<=0? null : skuMapper.selectBatchIds(skuids); } }
@RestController @RequestMapping(value = "/sku") public class SkuController { @Autowired private SkuService skuService; /**** * 根据推广分类查询推广产品列表 * */ @GetMappingpublic List<Sku> typeItems(@RequestParam(value = "id")Integer id){ //查询 List<Sku> skus = skuService.typeSkuItems(id); return skus; } }
经过上面的代码就可以实现热门商品推广操作,但是有个问题,因为前面说过热点数据访问量大,是要放在缓存里面进行缓存处理的,下面就来完成这最后一步
三、缓存处理
在改代码前先要说明几个常用的注解:
#Redis配置 redis: host: 192.168.32.135 port: 6379
然后在启动类上开启缓存
然后修改SKuServiceImpl类,加上红框内内容
完成这一步缓存就做好了,但是有个问题,前面说过热点商品可能在多个地方都存被调用的情况,针对这个情况就要写feigin接口,在写feigin接口同时我也顺便把修改和删除操作给写了,后面就懒着改这个类了
在spring-cloud-api的pom文件中先引用以下包
<!--工具包--> <dependency> <groupId>com.ghy</groupId> <artifactId>spring-cloud-common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.5.RELEASE</version> </dependency>
然后在spring-cloud-goods-api服务器写feigin接口
@FeignClient(value = "spring-cloud-goods-service")
public interface SkuFeign {
/****
* 根据推广分类查询推广产品列表
*/
@GetMapping(value = "/sku")
List<Sku> typeItems(@RequestParam(value = "id")Integer id);
/****
* 根据推广分类删除推广产品列表
*
*/
@DeleteMapping(value = "/sku")
RespResult delTypeItems(@RequestParam(value = "id")Integer id);
/****
* 根据推广分类修改推广产品列表
*/
@PutMapping(value = "/sku")
RespResult updateTypeItems(@RequestParam(value = "id")Integer id);
}
四、Lua+Redis实现多级缓存
前面说过Lua和多级缓存,接下来就把这两个东西结合起来实现;前面说过前端请求过来后第一次会通过Nginx会分发到tomact进行查询,然后将查询的数据放到nginx中去;现在要做的操作就是通过Lua脚本完善后面的操作,操作流程是这样,当请求过来
nginx会先执行lua脚本数据先从redis中去查询先看下redis有没有对应的数据,如果有则直接把数据响应给用户,没有再经过tomact查询;这时有的人会问,如果一不小心把Redis服务器中的数据全清空了怎么办,其实问题也不大,前面也提过Nginx本身也带有缓存,当Redis没查找数据时,他会去Nginx自身查下,看能不能找到数据;
下面先写一个lua脚本;
进入/usr/local/openresty/nginx/lua,在里面创建一个aditem.lua脚本
--数据响应类型JSON ngx.header.content_type="application/json;charset=utf8" --Redis库依赖 local redis = require("resty.redis"); local cjson = require("cjson"); --获取id参数(type) local id = ngx.req.get_uri_args()["id"]; --key组装 local key = "ad-items-skus::"..id --创建链接对象 local red = redis:new() --设置超时时间 red:set_timeout(2000) --设置服务器链接信息 red:connect("192.168.32.135", 6379) --查询指定key的数据 local result=red:get(key); --关闭Redis链接 red:close() if result==nil or result==null or result==ngx.null then return true else --输出数据 ngx.say(result) end
#推广产品查询 location /sku{ content_by_lua_file /usr/local/openresty/nginx/lua/aditem.lua; }
五、nginx代理缓存
前面有提过,当Redis缓存没有数据时,请求会去nginx缓存找数据;在这时就要提到一个东西,那就是proxy_cache ;proxy_cache 是用于 proxy 模式的缓存功能,proxy_cache 在 Nginx 配置的 http 段、server 段中分别写入不同的配置。http 段中的配置用于定义 proxy_cache 空间,server 段中的配置用于调用 http 段中的定义,启用对server 的缓存功能。要想使用它就要完成两个步骤:第一个步骤是定义缓存空间;第二个步骤是在指定地方使用定义的缓存 。
1、开启Proxy_Cache缓存,这个需要在nginx.conf中配置才能开启缓存:
proxy_cache_path /usr/local/openresty/nginx/cache levels=1:2 keys_zone=proxy_cache:10m max_size=1g inactive=60m use_temp_path=off;
#先找Nginx缓存 rewrite_by_lua_file /usr/local/openresty/nginx/lua/aditem.lua; #启用缓存openresty_cache proxy_cache proxy_cache; #针对指定请求缓存 #proxy_cache_methods GET; #设置指定请求会缓存 proxy_cache_valid 200 304 60s; #最少请求1次才会缓存 proxy_cache_min_uses 1; #如果并发请求,只有第1个请求会去服务器获取数据 #proxy_cache_lock on; #唯一的key proxy_cache_key $host$uri$is_args$args; #动态代理,这是在缓存中都没查到的情况
proxy_pass http://192.168.32.32:8081;
1:先查找Redis缓存 2:Redis缓存没数据,直接找Nginx缓存 3:Nginx缓存没数据,则找真实服务器
这时还可以发现在cache目录下多了目录个文件,这个东西就是Nginx缓存;这里补充下,如果你的linux是布在云上而你的项目是内外,那么动态代理配置的就是项目的公网地址;
六、Cache_Purge代理缓存清理
#清理缓存
location ~ /purge(/.*) {
#清理缓存
proxy_cache_purge proxy_cache $host$1$is_args$args;
}