nginx技术分享之二
Nginx是一个轻量级、高性能、稳定性高、并发性好的HTTP和反向代理服务器。也是由于其的特性,其应用非常广。
主要功能
- 反向代理
Nginx在反向代理上,提供灵活的功能,可以根据不同的正则采用不同的转发策略,如图设置好后不同的请求就可以走不同的服务器,如下图。
- 负载均衡
其原理就是将数据流量分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量.如下图。
- 动静分离
Nginx可以根据配置对不同的请求做不同转发,这是动态分离的基础。静态请求对应的静态资源可以直接放在Nginx上做缓冲,更好的做法是放在相应的缓冲服务器上。动态请求由相应的后端服务器处理。
配置文件
ngxin的各种功能的实现基本上是靠配置文件驱动的,配置文件大致划分为以下几个板块,如下图
设置防盗链
- 根据请求文件类型实现防盗链配置实列如下:
server {
listen 8080;
server_name xxx.abc.com
location ~* ^.+\.(gif|jpg|png|swf|flv|rar|zip)$ {
valid_referers none blocked www.xxx.com www.yyy.com *.baidu.com *.tabobao.com;
if ($invalid_referer) {
rewrite ^/ http://www.xxx.com/images/forbidden.png;
}
}
}
- 根据请求目录实现防盗链的配置实列如下:
server {
listen 8080;
server_name xxx.abc.com
location /file/ {
root /server/file/;
valid_referers none blocked www.xxx.com www.yyy.com *.baidu.com *.tabobao.com;
if ($invalid_referer) {
rewrite ^/ http://www.xxx.com/images/forbidden.png;
}
}
}
Nginx的负载均衡策略
参数 | 说明 |
不写任何参数 | 默认轮询 |
weight | 权重 |
ip_hash | 依据ip分配 |
least_conn | 最少连接 |
url_hash(第三方) | 依据URL分配 |
air(第三方) | 响应时间 |
nginx负载均衡策略(轮询、hash_ip、随机、权重、fail、url_hash)
- 轮询
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端务器down掉,能自动剔除,nginx如下配置
upstream backserver {
server 192.168.0.14;
server 192.168.0.15;
}
- 权重
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况,nginx如下配置
upstream backserver {
server 192.168.0.14 weight=3;
server 192.168.0.15 weight=7;
}
- hash_ip
解决了已经登录某一个服务器的用户再重新定位到另一个服务器,其登录信息会丢失的问题,nginx如下配置
upstream backserver {
ip_hash;
server 192.168.0.14:88;
server 192.168.0.15:80;
}
待会重点讲ip_hash
- fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
upstream backserver {
server server1;
server server2;
fair;
}
- url_hash(第三方)
按访问url的hash结果来分配请求,使每个url定向到同一个(对应的)后端服务器,后端服务器为缓存时比较有效。
upstream backserver {
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}
举个栗子
nginx限流之令牌桶算法和漏桶算法
- 令牌桶
令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务
package main
import (
"fmt"
"time"
)
func logs(args ...interface{}) {
fmt.Println(args...)
}
func tokenBucket(limit int, rate int) chan struct{} {
tb := make(chan struct{}, limit)
ticker := time.NewTicker(time.Duration(1) * time.Second)
for i := 0; i < limit; i++ {
tb <- struct{}{}
}
go func() {
for {
for i := 0; i < rate; i++ {
tb <- struct{}{}
}
<-ticker.C
}
}()
return tb
}
func popToken(bucket chan struct{}, n int) {
for i := 0; i < n; i++ {
<-bucket
}
}
func testTokenBucket() {
rate := 10
tb := tokenBucket(20, rate)
dataLen := 100
for i := 0; i <= dataLen; i += rate {
popToken(tb, rate)
logs(i)
}
}
- 漏桶
漏桶(Leaky Bucket)算法思路也比较简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下
package main
import (
"fmt"
"time"
)
// 定义一个漏桶
type LeakBucket struct {
capacity int // 容量,固定时间语序访问次数
interval time.Duration // 允许访问的时间间隔
dropsNum int // 固定时间访问了多少次
lastAccessTime time.Time // 最近一次的访问时间
}
func (b *LeakBucket) accessControl() bool {
now := time.Now()
pastTime := now.Sub(b.lastAccessTime)
// 过去这段时间pastTime可以允许多少访问
leaks := int(float64(pastTime) / float64(b.interval))
if leaks > 0 { // 说明这段时间可以有leaks可以访问,但没有用户访问
// 所以放宽访问,下一段访问限制减少一定leaks次限制,通过这种机制达到平滑控制流量
if b.dropsNum <= leaks {
b.dropsNum = 0
} else {
b.dropsNum -= leaks
}
// 更新访问时间
b.lastAccessTime = now
}
if b.dropsNum < b.capacity { // 在允许访问之内
b.dropsNum ++
return true
} else {
return false
}
}
func main() {
bucket := &LeakBucket{
capacity: 10,
interval: time.Second,
}
for i := 0; i < 12; i++ {
allowed := bucket.accessControl()
fmt.Println("i",i)
fmt.Println("i",allowed)
time.Sleep(time.Millisecond * 500)
}
time.Sleep(time.Second*3) // 模拟3秒中时间内没有访问
fmt.Println("空档期走完")
for j := 0; j < 20; j++ {
fmt.Println("BEFORE",bucket.dropsNum)
allowed := bucket.accessControl()
fmt.Println("AFTER",bucket.dropsNum)
fmt.Println("j", j)
fmt.Println("j",allowed)
time.Sleep(time.Millisecond * 500)
}
}