15 Star 87 Fork 24

konyshe / gogo

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

GoGo Logo

Go 语言轻量 web 开发框架,特点是一行代码搞定 RESTFul,不依赖第三方 ORM,也不需要生成一堆的 controllers 和 models 文件,快速使用,性能优秀。

版本介绍

v1(已发布)

  • 基于Google的http框架, 重写了router,支持正则URL,支持restful。无需针对每张表写一个go文件。

v2(已发布)

  • 基于Google的http框架,重写了websocket,独立端口号,增加往返数据包的缓冲队列,定时发送心跳包检测存活。

v3(main分支,测试中)

  • 基于epoll重写了http框架,重点针对低性能设备的CPU线程优化,以及socket维护方案。自测十万并发时,相比Google的http框架,客户端响应慢了0.1秒(因为目前只用到单核),服务端CPU消耗下降了50%。

  • gogo.StartHTTP仍然使用Google的http,gogo.StartV3HTTP则使用v3方案,两种方式使用方法一致,无学习成本。

注:通过分析源码,发现Google的http针对每个请求,都会产生一个goroutine。而http请求可能涉及到后端数据查询等操作,瓶颈并不在接收到请求的服务器本身。当后端服务器处理能力到达极限时,就会导致前端积压大量的goroutine,结果大家一起崩溃,造成整体业务响应曲线波动。GoGo目前只用了单线程处理http请求,不积压goroutine。预计v4会针对CPU核心数量产生固定的goroutine来处理http请求,并且goroutine和CPU绑定,避免上下文切换,进一步提升性能,稳定整体业务响应曲线。

v4(开发中,即将开源)

  • 封装PCI网卡通用驱动,移植网卡驱动的主要功能到用户态,利用DMA将到达网卡寄存器的报文拷贝到用户态,中间绕过内核。再搭配用户态的协议栈,处理ARP/UDP/TCP流量,并响应回复。目标是全程数据包零拷贝,集成bbr拥塞控制,封装用户态epoll相关函数, 保持和系统函数使用方式一致。最后接入到GoGo框架,预计性能再提升30%左右。该部分可能独立开源,供其他应用程序使用。v4理论上是目前性能最高的解决方案,优于kafka,nginx,spring,明显提升客户体验的同时,还能减少服务器投入。

注:正常情况下,数据包到达网卡寄存器后,会被DMA拷贝到驱动事先指定的内存地址,然后由驱动拷贝到skbuf等待内核接收,内核接收后再拷贝进队列等待协议栈接收,协议栈处理完成后,再次拷贝进队列等待用户态程序接收,用户态程序通过recv函数再拷贝到自己的内存地址。除了第一次DMA拷贝不占用CPU时间,后面足足有4次CPU拷贝,特别是最后一次内核态到用户态的拷贝,内核态还要内存空间是否合法。kafka和nginx之所以快,是因为省去了最后一次拷贝,将等待用户态程序接收的内存数据,直接映射到了用户态。nginx发送静态数据时,也将静态数据内容直接映射给了内核,这比正常的先read file,再send socket要快很多。kafka甚至将内核态的数据地址直接丢给了send socket,这使得数据接收和发送都在内核态处理完了。只是针对最后1次的CPU拷贝优化,性能提升就如此明显。v4目前已经优化了最后2次CPU拷贝,以及将针对内存池、线程绑定优化。

需求和规划

  • v4目前只支持e1000网卡,未来需支持更多网卡

  • 计划支持DPDK,因为DPDK已支持很多网卡,并且做了优化

  • 无论是v4还是DPDK,都有个问题就是独占网卡,这对只有一个网卡的云主机而言,是无法使用的。当然也可以支持RING方案,这是内核支持的,先通过DMA将数据从网卡拷贝到内核,再将数据从内核映射到用户态处理。虽然多了一次拷贝,但也可优化。

  • 如有建议请留言,感谢!

快速使用

备注:由于嵌入了C代码,若遇到提示gcc无法编译的问题,可删掉UtilsBuild_linux.go文件后再尝试

package main

import (
	"net/http"
	"strconv"

	"gitee.com/konyshe/gogo"
)

func main() {

	//初始化日志输出功能,DEBUG为日志输出级别,具体参考函数说明
	gogo.LogInit("DEBUG", 1024)

	// 初始化数据库连接
	if err := gogo.SQLInit("mysql", "数据库用户名:数据库密码@tcp(数据库地址:数据库端口)/表名?charset=utf8", 10, 1); err != nil {
		gogo.CheckErrorExit(err)
		return
	}

	// 增
	gogo.POST("/restful/:tablename", func(ctx *gogo.HTTPContext) {
		affect, err := gogo.SQLInsert(
			ctx.GetPathParam(":tablename"),
			ctx.GetPostBody())

		if err != nil {
			gogo.LogError(err)
			ctx.WriteString(err.Error())
		} else {
			ctx.WriteString(strconv.FormatInt(affect, 10))
		}
	})

	// 删
	gogo.DELETE("/restful/:tablename/:id", func(ctx *gogo.HTTPContext) {
		affect, err := gogo.SQLDelete(
			ctx.GetPathParam(":tablename"),
			"id="+ctx.GetPathParam(":id"))

		if err != nil {
			gogo.LogError(err)
			ctx.WriteString(err.Error())
		} else {
			ctx.WriteString(strconv.FormatInt(affect, 10))
		}
	})

	// 改
	gogo.PUT("/restful/:tablename/:id", func(ctx *gogo.HTTPContext) {
		affect, err := gogo.SQLUpdate(
			ctx.GetPathParam(":tablename"),
			"id="+ctx.GetPathParam(":id"),
			ctx.GetPostBody())

		if err != nil {
			gogo.LogError(err)
			ctx.WriteString(err.Error())
		} else {
			ctx.WriteString(strconv.FormatInt(affect, 10))
		}
	})

	// 查
	gogo.GET("/restful/:tablename/:id", func(ctx *gogo.HTTPContext) {
		queryData, err := gogo.SQLQueryByMap(
			"",
			ctx.GetString("feilds"),
			ctx.GetPathParam(":tablename"),
			"id="+ctx.GetPathParam(":id"),
			"", 0, 1)

		if err != nil {
			gogo.LogError(err)
			ctx.WriteString(err.Error())
		} else {
			ctx.WriteJSON(queryData)
		}
	})

	// 查
	gogo.GET("/restful/:tablename", func(ctx *gogo.HTTPContext) {
		queryData, err := gogo.SQLQueryByMap(
			ctx.GetString("columnname"),
			ctx.GetString("feilds"),
			ctx.GetPathParam(":tablename"),
			ctx.GetString("where"),
			ctx.GetString("order"),
			ctx.GetInt("offset", 0),
			ctx.GetInt("count", 10))

		if err != nil {
			gogo.LogError(err)
			ctx.WriteString(err.Error())
		} else {
			ctx.WriteJSON(queryData)
		}
	})

	// 404页面
	gogo.STATUS(http.StatusNotFound, func(ctx *gogo.HTTPContext) {
		ctx.WriteHeaderStatus(http.StatusNotFound)
		ctx.WriteString("Page Not Found !")
	})

	// 启动HTTP服务
	gogo.StartHTTP(3009)
}

数据库表结构

mysql> desc dede_flink;
+----------+----------------------+------+-----+---------+----------------+
| Field    | Type                 | Null | Key | Default | Extra          |
+----------+----------------------+------+-----+---------+----------------+
| id       | smallint(5) unsigned | NO   | PRI | NULL    | auto_increment |
| sortrank | smallint(6)          | NO   |     | 0       |                |
| url      | char(60)             | NO   |     |         |                |
| webname  | char(30)             | NO   |     |         |                |
| msg      | char(200)            | NO   |     |         |                |
| email    | char(50)             | NO   |     |         |                |
| logo     | char(60)             | NO   |     |         |                |
| dtime    | int(10) unsigned     | NO   |     | 0       |                |
| typeid   | smallint(5) unsigned | NO   |     | 0       |                |
| ischeck  | smallint(6)          | NO   |     | 1       |                |
+----------+----------------------+------+-----+---------+----------------+
10 rows in set

Windows

go build -o app.exe
app.exe

Linux/MacOS

go build -o app
./app

浏览器访问测试

POST http://localhost:3009/restful/dede_flink

以下是 application/json 内容,可以批量添加多条数据

[
	{
    	"id": 20,
    	"ischeck": 1,
    	"webname": "hxyw",
    	"url":"http://www.hxyw.org"
	},
	{
    	"id": 21,
    	"ischeck": 1,
    	"webname": "bejson",
    	"url":"http://www.bejson.com"
	}
]
PUT http://localhost:3009/restful/dede_flink/21

以下是 application/json 内容,这里只将 id=21 的数据,webname 字段修改为"hello"

{
    "webname": "hello"
}
GET http://localhost:3009/restful/dede_flink/21
GET http://localhost:3009/restful/dede_flink?order=-id
GET http://localhost:3009/restful/dede_flink?offset=10&&count=100&&columnname=webname
DELETE http://localhost:3009/restful/dede_flink/1
  • 404
GET http://localhost:3009/weqwe

Start using it

  1. Download and install it:
$ go get -u gitee.com/konyshe/gogo
  1. Import it in your code:
import "gitee.com/konyshe/gogo"
  1. (Optional) Import net/http. This is required for example if using constants such as http.StatusNotFound.
import "net/http"

示例中的方法介绍,更全的请看代码中的注释

// LogInit 初始化日志输出功能
// level 日志输出级别
// 		配置文件指定日志级别  ALL,DEBUG,INFO,WARN,ERROR,FATAL,OFF 级别由低到高
// 		其中 ALL表示所有调用打印日志的方法都会打出,而OFF则表示都不会打出。
// 		一般习惯是测试阶段为debug,生成环境为info以上
// maxSize 日志文件最大体积,单位MB
gogo.LogInit(level string, maxSize int64)

// 系统会优先判断是否命中固定URL,当固定URL无法命中的情况下,才会去判断是否命中正则URL(开发中部分支持)
gogo.GET("/restful/:tablename/:columnname", func(ctx *gogo.HTTPContext)

// 注册没有命中任何URL规则时进入的函数
gogo.STATUS(http.StatusNotFound, func(ctx *gogo.HTTPContext)

// SQLInit 初始化数据库操作句柄,这里要提供:
// driverName string: 数据库类型,例如mysql、sqlite、postgres等,参考github.com/go-sql-driver/mysql官方介绍
// dataSourceName string: 数据库地址,参考github.com/go-sql-driver/mysql官方介绍
// MaxOpenConns int: 最大缓存连接数,这个数值包含了MaxIdleConns
// MaxIdleConns int:预备的最大空闲连接数
gogo.SQLInit(driverName string, dataSourceName string, maxOpenConns int, maxIdleConns int) error

// SQLInsert 增加一条数据
// tableName string: 操作的表名
// data []byte: 需要更新的内容,用string转换后是json格式
gogo.SQLInsert(tableName string, data []byte) (int64, error)

// SQLDelete 根据where条件删除数据
// tableName string: 操作的表名
// where string: 过滤条件,就是where后面跟着的部分
gogo.SQLDelete(tableName, where string) (int64, error)

// SQLUpdate 更新一条数据
// tableName string: 操作的表名
// where string: 过滤条件,就是where后面跟着的部分
// data []byte: 需要更新的内容,用string转换后是json格式
gogo.SQLUpdate(tableName, where string, data []byte) (int64, error)

// SQLQueryByMap 将查询到的数据,按照指定字段的值做为索引构建map并返回
// columnName string: 作为索引的字段名称
// feilds string: 查询需要获取哪些字段的值,就是select后面跟着的部分,一般用"*"
// tableName string: 查询的表名
// where string: 过滤条件,就是where后面跟着的部分
// order string: 排序条件,就是order by后面跟着的部分。默认是ASC排序,除非"-"开头则DESC排序
// offset string: limit后面逗号相隔的两个数值,前者就是offset,后者就是count
// count string: limit后面逗号相隔的两个数值,前者就是offset,后者就是count
gogo.SQLQueryByMap(columname, feilds, tableName, where, order string, offset, count int) (interface{}, error)

// StartHTTP 启动HTTP服务
// port 端口号
func StartHTTP(port int)

// StartWebSocket 启动websocket服务
// pattern 服务路径
// port 端口号
// handler 注册回调函数
func StartWebSocket(pattern string, port int, handler func([]byte, *ImplConnection) ([]byte, error))

// GetResponseWriter 获取原始的http.ResponseWriter指针
(ctx *HTTPContext) GetResponseWriter() http.ResponseWriter

// GetRequest 获取原始的http.Request指针
(ctx *HTTPContext) GetRequest() *http.Request

// GetPathParam 获取GET请求路径中的String格式参数值,例如/restful/:table_name/:id
// key string: 参数名称
(ctx *HTTPContext) GetPathParam(key string) string

// GetPostBody 获取POST请求的内容
(ctx *HTTPContext) GetPostBody() []byte

// WriteExecute 将官方原始的ParseFiles和Execute接口做了合并
// data interface{}: 渲染模板需要的数据
// filenames ...string: 模板文件路径
(ctx *HTTPContext) WriteExecute(data interface{}, filenames ...string) error

// WriteByte 将[]byte格式的数据输出给HTTP客户端
// content []byte: 需要输出HTTP客户端的[]byte格式数据
(ctx *HTTPContext) WriteByte(content []byte)

// WriteString 将String格式的数据输出给HTTP客户端
// content string: 需要输出HTTP客户端的String格式数据
(ctx *HTTPContext) WriteString(content string)

// WriteJSON 将Struct结构体的数据转换成Json输出给HTTP客户端
// v interface{}: 需要输出HTTP客户端的Struct结构体数据
(ctx *HTTPContext) WriteJSON(v interface{}) error

// Log GoGo使用的第三方go_logger库,输出到日志文件的同时还会在控制台输出,并根据日志级别显示不同颜色
// 日志输出和官方fmt.Print、fmt.Printf使用一致
func LogDebug(a ...interface{})
func LogInfo(a ...interface{})
func LogWarning(a ...interface{})
func LogError(a ...interface{})

func LogDebugF(format string, a ...interface{})
func LogInfoF(format string, a ...interface{})
func LogWarning(format string, a ...interface{})
func LogErrorF(format string, a ...interface{})

// GlobalSignalWait 全局信号等待
// 需要程序等待协程运行的时候使用
func GlobalSignalWait()

// GlobalSignalRelease 全局信号继续
// 需要程序不再等待协程运行的时候使用
func GlobalSignalRelease()

欢迎贡献者加入

MIT License Copyright (c) 2018 kony Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

轻量 web 开发框架,特点是一行代码搞定 RESTFul,无需一堆的 controllers 和 models 文件,快速使用,性能优秀。v4重点部分性能优于spring、kafka、nginx,README有介绍 展开 收起
Go
MIT
取消

发行版 (6)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
Go
1
https://gitee.com/konyshe/gogo.git
git@gitee.com:konyshe/gogo.git
konyshe
gogo
gogo
v2

搜索帮助

14c37bed 8189591 565d56ea 8189591