【服务计算】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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值