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