前言
在前一段时间遇见过这样一件事,我们的部分服务要部署在两个机房,而且两个机房并不互通,但是两个服务要通过mysql数据库实现配合工作,从无到有想到了两种实现方式。 这里注明一点的是mysql是与主服务部署一起的,而子服务是单独在另一个机房,因为一些特殊原因,两个机房并不能直接互相访问,但是两个机房部署了proxy代理和socks5代理。 在其间想到了两个解决方法,一是子服务的所有sql操作均放在主服务侧,然后子服务的curd操作通过GET和POST请求主服务,然后主服务实现具体的curd,但是这种方式对以后的服务变更并不太友好,可能只是一个小的修改就需要同时改变二者,第二种方式是子服务通过socks5直接连接mysql服务,这样只需要考虑延迟就可以直接无感使用,而且数据量并非过大时,这是最好的处理手段。只需要将最后的处理结果导入mysql就可以了。
proxy代理方式
这种方式仅是在进行http请求时增加了代理,其本质与普通http请求并无不同。 下面是实例代码
package main
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
)
func RequestGeneral() {
webUrl := "https://www.qiniu.com/"
resp, err := http.Get(webUrl)
if err != nil {
fmt.Println(err)
return
}
time.Sleep(time.Second * 3)
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
func RequestNoHttps(webUrl, proxyUrl string) {
proxy, _ := url.Parse(proxyUrl)
tr := &http.Transport{
Proxy: http.ProxyURL(proxy),
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
Transport: tr,
Timeout: time.Second * 5,
}
resp, err := client.Get(webUrl)
if err != nil {
fmt.Println("出错了", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
func main() {
webUrl := "https://www.qiniu.com/"
proxyUrl := "http://xxx.xxx.xx.xx:xxx"
RequestNoHttps(webUrl, proxyUrl)
}
不难发现只是在创建http请求时,在http.Transport内增加了proxy字段,并将对应的代理地址赋值进去,就可以实现http请求的代理。 但是这种方式在我们的服务内是并不是最优解,所以并未使用。
socks5代理
这种方式是我们最后使用的方案。
package dbx
import (
"context"
"fmt"
proxy_mysql "github.com/go-sql-driver/mysql"
"github.com/sirupsen/logrus"
"golang.org/x/net/proxy"
"log"
"net"
"time"
gorm_mysql "gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Config struct {
DbUser string `yaml:"dbUser"`
DbPass string `yaml:"dbPass"`
DbUrl string `yaml:"dbUrl"`
DbPort string `yaml:"dbPort"`
DbSchema string `yaml:"dbSchema"`
Dbsocks string `yaml:"dbSocks"`
}
func InitDBProxyUrl(con Config) string {
logrus.Info("InitDBProxyUrl get Dbsocks:", conf.Dbsocks)
dialer, err := proxy.SOCKS5("tcp", conf.Dbsocks, nil, proxy.Direct)
checkErrFatal(err)
proxy_mysql.RegisterDialContext("fixieDial", func(ctx context.Context, addr string) (net.Conn, error) {
return dialer.Dial("tcp", addr)
})
dbUrl = fmt.Sprintf("%s:%s@fixieDial(%s:%s)/%s?parseTime=true&loc=Local&charset=utf8mb4",
conf.dbUser,
conf.dbPass,
conf.dbUrl,
conf.dbPort,
conf.dbSchema,
)
return dbUrl
}
func InitDBUrl(con Config) string {
dbUrl = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&loc=Local&charset=utf8mb4",
conf.dbUser,
conf.dbPass,
conf.dbUrl,
conf.dbPort,
conf.dbSchema,
)
return dbUrl
}
var db *gorm.DB
func GetDb() *gorm.DB {
return db
}
func OpenProxyDB() {
dbUrl := InitDBProxyUrl()
var err error
db, err = gorm.Open(gorm_mysql.Open(dbUrl), &gorm.Config{})
checkErrFatal(err)
db.Debug()
sqlDB, err := db.DB()
checkErrFatal(err)
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(10)
sqlDB.SetConnMaxLifetime(time.Hour)
}
func checkErrFatal(err error) {
if err != nil {
log.Fatalln(err)
}
}
这里先是使用了"golang.org/x/net/proxy" 模块,根据传入的socks5代理的IP和端口返回自定义代理拨号方法,然后利用"github.com/go-sql-driver/mysql" 将得到的自定义方法注册进系统,然后使用"gorm.io/driver/mysql" 驱动链接mysql,这里使用了两个mysql驱动,一个是为了注册socks5代理拨号,一个是为了使用gorm连接mysql。
虽然"github.com/go-sql-driver/mysql" 本身也是可以直接连接mysql的,但是"gorm.io/driver/mysql" 是为了使用gorm。
至于二者为什么是可以混合使用的,这不清楚,但是应该是上下文的关系,或者说是将自定义拨号方法临时注册进系统内部了。
因为如果不进行注册是无法这样使用的,而且使用tcpdump指令,也是可以检测到本机ip端口和代理ip端口之间的数据通信过程。
23:09:46.825460 IP xx.xx.xx.xx.xxxx > yy.yy.yy.yy.yyyy0: Flags [R.], seq 0, ack 3037942227, win 0, length 0
23:09:46.833522 IP yy.yy.yy.yy.yyyy2 > xx.xx.xx.xx.xxxx: Flags [S], seq 1928785681, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
23:09:46.833524 IP yy.yy.yy.yy.yyyy1 > xx.xx.xx.xx.xxxx: Flags [S], seq 155678182, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
23:09:46.833579 IP yy.yy.yy.yy.yyyy4 > xx.xx.xx.xx.xxxx: Flags [S], seq 1472247145, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
23:09:46.833581 IP xx.xx.xx.xx.xxxx > yy.yy.yy.yy.yyyy2: Flags [R.], seq 0, ack 1928785682, win 0, length 0
23:09:46.833634 IP xx.xx.xx.xx.xxxx > yy.yy.yy.yy.yyyy4: Flags [R.], seq 0, ack 1472247146, win 0, length 0
23:09:46.833751 IP yy.yy.yy.yy.yyyy5 > xx.xx.xx.xx.xxxx: Flags [S], seq 2981254641, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
23:09:46.833968 IP yy.yy.yy.yy.yyyy6 > xx.xx.xx.xx.xxxx: Flags [S], seq 1755001897, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
这里为了安全就不暴露ip和具体端口了,但是依然可以看出二者之间的数据交换过程。 有需要的可以去建立个socks5代理环境测试下。
如下命令可以设置系统的代理为socks5
export http_proxy=socks5://xx.xx.xx.xx:xxxx
如下命令可以设置系统的代理为proxy
export http_proxy=http://yy.yy.yy.yy:yyyy
然后搞个curl或者什么来测试下,能找到这个文章说明这里应该对你不是难事,而且有测试条件。
|