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

第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/profile

export PATH=/usr/local/openresty/nginx/sbin:$PATH

source /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 then
                return "java"
        else
                return "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 {

    @Autowired
    private AdItemsMapper adItemsMapper;

    @Autowired
    private SkuMapper skuMapper;

    /***
     * 根据推广产品分类ID查询Sku列表
     */
    @Override
    public List<Sku> typeSkuItems(Integer id) {
        //查询所有分类下的推广
        QueryWrapper<AdItems> adItemsQueryWrapper=new QueryWrapper<AdItems>();
        adItemsQueryWrapper.eq("type",id);
        List<AdItems> adItems = adItemsMapper.selectList(adItemsQueryWrapper);

        //获取所有SkuId
        List<String> skuIds = adItems.stream().map(adItem -> adItem.getSkuId()).collect(Collectors.toList());
        //批量查询Sku
        List<Sku> skus = skuMapper.selectBatchIds(skuIds);
        return skus;
    }
}

4)Controller

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

@RestController
@RequestMapping(value = "/sku")
@CrossOrigin
public class SkuController {

    @Autowired
    private 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.Driver
    url: jdbc:mysql://127.0.0.1:3306/shop_goods?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
  cloud:
    nacos:
      config:
        file-extension: yaml
        server-addr: 192.168.1.11:8848
      discovery:
        #Nacos的注册地址
        server-addr: 192.168.1.11:8848
  #Redis配置
  redis:
    host: 127.0.0.1
    port: 6379
    password: 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);

    //获取所有SkuId
    List<String> skuIds = adItems.stream().map(adItem -> adItem.getSkuId()).collect(Collectors.toList());
    //批量查询Sku
    List<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 {

    @Bean
    public 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;
    }


    @Bean
    public 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 then
	return 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_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.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-canal
  cloud:
    nacos:
      config:
        file-extension: yaml
        server-addr: 192.168.100.130:8848
      discovery:
        #Nacos的注册地址
        server-addr: 192.168.100.130:8848
#Canal配置
canal:
  server: 192.168.100.130:11111
  destination: 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> {

    @Autowired
    private SkuFeign skuFeign;

    @Override
    public void insert(AdItems adItems) {
        //加载缓存
        skuFeign.updateTypeItems(adItems.getType());
    }

    /***
     * 修改
     * @param before
     * @param after
     */
    @Override
    public void update(AdItems before, AdItems after) {
        //分类不同,则重新加载之前的缓存
        if(before.getType().intValue()!=after.getType().intValue()){
            //修改缓存
            skuFeign.updateTypeItems(before.getType());
        }
        //加载缓存
        skuFeign.updateTypeItems(after.getType());
    }

    @Override
    public 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);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【GESP】C++三级考试大纲知识点梳理, (1)二进制数据编码

GESP C三级官方考试大纲中&#xff0c;共有8条考点&#xff0c;本文针对C&#xff08;1&#xff09;号知识点进行总结梳理。 &#xff08;1&#xff09;了解二进制数据编码:原码、反码、补码。 全文详见&#xff1a;https://www.coderli.com/gesp-3-exam-syllabus-data-encodin…

B+树的原理及实现

文章目录 B树的原理及实现一、引言二、B树的特性1、结构特点2、节点类型3、阶数 三、B树的Java实现1、节点实现2、B树操作2.1、搜索2.2、插入2.3、删除2.4、遍历 3、B树的Java实现示例 四、总结 B树的原理及实现 一、引言 B树是一种基于B树的树形数据结构&#xff0c;它在数据…

毕业项目推荐:基于yolov8/yolov5/yolo11的动物检测识别系统(python+卷积神经网络)

文章目录 概要一、整体资源介绍技术要点功能展示&#xff1a;功能1 支持单张图片识别功能2 支持遍历文件夹识别功能3 支持识别视频文件功能4 支持摄像头识别功能5 支持结果文件导出&#xff08;xls格式&#xff09;功能6 支持切换检测到的目标查看 二、数据集三、算法介绍1. YO…

用Kimi做研究:准实验设计的智能解决方案

目录 1.研究策略设计 2.过程框架设计 3.背景变量 4.细节设计 准实验设计是一种介于实验与观察研究之间的研究方法&#xff0c;准实验设计是在无法完全控制实验条件的情况下进行因果关系的探索。与传统实验设计相比&#xff0c;准实验设计不具备随机分配实验对象到各处理组的…

【前端】【HTML】入门基础知识

参考视频&#xff1a;【狂神说Java】HTML5完整教学通俗易懂_哔哩哔哩_bilibili 一、基本结构 二、基本标签 <h1>&#xff1a;一级标题&#xff0c;通常用于页面的主标题&#xff0c;字体较大且醒目。 <h2>&#xff1a;二级标题&#xff0c;用于副标题或主要章节标…

DVT:消除视觉变换器中的噪声伪影

人工智能咨询培训老师叶梓 转载标明出处 近年来&#xff0c;视觉变换器&#xff08;Vision Transformers&#xff0c;简称ViTs&#xff09;在多种视觉任务中取得了卓越的性能&#xff0c;成为现代视觉基础模型的主流架构之一。然而&#xff0c;这些模型在特征图中存在一种网格…

OpenCV的双边滤波函数

OpenCV的双边滤波函数cv2.bilateralFilter是一种用于图像处理的强大工具&#xff0c;它能够在去除噪声的同时保持边缘的清晰度。以下是对该函数的详细说明&#xff1a; 一、函数原型 python cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])二、参…

项目实战——使用python脚本完成指定OTA或者其他功能的自动化断电上电测试

前言 在嵌入式设备的OTA场景测试和其他断电上电测试过程中&#xff0c;有的场景发生在夜晚或者随时可能发生&#xff0c;这个时候不可能24h人工盯着&#xff0c;需要自动化抓取串口日志处罚断电上电操作。 下面的python脚本可以实现自动抓取串口指定关键词&#xff0c;然后触发…

IT面试求职系列主题-人工智能(三)

13&#xff09;你对超参数的理解是什么&#xff1f; 在机器学习中&#xff0c;超参数是决定和控制整个训练过程的参数。这些参数的示例包括学习率、隐藏层、隐藏单元、激活函数等。这些参数是模型的外部参数。选择好的超参数可以产生更好的算法。 14&#xff09;解释隐马尔可夫…

深度剖析ETHERCAT转CCLINK网关与ethercat通讯协议的连接细节

在某汽车零部件制造工厂的自动化生产线升级项目中&#xff0c;部分关键设备采用了支持 ETHERCAT 总线的 PLC 进行控制&#xff0c;而工厂原有的一些设备则遵循 CCLINK 协议标准。由于这两种协议之间无法直接通信&#xff0c;导致生产线的数据交互受阻&#xff0c;难以实现整体的…

链式二叉树,递归的暴力美学

目录 1.链式二叉树概念 2.链式二叉树的实现 3.先序遍历 4.中序遍历 5.后序遍历 6.求链式二叉树的结点个数 7.链式二叉树的叶子结点个数 8.求二叉树的k层的结点个数 9.链式二叉树求深度 10.求值为x的结点 11.链式二叉树的销毁 12.二叉树的层序遍历 13.判断二叉树是否…

AI是IT行业的变革力量,还是“职业终结者”?

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 AI是…

基于华为ENSP的OSPF数据报文保姆级别详解(3)

本篇博文摘要 &#x1f31f; 基于华为ensp之OSPF数据报文——头部信息、Hello包、DR/BDR选举、DBD包等保姆级别具体详解步骤&#xff1b;精典图示举例说明、注意点及常见报错问题所对应的解决方法 引言 &#x1f4d8; 在这个快速发展的技术时代&#xff0c;与时俱进是每个IT人的…

如何用SQL语句来查询表或索引的行存/列存存储方式|OceanBase 用户问题集锦

一、问题背景 自OceanBase 4.3.0版本起&#xff0c;支持了列存引擎&#xff0c;允许表和索引以行存、纯列存或行列冗余的形式创建&#xff0c;且这些存储方式可以自由组合。除了使用 show create table命令来查看表和索引的存储类型外&#xff0c;也有用户询问如何通过SQL语句…

重生之我在异世界学编程之算法与数据结构:深入堆篇

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 正文一、堆的基本概念二、堆的存储表示三…

【网络】深入了解HTTPS协议

HTTPS协议&#xff1a; HTTPS 也是⼀个应用层协议 HTTPS本质上就是在HTTP的基础上加了一个加密层&#xff0c;抛开加密之后&#xff0c;剩下的内容跟HTTP一样&#xff1b; HTTP 协议内容都是按照文本的方式明文传输的. 这就导致在传输过程中出现一些被篡改的情况. 例如 &…

RabbitMQ基本介绍及简单上手

&#xff08;一&#xff09;什么是MQ MQ&#xff08;message queue&#xff09;本质上是队列&#xff0c;满足先入先出&#xff0c;只不过队列中存放的内容是消息而已&#xff0c;那什么是消息呢&#xff1f; 消息可以是字符串&#xff0c;json也可以是一些复杂对象 我们应用场…

sys.dm_exec_connections:查询与 SQL Server 实例建立的连接有关的信息以及每个连接的详细信息(客户端ip)

文章目录 引言I 基于dm_exec_connections查询客户端ip权限物理联接时间范围dm_exec_connections表see also: 监视SQL Server 内存使用量资源信号灯 DMV sys.dm_exec_query_resource_semaphores( 确定查询执行内存的等待)引言 查询历史数据库客户端ip应用场景: 安全分析缺乏…

vscode如何离线安装插件

在没有网络的时候,如果要安装插件,就会麻烦一些,需要通过离线安装的方式进行。下面记录如何在vscode离线安装插件。 一、下载离线插件 在一台能联网的电脑中,下载好离线插件,拷贝到无法联网的电脑上。等待安装。 vscode插件商店地址:https://marketplace.visualstudio.co…

基于ADAS 与关键点特征金字塔网络融合的3D LiDAR目标检测原理与算法实现

一、概述 3D LiDAR目标检测是一种在三维空间中识别和定位感兴趣目标的技术。在自动驾驶系统和先进的空间分析中&#xff0c;目标检测方法的不断演进至关重要。3D LiDAR目标检测作为一种变革性的技术&#xff0c;在环境感知方面提供了前所未有的准确性和深度信息. 在这里&…