草庐IT

教你2种常用的电商高并发处理解决方案

CRMEB众邦科技 2023-03-28 原文
网站架构师面临的最大挑战之一就是并发。自Web服务开始以来,并发水平一直在不断增长,一个主流网站同时服务十万甚至数百万用户,这并不罕见。

就目前应用广泛的电商系统来说,各种营销场景的增加,让电商系统高并发也成为一种必然。为此,本文将给大家带来2种高并发解决方案,希望能为电商系统实现高并发提供一些灵感。

1、多级缓存

2、Nginx 限流

本文我们将以CRMEB商城为例,了解电商中常见的2种高并发方案

一、多级缓存

我们以CRMEB Pro版首页为例

在上图中,我们可以看到商品的分类变化不大,我们可以把它存储在缓存中,这样会大大减轻数据库的压力,这种情况我们可以用redis进行缓存,但是有时候电商网站的并发数只靠redis,会使redis的压力太大。这时我们在这里引入一个概念:多级缓存

1. 什么是多级缓存

为了解决上述问题,我们在redis的基础上增加了另一个nginx缓存。这时候用户访问我们的网站,会先访问nginx缓存。如果nginx缓存不存在,他们会再次访问redis缓存。如果redis也不存在,我们最终会访问MySQL来获取数据。这样一来redis的压力就大大降低了,然后nginx缓存和redis缓存就形成了多级缓存。

2、多级缓存怎么实现

了解了多级缓存的概念,我们该怎么去实现多级缓存呢?

我们可以使用OpenResty实现,这是一个基于Nginx和Lua的高性能Web平台,它将Nginx与大量复杂的Lua库、第三方模块和大多数依赖项集成在一起。Lua是一种轻量级紧凑脚本语言,用标准C语言编写,以源代码的形式开放。它被设计成嵌入在应用程序中,从而为应用程序提供灵活的扩展和定制功能。在实际操作中,我们通常使用Lua脚本来访问Nginx缓存、Redis缓存和MySQL。流程图如下:

图片来自网络,侵权联系删除

但是,在上述过程中,我们会发现一个问题。当Redis缓存已经存在时,数据库中的数据已经更改。此时所有用户都访问缓存的数据,那么如何解决这个问题呢?

3、Redis 缓存同步 MySql 数据

我们可以通过 canal 解决上面的问题。canal 是一个用来监控数据库数据的变化的工具,可以在Mysql 数据更新时获取其更新的数据。

解决方案:当Mysql数据发生变化时,我们可以通过canal微服务或者用RocketMQ或Kafka配置MQ模式来改变redis中的缓存数据,使Redis中的缓存数据与Mysql中的数据保持一致。​​​​​​

二、Nginx 限流

平常情况下,首页的并发量会比较大,即使使用多级缓存,当用户不断刷新页面时,或者大量恶意请求首页,也会对系统造成影响。这时候就需要采用限流方案了。

1、什么是限流

顾名思义,限流就是限制流量。比如你的手机流量包只有1 G的流量,用完了就没了。这时候我们就可以说我们的流量被限制了,这就叫限流。

常见的限流有漏桶算法

图片来自网络,侵权联系删除

算法思路:

水(请求)从上方倒入水桶,从水桶下面流出(被处理);

来不及流出的水,储存在水桶(缓冲器)里,以固定的速率流出;

水桶满后水溢出(丢弃)。

这个算法的核心是:缓存请求,统一处理,直接丢弃冗余请求。

2、nginx 限流的方式

控制速度

我们先来说第一种,控制速度。Nginx 控制速率主要使用的是漏桶算法,这样能够强行保证请求的实时处理速度不会超过设置的阈值。

配置 OpenResty 中 Nginx 的配置文件,使在访问 ip/test1 地址时进行限流,该地址会访问一个 Lua 脚本

user root root;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;

#限流设置
limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=10r/s;

sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;

location /test1{
#使用限流配置
limit_req zone=contentRateLimit;
content_by_lua_file /root/lua/test1.lua;
}
}
}
配置说明:

binary_remote_addr:是一种key,意思是基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用数。

zone:定义了一个共享内存区域来存储访问信息。 

contentRateLimit:10m 表示一个大小为10M,名字为 contentRateLimit 的内存区域。1M可以存储 16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。

Rate:用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。

Nginx:实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求。

限流速度为每秒 10 次请求,如果有10次请求同时到达一个空闲的 nginx,他们都能得到执行吗?

事实上,漏桶漏出请求是匀速的。10r/s是怎样匀速的呢?每100ms漏出一个请求。在这个配置中,桶是空的,所有不能实时漏出的请求,会被拒绝掉。所以如果10次请求同时到达,那么只有一个请求能够得到执行,其它的,都会被拒绝。这不太友好,大部分业务场景下我们希望这10个请求都能得到执行。

处理突发流量

上述情况称为突发流量,上面的例子仅限于10r/s,如果正常流量突然增加,超出的请求会被拒绝,无法处理突发流量,可以结合 burst 参数使用来解决该问题。

修改上文中的 Nginx 配置文件

server {
listen 80;
server_name localhost;
location /test1 {
limit_req zone=contentRateLimit burst=20 nodelay;
content_by_lua_file /root/lua/test1.lua;
}
}
burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数,当 rate=10r/s 时,将1s拆成10份,即每100ms可处理1个请求。

此处,burst=12,若同时有12个请求到达,Nginx 会处理第一个请求,剩余11个请求将放入队列,然后每隔100ms从队列中获取一个请求进行处理。若请求数大于12,将拒绝处理多余的请求,直接返回 503。

不过,单独使用 burst 参数并不实用。假设 burst=50 ,rate依然为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。

因此,burst 往往结合 nodelay 一起使用。

继续修改上文中的 Nginx 配置文件

server {
listen 80;
server_name localhost;
location /test1 {
limit_req zone=contentRateLimit burst=20 nodelay;
content_by_lua_file /root/lua/test1.lua;
}
}
平均每秒允许不超过10个请求,突发不超过20个请求,并且处理突发20个请求的时候,没有延迟,等到完成之后,按照正常的速率处理。

如上两种配置结合就达到了速率稳定,但突然流量也能正常处理的效果。

控制并发量(连接数)

nginx 还提供了利用连接数限制某一个用户的ip连接的数量来控制流量。一种是限制固定连接数,第二种是限制每个客户端IP与服务器的连接数,同时限制与服务器的连接总数。

注意:并非所有连接都被计算在内,只有当服务器正在处理请求并且已经读取了整个请求头时,才会计算有效连接。

配置限制固定连接数:

http {
include mime.types;
default_type application/octet-stream;
#限流设置
limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=10r/s;
#根据IP地址来限制,存储内存大小10M
limit_conn_zone $binary_remote_addr zone=addr:1m;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location /test2 {
limit_conn addr 2;
content_by_lua_file /root/lua/test2.lua;
}
location /test1 {
limit_req zone=contentRateLimit burst=20 nodelay;
content_by_lua_file /root/lua/test1.lua;
}
}
}
limit_conn_zone $binary_remote_addr zone=addr:10m; 表示限制根据用户的IP地址来显示,设置存储地址为的内存大小10M

limit_conn addr 2; 表示同一个地址只允许连接2次。

限制每个客户端IP与服务器的连接数,同时限制与服务器的连接总数:

http {
include mime.types;
default_type application/octet-stream;
#限流设置
limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=10r/s;
#存储个人请求IP的限流配置
limit_conn_zone $binary_remote_addr zone=perip:10m;
#整个location对应的请求并发容量配置
limit_conn_zone $server_name zone=perserver:100m;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location /test2 {
limit_conn perip 10;#单个客户端ip与服务器的连接数"10"
limit_conn perserver 100; #限制与服务器的总连接数"100"
content_by_lua_file /root/lua/test2.lua;
}
location /test1 {
limit_req zone=contentRateLimit burst=20 nodelay;
content_by_lua_file /root/lua/test1.lua;
}
}
}

总结:我们在redis的基础上增加了另一个nginx缓存就能实现多级缓存,使用漏桶算法可以实现限流的效果,再结合 burst 参数使用来解决突发流量的处理。这几种方法相结合,就能实现电商系统高并发。如果还有不明白之处可以在下方留言,大家一起学习讨论。

有关教你2种常用的电商高并发处理解决方案的更多相关文章

  1. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  2. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  3. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  4. 屏幕录制为什么没声音?检查这2项,轻松解决 - 2

    相信很多人在录制视频的时候都会遇到各种各样的问题,比如录制的视频没有声音。屏幕录制为什么没声音?今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音,你可以试试这个方法。 一、检查是否打开电脑系统声音相信很多小伙伴在录制视频后会发现录制的视频没有声音,屏幕录制为什么没声音?如果当时没有打开音频录制,则录制好的视频是没有声音的。因此,建议在录制前进行检查。屏幕上没有声音,很可能是因为你的电脑系统的声音被禁止了。您只需打开电脑系统的声音,即可录制音频和图画同步视频。操作方法:步骤1:点击电脑屏幕右下侧的“小喇叭”图案,在上方的选项中,选择“声音”。 步骤2:在“声

  5. 【高数】用拉格朗日中值定理解决极限问题 - 2

    首先回顾一下拉格朗日定理的内容:函数f(x)是在闭区间[a,b]上连续、开区间(a,b)上可导的函数,那么至少存在一个,使得:通过这个表达式我们可以知道,f(x)是函数的主体,a和b可以看作是主体函数f(x)中所取的两个值。那么可以有,  也就意味着我们可以用来替换 这种替换可以用在求某些多项式差的极限中。方法: 外层函数f(x)是一致的,并且h(x)和g(x)是等价无穷小。此时,利用拉格朗日定理,将原式替换为 ,再进行求解,往往会省去复合函数求极限的很多麻烦。使用要注意:1.要先找到主体函数f(x),即外层函数必须相同。2.f(x)找到后,复合部分是等价无穷小。3.要满足作差的形式。如果是加

  6. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  7. Ruby-vips 图像处理库。有什么好的使用示例吗? - 2

    我对图像处理完全陌生。我对JPEG内部是什么以及它是如何工作一无所知。我想知道,是否可以在某处找到执行以下简单操作的ruby​​代码:打开jpeg文件。遍历每个像素并将其颜色设置为fx绿色。将结果写入另一个文件。我对如何使用ruby​​-vips库实现这一点特别感兴趣https://github.com/ender672/ruby-vips我的目标-学习如何使用ruby​​-vips执行基本的图像处理操作(Gamma校正、亮度、色调……)任何指向比“helloworld”更复杂的工作示例的链接——比如ruby​​-vips的github页面上的链接,我们将不胜感激!如果有ruby​​-

  8. ruby - Faye WebSocket,关闭处理程序被触发后重新连接到套接字 - 2

    我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d

  9. ruby - 如何使用 Ruby HTTP::Net 处理 404 错误? - 2

    我正在尝试解析网页,但有时会收到404错误。这是我用来获取网页的代码:result=Net::HTTP::getURI.parse(URI.escape(url))如何测试result是否为404错误代码? 最佳答案 像这样重写你的代码:uri=URI.parse(url)result=Net::HTTP.start(uri.host,uri.port){|http|http.get(uri.path)}putsresult.codeputsresult.body这将打印状态码和正文。

  10. Ruby 守护进程和 JRuby - 备选方案 - 2

    我有一个应用程序正在从Ruby迁移到JRuby(由于需要通过Java提供更好的Web服务安全支持)。我使用的gem之一是daemons创建后台作业。问题在于它使用fork+exec来创建后台进程,但这对JRuby来说是禁忌。那么-是否有用于创建后台作业的替代gem/wrapper?我目前的想法是只从shell脚本调用rake并让rake任务永远运行......提前致谢,克里斯。更新我们目前正在使用几个与Java线程相关的包装器,即https://github.com/jmettraux/rufus-scheduler和https://github.com/philostler/acts

随机推荐