Lua+Nginx+Redis實現灰度測試和防刷限流

前言

瞭解到lua腳本和nginx的搭配之後,一直想知道這東西能用來做點什麼。於是動手實踐了一下... 下面開始正文

前期準備

獲取openresty:openresty官網(其實就是一個通過lua擴展的nginx服務器)  http://openresty.org/cn/getting-started.html

獲取redis:redis中文網站  http://www.redis.cn/

功能概述

1 灰度測試

用戶訪問某個地址,根據他的特徵值來給他返回不同的頁面,實現灰度發佈功能。在這個demo中我們使用用戶ip

2 防刷限流

在這個例子中,如果用戶在5秒內超過三次訪問,就禁止該用戶繼續訪問。通過redis的expire實現

爲什麼要用openresty做

畫一張圖幫助大家理解。這樣做的好處在於,將流量擋在業務之外,利用nginx、lua、redis高性能的特性來緩解業務服務器的壓力。

代碼實現與解析

準備兩個接口

在這裏我用java和golang分別做了一個hello world,運行在8080、8081端口上。java作爲默認版本,golang作爲測試版本。如下

@RestController
@RequestMapping("/")
public class HelloWorldController {

    @RequestMapping("/hello")
    public String hello(){
        return "hello world by java !";
    }
}
func main() {

	http.HandleFunc("/hello",helloHandler)

	err := http.ListenAndServe(":8081",nil)
	if err != nil {
		fmt.Printf("listen failed..., err:%v\n ",err)
		return
	}
}

func helloHandler(writer http.ResponseWriter,req *http.Request){
	_,_ = fmt.Fprintln(writer,"hello world by GoLang!")
}

然後寫一個nginx(openresty)的conf文件內容如下

worker_processes 1;
error_log logs/error.log;
events {
	worker_connections 1024;
}

http {
	upstream client1 {
		server 192.168.27.1:8080;
	}
	upstream client2 {
		server 192.168.27.1:8081;
	}

	server {
		listen 80;
		location ^~ /hello {
			content_by_lua_file /usr/local/openresty/workspace/conf/hello_abtest.lua;
		}
		location @client1 {
			proxy_pass http://client1;
		}
		location @client2 {
			proxy_pass http://client2;
		}
	}
}

準備好上面的hello_abtest.lua(當然我在這裏面還寫了限流的邏輯)

local ok, err = cache.connect(cache, '127.0.0.1', 6379)
if not ok then
	ngx.say("failed to connect:",err)
	return
end

local local_ip = ngx.req.get_headers()["X-Real-IP"]
if local_ip == nil then
	local_ip = ngx.req.get_headers()["x_forwarded_for"]
end

if local_ip == nil then
	local_ip = ngx.var.remote_addr
end

if cache:setnx(local_ip.."lock",0) == 0 then
	local times = cache:incr(local_ip.."lock")
	if times >= 3 then
		ngx.say("429 too many requests")
		return
	end
else
	cache:setnx(local_ip.."lock",0)
	cache:expire(local_ip.."lock",5)
end

local intercept = cache:get(local_ip)

if intercept==local_ip then
	ngx.exec("@client2")
	return
end

ngx.exec("@client1")

local ok, err = cache:close()

if not ok then
	ngx.say("failed to close: ",err)
	return
end

簡單解釋一下上面的邏輯。

限流:拿到客戶機的ip。然後向redis中寫一個key叫loca_ip.."lock"(對應xxx.xxx.xxx.xxxlock),如果這個key已經存在的話就給它加1,如果不存在就創建它 初始化爲0,並設置過期時間爲5秒。

灰度測試:把用客戶機ip作爲key去查詢redis,如果得到的結果與local_ip不同,則代表該客戶不具備權限,給他返回正式資源;否則,則代表該用戶在測試名單中,爲他返回測試資源。

測試結果

首先清空redis:

然後測試:

將192.168.27.1加入白名單

刷新網頁:

多點幾次試試:

寫在最後的話

其實還有很多種玩法,比如nginx+lua+kafka還能實現很多有趣的功能。由於nginx、lua、redis都有高性能的特點,所以將一些簡單的業務寫在這一級別,可以阻止不必要的流量進入業務系統帶來不必要的壓力。

有機會再更新kafka的玩法,yes! 吐槽:找個java工作太難了!!!

參考資料:

OpenResty官網 http://openresty.org/cn/

Redis中文網站 http://www.redis.cn/

參考博客:

Openresty+lua+redis實現灰度發佈 https://www.cnblogs.com/Eivll0m/p/6774622.html

Nginx+lua+redis防刷和限流 https://blog.csdn.net/fenglvming/article/details/51996406

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章