开yun体育网 mysqlConn-开云(中国大陆) Kaiyun·官方网站
译自 The Developer’s Guide to Database Proxies: When to Use Them and How to Create One,作家 Alex Pliutau。
#深度好文遐想#念念象一个高度依赖数据的复杂分散式系统,其中每个微奇迹或团队都单独聚会到数据库(不错是分享数据库或特定/圮绝的数据库)。如斯复杂的平台需要围聚监控、查询考据、警报、自界说分片以及更好的安全性等等。天然您不错从数据库奇迹器取得好多这些功能,但实施数据库代理可能是一个更好的活动(要是您准备投资)。
使用数据库代理的主要上风在于它将数据库拓扑与应用活动层圮绝开来,因此开辟东谈主员无需了解数据层的集群、节点和里面结构(天然在一定进度上)。
数据库代理用例
让咱们深切了解数据库代理何如赋能您的开辟团队、增强安全性并优化数据库性能的多样神志。
阻扰来自应用活动的 SQL 查询 并将其动态路由到正确的数据库/表(举例自界说分片)。Figma正在作念 exactly that使用他们的里面 Postgres 代理。理解/分析/考据来自开辟东谈主员的 SQL 查询并使用附加信息丰富反馈。这可能有助于告诉应用活动哪些表将被弃用。可推广性和架构篡改不会影反馈用活动。 平台/数据库团队不错寂寥篡改架构,而无需重写数百个微奇迹。大概透明地添加或删除数据库集群中的节点,而无需再行确立或再行启动应用活动。实际安全战略并实际身份考据和授权检验,以确保唯有授权的客户端智商探望数据库。也不错谢绝径直探望数据库。耕作数据库通讯的性能,通过围聚照管聚会池、诈欺缓存工夫等。围聚式可不雅察性。 当应用活动使用已弃用的表时收到示知,等等。
何时使用数据库代理
并非统共系统都需要数据库代理,尤其是在早期阶段。以下是一般准则,证实何时可能需要它:
您有多个由不同学科别离的开辟团队:举例多个后端团队、数据工程团队。您有一个平台/数据库团队来领有它。天然其他团队也不错领有它。您的系统是分散式的,况且您小器着许多微奇迹和许无数据库。您的系统数据量很大。您需要更好的安全性和可不雅察性。
使用数据库代理的资本
使用数据库代理如实会带来资本:
数据库代理是基础设施中的一个新元素,它本人具有复杂性。可能是单点故障,因此必须至极踏实且流程实战考试。特别的网罗蔓延。
数据库代理类型
您不错通过几种神志部署数据库代理:
自界说代理奇迹(底下我将提供一个浅易的 Go 示例)托管云处置决议,举例 Amazon RDS ProxySidecars,举例 Cyral买卖和开源产物,举例 ProxySQL,或dbpack
使用 Go 编写自界说数据库代理奇迹
当今,咱们将使用 Go 竣事我方的 MySQL 代理。请记取,这仅仅一个证实念念法的实验。
咱们的代理将处置一个至极浅易的用例:阻扰 SQL 查询并在匹配格局时重写表名。
-- Application-generated querySELECT * FROM orders_v1;-- Rewritten querySELECT * FROM orders_v2;
竣事分为两个部分:
将查询从客户端路由到 MySQL 奇迹器的基本代理。SQL 理解器,具有一些在发送查询之前操作查询的逻辑。
您不错在此 Github 存储库中搜检齐全的源代码。
从客户端到 MySQL 奇迹器的 TCP 代理
咱们的 TCP 代理摄取至极浅易的活动竣事,皆备不合乎坐蓐环境,但足以演示 TCP 传输的责任旨趣:
创建一个代理 TCP 奇迹器给与聚会创建到 MySQL 的 TCP 聚会使用管谈将字节流从客户端代理到 MySQL 奇迹器,反之也是
main.go
package mainimport ( "fmt" "io" "log" "net" "os")func main() { // proxy listens on port 3307 proxy, err := net.Listen("tcp", ":3307") if err != nil { log.Fatalf("failed to start proxy: %s", err.Error()) } for { conn, err := proxy.Accept() log.Printf("new connection: %s", conn.RemoteAddr()) if err != nil { log.Fatalf("failed to accept connection: %s", err.Error()) } go transport(conn) }}func transport(conn net.Conn) { defer conn.Close() mysqlAddr := fmt.Sprintf("%s:%s", os.Getenv("MYSQL_HOST"), os.Getenv("MYSQL_PORT")) mysqlConn, err := net.Dial("tcp", mysqlAddr) if err != nil { log.Printf("failed to connect to mysql: %s", err.Error()) return } readChan := make(chan int64) writeChan := make(chan int64) var readBytes, writeBytes int64 // from proxy to mysql go pipe(mysqlConn, conn, true) // from mysql to proxy go pipe(conn, mysqlConn, false) readBytes = <-readChan writeBytes = <-writeChan log.Printf("connection closed. read bytes: %d, write bytes: %d", readBytes, writeBytes)}func pipe(dst, src net.Conn, send bool) { if send { intercept(src, dst) } _, err := io.Copy(dst, src) if err != nil { log.Printf("connection error: %s", err.Error()) }}func intercept(src, dst net.Conn) { buffer := make([]byte, 4096) for { n, _ := src.Read(buffer) dst.Write(buffer[0:n]) }}
函数证实:
transport - 处理 TCP 聚会,双向传输字节。pipe - 传递字节,要是是 proxy → mysql,它还会调用 intercept 来处理查询。intercept - 咱们将在之后竣事它来理解查询。
Dockerfile
FROM golang:1.22 as builderWORKDIR /COPY . .RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o proxy main.goFROM alpine:latestCOPY --from=builder /proxy .EXPOSE 3307CMD ["./proxy"]
docker-compose.yaml
services: proxy: restart: always build: context: . ports: - 3307:3307 environment: - MYSQL_HOST=mysql - MYSQL_PORT=3306 links: - mysql mysql: restart: always image: mysql:5.7 platform: linux/amd64 ports: - 3306:3306 environment: - MYSQL_ROOT_PASSWORD=root command: --init-file /data/application/init.sql volumes: - ./init.sql:/data/application/init.sql
init.sql
CREATE DATABASE IF NOT EXISTS packagemain;CREATE TABLE IF NOT EXISTS packagemain.orders_v2 ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL) ENGINE=InnoDB;INSERT INTO packagemain.orders_v2 (name) VALUES ('order1');
SQL 理解
咱们的 intercept 函数照旧不错获取字节包。了解 MySQL 包的结构很有匡助。我不会深切细节,但你不错 在这里阅读。
在咱们的 intercept 函数中,咱们实际以下操作:
查找 COM_QUERY 客户端呐喊,其数字代码为 3。获取原始查询。进行至极基本的表重定名。
有一个很棒的包 sqlparser 来自 YouTube 的 Vitess 名堂,咱们不错用它来理解 SQL 查询。然则,为了简化演示,咱们将使用字符串匹配和替换。
main.go
const COM_QUERY = byte(0x03)func intercept(src, dst net.Conn) { buffer := make([]byte, 4096) for { n, _ := src.Read(buffer) if n > 5 { switch buffer[4] { case COM_QUERY: clientQuery := string(buffer[5:n]) newQuery := rewriteQuery(clientQuery) fmt.Printf("client query: %s\n", clientQuery) fmt.Printf("server query: %s\n", newQuery) writeModifiedPacket(dst, buffer[:5], newQuery) continue } } dst.Write(buffer[0:n]) }}func rewriteQuery(query string) string { return strings.NewReplacer("from orders_v1", "from orders_v2").Replace(strings.ToLower(query))}func writeModifiedPacket(dst net.Conn, header []byte, query string) { newBuffer := make([]byte, 5+len(query)) copy(newBuffer, header) copy(newBuffer[5:], []byte(query)) dst.Write(newBuffer)}
开动代理并聚会到它
在这里,咱们聚会到开动在端口 3307 上的代理,而不是 MySQL 奇迹器本人(端口 3306)。如你所见,咱们不错使用旧例的 MySQL 客户端,这简化了代理的使用。
这意味着 orders_v1 表被重定向到 orders_v2。代理日记:
client query: select * from orders_v1;server query: select * from orders_v2;
论断
总之,数据库代理在应用活动和底层数据库之间提供了一个雄壮的空洞层。它通过圮绝数据库复杂性来简化开辟,使数据库团队大概寂寥进行格局篡改,并通过围聚式探望扫尾来增强安全性。天然存在基础设施支出和潜在蔓延等特别资本,但关于具有多个团队和数据密集型需求的复杂分散式系统,数据库代理可能是一项值得的投资。
以上内容由本站根据公开信息整理,由算法生成(网信算备310104345710301240019号)开yun体育网,与本站立场无关,如数据存在问题请联系我们。本文为数据整理,不对您构成任何投资建议,投资有风险,请谨慎决策。
以上内容由本站根据公开信息整理,由算法生成(网信算备310104345710301240019号),与本站立场无关,如数据存在问题请联系我们。本文为数据整理,不对您构成任何投资建议,投资有风险,请谨慎决策。