【服務計算】web簡單服務程序

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)也是可以的。

5. 參考資料

  1. windows Apache ab安裝及壓力測試
  2. 使用AB壓力測試工具進行系統壓力測試
  3. Apache自帶的ab壓力測試工具用法詳解
  4. 服務計算學習之路-開發 web 服務程序
  5. https://github.com/rainCreek/web-cloudgo
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章