关于商城首页简易高并发案例解析

一、商城门户数据缓存

一般商城首页门户需要展示商品分类和广告数据,可以把这些内容数据存入redis缓存中。
对于变更频率低,查询频率高的数据,如何提高访问速率?

  1. 数据做成静态网页
  2. 做缓存(redis)

对于商城首页高并发实现思路:

在这里插入图片描述

  1. 当用户访问首页时,使用Lua查询Nginx缓存,有缓存则返回缓存中商城首页分类的数据
  2. 当Nginx中没有分类数据缓存时,统过Lua脚本查询Redis,如果Redis中有数据,把查询到的数据存入Nginx缓存中,并返回查询的分类数据结果
  3. 当Redis中也没有分类缓存数据时,则通过Lua脚本查询数据库,如果数据库中有数据,就将查询到的数据存入Redis缓存中,并返回结果

二、Lua了解

2.1、lua是什么

Lua是一种轻量小巧的脚本语言,它是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo三人所组成的研究小组于1993年开发的。 其设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。

2.2、特性

  • 支持面向过程编程(procedure oriented Programming)和函数式编程(functional programming)
  • 自动内存管理,只提供一种类型的表(table),用它可以实现数组、哈希表、集合、对象
  • 语言内置模式匹配,闭包(closure),函数也可以看作一个值,提供多线程支持(协同进程,并非操作系统所支持的线程)
  • 通过闭包和table可以很方便的支持面对对象编程所需要的一些关键机制,比如数据抽象、虚函数、继承和重载

2.3、应用场景

  • 游戏开发
  • 独立脚本应用
  • web应用脚本
  • 扩展和数据库插件(如Mysql Proxy、Mysql WorkBench)
  • 安全系统,如入侵检测系统
  • redis中嵌套调用实现类似事务的功能
  • web容器中处理一些过滤、缓存等等逻辑,例如nginx

2.4、lua安装

本篇关于Linux下安装lua

安装步骤:

# 下载lua压缩包
curl -R -O http://www.lua.org/ftp/lua-5.4.3.tar.gz
# 解压lua文件
tar -zxf lua-5.4.3.tar.gz
# 进入lua软件目录中
cd lua-5.4.3/
# 安装lua
make linux test
make install

注意: 安装时可能会出现以下错误
在这里插入图片描述
此时需要安装lua相关的依赖库支持:
yum -y install gcc gcc-c++ kernel-devel
安装gcc依赖完后再执行lua安装命令

检测lua是否安装成功
在这里插入图片描述

Tips:ctrl+c 退出lua编辑。在Linux或windows中大部分脚本运行的程序都可以用 ctrl+c 退出

2.5、编写入门程序

创建并编辑hello.lua文件

# 用vim用vim创建hello.lua文件并进入编辑模式
vim hello.lua
# 输入 i 进入编辑模式
print("Hello Lua!");
# 编辑完后esc 退出编辑模式 :wq保存文件

执行命令

lua hello.lua

输出效果:
在这里插入图片描述

2.6、lua的基本语法

lua有交互式编程和脚本式编程。
交互式编程就是直接输入语法,就能执行。
脚本式编程需要编写脚本,然后再执行命令 执行脚本才可以。
一般采用脚本式编程。(例如:编写一个hello.lua的文件,输入文件内容,并执行lua hell.lua即可)

  1. 交互式编程
    Lua 提供了交互式编程模式。我们可以在命令行中输入程序并立即查看效果。
    Lua 交互式编程模式可以通过命令 lua -ilua 来启用:
    在这里插入图片描述

  2. 脚本式编程
    我们可以将 Lua 程序代码保持到一个以 lua 结尾的文件,并执行,该模式称为脚本式编程,例如上面入门程序中将lua语法写到hello.lua文件中。

2.6.1、注释

单行注释:两个减号表示单行注释

--

多行注释:

--[[
多行注释
多行注释
--]]

Tips: Shift+Delete 删除 Ctrl+Enter 换行输入

2.6.2、定义变量

全局变量,默认的情况下,定义一个变量都是全局变量,
如果要用局部变量 需要声明为 local 例如:

-- 全局变量赋值
a=1
-- 局部变量赋值
local b=2

输出结果:
在这里插入图片描述

如果变量没有初始化,则它的值为nil;这和java中的null不同。
因为变量b为局部变量,所以输出的为nil

2.6.3、Lua中数据类型

Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和table。

数据类型描述
nil这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。
boolean包含两个值:false和true。
number表示双精度类型的实浮点数
string字符串由一对双引号或单引号来表示
function由 C 或 Lua 编写的函数
userdata表示任意存储在变量中的C数据结构
thread表示执行的独立线路,用于执行协同程序
tableLua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。

实例:
在这里插入图片描述

2.6.4 、流程控制

  1. if语句
    Lua if语句 由一个布尔表达式作为判断条件判断,其后紧跟其他语句组成
    语法:
if()
then
	--[[
		在布尔表达式为 true 时执行的语句
		--]]
end

实例:
在这里插入图片描述

  1. if … else语句
    Lua if 语句可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码块。
    语法:
if (布尔表达式)
then 
--[[
	布尔表达式为 true 时执行该语句块
--]]
else
--[[
	布尔表达式为 false 时执行该语句块
--]]
end

实例:
在这里插入图片描述

2.6.5、循环

  1. while循环
    Lua 编程语言中 while 循环语句在判断条件为 true 时会重复执行循环体语句。
    语法:
while (循环条件)
do
	执行体
end

实例:
在这里插入图片描述

  1. for循环
    Lua 编程语言中 for 循环语句可以重复执行指定语句,重复次数可在 for 语句中控制。
    语法: 1->10 1:exp1 10:exp2 2:exp3:递增的数量
for var=exp1,exp2,exp3
do
	执行体
end

var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 “执行体”。exp3 是可选的,如果不指定,默认为1。
实例:

for i=1,20,3
do 
print(i)
end

for i=1,20,3 1表示从1开始,20表示到循环到20结束,3表示每次循环递增3
在这里插入图片描述

  1. repeat…until语句
    Lua 编程语言中 repeat…until 循环语句不同于 for 和 while循环,for 和 while 循环的条件语句在当前循环执行开始时判断,而 repeat…until 循环的条件语句在当前循环结束后判断。
    语法:
repeat
	执行体
until (循环条件)

案例:
在这里插入图片描述

2.6.6、函数

lua中也可以定义函数,类似于java中的方法。
语法:

function  var ()
if ( 条件 ) 
then
	条件真内容
else
	条加假内容
end

例如:

 -- 定义一个比较大小的函数    
 function max(a,b)
   if (a>b)
   then 
      result=a;
   else      
      result=b;
   end
 
   return result;
 end
 -- 调用函数示例
 print("两数比较最大值:",max(10,15))
 print("两数比较最大值:",max(20,10))

在这里插入图片描述

2.6.7、表

table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。
Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。

案例:

-- 初始化表
tb1 = {}
print("tb1的类型为:",type(tb1))

-- 给tb1指定值
tb1[0] = "hello"
tb1["lua"] = "Hello Lua!"
print("tb1表索引0的值为:",tb1[0])
print("tb1表索引lua的值为:",tb1["lua"])

-- 移除tb1表的引用
tb1 = nil
print("tb1表索引0的值为:",tb1[0])

在这里插入图片描述

2.6.8、模块

  1. 模块的定义
    模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放
    在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
    创建一个文件叫module.lua,在module.lua中创建一个独立的模块,代码如下:
-- 文件名为 module.lua
-- 定义一个名为 module的模块
module = {}
-- 定义一个常量
module.constant = "这是定义的一个常量"
-- 定义一个函数
function module.a()
    print("这是一个公共函数")
end

local function b()
    print("这是一个私有函数")
end

function module.c()
    b()
end

return module
  1. require 函数

require 用于 引入其他的模块,类似于java中的类要引用别的类的效果。
用法:

# 两种方法均可引用
require("module")
require "module"
# 引用外部文件(./module :文件路径)
require("./module")

案例:
在这里插入图片描述

三、OpenResty介绍

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

OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。

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

360,UPYUN,阿里云,新浪,腾讯网,去哪儿网,酷狗音乐等都是 OpenResty 的深度用户。
OpenResty 简单理解成 就相当于封装了nginx,并且集成了LUA脚本,开发人员只需要简单的使用其提供了模块就可以实现相关的逻辑,而不再像之前,还需要在nginx中自己编写lua的脚本,再进行调用了。

3.1、安装OpenResty

本教程是基于 centos7安装,对于 CentOS 8 或以上版本,应将下面的 yum 都替换成 dnf

  1. 添加OpenResty仓库
wget https://openresty.org/package/centos/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/
# 更新yum索引目录
sudo yum check-update
  1. 执行安装
sudo yum install -y openresty

安装成功后默认默认目录为:/usr/local/openresty/

3.2、测试访问

安装OpenResty时默认已经安装好了nginx,在目录:/usr/local/openresty/nginx 下。
修改/usr/local/openresty/nginx/conf/nginx.conf,将配置文件使用的根设置为root,目的就是以后要使用lua脚本的时候 ,可以直接加载在root下的lua脚本。

cd /usr/local/openresty/nginx/conf/
vim nginx.conf
# 修改代码
user root root;

在这里插入图片描述
启动nginx服务:

cd /usr/local/openresty/nginx/sbin
# 启动
./nginx
# 重启
./nginx -s reload
# 关闭
./nginx -s stop

查询nginx状态: ps -ef | grep nginx ,带master表示启动成功
在这里插入图片描述

四、场景模拟-商城首页缓存处理

4.1、需求分析

在页面显示商品的分类信息

4.2、docker安装redis

# 使用docker安装redis
docker pull redis
docker run --name redis -p 6379:6379 -d redis

4.3、Lua+Nginx配置

使用lua来完成一个操作nginx缓存访问redis、mysql数据库的案例

4.3.1 Lua案例1需求:根据分类ID查询分类数据放入redis中

实现思路:

  1. 定义请求:用于查询数据库中的数据更新到redis中。
  2. 连接mysql ,按照分类ID读取商品分类,转换为json字符串。
  3. 连接redis,将广告列表json字符串存入redis 。

定义请求:

请求: /update_content
参数: id(分类id)
返回值: json

请求地址:http://192.168.181.130/update_content?id=1
创建/root/lua目录,在该目录下创建update_content.lua,连接mysql 查询数据 并存储到redis中。

ngx.header.content_type = "application/json;charset=utf8"
local cjson = require("cjson")
local mysql = require("resty.mysql")
local uri_args = ngx.req.get_uri_args()
local id = uri_args["id"]
local db = mysql:new()
db:set_timeout(1000)
local props = {
    host = "192.168.181.130",
    port = 3306, 
    database = "legou",
    user = "root",
    password = "root"
}
local res = db:connect(props)
local select_sql = "SELECT id_,is_parent_,order_,parent_id_,title_,expand_ FROM category_ WHERE id_= " .. id
res = db:query(select_sql)
db:close()
local redis = require("resty.redis")
local red = redis:new()
red:set_timeout(2000)
local ip = "192.168.181.130"
local port = 6379
red:connect(ip, port)
red:set("content_" .. id, cjson.encode(res))
red:close()
ngx.say("{flag:true}")

tips: .. 表示字符串拼接

修改nginx配置文件:

vim /usr/local/openresty/nginx/conf/nginx.conf

server {
	listen       80;
	server_name  localhost;

   # 在server中添加
   location /update_content {
            content_by_lua_file /root/lua/update_content.lua;
   }
}

重启nginx服务 ./ngixn -s reload
请求测试:http://192.168.181.130/update_content?id=1
在这里插入图片描述
在这里插入图片描述

4.3.2、从redis中获取数据

实现思路:定义请求,用户根据分类的ID 获取分类信息。通过lua脚本直接从redis中获取数据即可。
定义请求:

请求: /read_content
参数: id(分类id)
返回值: json

在/root/lua目录下创建read_content.lua:

--设置响应头类型 
ngx.header.content_type = "application/json;charset=utf8"
--获取请求中的参数ID 
local uri_args = ngx.req.get_uri_args()
local id = uri_args["id"]
--引入redis库 
local redis = require("resty.redis")
--创建redis对象 
local red = redis:new()
--设置超时时间 
red:set_timeout(2000)
--连接 
local ok, err = red:connect("192.168.181.130", 6379)
--获取key的值 
local resContent = red:get("content_" .. id)
--输出到返回响应中 
ngx.say(resContent)
--关闭连接 
red:close()

修改nginx配置文件:

vim /usr/local/openresty/nginx/conf/nginx.conf

server {
	listen       80;
	server_name  localhost;

   # 在server中添加
   location /read_content { 
   			content_by_lua_file /root/lua/read_content.lua; 
   }
}

访问测试: http://192.168.181.130/read_content?id=1
在这里插入图片描述

4.3.3、加入OpenResty本地缓存

如果请求都到redis,redis压力也很大,所以我们一般采用多级缓存的方式
来减少下游系统的服务压力。

先查询openresty本地缓存,如果没有缓存数据=》再查询redis中的数据,如果没有缓存数据=》再查询mysql中的数据,返回查询结果。

Created with Raphaël 2.3.0 发送请求 查询openresty本地缓存 没有缓存 查询redis中的缓存 没有缓存 查询mysql数据库中的数据 返回数据 yes no yes no

修改优化read_content.lua文件,代码如下:

ngx.header.content_type = "application/json;charset=utf8"
local uri_args = ngx.req.get_uri_args();
local id = uri_args["id"];
local cache_ngx = ngx.shared.dis_cache;
local contentCache = cache_ngx:get('content_cache_' .. id);

ngx.say("获取nginx缓存:", contentCache)

if contentCache == "" or contentCache == nil then
    local redis = require("resty.redis");
    local red = redis:new()
    red:set_timeout(2000)
    red:connect("192.168.181.130", 6379)
    local resContent = red:get("content_" .. id);

    if ngx.null == resContent then
        local cjson = require("cjson");
        local mysql = require("resty.mysql");
        local db = mysql:new();
        db:set_timeout(2000)
        local props = {
            host = "192.168.181.130",
            port = 3306,
            database = "legou",
            user = "root",
            password = "root"
        }
        local res = db:connect(props);
        local select_sql = "SELECT id_,is_parent_,order_,parent_id_,title_,expand_ FROM category_ WHERE id_= " .. id
        res = db:query(select_sql);
        local responseJson = cjson.encode(res);
        red:set("content_" .. id, responseJson);
        ngx.say("数据库数据:",responseJson);
        db:close()
    else
        cache_ngx:set('content_cache_' .. id, resContent, 10 * 60);
        ngx.say("redis缓存不为空:",resContent)
    end
    red:close()
else
    ngx.say("nginx缓存不为空:",contentCache)
end

定义lua缓存命名空间及大小,修改nginx.conf,添加如下代码即可:

#nginx本地缓存
lua_shared_dict dis_cache 128m;

在这里插入图片描述
测试地址:http://192.168.181.130/read_content?id=1
首次访问:nginx本地缓存和redis缓存都没有,查询数据库并把查询结果存入redis中
在这里插入图片描述
在这里插入图片描述
第二次访问:redis缓存不为空,查询redis缓存同时把缓存存入nginx本地缓存
在这里插入图片描述
第三次及以后访问:获取nginx本地缓存
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

麋鹿不知归途

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值