1. 實驗內容
這次實驗要求完成一個web簡單服務程序(詳細內容見課程作業博客)。
本次實驗需要用到Apache ab進行壓力測試,但是老師的博客只介紹了centOS上的安裝方法
yum -y install httpd-tools
而虛擬機用着終究感覺不是很舒服,所以我就找了windows上使用Apache ab的辦法。
2. 準備工作
安裝martini
由於本次實驗中用到了martin,所以需要事先獲取martin
go get -u github.com/go-martini/martini
完成後就需要下載Apache。
下載Apache
Apache ab
ab 是apachebench的縮寫。
ab命令會創建多個併發訪問線程,模擬多個訪問者同時對某一URL地址進行訪問。它的測試目標是基於URL的,因此,它既可以用來測試apache的負載壓力,也可以測試nginx、lighthttp、tomcat、IIS等其它Web服務器的壓力。
ab命令對發出負載的計算機要求很低,它既不會佔用很高CPU,也不會佔用很多內存。但卻會給目標服務器造成巨大的負載,其原理類似CC攻擊。自己測試使用也需要注意,否則一次上太多的負載。可能造成目標服務器資源耗完,嚴重時甚至導致死機。
下載
下載地址:http://httpd.apache.org/download.cgi
選擇Files for Mircosoft Windows
直接選擇ApacheHaus
之後由兩個圖標可以點擊下載,點擊第一個(第二個用的是德國鏡像,下載速度較慢)
下載後解壓即可。
配置
按照參考資料1,需要對Apache24/conf/httpd.conf文件進行3出修改,但是我改了之後還是無法運行安裝語句:
httpd.exe -k install
後來得知只要把Apache24/bin路徑添加到系統變量Path即可,如
之後,使用ab -V(大寫)或者ab -h可查看ab的版本以及相應的幫助信息:
C:\Users\asus>ab -V
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
C:\Users\asus>ab - h
ab: wrong number of arguments
Usage: ab [options] [http://]hostname[:port]/path
Options are:
-n requests Number of requests to perform
-c concurrency Number of multiple requests to make at a time
-t timelimit Seconds to max. to spend on benchmarking
This implies -n 50000
-s timeout Seconds to max. wait for each response
Default is 30 seconds
-b windowsize Size of TCP send/receive buffer, in bytes
-B address Address to bind to when making outgoing connections
-p postfile File containing data to POST. Remember also to set -T
-u putfile File containing data to PUT. Remember also to set -T
-T content-type Content-type header to use for POST/PUT data, eg.
'application/x-www-form-urlencoded'
Default is 'text/plain'
-v verbosity How much troubleshooting info to print
-w Print out results in HTML tables
-i Use HEAD instead of GET
-x attributes String to insert as table attributes
-y attributes String to insert as tr attributes
-z attributes String to insert as td or th attributes
-C attribute Add cookie, eg. 'Apache=1234'. (repeatable)
-H attribute Add Arbitrary header line, eg. 'Accept-Encoding: gzip'
Inserted after all normal header lines. (repeatable)
-A attribute Add Basic WWW Authentication, the attributes
are a colon separated username and password.
-P attribute Add Basic Proxy Authentication, the attributes
are a colon separated username and password.
-X proxy:port Proxyserver and port number to use
-V Print version number and exit
-k Use HTTP KeepAlive feature
-d Do not show percentiles served table.
-S Do not show confidence estimators and warnings.
-q Do not show progress when doing more than 150 requests
-l Accept variable document length (use this for dynamic pages)
-g filename Output collected data to gnuplot format file.
-e filename Output CSV file with percentages served
-r Don't exit on socket receive errors.
-m method Method name
-h Display usage information (this message)
3. 代碼解讀
項目地址
Github項目地址
共兩個代碼文件:
main.go(與潘老師博客中cloudgo中的相同,沒什麼必要進行修改)
package main
import (
"os"
"github.com/Passenger0/ServiceComputing/cloudgo/service"
// or "./service"
flag "github.com/spf13/pflag"
)
const (
//PORT default:8080
PORT string = "8080"
)
func main() {
//set the port,if not set ,user default value 8080
port := os.Getenv("PORT")
if len(port) == 0 {
port = PORT
}
// set the port for httpd listening
pPort := flag.StringP("port", "p", PORT, "PORT for httpd listening")
flag.Parse()
if len(*pPort) != 0 {
port = *pPort
}
// setup the server
server := service.NewServer()
//run the server
server.Run(":" + port)
}
server.go
package service
import (
// use martini framework
"github.com/go-martini/martini"
)
//the server struct
type Server struct{
handle * martini.ClassicMartini
}
//run the server
func (server * Server)Run(port string){
// call martini.Martini.RunOnAddr()
server.handle.RunOnAddr(port)
}
func NewServer() *Server {
// get the ClassicMartini, a struct consisting a new Router and Martini
server := &Server{
handle : martini.Classic(),
}
// call martini.Router.Get(),the action when server is access in the required form
server.handle.Get("/:name", func(params martini.Params) string {
return "Hello " + params["name"] +"\n"
})
return server
}
newServer():
首先利用martini.Classic()構造一個ClassicMartini的結構體,其中包含路由和網絡handle等信息,詳情見martini/martini.go(martini及其使用文檔的Github地址)
// Martini represents the top level web application. inject.Injector methods can be invoked to map services on a global level.
type Martini struct {
inject.Injector
handlers []Handler
action Handler
logger *log.Logger
}
// ClassicMartini represents a Martini with some reasonable defaults. Embeds the router functions for convenience.
type ClassicMartini struct {
*Martini
Router
}
// Classic creates a classic Martini with some basic default middleware - martini.Logger, martini.Recovery and martini.Static.
// Classic also maps martini.Routes as a service.
func Classic() *ClassicMartini {
r := NewRouter()
m := New()
m.Use(Logger())
m.Use(Recovery())
m.Use(Static("public"))
m.MapTo(r, (*Routes)(nil))
m.Action(r.Handle)
return &ClassicMartini{m, r}
}
其中Router內容見martini/Router.go。
newServer()的第二句代碼
server.handle.Get("/:name", func(params martini.Params) string {
return "Hello " + params["name"] +"\n"
})
其實是利用ClassicMartini指針調用Router.Get()函數(因爲ClassicMartini之中有Router)設置當指定端口被申請鏈接時的輸出內容:
func newRoute(method string, pattern string, handlers []Handler) *route {
route := route{method, nil, handlers, pattern, ""}
pattern = routeReg1.ReplaceAllStringFunc(pattern, func(m string) string {
return fmt.Sprintf(`(?P<%s>[^/#?]+)`, m[1:])
})
var index int
pattern = routeReg2.ReplaceAllStringFunc(pattern, func(m string) string {
index++
return fmt.Sprintf(`(?P<_%d>[^#?]*)`, index)
})
pattern += `\/?`
route.regex = regexp.MustCompile(pattern)
return &route
}
func (r *router) appendRoute(rt *route) {
r.routesLock.Lock()
defer r.routesLock.Unlock()
r.routes = append(r.routes, rt)
}
func (r *router) addRoute(method string, pattern string, handlers []Handler) *route {
if len(r.groups) > 0 {
groupPattern := ""
h := make([]Handler, 0)
for _, g := range r.groups {
groupPattern += g.pattern
h = append(h, g.handlers...)
}
pattern = groupPattern + pattern
h = append(h, handlers...)
handlers = h
}
route := newRoute(method, pattern, handlers)
route.Validate()
r.appendRoute(route)
return route
}
func (r *router) Get(pattern string, h ...Handler) Route {
return r.addRoute("GET", pattern, h)
}
結構體router
type router struct {
routes []*route
notFounds []Handler
groups []group
routesLock sync.RWMutex
}
之所以在server.go中構造Server結構體裝載ClassicMartini指針,是爲了重新構造一個可以給ClassicMartini調用的Run(port string)函數。
martini.Martini也有Run()函數,可供ClassicMartini調用,但是其形式如下
// Run the http server on a given host and port.
func (m *Martini) RunOnAddr(addr string) {
// TODO: Should probably be implemented using a new instance of http.Server in place of
// calling http.ListenAndServer directly, so that it could be stored in the martini struct for later use.
// This would also allow to improve testing when a custom host and port are passed.
logger := m.Injector.Get(reflect.TypeOf(m.logger)).Interface().(*log.Logger)
logger.Printf("listening on %s (%s)\n", addr, Env)
logger.Fatalln(http.ListenAndServe(addr, m))
}
// Run the http server. Listening on os.GetEnv("PORT") or 3000 by default.
func (m *Martini) Run() {
port := os.Getenv("PORT")
if len(port) == 0 {
port = "3000"
}
host := os.Getenv("HOST")
m.RunOnAddr(host + ":" + port)
}
其中Run()獲得的端口並不是我們的指令 -p指定的端口,所以需要重新構造一個Run(port string)函數,但是由於martini已經是一個完整的庫,不能直接在非局部變量ClassicMartini上面增加函數,所以將ClassicMartini用一個結構體裝載以增加實現目標函數的增加。當然,如果直接在main.go將server.Run(port)直接改成server.RunOnAddr(port)也是可以的。