Kingshard数据库中间件

miproxy就是基于kingshard进行开发的,该中间件是用Go语言写的,今天就简单看了一下代码。我主要是想看看它是怎么实现读写分离,如何强制读主库的。因为有了主从库,必然就有延时性的问题,一致性的问题。

看了代码感觉挺简单的,主要是下面的代码:

入口就是HTTP server的监听端口,Kingshard写了一个dispatch分发器:

func (c *ClientConn) Run() {

	for {
		data, err := c.readPacket()

		if err != nil {
			return
		}

	
		if err := c.dispatch(data); err != nil {
			c.proxy.counter.IncrErrLogTotal()
			golog.Error("ClientConn", "Run",
				err.Error(), c.connectionId,
			)
			c.writeError(err)
			if err == mysql.ErrBadConn {
				c.Close()
			}
		}

		if c.closed {
			return
		}

		c.pkg.Sequence = 0
	}
}

分发器会根据不同的连接类型,进行分发:

func (c *ClientConn) dispatch(data []byte) error {
    //原子操作,QPS统计
	c.proxy.counter.IncrClientQPS()
	cmd := data[0]
	data = data[1:]

	switch cmd {
	case mysql.COM_QUIT:
		c.handleRollback()
		c.Close()
		return nil
	case mysql.COM_QUERY:
		return c.handleQuery(hack.String(data))
	case mysql.COM_PING:
		return c.writeOK(nil)
	case mysql.COM_INIT_DB:
		return c.handleUseDB(hack.String(data))
	case mysql.COM_FIELD_LIST:
		return c.handleFieldList(data)
	case mysql.COM_STMT_PREPARE:
		return c.handleStmtPrepare(hack.String(data))
	case mysql.COM_STMT_EXECUTE:
		return c.handleStmtExecute(data)
	case mysql.COM_STMT_CLOSE:
		return c.handleStmtClose(data)
	case mysql.COM_STMT_SEND_LONG_DATA:
		return c.handleStmtSendLongData(data)
	case mysql.COM_STMT_RESET:
		return c.handleStmtReset(data)
	case mysql.COM_SET_OPTION:
		return c.writeEOF(0)
	default:
		msg := fmt.Sprintf("command %d not supported now", cmd)
		golog.Error("ClientConn", "dispatch", msg, 0)
		return mysql.NewError(mysql.ER_UNKNOWN_ERROR, msg)
	}

	return nil
}

handQuery是处理查询语句的,在解析sql前,会先获得数据库连接,调用下面的函数。

func (c *ClientConn) getBackendConn(n *backend.Node, fromSlave bool) (co *backend.BackendConn, err error) {
   if !c.isInTransaction() {
      if fromSlave {
         co, err = n.GetSlaveConn()
         if err != nil {
            co, err = n.GetMasterConn()
         }
      } else {
         co, err = n.GetMasterConn()
      }
      if err != nil {
         golog.Error("server", "getBackendConn", err.Error(), 0)
         return
      }
   } else {
      var ok bool
      co, ok = c.txConns[n]

      if !ok {
         if co, err = n.GetMasterConn(); err != nil {
            return
         }

         if !c.isAutoCommit() {
            if err = co.SetAutoCommit(0); err != nil {
               return
            }
         } else {
            if err = co.Begin(); err != nil {
               return
            }
         }

         c.txConns[n] = co
      }
   }

   if err = co.UseDB(c.db); err != nil {
      //reset the database to null
      c.db = ""
      return
   }

   if err = co.SetCharset(c.charset, c.collation); err != nil {
      return
   }

   return
}

如果是事务,直接建立Master连接;

对于非事务的:

如果fromslave=false,就选择主库,否则选择从库。那么这个fromslave从哪来呢?

往上找:

//处理select语句
func (c *ClientConn) handleSelect(stmt *sqlparser.Select, args []interface{}) error {
   var fromSlave bool = true
   plan, err := c.schema.rule.BuildPlan(c.db, stmt)
   if err != nil {
      return err
   }
   if 0 < len(stmt.Comments) {
      comment := string(stmt.Comments[0])
      if 0 < len(comment) && strings.ToLower(comment) == MasterComment {
         fromSlave = false
      }
   }
const (
   MasterComment    = "/*master*/"

如果在sql语句中,加入了上面的文本,就可以直接选择主库。

其实除了上述的方法,还可以根据账号来决定。

2020.10.20补充。借着学习Mysql的机会,又看了一遍Kingshard代码,觉得开发得真挺好的。我也希望我未来自己一个人也可以开发初类似的系统。

--------EOF---------
微信分享/微信扫码阅读