一、高并发基础架构
简要流程:
1.客户端发请求。
2.又LVS等四层负载均衡系统将请求转发给不同的Nginx服务器。
3.Nginx与客户端建立TCP连接,拿到请求后分析URI,然后将其转发给对应的功能模块服务(Tomcat容器)。
4.等待后端功能模块服务的响应。
5.功能模块进行计算,并从后端存储中获取数据,并返回。
6.Nginx收到响应后返回给客户端。
二、Nginx和Tengine
Nginx(engin x)是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP代理服务器。
主要以稳定性、丰富的功能集、低系统资源消耗而闻名。
官方测试nginx能够支撑5万并发连接,并且CPU、内存等资源消耗非常低,运行稳定。
什么是反向代理(通俗理解)?
正向代理:是代用户访问远程资源。 例如我们要访问国外的网站,我们可以通过位于香港等地的代理服务器来帮我们从国外获取资源,但我们请求的目的还是真正的国外服务器地址。国外服务器看到的请求方是代理服务器。
反向代理:就是帮后端资源进行代理,也就是我们看到的目标服务器就是该反向代理服务器,而看不到真正提供资源的服务器。我们看到的资源地址是反向代理服务器。
Nginx相对apache的优点:
1.nginx是轻量级,同样web服务,比apache占用更少的内存及资源。
2.抗并发,nginx是异步非阻塞的,而apache是阻塞型的,在高并发下nginx保持低资源消耗,高性能
3.高度模块化的设计,编写模块相对简单
4.社区活跃,各种高性能模块出品迅速
5.配置简洁
apache的优点:
1.rewrite强大
2.模块超多
3.bug少
最核心的不同:
apache是同步多进程模型(select),一个链接对应一个进程;nginx是异步(epoll),多个链接(万级别)对应一个进程。
nginx不会浪费时间在进程的切换上,所以效率很高。
三、安装Nginx(Tengine)
1.安装依赖
yum install gcc pcre-devel openssl-devel -y
2.下载tengine包,并解压
cd ~
wget http://tengine.taobao.org/download/tengine-2.3.0.tar.gz
tar zxf tengine-2.3.0.tar.gz
3.安装tengine
cd tengine-2.3.0
./configure --prefix=/opt/nginx
**企业标准安装:
./configure
--prefix=/usr
--sbin-path=/usr/sbin/nginx
--conf-path=/etc/nginx/nginx.conf
--error-log-path=/var/log/nginx/error.log
--http-log-path=/var/log/nginx/access.log
--pid-path=/var/run/nginx/nginx.pid
--lock-path=/var/lock/nginx.lock
--user=nginx
--group=nginx
--with-http_ssl_module
--with-http_flv_module
--with-http_stub_status_module
--with-http_gzip_static_module
--http-client-body-temp-path=/var/tmp/nginx/client/
--http-proxy-temp-path=/var/tmp/nginx/proxy/
--http-fastcgi-temp-path=/var/tmp/nginx/fcgi/
--http-uwsgi-temp-path=/var/tmp/nginx/uwsgi
--http-scgi-temp-path=/var/tmp/nginx/scgi
--with-pcre
make && make install
4.设置nginx为系统服务
添加nginx.service文件:
vi /usr/lib/systemd/system/nginx.service
[Unit]
Description=The nginx HTTP and reverse proxy server
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/opt/nginx/logs/nginx.pid
ExecStartPre=/opt/nginx/sbin/nginx -t
ExecStart=/opt/nginx/sbin/nginx -c /opt/nginx/conf/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
注意,所有path的部分都要修改为实际安装nginx的目录。
使用systemctl启动nginx:
systemctl start nginx.service
使用浏览器访问(默认监听80端口,可以在nginx.conf中修改):
其他服务操作:
# 设置开机启动
systemctl enable nginx.service
# 停止服务
systemctl stop nginx.service
# 重启服务
systemctl restart nginx.service
# 取消开机启动
systemctl disable nginx.service
# 查看服务运行状态
systemctl status nginx.service
查看所有已启动服务:
systemctl list-units --type=service
查看开机启动服务列表:
[root@real-server-1 conf]# systemctl list-unit-files
UNIT FILE STATE
proc-sys-fs-binfmt_misc.automount static
dev-hugepages.mount static
dev-mqueue.mount static
proc-sys-fs-binfmt_misc.mount static
sys-fs-fuse-connections.mount static
sys-kernel-config.mount static
sys-kernel-debug.mount static
tmp.mount disabled
brandbot.path enabled
systemd-ask-password-console.path static
systemd-ask-password-plymouth.path static
systemd-ask-password-wall.path static
session-1.scope static
arp-ethers.service disabled
auditd.service enabled
autovt@.service enabled
blk-availability.service disabled
brandbot.service static
chrony-dnssrv@.service static
chrony-wait.service disabled
chronyd.service enabled
console-getty.service disabled
console-shell.service disabled
container-getty@.service static
cpupower.service disabled
crond.service enabled
dbus-org.freedesktop.hostname1.service static
dbus-org.freedesktop.import1.service static
dbus-org.freedesktop.locale1.service static
dbus-org.freedesktop.login1.service static
dbus-org.freedesktop.machine1.service static
dbus-org.freedesktop.timedate1.service static
dbus.service static
debug-shell.service disabled
dm-event.service static
dnsmasq.service disabled
四、配置Nginx(Tengine)
1.修改nginx配置文件
[root@real-server-1 conf]# vi /opt/nginx/conf/nginx.conf
1
# 虽然user nobody是注释掉的,但仍然在使用,当然也可以修改为任意用户。子进程worker是nobody用户所有。父进程是root用户的。
2 #user nobody;
# worker_processes 是真正做事的进程的数量,一般为物理核心的1-2倍
3 worker_processes 1;
4
# 配置日志
5 #error_log logs/error.log;
6 #error_log logs/error.log notice;
7 #error_log logs/error.log info;
8 #error_log "pipe:rollback logs/error_log interval=1d baknum=7 maxsize=2G";
9
10 #pid logs/nginx.pid;
11
12
# worker_connection很重要,除了修改此处的数字,还需要修改操作系统内核允许进程所能操作文件描述符的数量。
# 使用ulimit -a可以查看当前内核允许的进程操作文件描述符的数量(open files)。
# 操作系统所能操作文件描述符的总数一般和内存成正比(例如1GB对应10W个文件描述符)。
# 我们在考虑worker_connnection时,除了考虑客户端连接数,还要考虑nginx从后端请求数据时的socket,所以需要配置得更大一些。
# 使用ulimit -SHn 65535可以修改内核限制,并且这里也修改为对应的数量。
13 events {
14 #worker_connections 1024;
worker_connections 65535;
15 }
16
17 # load modules compiled as Dynamic Shared Object (DSO)
18 #
19 #dso {
20 # load ngx_http_fastcgi_module.so;
21 # load ngx_http_rewrite_module.so;
22 #}
23
# http区域块,定义nginx的核心web功能
24 http {
25 include mime.types;
26 default_type application/octet-stream;
27
# 日志记录的格式化,控制日志怎么记录,做日志分析的时候可以参照这里的规范(重要)
# 有些公司使用nginx做小数据的采集,就可以直接使用日志来记录,例如传感器数据,直接访问nginx,将小数据传递过来,写进日志。
28 #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
29 # '$status $body_bytes_sent "$http_referer" '
30 # '"$http_user_agent" "$http_x_forwarded_for"';
31
32 #access_log logs/access.log main;
33 #access_log "pipe:rollback logs/access_log interval=1d baknum=7 maxsize=2G" main;
34
# (重要)零拷贝,例如nginx读取文件内容,然后返回给客户端的过程。首先调内核,让内核读取文件,读到的内容放在内核的缓冲区。
# 然后将内核缓冲区的内容拷贝到用户态buffer中。当要从socket发送给客户端时,又要从用户态buffer将内容拷贝到socket在内核的缓冲区。
# 这样就要进行两次内核态和用户态之间的数据拷贝。
# 零拷贝的意思就是,nginx调内核读取文件的时候,直接告诉内核文件和socker(也就是输入和输出),然后内核读取数据后,直接将数据用socket发送给客户端。
# 这就减少了2次拷贝的时间,效率大大提高。
35 sendfile on;
# 就是socket buffer是否写满才发送,类似于执行不执行flush。
36 #tcp_nopush on;
37
# http1.1扩充了一个字段叫keepalive,就是tcp链接保持长连接多久才断开。做实验我们为了看效果,设置为0。正式环境应该配一个合适的值。
38 keepalive_timeout 0;
39 #keepalive_timeout 65;
40
# 返回时是否压缩数据,减少IO和带宽消耗。可以提供更多的请求响应。
41 #gzip on;
42
# 其中一个虚拟服务器(nginx可以支持多个虚拟服务器,他可以用请求头中的Host域名来区分,通过浏览器F12查看)
# 也就是说如果DNS上有两个域名指向同一个IP地址,nginx可以通过两个域名来提供2个虚拟服务,都使用80端口
43 server {
# 监听的端口是80
44 listen 80;
# 虚拟服务器名,就是域名。例如DNS中有两个域名,www.123.com,www.234.com,这里填写其中一个。
45 server_name localhost; # www.123.com
46
47 #charset koi8-r;
48
49 #access_log logs/host.access.log main;
50 #access_log "pipe:rollback logs/host.access_log interval=1d baknum=7 maxsize=2G" main;
51
# 访问的根,也就是http://www.123.com/
52 location / {
# root是相对路径,html就是我们安装nginx的地方/opt/nginx/html目录
53 root html;
# index用来定义默认根页面
54 index index.html index.htm;
55 }
56
57 #error_page 404 /404.html;
58
59 # redirect server error pages to the static page /50x.html
60 #
61 error_page 500 502 503 504 /50x.html;
62 location = /50x.html {
63 root html;
64 }
65
66 # proxy the PHP scripts to Apache listening on 127.0.0.1:80
67 #
68 #location ~ \.php$ {
69 # proxy_pass http://127.0.0.1;
70 #}
71
72 # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
73 #
74 #location ~ \.php$ {
75 # root html;
76 # fastcgi_pass 127.0.0.1:9000;
77 # fastcgi_index index.php;
78 # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
79 # include fastcgi_params;
80 #}
81
82 # deny access to .htaccess files, if Apache's document root
83 # concurs with nginx's one
84 #
85 #location ~ /\.ht {
86 # deny all;
87 #}
88 } # server的end
89 } # http的end
其中Location是非常重要的,我们可以参看官方文档中Location的语法:
http://tengine.taobao.org/nginx_docs/cn/docs/http/ngx_http_core_module.html#location
可以看到以下内容:
让我们用一个例子解释上面的说法:
location = / {
[ configuration A ]
}
location / {
[ configuration B ]
}
location /documents/ {
[ configuration C ]
}
location ^~ /images/ {
[ configuration D ]
}
location ~* \.(gif|jpg|jpeg)$ {
[ configuration E ]
}
请求“/”匹配配置A, 请求“/index.html”匹配配置B, 请求“/documents/document.html”匹配配置C, 请求“/images/1.gif”匹配配置D, 请求“/documents/1.jpg”匹配配置E。
符号解释:
"=":精确匹配
"/dir/":当前路径及子路径都匹配,最大前缀匹配,但注意,是用这个字段去和用户请求的URI匹配,而不是反过来。
"~":后面用正则表达式。区分大小写
"~*":后面用正则表达式。不区分大小写
"^~":阻断,匹配到这里就不再进行正则匹配,例如上面的/images/1.gif,匹配到/images/,就不再匹配后面的".gif结尾"。
匹配规则:
他们有优先级关系: "=" > "^~" > "~ | ~*" > "/ | /dir/"
普通location之间是无匹配顺序的。而正则location之间是有顺序的,只要匹配到第一个就不匹配后面的。
如果匹配到^~项,后面的正则也不进行匹配。
普通location和正则location之间,先匹配普通的,再考虑是否匹配正则:
这里的考虑就是指“可能”,当普通location前使用了^~,则不匹配后面的正则。或者当普通location刚好匹配好(非最大前缀匹配),则也不匹配后面的正则。
如下图所示:
2.添加一个虚拟服务器(yum本地源)
# 这里添加一个虚拟服务器www.repo.com,将/mnt作为repo源,通过nginx发布
server {
listen 80;
server_name www.repo.com;
location / {
root /mnt;
# autoindex就是将/mnt的文件列表展示出去
autoindex on;
}
}
这里的域名为www.repo.com,而访问index.html的域名为www.123.com,所以可以区分我们要访问哪个服务器。
配置完nginx.conf后,我们要重置nginx:
systemctl reload nginx.service
将光盘挂载在/mnt:
mount /dev/cdrom /mnt
在浏览器操作系统的hosts中添加响应的DNS映射:
#在windows的hosts中添加以下内容
192.168.1.201 real-server-1 www.123.com www.repo.com
此时我们访问http://www.repo.com:80:
在访问http://www.123.com:80:
我们在浏览器的F12中查看一下两个域名访问时的host字段:
我们可以看到,两次请求的请求头中的Request URL分别是两个域名,Nginx就可以通过域名与配置文件的server_name匹配来区分我们要访问的服务,匹配到某个服务名后,再将域名后的URI(也就是"/"、"/search/"等)用location字段去匹配。
3.反向代理
在location中配置反向代理,将上面第一个server配置加上一个location做反向代理:
server {
listen 8000;
server_name www.123.com;
location / {
root html;
index index.html index.htm;
}
# 反向代理,访问192.168.1.202:80
location /ooxx {
proxy_pass http://192.168.1.202:80/;
}
}
此时,我们访问www.123.com:80,就不是返回tengine的默认页面,而是会帮我们请求192.168.1.202:80的页面;
从结果可以看出,我们通过www.123.com:80/ooxx(也就是real-server-1 192.168.1.201)访问了real-server-2(192.168.1.202的服务),并返回给客户端。
示例:反向代理www.baidu.com:
server {
listen 8000;
server_name www.123.com;
location / {
root html;
index index.html index.htm;
}
# 反向代理,访问192.168.1.202:80
location /ooxx {
proxy_pass http://192.168.1.202:80/;
}
# 代理baidu首页
location /baidu {
proxy_pass https://www.baidu.com/;
}
}
使用浏览器访问http://www.123.com/baidu:
注意,这里的proxy_pass配置的是https://www.baidu.com。如果配置成http://www.baidu.com,则百度可能先返回页面跳转给浏览器,浏览器会直接用https://www.baidu.com去访问百度。所以这里一定要写成https协议。
我们使用代理后的百度进行搜索:
报错信息:找不到URL,主要关注后面的URI(s?wd=*****):
我们再添加一跳location:
server {
listen 8000;
server_name www.123.com;
location / {
root html;
index index.html index.htm;
}
# 反向代理,访问192.168.1.202:80
location /ooxx {
proxy_pass http://192.168.1.202:80/;
}
# 代理baidu首页
location /baidu {
proxy_pass https://www.baidu.com/;
}
# 匹配以/s开头的URI
location ~* ^/s {
proxy_pass https://www.baidu.com;
}
}
注意:location /baidu和location ~* ^/s的proxy_pass最后一个带"/",一个不带"/",区别很大。
如果带"/"或"/xxx",则会直接使用其进行访问,例如www.baidu.com/xxx。
如果什么都不带,则会使用location的匹配到的URI来串接在后面进行访问,例如www.baidu.com/s?xxxxxxx(location ~* ^/s)。
此时使用代理baidu搜索"香港":
4.负载均衡
在nginx配置文件中配置负载均衡:
# 在server前面定义一个upstream池
upstream leeoo {
server 192.168.1.121:80;
server 192.168.1.202;
}
# 然后在对应的location中,将目标服务器的域名或IP替换为leeoo
location /ooxx {
proxy_pass http://leeoo/;
}
此时,当我们访问http://www.123.com:80/ooxx时,nginx会自动进行负载均衡,将轮询访问leeoo中的两台real server。
5.Session一致性问题
如果我们的后端服务器使用的是tomcat等容器,需要为用户保存Session。当Nginx将一个用户链接负载到不同的后端服务器时,我们需要保证他们能够使用同一个Session对用户进行验证,否则会出现让用户重复登录的问题;
1)首先,要保证集群中的服务器时间一致性。
2)使用专门管理Session的软件,或者使用memcache、Redis等内存数据库等来帮助Tomcat共享Session。
在192.168.1.199上安装memcached:
yum install memcached -y
使用以下命令启动:
memcached -d -m 128m -p 11211 -l 192.168.1.199 -u root -P /tmp/
-m是使用128M内存空间
-p是使用11211端口进行通信
-l是服务器地址
-u用户名
使用netstat查看memcached监听情况:
[root@lvs-server-1 etc]# netstat -natp | grep 11211
tcp 0 0 192.168.1.199:11211 0.0.0.0:* LISTEN 1540/memcached
在192.168.1.202和192.168.1.121上安装JDK和Tomcat:
下载JDK和Tomcat:
apache-tomcat-7.0.96.tar.gz
jdk-7u80-linux-x64.rpm
安装JDK:
rpm -i jdk-7u80-linux-x64.rpm
配置环境变量:
vi /etc/profile
# 在最后添加
export JAVA_HOME=/usr/java/jdk1.7.0_80
export PATH=$PATH:$JAVA_HOME/bin
# 使其生效
source /etc/profile
运行jps命令,检查是否安装成功:
[root@real-server-2 etc]# jps
1793 Jps
解压Tomcat:
tar xf apache-tomcat-7.0.96.tar.gz
创建一个页面,让其打印Session:
cd ~/apache-tomcat-7.0.96/webapps/ROOT
cp index.jsp index.jsp.bak
vi index.jsp
from 192.168.1.202<br>Session: <%= session.getId()%>
启动Tomcat:
cd ~/apache-tomcat-7.0.96/bin
./startup.sh
[root@real-server-2 bin]# ./startup.sh
Using CATALINA_BASE: /root/apache-tomcat-7.0.96
Using CATALINA_HOME: /root/apache-tomcat-7.0.96
Using CATALINA_TMPDIR: /root/apache-tomcat-7.0.96/temp
Using JRE_HOME: /usr/java/jdk1.7.0_80
Using CLASSPATH: /root/apache-tomcat-7.0.96/bin/bootstrap.jar:/root/apache-tomcat-7.0.96/bin/tomcat-juli.jar
Tomcat started.
访问192.168.1.202:8080:
重复刷新,Session是不会变化的,因为我们访问的是同一个Tomcat服务器。
在192.168.1.202和192.168.1.121上都安装好Tomcat之后,我们使用nginx来做负载均衡(192.168.1.201上):
在/opt/nginx/conf/nginx.conf中做如下配置:
upstream tom {
server 192.168.1.121:8080;
server 192.168.1.202:8080;
}
server {
listen 80;
server_name www.123.com;
location / {
root html;
index index.html index.htm;
}
location /cat/ {
proxy_pass http://tom/;
}
}
重新载入:
systemctl reload nginx
然后此时访问www.123.com:80:
我们可以看到,重复访问的时候,负载均衡是起效的,但是他们的Session不一致的。
停掉两台机器的Tomcat:
cd ~/apache-tomcat-7.0.96/bin
./shutdown.sh
配置Tomcat使用memcached:
cd /root/apache-tomcat-7.0.96/conf
vi context.xml
在最后添加如下内容:
<Manager className="de.javakaffee.web.msm.MencachedBackupSessionManager"
memcachedNodes="n1:192.168.1.199:11211"
sticky="false"
lockingMode="auto"
sessionBackupAsync="false"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
sessionBackupTimeout="1000" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>
再为Tomcat的lib中补充需要的一些jar包:
[root@real-server-2 tomcat_jar]# ll
total 804
-rw-r--r-- 1 root root 43398 Feb 26 2019 asm-3.2.jar
-rw-r--r-- 1 root root 94830 Feb 26 2019 kryo-1.04.jar
-rw-r--r-- 1 root root 62112 Feb 26 2019 kryo-serializers-0.11.jar
-rw-r--r-- 1 root root 142281 Feb 26 2019 memcached-session-manager-1.7.0.jar
-rw-r--r-- 1 root root 11283 Feb 26 2019 memcached-session-manager-tc7-1.8.1.jar
-rw-r--r-- 1 root root 4879 Feb 26 2019 minlog-1.2.jar
-rw-r--r-- 1 root root 26511 Feb 26 2019 msm-kryo-serializer-1.7.0.jar
-rw-r--r-- 1 root root 11615 Feb 26 2019 reflectasm-1.01.jar
-rw-r--r-- 1 root root 407912 Feb 26 2019 spymemcached-2.7.3.jar
将这些jar包拷贝到Tomcat的lib目录中:
cp ~/tomcat_jar/* ~/apache-tomcat-7.0.96/lib
重新启动tomcat,后再次尝试访问http://www.123.com/cat,发现负载均衡正常,Session也不会发生变化。
来源:oschina
链接:https://my.oschina.net/u/4293547/blog/4079606