当前位置: 首页 > news >正文

怎么设网站网站开发的3个阶段

怎么设网站,网站开发的3个阶段,模板网点地址信息错误,膜结构行业做网站一、多级缓存 1. 传统缓存的问题 传统的缓存策略一般是请求到达Tomcat后#xff0c;先查询Redis#xff0c;如果未命中则查询数据库#xff0c;存在下面的问题#xff1a; 请求要经过Tomcat处理#xff0c;Tomcat的性能成为整个系统的瓶颈Redis缓存失效时#xff0c;会…一、多级缓存 1. 传统缓存的问题 传统的缓存策略一般是请求到达Tomcat后先查询Redis如果未命中则查询数据库存在下面的问题 请求要经过Tomcat处理Tomcat的性能成为整个系统的瓶颈Redis缓存失效时会对数据库产生冲击 2. 多级缓存方案 多级缓存就是充分利用请求处理的每个环节分别添加缓存减轻Tomcat压力提升服务性能 用作缓存的Nginx是业务Nginx需要部署为集群再由专门的Nginx用来做反向代理 3. JVM进程缓存 3.1 导入商品案例 1安装MySQL 后期做数据同步需要用到MySQL的主从功能所以需要在虚拟机中利用Docker来运行一个MySQL容器。 ①为了方便后期配置MySQL先准备两个目录用于挂载容器的数据和配置文件目录 # 进入/tmp目录 cd /tmp # 创建文件夹 mkdir mysql # 进入mysql目录 cd mysql ②进入mysql目录后执行下面的Docker命令 docker run \-p 3306:3306 \--name mysql \-v $PWD/conf:/etc/mysql/conf.d \-v $PWD/logs:/logs \-v $PWD/data:/var/lib/mysql \-e MYSQL_ROOT_PASSWORD123 \--privileged \-d \mysql:5.7 ③在/tmp/mysql/conf目录下添加一个my.cnf文件作为mysql的配置文件 # 创建文件 touch /tmp/mysql/conf/my.cnf 文件内容如下 [mysqld] skip-name-resolve character_set_serverutf8 datadir/var/lib/mysql server-id1000 ④重启容器 docker restart mysql 2导入SQL ①利用Navicat客户端连接MySQL导入课前资料提供的sql文件 新建数据库 运行SQL文件 其中包含两张表 tb_item商品表包含商品的基本信息tb_item_stock商品库存表包含商品的库存信息 之所以将库存分离出来是因为库存是更新比较频繁的信息写操作比较多而其他信息的修改频率非常低。 3导入Demo工程 ①导入课前资料提供的工程 修改数据库连接地址 修改配置文件中lombok的版本 dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.30/version/dependency 启动项目进行测试 4导入商品查询页面 商品查询是购物页面与商品管理的页面是分离的。 部署方式如图 我们需要准备一个反向代理的nginx服务器如上图红框所示将静态的商品页面存放到nginx目录中。页面需要的数据通过ajax向服务端nginx业务集群查询。 ①找到课前资料中的nginx目录放到一个非中文的目录下运行这个nginx服务 若存在下面的问题 2025/05/18 20:54:22 [notice] 19076#7520: signal process started 2025/05/18 20:55:27 [emerg] 22404#14572: bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a socket in a way forbidden by its access permissions) 修改nginx的监听端口 ②访问http://localhost:81/item.html?id10001 5反向代理 现在页面是假数据展示的。我们需要向服务器发送ajax请求查询商品数据。 这个请求地址是81端口所以被当前的nginx反向代理了。 查看nginx的conf目录下的nginx.conf文件其中的关键配置如下 其中192.168.200.130是虚拟机的IP地址也就是nginx业务集群部署的地方 3.2 初始Caffeine 本地进程缓存 缓存在日常开发中起着至关重要的作用由于是存储在内存中数据的读取速度非常快能大量减少对数据库的访问减少数据库的压力。我们把缓存分为两类 分布式缓存例如Redis 优点存储容量大、可靠性更好、可以在集群间共享缺点访问缓存有网络开销场景缓存数据量较大、可靠性要求高、需要在集群间共享 进程本地缓存例如HashMap、GuavaCache 优点读取本地内存没有网络开销速度更快缺点存储容量有限、可靠性较低、无法共享场景性能要求较高缓存数据量较小 Caffeine Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。Github地址GitHub - ben-manes/caffeine: A high performance caching library for Java Caffeine示例 可以通过item-service项目中的单元测试来学习Caffine的使用 Testvoid testBasicOps() {// 创建缓存对象CacheString, String cache Caffeine.newBuilder().build();// 存数据cache.put(gf, 迪丽热巴);// 取数据不存在则返回nullString gf cache.getIfPresent(gf);System.out.println(gf gf);// 取数据不存在则去数据库查询String defaultGF cache.get(defaultGF, key - {// 这里可以去数据库根据 key查询valuereturn 柳岩;});System.out.println(defaultGF defaultGF);} Caffeine提供了三种缓存驱逐策略 基于容量设置缓存的数量上限 // 创建缓存对象CacheString, String cache Caffeine.newBuilder()// 设置缓存大小上限为 1.maximumSize(1).build(); 基于时间设置缓存的有效时间 // 创建缓存对象CacheString, String cache Caffeine.newBuilder()// 设置缓存有效期为10秒从最后一次写入开始计时.expireAfterWrite(Duration.ofSeconds(10)).build(); 基于引用设置缓存为软引用或弱引用利用GC来回收缓存数据。性能较差不建议使用 在默认情况下当一个缓存元素过期时Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后或者在空闲时间完成对失效数据的驱逐。 /*基于大小设置驱逐策略*/Testvoid testEvictByNum() throws InterruptedException {// 创建缓存对象CacheString, String cache Caffeine.newBuilder()// 设置缓存大小上限为 1.maximumSize(1).build();// 存数据cache.put(gf1, 柳岩);cache.put(gf2, 范冰冰);cache.put(gf3, 迪丽热巴);// 延迟10ms给清理线程一点时间Thread.sleep(10L);// 获取数据System.out.println(gf1: cache.getIfPresent(gf1));System.out.println(gf2: cache.getIfPresent(gf2));System.out.println(gf3: cache.getIfPresent(gf3));}/*基于时间设置驱逐策略*/Testvoid testEvictByTime() throws InterruptedException {// 创建缓存对象CacheString, String cache Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(1)) // 设置缓存有效期为 10 秒.build();// 存数据cache.put(gf, 柳岩);// 获取数据System.out.println(gf: cache.getIfPresent(gf));// 休眠一会儿Thread.sleep(1200L);System.out.println(gf: cache.getIfPresent(gf));} 3.3 实现进程缓存 案例实现商品的查询的本地进程缓存 利用Caffeine实现下列需求 给根据id查询商品的业务添加缓存缓存未命中时查询数据库给根据id查询商品库存的业务添加缓存缓存未命中时查询数据库缓存初始大小为100缓存上限为10000 ①添加配置类CaffeineConfig package com.heima.item.config;import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.heima.item.pojo.Item; import com.heima.item.pojo.ItemStock; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;Configuration public class CaffeineConfig {/*** 商品缓存* return*/Beanpublic CacheLong, Item itemCache() {return Caffeine.newBuilder().initialCapacity(100).maximumSize(10_000).build();}/*** 商品库存缓存* return*/Beanpublic CacheLong, ItemStock stockCache() {return Caffeine.newBuilder().initialCapacity(100).maximumSize(10_000).build();} }②改造itemController GetMapping(/{id})public Item findById(PathVariable(id) Long id){return itemCache.get(id, key - itemService.query().ne(status, 3).eq(id, key).one());}GetMapping(/stock/{id})public ItemStock findStockById(PathVariable(id) Long id){return stockCache.get(id, key - stockService.getById(key));} ③启动项目进行测试各请求两次第二次请求走进程本地缓存 http://localhost:8081/item/10002 http://localhost:8081/item/stock/10002 4. Lua语法入门 4.1 初识Lua Lua是一种轻量小巧的脚本语言用标准C语言编写并以源代码形式开放其设计目的是为了嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。 官网The Programming Language Lua HelloWorld 1. 在Linux虚拟机的任意目录下新建一个hello.lua文件 touch hello.lua 2. 添加下面的内容 print(Hello World!) 3. 运行 lua hello.lua 4.2 变量和循环 数据类型 数据类型描述nil这个最简单只有值nil属于该类表示一个无效值在条件表达式中相当于falseboolean包含两个值false和truenumber表示双精度类型的实浮点数string字符串由一对双引号或单引号来表示function由 C 或 Lua 编写的函数tableLua中的表table其实是一个“关联数组”associative arrays数组的索引可以是数字、字符串或表类型。在Lua里table的创建是通过“构造表达式”来完成最简单构造表达式是{}用来创建一个空表 可以利用type函数测试给定变量或值的类型 变量 Lua声明变量的时候并不需要指定数据类型 -- 声明字符串 local str hello -- 声明数字 local num 21 -- 声明布尔类型 local flag true -- 声明数组 key为索引的table local arr {java, python, lua} -- 声明table类似java的map local map {nameJack, age21} 访问table -- 访问数组lua数组的角标从1开始 print(arr[1]) -- 访问table print(map[name]) print(map.name) 循环 数组、table都可以利用for循环来遍历 遍历数组 -- 声明数组 key为索引的table local arr {java, python, lua} -- 遍历数组 for index,value in ipairs(arr) doprint(index, value) end 遍历table -- 声明map也就是table local map {nameJack, age21} -- 遍历table for key, value in pairs(map) doprint(key, value) end 4.3 条件控制、函数 函数 定义函数的语法 function 函数名(argument1, argument2, ..., argumentn)-- 函数体return 返回值 end 例如定义一个函数用来打印数组 function printArr(arr)for index, value in ipairs(arr) doprint(value)end end 条件控制 类似Java的条件控制例如if、else语法 if(布尔表达式 then-- [布尔表达式 为true时 执行该语句块 --] else--[布尔表达式 为false时 执行该语句块 --] end 与java不同布尔表达式中的逻辑运算是基于英文单词 操作符描述实例and逻辑与操作符。若A为false则返回A否则返回BA and B为falseor逻辑或操作符。若A为true则返回A否则返回BA or B为truenot逻辑非操作符。与逻辑运算结果相反如果条件为true逻辑非为falsenot(A and B)为true 案例自定义函数打印table 需求自定义一个函数可以打印table当参数为nil时打印错误信息 local function printArr(arr)if (not arr) thenprint(数组不能为空!)return nilendfor index, value ipairs(arr) doprint(value)end end 5. 多级缓存 5.1 安装OpenResty ①安装OpenResty的依赖开发库执行命令 yum install -y pcre-devel openssl-devel gcc --skip-broken ②安装OpenResty仓库这样就可以便于未来安装或更新我们的软件包通过yum check-update命令 yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo 如果提示说命令不存在则运行 yum install -y yum-utils 然后再重复上面的命令 ③安装OpenResty yum install -y openresty ④安装opm工具。opm是OpenResty的一个管理工具可以帮助我们安装一个第三方的Lua模块。如果你想安装命令行工具opm那么可以像下面这样安装openresty-opm包 yum install -y openresty-opm ⑤目录结构。默认情况下OpenResty安装的目录是/usr/local/openresty。OpenResty就是再Nginx基础上继承了一些Lua模块 ⑥配置nginx的环境变量 打开配置文件 vi /etc/profile 在最下面加入两行 export NGINX_HOME/usr/local/openresty/nginx export PATH${NGINX_HOME}/sbin:$PATH NGINX_HOME后面是OpenResty安装目录下的nginx的目录 然后让配置生效 source /etc/profile ⑦修改nginx的配置文件。修改 /usr/local/openresty/nginx/conf/nginx.conf 文件内容如下注释太多影响后续编辑 #user nobody; worker_processes 1; error_log logs/error.log;events {worker_connections 1024; }http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;server {listen 8081;server_name localhost;location / {root html;index index.html index.htm;}error_page 500 502 503 504 /50x.html;location /50x.html {root html;}} } ⑧在Linux的控制台输入命令以启动nginx nginx 访问页面http://192.168.200.130:8081注意ip地址换为你自己的虚拟机IP 5.2 OpenResty快速入门 初识OpenResty OpenResty是一个基于Nginx的高性能Web平台用于方便地搭建能够处理高并发、扩展性极高的动态Web应用、Web服务和动态网关。具备下列特点 具备Nginx的完整功能基于Lua语言进行扩展集成了大量精良的Lua库、第三方模块允许使用Lua自定义业务逻辑、自定义库 官方网站OpenResty® - 开源官方站 案例OpenResty快速入门实现商品详情页数据查询 商品详情页面目前展示的是假数据在浏览器的控制台可以看到查询商品信息的请求 而这个请求最终被反向代理到虚拟机的OpenResty集群 需求在OpenResty中接收这个请求并返回一段商品的假数据 步骤①在nginx.conf的http下面添加对OpenResty的Lua模块的加载 # 加载lua 模块 lua_package_path /usr/local/openresty/lualib/?.lua;;; # 加载c模块 lua_package_cpath /usr/local/openresty/lualib/?.so;;; ②在nginx.conf的server下面添加对/api/item这个路径的监听 location /api/item {# 响应类型这里返回jsondefault_type application/json;# 响应数据由 lua/item.lua这个文件来决定content_by_lua_file lua/item.lua;} #user nobody; worker_processes 1; error_log logs/error.log;events {worker_connections 1024; }http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;# 加载lua 模块lua_package_path /usr/local/openresty/lualib/?.lua;;;# 加载c模块 lua_package_cpath /usr/local/openresty/lualib/?.so;;; server {listen 8081;server_name localhost;location /api/item {# 响应类型这里返回jsondefault_type application/json;# 响应数据由 lua/item.lua这个文件来决定content_by_lua_file lua/item.lua;}location / {root html;index index.html index.htm;}error_page 500 502 503 504 /50x.html;location /50x.html {root html;}} }②新建/lua/item.lua文件 内容如下注意不能换行 -- 返回假数据这里的ngx.say()函数就是写数据到Response中 ngx.say({id: 10001,name: SALSA AIR,title: RIMOWA 26寸托运箱拉杆箱 SALSA AIR系列果绿色 820.70.36.4,price: 21900,image: https://m.360buyimg.com/mobilecms/s720x720_jfs/t6934/364/1195375010/84676/e9f2c55f/597ece38N0ddcbc77.jpg!q70.jpg.webp,category: 拉杆箱,brand: RIMOWA,spec: ,status: 1,createTime: 2019-04-30T16:00:00.00000:00,updateTime: 2019-04-30T16:00:00.00000:00,stock: 2999,sold: 31290}) Vue打不开的可以在这里copy: ③重新加载配置 nginx -s reload 5.3 请求参数处理 OpenResty获取请求参数 OpenResty提供了各种API用来获取不同类型的请求参数 参数个数参数示例参数解析代码示例路径占位符/item/1001 1. 正则表达式匹配 location ~ /item/(\d) {         content_by_lua_file lua/item.lua } 2. 匹配到的参数会存入ngx.var数组中 -- 可以用角标获取 local id ngx.var[1] 请求头id1001 -- 获取请求头返回值是table类型 local headers ngx.get_headers() Get请求参数?id1001 -- 获取GET请求参数返回值是table类型 local getParams ngx.req.get_uri_args() Post表单参数id1001 -- 读取请求体 ngx.req.read_body() -- 获取POST表单参数返回值是table类型 local postParams ngx.req.get_pos_args() JSON参数{id: 1001} -- 读取请求体 ngx.req.read_body() -- 获取body中的json参数返回值是string类型 local josnBody ngx.req.get_body_data() 案例获取请求路径中的商品id信息拼接到json结果中返回 在查询商品信息的请求中通过路径占位符的方式传递了商品id到后台 需求在OpenResty中接收这个请求并获取路径中的id信息拼接到结果的json字符串中返回 ①修改nginx.conf #user nobody; worker_processes 1; error_log logs/error.log;events {worker_connections 1024; }http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;# 加载lua 模块lua_package_path /usr/local/openresty/lualib/?.lua;;;# 加载c模块 lua_package_cpath /usr/local/openresty/lualib/?.so;;; server {listen 8081;server_name localhost;location ~ /api/item/(\d) {# 响应类型这里返回jsondefault_type application/json;# 响应数据由 lua/item.lua这个文件来决定content_by_lua_file lua/item.lua;}location / {root html;index index.html index.htm;}error_page 500 502 503 504 /50x.html;location /50x.html {root html;}} }②修改item.lua -- 获取路径参数 local id ngx.var[1] -- 返回假数据这里的ngx.say()函数就是写数据到Response中 ngx.say({id: .. id .. ,name: SALSA AIR,title: RIMOWA 26寸托运箱拉杆箱 SALSA AIR系列果绿色 820.70.36.4,price: 21900,image: https://m.360buyimg.com/mobilecms/s720x720_jfs/t6934/364/1195375010/84676/e9f2c55f/597ece38N0ddcbc77.jpg!q70.jpg.webp,category: 拉杆箱,brand: RIMOWA,spec: ,status: 1,createTime: 2019-04-30T16:00:00.00000:00,updateTime: 2019-04-30T16:00:00.00000:00,stock: 2999,sold: 31290}) ③重新加载配置文件发起请求 nginx -s reload 5.4 查询Tomcat 案例获取请求路径中的商品id信息根据id向Tomcat查询商品信息 这里要修改item.lua满足下面的需求 获取请求参数中的id根据id向Tomcat服务发送请求查询商品信息根据id向Tomcat服务发送请求查询库存信息组装商品信息、库存信息序列化为JSON格式并返回 nginx内部发送http请求 nginx提供了内部API用于发送http请求 local resp ngx.location.capture(/path, {method ngx.HTTP_GET, -- 请求方式args {a1,b2}, -- get方式传参数bodyc3d4 -- post方式传参数 }) 返回的响应内容包括 resp.status响应状态码resp.header响应头是一个tableresp.body响应体就是响应数据 注意这里的path是路径并不包含IP和端口。这个请求会被nginx内部的server监听并处理。 但是我们希望这个请求发送到Tomcat服务器所以需要编写一个server来对这个路径做反向代理 location /path {# 这里是windows电脑的ip和Java服务端口需要确保windows防火墙处于关闭状态proxy_pass http://192.168.200.1:8081; windows的IPv4地址可以通过ipconfig命令来查看如192.168.200.1 封装http查询的函数 我们可以把http查询的请求封装为一个函数放到OpenResty函数库中方便后期使用。 ①在/usr/local/openresty/lualib目录下创建common.lua文件 ②在common.lua中封装http查询的函数 -- 封装函数发送http请求并解析响应 local function read_http(path, params)local resp ngx.location.capture(path,{method ngx.HTTP_GET,args params,})if not resp then-- 记录错误信息返回404ngx.log(ngx.ERR, http not found, path: , path , , args: , args)ngx.exit(404)endreturn resp.body end -- 将方法导出 local _M { read_http read_http } return _M JSON结果处理 OpenResty提供了一个cjson的模块用来处理JSON的序列化和反序列化 官方地址https://github.com/openresty/lua-cjson/ ①引入cjson模块 local cjson require(cjson) ②序列化 local obj {name jack,age 21 } local json cjson.encode(obj) ③反序列化 local json {name: jack, age: 21} -- 反序列化 local obj cjson.decode(json) print(obj.name) ③改造item.lua -- 导入common函数库 local common require(common) local read_http common.read_http-- 导入cjson库 local cjson require(cjson)-- 获取路径参数 local id ngx.var[1]-- 查询商品信息 local itemJson read_http(/item/ .. id, nil)-- 查询库存信息 local stockJson read_http(/item/stock/ .. id, nil)-- JSON转化为lua的table local item cjson.decode(itemJson) local stock cjson.decode(stockJson) -- 组合数据 item.stock stock.stock item.sold stock.sold-- 返回假数据这里的ngx.say()函数就是写数据到Response中 ngx.say(cjson.encode(item)) ④重新加载配置 nginx -s reload ⑤启动itemApplication进行测试 Tomcat集群的负载均衡 步骤①修改nginx.conf #user nobody; worker_processes 1; error_log logs/error.log;events {worker_connections 1024; }http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;# 加载lua 模块lua_package_path /usr/local/openresty/lualib/?.lua;;;# 加载c模块 lua_package_cpath /usr/local/openresty/lualib/?.so;;; upstream tomcat-cluster {hash $request_uri;server 192.168.239.1:8081;server 192.168.239.1:8081;}server {listen 8081;server_name localhost;location /item {proxy_pass http://tomcat-cluster;}location ~ /api/item/(\d) {# 响应类型这里返回jsondefault_type application/json;# 响应数据由 lua/item.lua这个文件来决定content_by_lua_file lua/item.lua;}location / {root html;index index.html index.htm;}error_page 500 502 503 504 /50x.html;location /50x.html {root html;}} }②重新加载nginx配置 nginx -s reload ③启动两台Tomcat进行测试 5.5 Redis缓存预热 冷启动与缓存预热 冷启动服务刚刚启动时Redis中并没有缓存如果所有商品数据都在第一次查询时添加缓存可能会给数据库带来较大压力缓存预热在实际开发中我们可以利用大数据统计用户访问的热点数据在项目启动时将这些热点数据提前查询并保存到redis中。 我们数据量较少可以在启动时将所有数据都放入缓存中 缓存预热 步骤①利用Docker安装redis docker run --name redis -p 6379:6379 -d redis redis-server --appendonly yes ②在item-service服务中引入Redis依赖 pom.xml dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency ③配置Redis地址 application.yml spring:redis:host: 192.168.200.130 ④编写初始化类 package com.heima.item.config;import com.fasterxml.jackson.databind.ObjectMapper; import com.heima.item.pojo.Item; import com.heima.item.pojo.ItemStock; import com.heima.item.service.IItemService; import com.heima.item.service.IItemStockService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component;import java.util.List;Component public class RedisHandler implements InitializingBean {Autowiredprivate StringRedisTemplate redisTemplate;Autowiredprivate IItemService itemService;Autowiredprivate IItemStockService stockService;private static final ObjectMapper MAPPER new ObjectMapper();Overridepublic void afterPropertiesSet() throws Exception {// 初始化缓存// 1. 查询商品信息ListItem itemList itemService.list();// 2. 放入缓存for (Item item : itemList) {// 2.1 item序列化为JSONString json MAPPER.writeValueAsString(item);// 2.2 存入redisredisTemplate.opsForValue().set(item:id: item.getId(), json);}// 3. 查询商品库存ListItemStock itemStockList stockService.list();// 4. 放入缓存for (ItemStock stock : itemStockList) {// 4.1 stock序列化为JSONString json MAPPER.writeValueAsString(stock);// 4.2 存入redisredisTemplate.opsForValue().set(item:stock:id: stock.getId(), json);}} }⑤启动ItemApplication 5.6 查询Redis缓存 OpenResty提供了操作Redis的模块我们只要引入该模块就能直接使用 ①引入Redis模块并初始化Redis对象common.lua -- 引入redis模块 local redis require(resty.redis) -- 初始化Redis对象 local red redis:new() -- 设置Redis超时时间 red:set_timeouts(1000, 1000, 1000) ②封装函数用来释放Redis连接其实就是放入连接池 -- 关闭redis连接的工具方法其实是放入连接池 local function close_redis(red)local pool_max_idle_time 10000 -- 连接的空闲时间单位是毫秒local pool_size 100 --连接池大小local ok, err red:set_keepalive(pool_max_idle_time, pool_size)if not ok thenngx.log(ngx.ERR, 放入redis连接池失败: , err)end end ③封装函数从Redis读取数据并返回 -- 查询redis的方法 ip和port是redis地址key是查询的key local function read_redis(ip, port, key)-- 获取一个连接local ok, err red:connect(ip, port)if not ok thenngx.log(ngx.ERR, 连接redis失败 : , err)return nilend-- 查询redislocal resp, err red:get(key)-- 查询失败处理if not resp thenngx.log(ngx.ERR, 查询Redis失败: , err, , key , key)end--得到的数据为空处理if resp ngx.null thenresp nilngx.log(ngx.ERR, 查询Redis数据为空, key , key)endclose_redis(red)return resp end common.lua完整代码 -- 封装函数发送http请求并解析响应 local function read_http(path, params)local resp ngx.location.capture(path,{method ngx.HTTP_GET,args params,})if not resp then-- 记录错误信息返回404ngx.log(ngx.ERR, http not found, path: , path , , args: , args)ngx.exit(404)endreturn resp.body end-- 引入redis模块 local redis require(resty.redis) -- 初始化Redis对象 local red redis:new() -- 设置Redis超时时间 red:set_timeouts(1000, 1000, 1000)-- 关闭redis连接的工具方法其实是放入连接池 local function close_redis(red)local pool_max_idle_time 10000 -- 连接的空闲时间单位是毫秒local pool_size 100 --连接池大小local ok, err red:set_keepalive(pool_max_idle_time, pool_size)if not ok thenngx.log(ngx.ERR, 放入redis连接池失败: , err)end end-- 查询redis的方法 ip和port是redis地址key是查询的key local function read_redis(ip, port, key)-- 获取一个连接local ok, err red:connect(ip, port)if not ok thenngx.log(ngx.ERR, 连接redis失败 : , err)return nilend-- 密码认证新增部分local auth_ok, auth_err red:auth(leadnews) -- 你的redis密码if not auth_ok thenngx.log(ngx.ERR, Redis认证失败: , auth_err)return nilend-- 查询redislocal resp, err red:get(key)-- 查询失败处理if not resp thenngx.log(ngx.ERR, 查询Redis失败: , err, , key , key)end--得到的数据为空处理if resp ngx.null thenresp nilngx.log(ngx.ERR, 查询Redis数据为空, key , key)endclose_redis(red)return resp end-- 将方法导出 local _M { read_http read_http,read_redis read_redis } return _M 案例查询商品时优先Redis缓存查询 需求 修改item.lua封装一个函数read_data实现先查询Redis如果未命中再查询tomcat修改item.lua查询商品和库存时都调用read_data这个函数 ①修改item.lua -- 导入common函数库 local common require(common) local read_http common.read_http local read_redis common.read_redis-- 导入cjson库 local cjson require(cjson)-- 封装函数先查询redis再查询tomcat function read_data(key, path, params) -- 查询redislocal resp read_redis(127.0.0.1, 6379, key)-- 判断redis是否命中if not resp thenngx.log(ngx.ERR, redis查询失败尝试查询tomcatkey: , key)-- redis查询失败查询tomcatresp read_http(path, params)endreturn resp end-- 获取路径参数 local id ngx.var[1]-- 查询商品信息 local itemJson read_data(item:id: .. id, /item/ .. id, nil)-- 查询库存信息 local stockJson read_data(item:stock:id: .. id, /item/stock/ .. id, nil)-- JSON转化为lua的table local item cjson.decode(itemJson) local stock cjson.decode(stockJson) -- 组合数据 item.stock stock.stock item.sold stock.sold-- 返回假数据这里的ngx.say()函数就是写数据到Response中 ngx.say(cjson.encode(item)) 重新加载nginx配置 nginx -s reload ②停掉ItemApplication和ItemApplication(2)测试还能访问到数据 5.7 Nginx本地缓存 案例在查询商品时优先查询OpenResty的本地缓存 需求 修改item.lua中的read_data函数优先查询本地缓存未命中时再查询Redis、Tomcat查询Redis或Tomcat成功后将数据写入本地缓存并设置有效期商品基本信息有效期30分钟库存信息有效期1分钟 OpenResty为nginx提供了shard dict的功能可以在nginx的多个worker之间共享数据实现缓存功能。 ①开启共享字典在nginx.conf的http下添加配置 # 共享字典也就是本地缓存名称叫做item_cache大小150m lua_shared_dict item_cache 150m;  ②操作共享字典item.lua -- 导入common函数库 local common require(common) local read_http common.read_http local read_redis common.read_redis-- 导入cjson库 local cjson require(cjson) -- 导入共享字典本地缓存 local item_cache ngx.shared.item_cache-- 封装函数先查询redis再查询tomcat function read_data(key, expire, path, params) -- 查询本地缓存local val item_cache:get(key)if not val thenngx.log(ngx.ERR, 本地缓存查询失败尝试查询rediskey: , key)-- 查询redisval read_redis(127.0.0.1, 6379, key)-- 判断redis是否命中if not val thenngx.log(ngx.ERR, redis查询失败尝试查询tomcatkey: , key)-- redis查询失败查询tomcatval read_http(path, params)endend-- 查询成功把数据写入本地缓存item_cache:set(key, val, expire)-- 返回数据return val end-- 获取路径参数 local id ngx.var[1]-- 查询商品信息 local itemJson read_data(item:id: .. id, 1800, /item/ .. id, nil)-- 查询库存信息 local stockJson read_data(item:stock:id: .. id, 60, /item/stock/ .. id, nil)-- JSON转化为lua的table local item cjson.decode(itemJson) local stock cjson.decode(stockJson) -- 组合数据 item.stock stock.stock item.sold stock.sold-- 返回假数据这里的ngx.say()函数就是写数据到Response中 ngx.say(cjson.encode(item)) ③重新加载配置进行测试 nginx -s reload 查看日志 [rootitcast ~]# cd /usr/local/openresty/nginx/logs/ [rootitcast logs]# tail -f error.log访问http://localhost:81/item.html?id10003 6. 缓存同步 6.1 数据同步策略 缓存数据同步的常见方式有三种 设置有效期给缓存设置有效期到期后自动删除。再次查询时更新缓存 优势简单、方便缺点时效性差缓存过期之前可能不一致场景更新频率较低时效性要求低的业务 同步双写在修改数据库的同时直接修改缓存 优势时效性强缓存与数据库强一致缺点有代码侵入耦合度高场景对一致性、时效性要求较高的缓存数据 异步通知修改数据库时发送事件通知相关服务监听到通知后修改缓存数据 优势低耦合可以同时通知多个缓存服务缺点时效性一般可能存在中间不一致状态场景时效性要求一般有多个服务需要同步 基于MQ的异步通知 基于Canal的异步通知 6.2 安装Canal 初识Canal Canal译意为管道/水道/沟渠是阿里巴巴旗下的一款开源项目基于Java开发。基于数据库增量日志解析提供增量数据订阅消费。 Github的地址GitHub - alibaba/canal: 阿里巴巴 MySQL binlog 增量订阅消费组件 Canal是基于mysql的主从同步来实现的MySQL主从同步的原理如下 MySQL master将数据变更写入二进制日志binary log其中记录的数据叫作binary log eventsMySQL slave将master的binary log events拷贝到它的中继日志relay logMySQL slave重放relay log中事件将数据变更反映它自己的数据 Canal就是把自己伪装成MySQL的一个slave节点从而监听master的binary log变化。再把得到的变化信息通知给Canal的客户端进而完成对其他数据库的同步。 安装和配置Canal 步骤①打开mysql容器挂载的日志文件我的在/tmp/mysql/conf目录下修改my.cnf文件添加如下内容 log-bin/var/lib/mysql/mysql-bin binlog-do-dbheima log-bin/var/lib/mysql/mysql-bin设置binary log文件的存放地址和文件名叫作mysql-binbinlog-do-dbheima指定对哪个database记录binary log events这里记录heima这个库 ②设置用户权限 添加一个仅用于数据同步的账户出于安全考虑仅提供对heima这个库的操作权限 create user canal% IDENTIFIED by canal; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO canal% identified by canal; FLUSH PRIVILEGES; 重启mysql容器 docker restart mysql 测试设置是否成功在mysql控制台或者Navicat中输入命令 show master status; ③安装Canal 创建一个网络将MySQL、Canal、MQ放到同一个Docker网络中 docker network create heima 让mysql加入这个网络 docker network connect heima mysql ④安装Canal 将课前资料提供的Canal镜像压缩包上传至虚拟机 通过下面的命令导入 docker load -i canal.tar 然后运行命令创建Canal容器 docker run -p 11111:11111 --name canal \ -e canal.destinationsheima \ -e canal.instance.master.addressmysql:3306 \ -e canal.instance.dbUsernamecanal \ -e canal.instance.dbPasswordcanal \ -e canal.instance.connectionCharsetUTF-8 \ -e canal.instance.tsdb.enabletrue \ -e canal.instance.gtidonfalse \ -e canal.instance.filter.regexheima\\..* \ --network heima \ -d canal/canal-server:v1.1.5 说明 -p 11111:11111这是canal的默认监听端口-e canal.instance.master.addressmysql:3306数据库地址和端口如果不知道mysql容器地址可以通过docker inspect 容器id来查看-e canal.instance.dbUsernamecanal数据库用户名-e canal.instance.dbPasswordcanal数据库密码-e canal.instance.filter.regex要监听的表名称 ⑤查看canal的日志 docker logs -f canal docker exec -it canal bash tail -f canal-server/logs/canal/canal.log tail -f canal-server/logs/heima/heima.log6.3 监听Canal Canal客户端 Canal提供了各种语言的客户端当Canal监听到binlog变化时会通知Canal的客户端。不过这里我们会使用Github上的第三方开源的canal-starter。 地址GitHub - NormanGyllenhaal/canal-client: spring boot canal starter 易用的canal 客户端 canal client 步骤①引入依赖 pom.xml dependencygroupIdtop.javatool/groupIdartifactIdcanal-spring-boot-starter/artifactIdversion1.2.1-RELEASE/version/dependency ②编写配置 application.yml canal:destination: heima # canal实例名称要跟canal-server运行时设置的destination一致server: 192.168.200.130:11111 # canal地址 ③编写监听器监听Canal消息 Canal推送给canal-client的是被修改的这一行数据row而我们引入的canal-client则会帮我们把行数据封装到Item实体类中。这个过程中需要知道数据库与实体的映射关系要用到JPA的几个注解 package com.heima.item.pojo;import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient;import javax.persistence.Column; import java.util.Date;Data TableName(tb_item) public class Item {TableId(type IdType.AUTO)Idprivate Long id;//商品idColumn(name name)private String name;//商品名称private String title;//商品标题private Long price;//价格分private String image;//商品图片private String category;//分类名称private String brand;//品牌名称private String spec;//规格private Integer status;//商品状态 1-正常2-下架private Date createTime;//创建时间private Date updateTime;//更新时间TableField(exist false)Transientprivate Integer stock;TableField(exist false)Transientprivate Integer sold; }④在RedisHandler新增两个方法 package com.heima.item.config;import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.heima.item.pojo.Item; import com.heima.item.pojo.ItemStock; import com.heima.item.service.IItemService; import com.heima.item.service.IItemStockService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component;import java.util.List;Component public class RedisHandler implements InitializingBean {Autowiredprivate StringRedisTemplate redisTemplate;Autowiredprivate IItemService itemService;Autowiredprivate IItemStockService stockService;private static final ObjectMapper MAPPER new ObjectMapper();Overridepublic void afterPropertiesSet() throws Exception {// 初始化缓存// 1. 查询商品信息ListItem itemList itemService.list();// 2. 放入缓存for (Item item : itemList) {// 2.1 item序列化为JSONString json MAPPER.writeValueAsString(item);// 2.2 存入redisredisTemplate.opsForValue().set(item:id: item.getId(), json);}// 3. 查询商品库存ListItemStock itemStockList stockService.list();// 4. 放入缓存for (ItemStock stock : itemStockList) {// 4.1 stock序列化为JSONString json MAPPER.writeValueAsString(stock);// 4.2 存入redisredisTemplate.opsForValue().set(item:stock:id: stock.getId(), json);}}/*** 新增保存* param item*/public void saveItem(Item item) {try {// 1. item序列化为JSONString json MAPPER.writeValueAsString(item);// 2. 存入redisredisTemplate.opsForValue().set(item:id: item.getId(), json);} catch (JsonProcessingException e) {throw new RuntimeException(e);}}/*** 删除* param id*/public void deleteItemById(Long id) {redisTemplate.delete(item:id: id);} }⑤新增ItemHandler package com.heima.item.canal;import com.github.benmanes.caffeine.cache.Cache; import com.heima.item.config.RedisHandler; import com.heima.item.pojo.Item; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import top.javatool.canal.client.annotation.CanalTable; import top.javatool.canal.client.handler.EntryHandler;CanalTable(tb_item) Component public class ItemHandler implements EntryHandlerItem {Autowiredprivate RedisHandler redisHandler;Autowiredprivate CacheLong, Item itemCache;Overridepublic void insert(Item item) {// 写数据到JVM进程缓存itemCache.put(item.getId(), item);// 新增数据到RedisredisHandler.saveItem(item);}Overridepublic void update(Item before, Item after) {// 更新JVM本地缓存itemCache.put(after.getId(), after);// 更新redis数据redisHandler.saveItem(after);}Overridepublic void delete(Item item) {// 清理JVM本地缓存itemCache.invalidate(item.getId());// 删除redis数据redisHandler.deleteItemById(item.getId());} }⑥启动ItemApplication和ItemApplication(2)访问http://localhost:8081/ 更新商品数据 观察控制台日志 表名称监听支持的语法 mysql 数据解析关注的表Perl正则表达式. 多个正则之间以逗号(,)分隔转义符需要双斜杠(\\) 常见例子 1. 所有表.* or .*\\..* 2. canal schema下所有表 canal\\..* 3. canal下的以canal打头的表canal\\.canal.* 4. canal schema下的一张表canal.test1 5. 多个规则组合使用然后以逗号隔开canal\\..*,mysql.test1,mysql.test2 多级缓存总结
http://www.zqtcl.cn/news/49649/

相关文章:

  • 品牌网站建设1毛尖ip设计
  • 湛江网站排名优化汕头网站关键排名
  • 网站建设方案设计ppt针织外贸公司
  • 极速云建站在线简易网页制作网站
  • 做网站会员金字塔系统中国山东建设监理协会官方网站
  • 网站建设玖金手指排名11义乌市评建设职称网站
  • 在线视频网站开发梧州seo排名
  • 百度快速收录方法seo关键词优化
  • 一台ip做两个网站自定义图片制作
  • 深圳外包网站公司建设网站如何赢利
  • WordPress全站跳转网站开发资金规模
  • 悬赏做海报的网站wordpress 页面 分类目录
  • html5网站上线模版个人购买链接
  • 世界上有php应用的网站wordpress主题淘宝客
  • 做外贸没有网站需要注意什么问题wordpress缩略图采集火车头
  • 网站内容建设和管理免费用手机做网站
  • 建站之星 discuz手机网站建站
  • 官方网站的资料做证据wordpress+仿简书模板
  • 安康做企业网站的邢台手机网站制作
  • 百度推广是否做网站wordpress怎么放图片不显示
  • 做推广都有什么网站网站动态海报效果怎么做的
  • 杭州电商网站建设品牌网络推广公司
  • 无锡手机网站制作费用安徽建站网站
  • 福州网站开发si7.cc传奇游戏在线玩网页版
  • 广州网站维护公司常见的网络服务有哪些
  • 中山低价网站建设网页设计培训班学校排名
  • 建外文网站湖南省建设信息网站查询
  • 动态倒计时网站模板怎么做直播室的网站
  • 软件开发建设网站网站建设百度推广
  • 网站运行速度慢html建站