go语言实现一个简单的简单网关
网关=反向代理+负载均衡+各种策略,技术实现也有多种多样,有基于 nginx 使用 lua 的实现,比如 openresty、kong;也有基于 zuul 的通用网关;还有就是 golang 的网关,比如 tyk。
目前创新互联建站已为上千家的企业提供了网站建设、域名、网络空间、网站托管、企业网站设计、栾川网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。
这篇文章主要是讲如何基于 golang 实现一个简单的网关。
转自: troy.wang/docs/golang/posts/golang-gateway/
整理:go语言钟文文档:
启动两个后端 web 服务(代码)
这里使用命令行工具进行测试
具体代码
直接使用基础库 httputil 提供的NewSingleHostReverseProxy即可,返回的reverseProxy对象实现了serveHttp方法,因此可以直接作为 handler。
具体代码
director中定义回调函数,入参为*http.Request,决定如何构造向后端的请求,比如 host 是否向后传递,是否进行 url 重写,对于 header 的处理,后端 target 的选择等,都可以在这里完成。
director在这里具体做了:
modifyResponse中定义回调函数,入参为*http.Response,用于修改响应的信息,比如响应的 Body,响应的 Header 等信息。
最终依旧是返回一个ReverseProxy,然后将这个对象作为 handler 传入即可。
参考 2.2 中的NewSingleHostReverseProxy,只需要实现一个类似的、支持多 targets 的方法即可,具体实现见后面。
作为一个网关服务,在上面 2.3 的基础上,需要支持必要的负载均衡策略,比如:
随便 random 一个整数作为索引,然后取对应的地址即可,实现比较简单。
具体代码
使用curIndex进行累加计数,一旦超过 rss 数组的长度,则重置。
具体代码
轮询带权重,如果使用计数递减的方式,如果权重是5,1,1那么后端 rs 依次为a,a,a,a,a,b,c,a,a,a,a…,其中 a 后端会瞬间压力过大;参考 nginx 内部的加权轮询,或者应该称之为平滑加权轮询,思路是:
后端真实节点包含三个权重:
操作步骤:
具体代码
一致性 hash 算法,主要是用于分布式 cache 热点/命中问题;这里用于基于某 key 的 hash 值,路由到固定后端,但是只能是基本满足流量绑定,一旦后端目标节点故障,会自动平移到环上最近的那么个节点。
实现:
具体代码
每一种不同的负载均衡算法,只需要实现添加以及获取的接口即可。
然后使用工厂方法,根据传入的参数,决定使用哪种负载均衡策略。
具体代码
作为网关,中间件必不可少,这类包括请求响应的模式,一般称作洋葱模式,每一层都是中间件,一层层进去,然后一层层出来。
中间件的实现一般有两种,一种是使用数组,然后配合 index 计数;一种是链式调用。
具体代码
java关于Random的用法
在Java的API帮助文档中,Random函数功能的描述:
1、java.util.Random类中实现的随机算法是伪随机,也就是有规则的随机,所谓有规则的就是在给定种(seed)的区间内随机生成数字;
2、相同种子数的Random对象,相同次数生成的随机数字是完全相同的;
3、Random类中各方法生成的随机数字都是均匀分布的,也就是说区间内部的数字生成的几率均等。
扩展资料:
Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等
语言特点:
1.简单性:
Java看起来设计得很像C++,但是为了使语言小和容易熟悉,设计者们把C++语言中许多可用的特征去掉了,这些特征是一般程序员很少使用的。例如,Java不支持go to语句,代之以提供break和continue语句以及异常处理。Java还剔除了C++的操作符过载(overload)和多继承特征,并且不使用主文件,免去了预处理程序。
因为Java没有结构,数组和串都是对象,所以不需要指针。Java能够自动处理对象的引用和间接引用,实现自动的无用单元收集,使用户不必为存储管理问题烦恼,能更多的时间和精力花在研发上。
2.面向对象:
Java是一个面向对象的语言。对程序员来说,这意味着要注意应中的数据和操纵数据的方法(method),而不是严格地用过程来思考。在一个面向对象的系统中,类(class)是数据和操作数据的方法的集合。数据和方法一起描述对象(object)的状态和行为。每一对象是其状态和行为的封装。类是按一定体系和层次安排的,使得子类可以从超类继承行为。
在这个类层次体系中有一个根类,它是具有一般行为的类。Java程序是用类来组织的。Java还包括一个类的扩展集合,分别组成各种程序包(Package),用户可以在自己的程序中使用。例如,Java提供产生图形用户接口部件的类(java.awt包),这里awt是抽象窗口工具集(abstract windowing toolkit)的缩写,处理输入输出的类(java.io包)和支持网络功能的类(java.net包)。
3.分布性:
Java设计成支持在网络上应用,它是分布式语言。Java既支持各种层次的网络连接,又以Socket类支持可靠的流(stream)网络连接,所以用户可以产生分布式的客户机和服务器。网络变成软件应用的分布运载工具。Java程序只要编写一次,就可到处运行。
4.编译和解释性:
Java编译程序生成字节码(byte-code),而不是通常的机器码。Java字节码提供对体系结构中性的目标文件格式,代码设计成可有效地传送程序到多个平台。Java程序可以在任何实现了Java解释程序和运行系统(run-time system)的系统上运行。
在一个解释性的环境中,程序开发的标准“链接”阶段大大消失了。如果说Java还有一个链接阶段,它只是把新类装进环境的过程,它是增量式的、轻量级的过程。因此,Java支持快速原型和容易试验,它将导致快速程序开发。这是一个与传统的、耗时的“编译、链接和测试”形成鲜明对比的精巧的开发过程。
参考资料:百度百科-java
如何配置go语言开发环境
1、下载go的zip文件。并且一定要把文件解压到c:\go目录下。
2、配置windows的高级环境变量。包括:GOROOT、GOOS、GOBIN、GOARCH。并且在path变量里面把c:\go\bin加入。以便可以在命令行直接运行go命令。
举例:我的机器:
GOPATH= c:\go;c:\go\src;F:\workspace\goSample01;
GOBIN=c:\go\bin;F:\workspace\goSample01\bin;
其中,c:\go是go的安装路径;
F:\workspace\goSample01是我写的go语言项目的工程目录;
F:\workspace\goSample01\bin是go语言项目的工程目录下的可执行文件路径;
3、在完成环境变量配置后,打开一个命令行窗口,直接输入go,然后回车,看看是否出现go的帮助信息。如果出现,那么go的基本环境就OK了。
注意:这个基本环境不包含开发工具,也不能直接编译带C代码的go程序。
4、(可选)为了支持Import远程包,最好装个gomingw。下载地址:。如果下的是压缩包,请把它解压到C盘。例如,C:\gowin-env。里面有个Console.bat是以后使用go get的环境。举例:有个文件a.go,里面import(
"fmt"
"github.com/astaxie/beedb"
_ "github.com/ziutek/mymysql/godrv"
为了编译该a.go文件,需要启动Console.bat,然后在该命令行窗口,进入c:\go\src目录下,执行go getgithub.com/astaxie/beedb
Go get github.com/ziutek/mymysql/godrv .
Go会自动下载该远程包并编译和安装这些包。
配置goclipse(可选)
(如果不喜欢eclipse开发工具,请跳过这个配置。)
1、下载并安装goclipse插件。Goclipse是go语言for eclipse的插件,下载地址:
2、启动eclipse并创建go项目。然后写个最简单的helloworld.go文件,并运行。代码如下:
packagemainimport"fmt"func main(){ fmt.Printf("hello, world")}
配置gocode(可选)
如果不需要go语法辅助和eclipse里面的(按ALT+/)弹出go语言自动辅助功能,请跳过这个配置。
1、下载gocode的zip文件,解压后放在go的bin目录下。
2、下载并安装Git软件。并且在path里面配置git的执行路径。例如c:\git\bin
3、在命令行执行:go build .\gocode。如果一切正常,那么将会编译生成一个gocode.exe文件在go的bin目录下。如果编译失败,那么就转第4步。
4、如果第3步直接编译gocode源文件成功,那就直接到第5步。否则,就需要通过git下载gocode源文件,然后再编译。在命令行执行:go get -u github.com/nsf/gocode 。就会生成gocode.exe文件。
5、在goclipse插件里面指定gocode的路径。就可以在elcipse里面调用gocode来帮助写编码了。
从开发工具这块看,go语言还不够成熟,开发工具都还不完善,有待改进。
下载go-tour教程源代码(可选)
Google有个在线运行go语言的教程(),很不错。支持在web上直接运行大部分的go程序,想了解这个教程的源代码的朋友可以通过以下方式获取。如果没兴趣,可以跳过这个步骤。
1、下载安装Mercurial软件。
2、在命令行下输入:
hg clone
这个URL是我从google的go-tour源代码的一个clone。作为测试用的。如果把http改成https协议,下载就会失败。搞不懂。
编译带调用C代码的go文件(可选)
1、为了在windows下编译带C代码的go程序,你首先需要下载并安装MinGW或者Cygwin。
2、首选安装MinGW。在安装MinGW之后,记得要把MinGW安装目录\bin路径设置在path环境变量里面,以便能在dos窗口下直接调用gcc。
3、下载一个gowin-env。下载地址:gowin-env。下载后解压到某个目录下,例如:C:\gowin-env. 然后,编辑go-env.bat。配置相关的go参数。例如,我的配置是:
set GOARCH=386
set GOOS=windows
set GOROOT=c:\go
set GOBIN=%GOROOT%\bin
set GOPATH=%GOROOT%;F:\workspace\goSample01;
设置好go-env.bat后,就可以点击Console.bat来启动编译和运行窗口。
4、编写一个带C代码的go程序。例如,testc.go
5、编译
例如:
go build -compiler gccgo test_c.go
运行调用C代码的go文件(可选)
1、testc.go.
创建rand目录,然后在rand里面创建testc.go. 代码如下:
package rand
/*
//
#include stdio.h
*/
import "C"
func PrintHello() {
C.puts(C.CString("Hello, world\n"))
}
2、a.go
在rand下创建a.go.代码如下:
package rand
import "fmt"
func SayHello(name string){
fmt.Println(name)
}
3、test_import.go
在rand的上一级创建test_import.go。代码如下:
package main
import "./rand"
func main(){
rand.SayHello("tom")
rand.PrintHello()
}
4、运行test_import.go
go run test_import.go
在测试其它几个C代码的时候,发现windows版本的cgo还有些编译问题,同样的代码转移到苹果的XCODE下就没有问题。后来终于发现原因了,原来有些例子是unix平台下的,而在windows平台下,方法名和参数需要做调整。
例如:下面代码在windows下编译报一堆错误。
package rand
/*
#include stdlib.h
*/
import "C"
func Random() int {
return int(C.random())
}
func Seed(i int) {
C.srandom(C.uint(i))
}
这里需要把return int(C.random()) 修改为“return int(C.rand())”
C.srandom(C.uint(i))修改为“C.srand(C.uint(i))”编译就OK了。
代码元素地位跟什么一样
相比很多其它流行语言,Go的语法相对简洁。 此篇文章将介绍编程中常用的代码元素,并展示一份简单的Go程序代码,以便让刚开始学Go编程的程序员对Go代码结构有一个大概的印象。
编程和程序代码元素
简单来讲,编程可以看作是以各种方式控制和组合计算机运行中的各种操作,以达到各种各样的目的。 一个操作可能从一个硬件设备读取、或者向一个硬件设备写入一些数据,从而完成一个特定的任务。 对于现代计算机来说,最基本的操作是底层计算机指令,比如CPU和GPU指令。 常见的硬件设备包括内存、磁盘、网卡、显卡,显示器、键盘和鼠标等。
直接操控底层计算机指令进行编程是非常繁琐和容易出错的。 高级编程语言通过对底层指令进行一些封装和对数据进行一些抽象,从而使得编程变得直观和易于理解。
在流行高级编程语言中,一个操作通常是通过函数(function)调用或者使用操作符(operator)运算来完成的。 大多数高级编程语言都支持一些条件和循环控制语句。 这些条件和循环控制语句可以看作是特殊的操作。 它们的语法接近于人类语言,因此一个人写的代码很容易被其他人理解。
在大多数高级编程语言中,数据通常被抽象为各种类型(type)和值(value)。 一个类型可以看作是值的模板。一个值可以看作是某个类型的实例。 大多数编程语言支持自定义类型和若干预声明类型(即内置类型)。 一门语言的类型系统可以说是这门语言的灵魂。
编程中常常会使用大量的值。 一些在编码阶段可确定的值可以用它们的字面形式(literal,即字面量)来表示。 为了编程灵活和不易出错,其它的值一般使用变量(variable)和(具名)常量(named constant)来表示。
在《Go语言101》中,具名的函数、具名的值(包括变量和具名常量)、以及定义类型和类型别名将被统称为代码要素。 代码要素名必须为标识符(identifier)。
高级编程语言代码将被编译器或者解释器转换为底层机器码进行执行。 为了帮助编译器和解释器解析高级语言代码,一些单词将被用做关键字(keyword)。 这些单词不能被当做标识符使用。
很多现代高级语言使用包(package)来组织代码。 一个包必须引入(import)另一个包才能使用另一个包中的公有(导出的)代码要素。 包名和包的引入名也都必须是标识符。
尽管高级编程语言代码比底层机器指令友好和易懂,我们还是需要一些注释来帮助自己和其他程序员理解我们所写的代码。 在下一节的程序示例中,我们可以看到很多注释。
一个简单的Go示例程序
为了对各种代码元素有一个更清楚的认识,让我们来看一个简短的Go示例程序。 和很多其流行语言一样,Go使用//来起始一个行注释,使用一个/*和*/对来包裹一个块注释。
下面是这个Go示例程序。请注意阅读其中的注释。程序之后有更多解释。
package main // 指定当前源文件所在的包名
import "math/rand" // 引入一个标准库包
const MaxRand = 16 // 声明一个具名整型常量
// 一个函数声明
/*
StatRandomNumbers生成一些不大于MaxRand的非负
随机整数,并统计和返回小于和大于MaxRand/2的随机数
个数。输入参数numRands指定了要生成的随机数的总数。
*/
func StatRandomNumbers(numRands int) (int, int) {
// 声明了两个变量(类型都为int,初始值都为0)
var a, b int
// 一个for循环代码块
for i := 0; i numRands; i++ {
// 一个if-else条件控制代码块
if rand.Intn(MaxRand) MaxRand/2 {
a = a + 1
} else {
b++ // 等价于:b = b + 1
}
}
return a, b // 此函数返回两个结果
}
// main函数,或主函数,是一个程序的入口函数。
func main() {
var num = 100
// 调用上面声明的StatRandomNumbers函数,
// 并将结果赋给使用短声明语句声明的两个变量。
x, y := StatRandomNumbers(num)
// 调用两个内置函数(print和println)。
print("Result: ", x, " + ", y, " = ", num, "? ")
println(x+y == num)
}
将上面的程序代码存盘到一个名为basic-code-element-demo.go 的文件中并使用下列命令运行此程序:
$ go run basic-code-element-demo.go
Result: 46 + 54 = 100? true
在上面的示例程序中,单词package、import、const、func、var、for、if、else和return均为关键字。 其它大多数单词均为标识符。 请阅读关键字和标识符以获得更多关于关键字和标识符的信息。
四个int(一个在第15行,另三个在第13行) 表示内置基本类型int。int类型是Go中的基本整数类型之一。 第5行中的16、第17行中的0、 第20行中的1以及第30行的100均为整型字面量。 第35行的"Result: "是一个字符串字面量。 请阅读基本类型和它们的字面量表示以获取更多关于基本类型和它们的字面量的信息。 Go中的非基本类型(均为组合类型)将在以后的其它文章中介绍和解释。
第20行是一个赋值语句。 第5行声明了一个具名常量,叫做MaxRand。 第15行和第30行使用标准变量声明语句声明了三个变量。 第17行的变量i以及第33行的变量x和y是使用变量短声明语句声明的。 变量a和b在声明的时候被指定为int类型。 编译器会自动推导出变量i、num、x和y的类型均为int类型,因为它们的初始值都是整型字面量表示的。 请阅读常量和变量以获取什么是类型不确定值、类型推导、赋值、以及如何声明变量和具名常量。
上面的示例程序中使用了很多操作符,比如第17和19行的小于比较符,第36行的等于比较符==,还有第20和36行的加法运算符+。 第35行中的+不是一个运算符,它是一个字符串字面量中的一个字符。 一个使用操作符的操作中涉及到的值称为操作值(有时也可称为运算数)。 请阅读常用操作符以获取更多关于操作符的信息。 更多操作符将在后续其它文章中介绍。
第35和36行调用了两个内置函数print和println。 从第13行到第26行声明的函数StatRandomNumbers在第33行被调用。 第19行也调用了一个函数 Intn。 这个函数声明在math/rand标准库包中。 请阅读函数声明及函数调用以获取更多关于函数声明及函数调用的信息。
(注意,一般print和println这两个内置函数并不推荐使用。 在正式的项目中,我们应该尽量使用fmt标准库包中声明的相应函数。 《Go语言101》只在开始的几篇文章中使用了这两个函数。)
第1行指定了当前源文件所处的包的名称。 一个Go程序的主函数(main函数)必须被声明在一个名称为main的包中。 第3行引入了math/rand标准库包,并以rand做为引入名。 在这个包中声明的Intn函数将在第19行被调用。 请阅读代码包和包引入,以获取更多关于代码包和包引入的信息。
表达式、语句和简单语句一文中介绍了什么是表达式和语句。特别地,此文列出了所有的简单语句类型。 在Go代码中,各种流程控制代码块中的某些部分必须为简单语句,某些部分必须为表达式。
StatRandomNumbers函数的声明体中使用了两个流程控制代码块。 其中一个是for循环代码块,它内嵌了另外一个代码块。 另外一个代码块是一个if-else条件控制代码块。 请阅读基本流程控制语法以获取更多关于流程控制代码块的信息。 更多的特殊的流程控制代码块将在以后的其它文章中介绍。
空行常常用来增加代码的可读性。 上面的程序中也包涵了很多注释,但它们大多是为了Go初学者快速理解的目的而加入的。 我们应该尽量使代码自解释,只在确实需要解释的地方进行注释。
关于代码断行
像很多其它流行编程语言一样,Go也使用一对大括号{ and }来形成一个显式代码块。但是在Go代码中,编码样式风格有一些限制。 比如,很多左大括号{不能被放到下一行。 如果,上面的StatRandomNumbers被修改成如下所示,则上面的示例程序将编译不通过。
func StatRandomNumbers(numRands int) (int, int)
{ // 编译错误:语法错误
var a, b int
for i := 0; i numRands; i++
{ // 编译错误:语法错误
if rand.Intn(MaxRand) MaxRand/2
{ // 编译错误:语法错误
a = a + 1
} else {
b++
}
}
return a, b
}
一些程序员不是很喜欢这些限制。但是这些限制有两个好处:
它们使得Go程序编译得非常快。
它们使得不同的Go程序员编写的代码风格类似,从而一个Go程序员写的代码很容易被另一个程序员看懂。
我们可以阅读代码断行规则一文以获取更多关于代码换行规则的细节。在目前,我们最好避免将左大括号放在下一行。 或者说,每行的非空起始字符不能是左大括号(但是,请记住,这不是一个普适的规则
如何在golang 中调用c的静态库或者动态库
Cgo 使得Go程序能够调用C代码. cgo读入一个用特别的格式写的Go语言源文件, 输出Go和C程序, 使得C程序能打包到Go语言的程序包中.
举例说明一下. 下面是一个Go语言包, 包含了两个函数 -- Random 和 Seed -- 是C语言库中random和srandom函数的马甲.
package rand
/*
#include stdlib.h
*/ import "C" func Random() int { return int(C.random()) } func Seed(i int) { C.srandom(C.uint(i)) }
我们来看一下这里都有什么内容. 开始是一个包的导入语句.
rand包导入了"C"包, 但你会发现在Go的标准库里没有这个包. 那是因为C是一个"伪包", 一个为cgo引入的特殊的包名, 它是C命名空间的一个引用.
rand 包包含4个到C包的引用: 调用 C.random和C.srandom, 类型转换 C.uint(i)还有引用语句.
Random函数调用libc中的random函数, 然后回返结果. 在C中, random返回一个C类型的长整形值, cgo把它轮换为C.long. 这个值必需转换成Go的类型, 才能在Go程序中使用. 使用一个常见的Go类型转换:
func Random() int { return int(C.random()) }
这是一个等价的函数, 使用了一个临时变量来进行类型转换:
func Random() int { var r C.long = C.random() return int(r) }
Seed函数则相反. 它接受一个Go语言的int类型, 转换成C语言的unsigned int类型, 然后传递给C的srandom函数.
func Seed(i int) { C.srandom(C.uint(i)) }
需要注意的是, cgo中的unsigned int类型写为C.uint; cgo的文档中有完整的类型列表.
这个例子中还有一个细节我们没有说到, 那就是导入语句上面的注释.
/*
#include stdlib.h
*/ import "C"
Cgo可以识别这个注释, 并在编译C语言程序的时候将它当作一个头文件来处理. 在这个例子中, 它只是一个include语句, 然而其实它可以是使用有效的C语言代码. 这个注释必需紧靠在import "C"这个语句的上面, 不能有空行, 就像是文档注释一样.
Strings and things
与Go语言不同, C语言中没有显式的字符串类型. 字符串在C语言中是一个以0结尾的字符数组.
Go和C语言中的字符串转换是通过C.CString, C.GoString,和C.GoStringN这些函数进行的. 这些转换将得到字符串类型的一个副本.
下一个例子是实现一个Print函数, 它使用C标准库中的fputs函数把一个字符串写到标准输出上:
package print // #include stdio.h // #include stdlib.h import "C" import "unsafe" func Print(s string) { cs := C.CString(s) C.fputs(cs, (*C.FILE)(C.stdout)) C.free(unsafe.Pointer(cs)) }
在C程序中进行的内存分配是不能被Go语言的内存管理器感知的. 当你使用C.CString创建一个C字符串时(或者其它类型的C语言内存分配), 你必需记得在使用完后用C.free来释放它.
调用C.CString将返回一个指向字符数组开始处的指错, 所以在函数退出前我们把它转换成一个unsafe.Pointer(Go中与C的void 等价的东西), 使用C.free来释放分配的内存. 一个惯用法是在分配内存后紧跟一个defer(特别是当这段代码比较复杂的时候), 这样我们就有了下面这个Print函数:
func Print(s string) { cs := C.CString(s) defer C.free(unsafe.Pointer(cs)) C.fputs(cs, (*C.FILE)(C.stdout)) }
构建 cgo 包
如果你使用goinstall, 构建cgo包就比较容易了, 只要调用像平常一样使用goinstall命令, 它就能自动识别这个特殊的import "C", 然后自动使用cgo来编译这些文件.
如果你想使用Go的Makefiles来构建, 那在CGOFILES变量中列出那些要用cgo处理的文件, 就像GOFILES变量包含一般的Go源文件一样.
rand包的Makefile可以写成下面这样:
include $(GOROOT)/src/Make.inc
TARG=goblog/rand
CGOFILES=\ rand.go\ include $(GOROOT)/src/Make.pkg
然后输入gomake开始构建.
更多 cgo 的资源
cgo的文档中包含了关于C伪包的更多详细的说明, 以及构建过程. Go代码树中的cgo的例子给出了更多更高级的用法.
一个简单而又符合Go惯用法的基于cgo的包是Russ Cox写的gosqlite. 而Go语言的网站上也列出了更多的的cgo包.
最后, 如果你对于cgo的内部是怎么运作这个事情感到好奇的话, 去看看运行时包的cgocall.c文件的注释吧.
当前名称:go语言random Go语言range 函数
文章分享:http://lswzjz.com/article/hicgjh.html