【云商城】高性能门户网构建

第3章 高性能门户网构建

网站门户就是首页

1.OpenResty 百万并发站点架构

​ 1).OpenResty 特性介绍

​ 2).搭建OpenResty

​ 3).Web站点动静分离方案剖析

2.Lua语法学习

​ 1).Lua基本语法

3.多级缓存架构实战

​ 1).多级缓存架构分析

用户请求网站,最开始经过代理层nginx,经过tomcat,最后才到我们的java项目

在这里插入图片描述

​ 2).Lua操作Redis实战

​ 3).首页推广产品异步高效加载实战

4.Nginx代理缓存

​ 1).Nginx代理缓存学习

​ 2).Nginx代理缓存热点数据应用

​ 3).Cache_Purge代理缓存清理

5.缓存一致性

​ 1).Canal原理讲解

​ 2).Canal安装

​ 3).多级缓存架构缓存一致性实战

1 OpenResty高性能Web站点架构

在这里插入图片描述

http://openresty.org/en/

http://openresty.org/cn/

OpenResty® 是一款基于 NGINX 和 LuaJIT 的 Web 平台

1.1 OpenResty简介

OpenResty 是一个基于 Nginx与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关

OpenResty® 通过汇聚各种设计精良的 Nginx模块(主要由 OpenResty 团队自主开发),从而将 Nginx有效地变成一个强大的通用 Web 应用平台

OpenResty® 的目标是让你的Web服务直接跑在 Nginx服务内部,充分利用 Nginx的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL 以及 Redis 等都进行一致的高性能响应

区别:

Nginx:并发能力强、稳定、消耗资源小

Lua:所有脚本语言中性能最好的

1.2 OpenResty搭建

关于OpenResty的搭建,可以参考官方提供的网址进行搭建。http://openresty.org/cn/installation.html,我们采用源码安装的方式进行安装。

官方提供了源码安装的方式:http://openresty.org/cn/linux-packages.html

在这里插入图片描述

安装OpenResty:

1)安装依赖库:

yum install libtermcap-devel ncurses-devel libevent-devel readline-devel pcre-devel gcc openssl openssl-devel per perl wget

2)下载安装包:

wget https://openresty.org/download/openresty-1.11.2.5.tar.gz

3)解压安装包

tar -xf openresty-1.11.2.5.tar.gz

4)进入安装包,并安装

#进入安装包
cd openresty-1.11.2.5#安装
./configure --prefix=/usr/local/openresty --with-luajit --without-http_redis2_module --with-http_stub_status_module --with-http_v2_module --with-http_gzip_static_module --with-http_sub_module --add-module=/usr/local/gupao/ngx_cache_purge-2.3/#编译并安装
make && make install

说明:

--prefix=/usr/local/openresty:安装路径--with-luajit:安装luajit相关库,luajit是lua的一个高效版,LuaJIT的运行速度比标准Lua快数十倍。--without-http_redis2_module:现在使用的Redis都是3.x以上版本,这里不推荐使用Redis2,表示不安装redis2支持的lua库--with-http_stub_status_module:Http对应状态的库--with-http_v2_module:对Http2的支持--with-http_gzip_static_module:gzip服务端压缩支持--with-http_sub_module:过滤器,可以通过将一个指定的字符串替换为另一个字符串来修改响应--add-module=/usr/local/gupao/ngx_cache_purge-2.3/:Nginx代理缓存清理工具

关于每个模块的具体作用,大家可以参考腾讯云的开发者手册:https://cloud.tencent.com/developer/doc/1158

如下图安装完成后,在/usr/local/openrestry/nginx目录下是安装好的nginx,以后我们将在该目录的nginx下实现网站发布

在这里插入图片描述

5)配置环境变量:

vi /etc/profileexport PATH=/usr/local/openresty/nginx/sbin:$PATHsource /etc/profile

6)开机启动:

linux系统结构/lib/systemd/system/目录,该目录自动存放启动文件的配置位置,里面一般包含有xxx.service,例如systemctl enable nginx.service,就是调用 /lib/systemd/system/nginx.service文件,使nginx开机启动。

我们可以创建/usr/lib/systemd/system/nginx.service,在该文件中编写启动nginx脚本:

[Service]
Type=forking
PIDFile=/usr/local/openresty/nginx/logs/nginx.pid
ExecStartPre=/usr/local/openresty/nginx/sbin/nginx -t
ExecStart=/usr/local/openresty/nginx/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true[Install]
WantedBy=multi-user.target

执行systemctl daemon-reload:重新加载某个服务的配置文件

执行systemctl enable nginx.service:开机启动

执行systemctl start nginx.service:启动nginx

在这里插入图片描述

访问http://192.168.100.130/,效果如下:

在这里插入图片描述

1.3 动静分离站点架构

1.3.1 动静分离架构分析

我们打开京东商城,搜索手机,查看网络可以发现响应页面后,页面又会发起很多请求,还没有查看多少信息就已经有393个请求发出了,而多数都是图片,一个人请求如此,人多了对后端造成的压力是非比寻常的,该如何降低静态资源对服务器的压力呢?

在这里插入图片描述

如下图:

在这里插入图片描述

项目完成后,项目上线如果所有请求都经过Tomcat,并发量很大的时候,对项目而言将是灭顶之灾,电商项目中一个请求返回的页面往往会再次发起很多请求,而绝大多数都是图片或者是css样式、js等静态资源,如果这些静态资源都去查询Tomcat,Tomcat的压力会增加数十倍甚至更高,这时候我们需要采用动静分离的策略:

1.所有静态资源,经过Nginx,Nginx直接从指定磁盘中获取文件,然后IO输出给用户
2.如果是需要查询数据库数据的请求,就路由到Tomcat集群中,让Tomcat处理,并将结果响应给用户

1.3.2 门户静态站点发布

门户front:

在这里插入图片描述

点击index.html,就是一个商城的首页

在这里插入图片描述

修改本地文件C:\Windows\System32\drivers\etc\HOSTS文件,将案例演示域名www.gpshopvip.com解析到192.168.100.130服务器,在HOSTS文件中添加如下配置即可:

192.168.100.130 www.gpshopvip.com

将front上传到/usr/local/gupao/web/static目录下,再修改/usr/local/openresty/nginx/conf/nginx.conf,配置如下:

用户请求www.gpshopvip.com这个网站下的所有路径,直接跳转到/usr/local/gupao/web/static/frant的文件

#门户发布
server { //虚拟机listen       80;server_name  www.gpshopvip.com;location / {root   /usr/local/gupao/web/static/frant;}
}

访问http://www.gpshopvip.com/效果如下:
在这里插入图片描述

2 Lua语法学习

在这里插入图片描述

Lua 是一个小巧的脚本语言, 其设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。

Lua特性:

1.一个小巧的脚本语言
2.设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能
3.所有操作系统和平台上都可以编译、运行Lua脚本
4.所有脚本引擎中,Lua的速度是最快的

应用场景:

1.游戏开发
2.独立应用脚本
3.高性能Web应用(天猫、京东都有应用)
4.扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench

2.1 Lua常用语法

2.1.1 Lua安装

首先我们准备一个linux虚拟机来安装Lua,在linux系统中按照如下步骤进行安装:

curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz
tar xf lua-5.3.5.tar.gz
cd lua-5.3.5
make linux test

出现如下界面,表示安装成功:

在这里插入图片描述

版本查看:lua -v
在这里插入图片描述

我们可以发现,Lua版本还是原来系统自带的版本,我们需要替换原来系统自带的lua,执行如下命令:

rm -rf /usr/bin/lua
ln -s /usr/local/gupao/lua-5.3.5/src/lua /usr/bin/lua

此时版本信息如下:

在这里插入图片描述

2.1.2 Lua常用操作

Lua 提供了交互式编程模式。我们可以在命令行中输入程序并立即查看效果,这种编程模式类似我们控制台操作,Lua 交互式编程模式可以通过命令 lua -i 或 lua 来启用:

[root@server1 lua-5.3.5]# lua -i
Lua 5.3.5  Copyright (C) 1994-2018 Lua.org, PUC-Rio
> 

1)打印

print("springcloud alibaba")

2)数据类型

在这里插入图片描述

类型测试:

print(type("Hello world"))      --> string
print(type(10.4*3))             --> number
print(type(print))              --> function
print(type(type))               --> function
print(type(true))               --> boolean
print(type(nil))                --> nil
print(type(type(X)))            --> string

3)变量

变量在使用前,需要在代码中进行声明,即创建该变量。Lua 变量有三种类型:全局变量、局部变量、表中的域

全局变量定义:

> age=19
> print(age)
19

局部变量定义:

> local username=wangwu
> print(username)
nil

此时username不是全局变量,一般在某个方法中使用,不能全局使用,所以输出nil

4)对象(table)

> --定义对象resp
> resp = {}
> --往对象resp中添加属性name,赋值为zhangsan
> resp["name"]="zhangsan"
> --往对象resp中添加属性address,赋值为hunanchangsha
> resp["address"]="hunanchangsha"
> --输出对象resp中的name属性值
> print(resp["name"])
zhangsan

5)函数

创建一个函数,其实就是创建一个方法,函数以function开始,end结束,可以在end之前有返回值,也可以有入参,定义一个方法如下:

> --定义userinfo方法,入参为age
> function userinfo(age)
>> --age在原有基础上+1
>> age=age+1
>> --返回变化后的age
>> return age
>> --结束
>> end
> print(userinfo(19))
20

6)拼接

在上面方法调用上拼接一段字符串,可以使用亮点来做…,如下:

> print(userinfo(19).."岁了")
20岁了

7)逻辑判断

我们经常会做一些条件判断,在lua中也可以实现,lua中有if xx then else end的流程判断语法。

> function userinfo(age)
>> if age>=18 then
>> return "成年人"
>> else
>> return "未成年"
>> end
>> end
> print(userinfo(17))
未成年

8)脚本编程

我们可以像写java一样,将lua脚本写到一个文件中,并且可以在一个脚本文件引入另外一个脚本文件,类似java中的导包。

创建course.lua,代码如下:

--定义一个对象
local course = {}--定义一个方法
function course.courseName(id)if id==1 thenreturn "java"elsereturn "UI"end
end
return course

创建student.lua,代码如下:

--导入course.lua
local cr = require("course")--调用courseName方法
local result = cr.courseName(1)print(result)

执行student.lua

[root@server1 lua]# lua student.lua
java

3 多级缓存架构实战

项目运行过程中往往为了提升项目对数据加载效率,一般都会增加缓存,但缓存如何加载效率最高?如何加载对后端服务造成的压力最小?我们需要设计一套完善的缓存架构体系

3.1 多级缓存架构分析

在这里插入图片描述

用户请求到达后端服务,先经过代理层nginx,nginx将请求路由到后端tomcat服务,tomcat去数据库中取数据,这是一个非常普通的流程,但在大并发场景下,需要做优化,而缓存是最有效的手段之一。缓存优化有,执行过程如下:

1:请求到达Nginx,Nginx抗压能力极强

2:Tomcat抗压能力很弱,如果直接将所有请求路由给Tomcat,Tomcat压力会非常大,很有可能宕机。我们可以在Nginx这里设置2道缓存,第1道是Redis缓存,第2道是Nginx缓存(nginx自身也是有缓存cache的)

3:先加载Redis缓存,如果Redis没有缓存,则加载Nginx缓存,Nginx如果没有缓存,则将请求路由到Tomcat

4:Tomcat发布的程序会加载数据,加载完成后需要做缓存的,及时将数据存入Redis缓存,再响应数据给用户

5:用户下次查询的时候,查询Redis缓存或Nginx缓存

6:后面用户请求的时候,就可以直接从Nginx缓存拿数据了,这样就可以实现后端Tomcat发布的服务被调用的次数大幅减少,负载大幅下降

在这里插入图片描述

上面这套缓存架构被多个大厂应用,除了可以有效提高加载速度、降低后端服务负载之外,还可以防止缓存雪崩,为服务稳定健康打下了坚实的基础,这也就是鼎鼎有名的多级缓存架构体系

3.2 推广商品高效加载

首页很多商品优先推荐展示,这些其实都是推广商品,并非真正意义上的热门商品,首页展示这些商品数据需要加载效率极高,并且商城首页访问频率也是极高,我们需要对首页数据做缓存处理,我们首先想到的就是Redis缓存

在这里插入图片描述

3.2.1 表结构分析

推广商品并非只在首页出现,有可能在列表页、分类搜索页多个地方出现,因此可以设计一张表用于存放不同位置展示不同商品的表,推广产品推荐表如下:

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 '展示的产品(对应Sku)',`sort` int(11) DEFAULT NULL COMMENT '排序',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3.2.2 推广商品异步加载

1)Bean创建

goods-api中创建com.gupaoedu.vip.mall.goods.model.AdItems

@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "ad_items")
public class AdItems {@TableId(type = IdType.AUTO)private Integer id;private String name;private Integer type;private String skuId;private Integer sort;
}

2)Mapper

创建com.gupaoedu.vip.mall.goods.mapper.AdItemsMapper

public interface AdItemsMapper extends BaseMapper<AdItems> {
}

3)Service

接口:修改com.gupaoedu.vip.mall.goods.service.SkuService,增加如下方法:

public interface SkuService extends IService<Sku> {/**** 根据推广产品分类ID查询Sku列表*/List<Sku> typeSkuItems(Integer id);
}

实现类:修改com.gupaoedu.vip.mall.goods.service.impl.SkuServiceImpl增加实现方法:

@Service
public class SkuServiceImpl extends ServiceImpl<SkuMapper,Sku> implements SkuService {@Autowiredprivate AdItemsMapper adItemsMapper;@Autowiredprivate SkuMapper skuMapper;/**** 根据推广产品分类ID查询Sku列表*/@Overridepublic List<Sku> typeSkuItems(Integer id) {//查询所有分类下的推广QueryWrapper<AdItems> adItemsQueryWrapper=new QueryWrapper<AdItems>();adItemsQueryWrapper.eq("type",id);List<AdItems> adItems = adItemsMapper.selectList(adItemsQueryWrapper);//获取所有SkuIdList<String> skuIds = adItems.stream().map(adItem -> adItem.getSkuId()).collect(Collectors.toList());//批量查询SkuList<Sku> skus = skuMapper.selectBatchIds(skuIds);return skus;}
}

4)Controller

修改com.gupaoedu.vip.mall.goods.controller.SkuController增加方法:

@RestController
@RequestMapping(value = "/sku")
@CrossOrigin
public class SkuController {@Autowiredprivate SkuService skuService;/***** 指定分类下的推广产品列表*/@GetMapping(value = "/aditems/type/{id}")public List<Sku> typeItems(@PathVariable(value = "id")Integer id){//查询List<Sku> adSkuItems = skuService.typeSkuItems(id);return adSkuItems;}
}

用Postman测试http://localhost:8081/sku/aditems/type/1效果如下:

在这里插入图片描述

3.2.3 缓存常用注解

先实现redis缓存加载(第一部分是右边redis)

在这里插入图片描述

@EnableCaching:

开关性注解,在项目启动类或某个配置类上使用此注解后,则表示允许使用注解的方式进行缓存操作

@Cacheable:

可用于类或方法上;在目标方法执行前,会根据key先去缓存中查询看是否有数据,有就直接返回缓存中的key对应的value值。不再执行目标方法;无则执行目标方法,并将方法的返回值作为value,并以键值对的形式存入缓存

@CacheEvict:

可用于类或方法上;在执行完目标方法后,清除缓存中对应key的数据(如果缓存中有对应key的数据缓存的话)

@CachePut:

可用于类或方法上;在执行完目标方法后,并将方法的返回值作为value,并以键值对的形式存入缓存中

@Caching:

此注解即可作为@Cacheable、@CacheEvict、@CachePut三种注解中的的任何一种或几种来使用

@CacheConfig:

可以用于配置@Cacheable、@CacheEvict、@CachePut这三个注解的一些公共属性,例如cacheNames、keyGenerator

注意:

@EnableCaching:如果方法有返回值就加到缓存中去,下次查询先去查询缓存,缓存有数据直接返回数据

@CachePut:每次查询就直接查询数据库,有数据就添加到缓存中

3.2.4 推广产品缓存操作

1)配置缓存链接

修改bootstrap.yml,增加配置Redis缓存链接,如下:

server:port: 8081
spring:application:name: mall-goods #服务名datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/shop_goods?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTCusername: rootpassword: 123456cloud:nacos:config:file-extension: yamlserver-addr: 192.168.1.11:8848discovery:#Nacos的注册地址server-addr: 192.168.1.11:8848#Redis配置redis:host: 127.0.0.1port: 6379password: 123456

2)开启缓存

com.gupaoedu.vip.mall.MallGoodsServiceApplication上添加缓存开启注解:

package com.gupaoedu.vip.mall;/*** 商品服务启动类*/
@SpringBootApplication
@MapperScan(basePackages = {"com.gupaoedu.vip.mall.goods.mapper"})
@EnableCaching //开启缓存
public class MallGoodsApplication {public static void main(String[] args) {SpringApplication.run(MallGoodsApplication.class,args);}}
3.2.4.1 推广产品缓存加载

com.gupaoedu.vip.mall.goods.service.impl.SkuServiceImpl添加@Cacheable注解,代码如下:

在这里插入图片描述

完整代码如下:

/**** 根据推广产品分类ID查询Sku列表* cacheNames = "ad-items-skus":当前缓存对应的以一级命名空间* key ="#id":入参id作为缓存的key,使用的是SpEL表达式*ad-items-skus::id*/
@Cacheable(cacheNames = "ad-items-skus",key ="#id")
@Override
public List<Sku> typeSkuItems(Integer id) {//查询所有分类下的推广QueryWrapper<AdItems> adItemsQueryWrapper=new QueryWrapper<AdItems>();adItemsQueryWrapper.eq("type",id);List<AdItems> adItems = adItemsMapper.selectList(adItemsQueryWrapper);//获取所有SkuIdList<String> skuIds = adItems.stream().map(adItem -> adItem.getSkuId()).collect(Collectors.toList());//批量查询SkuList<Sku> skus = skuMapper.selectBatchIds(skuIds);return skus;
}

注意:做缓存时,Bean实体类要序列化Serializable,不然会报错

package com.gupaoedu.vip.mall.goods.model;
@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;
}

请求http://localhost:8081/sku/aditems/type/1此时Redis缓存数据如下:

在这里插入图片描述

我们可以发现上面存储的数据是二进制数据,我们很难阅读,而且占空间极大,我们可以使用FastJSON将每次存入到Redis中的数据转成JSON字符串,此时我们需要把RedisConfig.java,其他工程也有可能需要,我们可以写到mall-service-dependency工程的com.gupaoedu.vip.mall.config包下

package com.gupaoedu.vip.mall.config;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(factory);GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();// 值采用json序列化redisTemplate.setValueSerializer(serializer);//使用StringRedisSerializer来序列化和反序列化redis的key值redisTemplate.setKeySerializer(new StringRedisSerializer());// 设置hash key 和value序列化模式redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(serializer);redisTemplate.afterPropertiesSet();return redisTemplate;}@Beanpublic RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(12))//设置默认缓存时间.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);}
}

此时清理再执行加载缓存后,效果如下:

在这里插入图片描述

3.2.4.2 推广产品缓存清理

1)Service

接口:添加清理缓存方法com.gupaoedu.vip.mall.goods.service.SkuService#delTypeSkuItems

/**** 清理分类ID下的推广产品*/
void delTypeSkuItems(Integer id);

实现类:添加实现方法com.gupaoedu.vip.mall.goods.service.impl.SkuServiceImpl#delTypeSkuItems

/***** 清理缓存* @param id*/
@CacheEvict(cacheNames = "ad-items-skus",key ="#id")
@Override
public void delTypeSkuItems(Integer id) {
}

2)Controller

添加删除缓存方法com.gupaoedu.vip.mall.goods.controller.SkuController#deleteTypeItems

/***** 删除指定分类下的推广产品列表*/
@DeleteMapping(value = "/aditems/type")
public RespResult deleteTypeItems(@RequestParam(value = "id") Integer id){//清理缓存skuService.delTypeSkuItems(id);return RespResult.ok();
}
3.2.4.3 注解缓存操作优化

使用@CacheConfig优化注解,可以将cacheNames挪到类上,每个方法上就不用重复写cacheNames了。

在这里插入图片描述

其他地方肯定会调用这几个方法用于实现缓存更新,我们可以在goods-api中添加feigin接口。

mall-api中引入common工具包和feign依赖包:

<!--工具包-->
<dependency><groupId>com.gupaoedu.vip.mall</groupId><artifactId>mall-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>

goods-api中创建com.gupaoedu.vip.mall.goods.feign.SkuFeign,代码如下:

@FeignClient(value = "mall-goods")
public interface SkuFeign {/***** 指定分类下的推广产品列表*/@GetMapping(value = "/sku/aditems/type")public List<Sku> typeItems(@RequestParam(value = "id") Integer id);/***** 删除指定分类下的推广产品列表*/@DeleteMapping(value = "/sku/aditems/type/{id}")public RespResult deleteTypeItems(@PathVariable(value = "id")Integer id);/***** 修改指定分类下的推广产品列表*/@PutMapping(value = "/sku/aditems/type/{id}")public RespResult updateTypeItems(@PathVariable(value = "id")Integer id);
}

3.3 多级缓存-Lua+Redis

在这里插入图片描述

按照上面分析的架构,可以每次在Nginx的时候使用Lua脚本查询Redis,如果Redis有数据,则将数据存入到Nginx缓存,再将数据响应给用户,此时我们需要实现使用Lua将数据从Redis中加载出来。

我们在/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.100.130", 6379)
--查询指定key的数据
local result=red:get(key);--关闭Redis链接
red:close()if result==nil or result==null or result==ngx.null thenreturn true
else--输出数据ngx.say(result)
end

修改nginx.conf添加如下配置:(最后记得将content_by_lua_file改成rewrite_by_lua_file)

#推广产品查询
location /sku/aditems/type {content_by_lua_file /usr/local/openresty/nginx/lua/aditem.lua;
}

访问http://www.gpshopvip.com/sku/aditems/type?id=1效果如下:

在这里插入图片描述

4 Nginx代理缓存

在这里插入图片描述

proxy_cache 是用于 proxy 模式的缓存功能,proxy_cache 在 Nginx 配置的 http 段、server 段中分别写入不同的配置。http 段中的配置用于定义 proxy_cache 空间,server 段中的配置用于调用 http 段中的定义,启用对server 的缓存功能。

使用:

1、定义缓存空间
2、在指定地方使用定义的缓存

4.1 Nginx代理缓存学习

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;

参数说明:

【proxy_cache_path】指定缓存存储的路径,缓存存储在/usr/local/openresty/nginx/cache目录【levels=1:2】设置一个两级目录层次结构存储缓存,在单个目录中包含大量文件会降低文件访问速度,因此我们建议对大多数部署使用两级目录层次结构。如果 levels 未包含该参数,Nginx 会将所有文件放在同一目录中。【keys_zone=proxy_cache:10m】设置共享内存区域,用于存储缓存键和元数据,例如使用计时器。拥有内存中的密钥副本,Nginx 可以快速确定请求是否是一个 HIT 或 MISS 不必转到磁盘,从而大大加快了检查速度。1 MB 区域可以存储大约 8,000 个密钥的数据,因此示例中配置的 10 MB 区域可以存储大约 80,000 个密钥的数据。【max_size=1g】设置缓存大小的上限。它是可选的; 不指定值允许缓存增长以使用所有可用磁盘空间。当缓存大小达到限制时,一个称为缓存管理器的进程将删除最近最少使用的缓存,将大小恢复到限制之下的文件。【inactive=60m】指定项目在未被访问的情况下可以保留在缓存中的时间长度。在此示例中,缓存管理器进程会自动从缓存中删除 60 分钟未请求的文件,无论其是否已过期。默认值为 10 分钟(10m)。非活动内容与过期内容不同。Nginx 不会自动删除缓存 header 定义为已过期内容(例如 Cache-Control:max-age=120)。过期(陈旧)内容仅在指定时间内未被访问时被删除。访问过期内容时,Nginx 会从原始服务器刷新它并重置 inactive 计时器。【use_temp_path=off】表示NGINX会将临时文件保存在缓存数据的同一目录中。这是为了避免在更新缓存时,磁盘之间互相复制响应数据,我们一般关闭该功能。

2)Proxy_Cache属性:

proxy_cache:设置是否开启对后端响应的缓存,如果开启的话,参数值就是zone的名称,比如:proxy_cache。proxy_cache_valid:针对不同的response code设定不同的缓存时间,如果不设置code,默认为200,301,302,也可以用any指定所有code。proxy_cache_min_uses:指定在多少次请求之后才缓存响应内容,这里表示将缓存内容写入到磁盘。proxy_cache_lock:默认不开启,开启的话则每次只能有一个请求更新相同的缓存,其他请求要么等待缓存有数据要么限时等待锁释放;nginx 1.1.12才开始有。
配套着proxy_cache_lock_timeout一起使用。proxy_cache_key:缓存文件的唯一key,可以根据它实现对缓存文件的清理操作。

4.2 Nginx代理缓存热点数据应用

在这里插入图片描述

1)开启代理缓存

修改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.conf,添加如下配置:

#门户发布
server {listen       80;server_name  www.gpshopvip.com;#推广产品查询location /sku/aditems/type {#先找Nginx缓存rewrite_by_lua_file /usr/local/openresty/nginx/lua/aditem.lua;#启用缓存openresty_cacheproxy_cache proxy_cache;#针对指定请求缓存#proxy_cache_methods GET;#设置指定请求会缓存proxy_cache_valid 200 304 60s;#最少请求1次才会缓存proxy_cache_min_uses 1;#如果并发请求,只有第1个请求会去服务器获取数据#proxy_cache_lock on;#唯一的keyproxy_cache_key $host$uri$is_args$args;#动态代理proxy_pass http://192.168.100.1:8081;}#其他所有请求location / {root   /usr/local/gupao/web/static/frant;}
}

重启nginx或者重新加载配置文件nginx -s reload,再次测试,可以发现下面个规律:

1:先查找Redis缓存
2:Redis缓存没数据,直接找Nginx缓存
3:Nginx缓存没数据,则找真实服务器

我们还可以发现cache目录下多了目录和一个文件,这就是Nginx缓存:

在这里插入图片描述

4.3 Cache_Purge代理缓存清理

很多时候我们如果不想等待缓存的过期,想要主动清除缓存,可以采用第三方的缓存清除模块清除缓存 nginx_ngx_cache_purge。安装nginx的时候,需要添加purge模块,purge模块我们已经下载了,在/usr/local/gupao目录下,添加该模块--add-module=/usr/local/gupao/ngx_cache_purge-2.3/,这一个步骤我们在安装OpenRestry的时候已经实现了。

安装好了后,我们配置一个清理缓存的地址:http://192.168.100.130/purge/sku/aditems/type?id=1

#清理缓存
location ~ /purge(/.*) {#清理缓存proxy_cache_purge proxy_cache $host$1$is_args$args;
}

此时访问http://www.gpshopvip.com/purge/sku/aditems/type?id=1,表示清除缓存,如果出现如下效果表示清理成功:

在这里插入图片描述

5 缓存一致性

上面我们虽然实现了多级缓存架构,但是问题也出现了,如果数据库中数据发生变更,如何更新Redis缓存呢?如何更新Nginx缓存呢?

我们可以使用阿里巴巴的技术解决方案Canal来实现,通过Canal监听数据库变更,并实时消费变更数据,并更新缓存。

canal [kə’næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费

学习地址:https://github.com/alibaba/canal

早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求,实现方式主要是基于业务 trigger 获取增量变更。从 2010 年开始,业务逐步尝试数据库日志解析获取增量变更进行同步,由此衍生出了大量的数据库增量订阅和消费业务。

基于日志增量订阅和消费的业务包括

  • 数据库镜像
  • 数据库实时备份
  • 索引构建和实时维护(拆分异构索引、倒排索引等)
  • 业务 cache 刷新
  • 带业务逻辑的增量数据处理

当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x。

5.1 Canal原理讲解

MySQL主备复制原理

  • MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
  • MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
  • MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据

Canal 工作原理

  • canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
  • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
  • canal 解析 binary log 对象(原始为 byte 流)

在这里插入图片描述

5.2 Canal安装

5.2.1 MySQL开启binlog

对于MySQL , 需要先开启 Binlog 写入功能,配置 binlog-format 为 ROW 模式,my.cnf 中配置如下

docker exec -it mysql /bin/bash
cd /etc/mysql/mysql.conf.d
vi mysqld.cnf

在最文件尾部添加如下配置:

log-bin=mysql-bin # 开启 binlog
binlog-format=ROW # 选择 ROW 模式
server_id=1 # 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复

**注意:**针对阿里云 RDS for MySQL , 默认打开了 binlog , 并且账号默认具有 binlog dump 权限 , 不需要任何权限或者 binlog 设置,可以直接跳过这一步。

授权 canal 链接 MySQL 账号具有作为 MySQL slave 的权限, 如果已有账户可直接 grant:

CREATE USER canal IDENTIFIED BY 'canal';GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';-- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
FLUSH PRIVILEGES;

重启mysql容器

docker restart canal

查看是否开启binlog:

show variables like 'log_bin';
5.2.2 Canal安装

我们采用docker安装方式:

docker run -p 11111:11111 --name canal -d docker.io/canal/canal-server

进入容器,修改核心配置canal.properties 和instance.properties,canal.properties 是canal自身的配置,instance.properties是需要同步数据的数据库连接配置。

修改配置如下:

# position info
canal.instance.master.address=192.168.100.130:3306

另一处配置:

# table regex
#canal.instance.filter.regex=.*\\..*
#监听配置
canal.instance.filter.regex=shop_goods.ad_items

配置完成后,重启canal容器

docker restart canal

5.3 多级缓存架构缓存一致性实战

在这里插入图片描述

5.3.1 Canal微服务搭建

工程坐标:

<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>mall-canal-service</artifactId>

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>mall-service</artifactId><groupId>com.gupaoedu.vip.mall</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>mall-canal-service</artifactId><description>Canal微服务</description><dependencies><!--springboot-canal快速构建依赖包--><dependency><groupId>top.javatool</groupId><artifactId>canal-spring-boot-starter</artifactId><version>1.2.1-RELEASE</version></dependency><!--依赖mall-goods-api--><dependency><groupId>com.gupaoedu.vip.mall</groupId><artifactId>goods-api</artifactId><version>0.0.1-SNAPSHOT</version></dependency></dependencies>
</project>

bootstrap.yml:

server:port: 8083
spring:application:name: mall-canalcloud:nacos:config:file-extension: yamlserver-addr: 192.168.100.130:8848discovery:#Nacos的注册地址server-addr: 192.168.100.130:8848
#Canal配置
canal:server: 192.168.100.130:11111destination: example
#日志配置
logging:pattern:console: "%msg%n"level:root: error

创建监听类:com.gupaoedu.vip.canal.listener.AdItemsHandler

@CanalTable(value = "ad_items")
@Component
public class AdItemsHandler implements EntryHandler<AdItems> {@Autowiredprivate SkuFeign skuFeign;@Overridepublic void insert(AdItems adItems) {//加载缓存skuFeign.updateTypeItems(adItems.getType());}/**** 修改* @param before* @param after*/@Overridepublic void update(AdItems before, AdItems after) {//分类不同,则重新加载之前的缓存if(before.getType().intValue()!=after.getType().intValue()){//修改缓存skuFeign.updateTypeItems(before.getType());}//加载缓存skuFeign.updateTypeItems(after.getType());}@Overridepublic void delete(AdItems adItems) {//删除缓存skuFeign.deleteTypeItems(adItems.getType());}
}

创建启动类:com.gupaoedu.vip.canal.MallCanalApplication

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients(basePackages = {"com.gupaoedu.vip.mall.goods.feign"})
public class MallCanalApplication {public static void main(String[] args) {SpringApplication.run(MallCanalApplication.class,args);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/505591.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cognitive architecture 又是个什么东东?

自Langchain&#xff1a; https://blog.langchain.dev/what-is-a-cognitive-architecture/ https://en.wikipedia.org/wiki/Cognitive_architecture 定义 A cognitive architecture refers to both a theory about the structure of the human mind and to a computational…

js代理模式

允许在不改变原始对象的情况下&#xff0c;通过代理对象来访问原始对象。代理对象可以在访问原始对象之前或之后&#xff0c;添加一些额外的逻辑或功能。 科学上网过程 一般情况下,在访问国外的网站,会显示无法访问 因为在dns解析过程,这些ip被禁止解析,所以显示无法访问 引…

多目标优化算法之一:基于分解的方法

在多目标优化算法中,“基于分解的方法”通常指的是将多目标优化问题(MOP)分解为多个单目标优化子问题,并同时优化这些子问题。这种方法的核心思想是通过引入权重向量或参考点,将多目标问题转化为多个标量优化问题,每个子问题都关注于原始问题的一个特定方面或视角。这样可…

【面试题】技术场景 4、负责项目时遇到的棘手问题及解决方法

工作经验一年以上程序员必问问题 面试题概述 问题为在负责项目时遇到的棘手问题及解决方法&#xff0c;主要考察开发经验与技术水平&#xff0c;回答不佳会影响面试印象。提供四个回答方向&#xff0c;准备其中一个方向即可。 1、设计模式应用方向 以登录为例&#xff0c;未…

uniapp 微信小程序内嵌h5实时通信

描述&#xff1a; 小程序webview内嵌的h5需要向小程序实时发送消息&#xff0c;有人说postMessage可以实现&#xff0c;所以试验一下&#xff0c;结果是实现不了实时&#xff0c;只能在特定时机后退、组件销毁、分享时小程序才能接收到信息&#xff08;小程序为了安全等考虑做了…

matlab编写分段Hermite插值多项式

文章目录 原理使用分段Hermite插值多项式原因公式第一类的两个插值积函数第二类的两个插值积函数 例题法一法二 代码分段 Hermite 插值的思路&#xff1a;分段 Hermite 插值多项式的构造&#xff1a;MATLAB 实现代码&#xff1a;结果如图&#xff1a;注归一化变量的作用&#x…

新时期下k8s 网络插件calico 安装

1、k8s master节点初始化完毕以后一直处于notreadey状态&#xff0c;一直怀疑是安装有问题或者是初始化有问题&#xff08;当然&#xff0c;如果真有问题要先解决这些问题&#xff09;&#xff0c;经过不断探索才发现是网络插件没有安装导致的&#xff0c;根据建议安装calico插…

《解锁图像的语言密码:Image Caption 开源神经网络项目全解析》

《解锁图像的语言密码&#xff1a;Image Caption 开源项目全解析》 一、开篇&#xff1a;AI 看图说话时代来临二、走进 Image Caption 开源世界三、核心技术拆解&#xff1a;AI 如何学会看图说话&#xff08;一&#xff09;深度学习双雄&#xff1a;CNN 与 RNN&#xff08;二&a…

【Maui】动态菜单实现(绑定数据视图)

前言 .NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架&#xff0c;用于使用 C# 和 XAML 创建本机移动和桌面应用。 使用 .NET MAUI&#xff0c;可从单个共享代码库开发可在 Android、iOS、macOS 和 Windows 上运行的应用。 .NET MAUI 是一款开放源代码应用&#xff0c;是 X…

FreePBX 17 on ubuntu24 with Asterisk 20

版本配置&#xff1a; FreePBX 17&#xff08;最新&#xff09; Asterisk 20&#xff08;最新Asterisk 22&#xff0c;但是FreePBX 17最新只支持Asterisk 21&#xff0c;但是21非LTS版本&#xff0c;所以选择Asterisk 20&#xff09; PHP 8.2 Maria DB (v10.11) Node J…

with as提高sql的执行效率

实战sql with cte(UNIT_ID, UNIT_NAME, PARENT_UNIT_ID, UNIT_CODE ) as (select UNIT_ID, UNIT_NAME, PARENT_UNIT_ID , UNIT_CODEfrom HPFM_UNITunion allselect t.UNIT_ID, t.UNIT_NAME, t.PARENT_UNIT_ID, t.UNIT_CODEfrom HPFM_UNIT tjoin cte on t.PARENT_UNIT_ID cte.U…

G-Star Landscape 2.0 重磅发布,助力开源生态再升级

近日&#xff0c;备受行业瞩目的 G-Star Landscape 迎来了其 2.0 版本的发布&#xff0c;这一成果标志着 GitCode 在开源生态建设方面又取得了重要进展。 G-Star Landscape仓库链接&#xff1a; https://gitcode.com/GitCode-official-team/G-Star-landscape 2024 GitCode 开…

如何在 Linux系统用中挂载和管理磁盘分区

在 Linux 系统中&#xff0c;挂载和管理磁盘分区是系统管理的基本任务之一。以下是详细步骤&#xff0c;帮助你完成这一过程。 1. 查看现有磁盘和分区 首先&#xff0c;使用以下命令来查看系统中的磁盘和分区&#xff1a; bash 复制 lsblk或者使用&#xff1a; bash 复制…

Unity:删除注册表内的项目记录

然后WinR按键输入regedit 打开注册表 在注册表 HKEY CURRENT USER—>SOFTWARE—>Unity—>UnityEditor—>DefaultCompany —>language_Test 中&#xff0c;删除我们的之前存储的语言环境数据。在 “ 三、文本调用和替换 ” 测试时已经将语言环境存储到注册表中了…

Zustand selector 发生 infinate loops的原因以及解决

Zustand selector 发生 infinate loops 做zustand tutorial project的时候&#xff0c;使用选择器方法引入store&#xff0c;出现Maximum update depth exceeded,也就是组件一直重新渲染&#xff0c;改成直接使用store就没有不会出现这个问题。如下&#xff1a; // const [xIs…

世优波塔数字人 AI 大屏再升级:让智能展厅讲解触手可及

近日&#xff0c;世优波塔大屏AI数字人再度升级&#xff0c;将数字人技术与大屏交互推向了新的高度&#xff0c;为用户带来了全方位的卓越体验&#xff0c;让人工智能不断重塑我们的生活与工作方式。 新形象&#xff1a;数字人的独特魅力 高精度的数字人形象一直是波塔智能体…

STM32F4分别驱动SN65HVD230和TJA1050进行CAN通信

目录 一、CAN、SN65HVD230DR二、TJA10501、TJA1050 特性2、TJA1050 引脚说明 三、硬件设计1、接线说明2、TJA1050 模块3、SN65HVD230 模块 四、程序设计1、CAN_Init&#xff1a;CAN 外设初始化函数2、CAN_Send_Msg、CAN_Receive_Msg 五、功能展示1、接线图2、CAN 数据收发测试 …

Redis数据库笔记——主从复制

大家好&#xff0c;这里是Good Note&#xff0c;关注 公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍Redis的主从复制模式&#xff0c;包括作用&#xff0c;原因&#xff0c;工作原理&#xff0c;同步流程等。 文章目录 主从复制什么是 Redis 主从…

【Unity功能集】TextureShop纹理工坊(十二)画笔工具、橡皮擦工具

项目源码:在终章发布 索引 画笔工具橡皮擦工具PS画笔工具、橡皮擦工具TextureShop画笔工具绘制点绘制线段画笔逻辑TextureShop橡皮擦工具画笔工具 画笔工具,可在绘画板上进行自由绘画的工具(了解PS画笔工具)。 橡皮擦工具 橡皮擦工具,可在绘画板上进行自由擦除颜色的工…

MMDetection框架下的常见目标检测与分割模型综述与实践指南

目录 综述与实践指南 SSD (Single Shot MultiBox Detector) 基本配置和使用代码 RetinaNet 基本配置和使用代码 Faster R-CNN 基本配置和使用代码 Mask R-CNN 基本配置和使用代码 Cascade R-CNN 基本配置和使用代码 总结 综述与实践指南 MMDetection是一个基于Py…