- Object–relational mapping (ORM) is a programming technique for converting data between a relational database and the heap of an object-oriented programming language (Wikipedia), like Mybatis in java.
- Using ORM, we can associate a data table in database with a certain class/struct, and by modifying the class/struct instance, we can easily CRUD the database without using SQL statements.
- Turn to Gorm
- Remote Procedure Call(RPC) is a software communication protocol that one program can use to request a service from a program located in another computer on a network without having to understand the network's details.
- RPC Process:
In the application layer, client programs request services by invoking functions on remote servers, which respond by executing the requested operations and returning results to the client. The application layer also addresses higher-level concerns such as authentication, authorization, and error handling.
The encoding layer is responsible for converting function calls, parameters, and results into formats suitable for transmission over the network, as well as converting received data back into the format required for local invocation. This layer involves data serialization and deserialization to ensure that data can be properly parsed and processed during network transmission.
- Type–length–value (TLV)
struct Person{
1: required string userName,
2: optional int64 interestNumber,
3: optional []string interests,
}
At this layer, an appropriate transport protocol is chosen to ensure reliable data transmission, including error detection and recovery mechanisms. Common transport protocols include HTTP (Hypertext Transfer Protocol) and specific RPC protocols like gRPC.
- Special Fields
-
Terminator: eg, Message + \r\n + Message + \r\n
-
Variable Length: eg, Length + Message Body + Length + Message Body
- Process
Peek -> Magic Number (To know which protocol is used) -> Peek -> PayloadCodeC (To know encode method) -> Peek -> Payload
This is the bottommost layer responsible for handling the details of data transmission over the network, such as packet segmentation, sending, and receiving. It provides the underlying network connection and communication mechanisms. Common protocols at this layer include TCP (Transmission Control Protocol) and UDP (User Datagram Protocol).
- Stability
- Timeout: Avoid wastes on unavailable nodes.
- Rate Limiter: Protect the callee and prevent the server from being overwhelmed by heavy traffic.
- Circuit Breaker: Protect the callee and prevent the problem of the server from affecting the entire link.
- Request Success Rate: Load balancing and Retry.
- Long Tail Request: Backup Request.
Note: Stability indicators are usually implemented through middleware, eg, WithTimeout(), withRetry().
-
Usability
-
Scalability
- Observability
- Log
- Metric
- Tracing
- Others
- High Throughput: Connection Pool
- Low Latency: Multiplexing
- Turn to Kitex
- Interaction: Png-Pong / Streaming / Oneway
- Codec: Thrift / Protobuf
- Application Layer Protocol: TTHeader / HTTP2
- Transport Layer: TCP
- Scheduling: epoll_wait, Reuse goroutines.
- LinkBuffer: Nocopy Buffer.
- Pool: Object Pool, Memory Pool.
- Codegen: Pre-allocated memory, Inline operation, Thrift IDL encoding.
- JIT (Just-In-Time compilation): Frugal.
- Request Line
- Methods: GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
- URL
- Protocol Version
- Header Fields
- Message Body
- State Line
- Protocol Version
- State Code
- State Description
- Header Fields
- Response Body
h := server.Default()
h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
ctx.Data(200, "text/plain; charset=utf-8", []byte("OK"))
})
h.Spin()
Application Layer provides APIs, eg ctx.Request.Header.Peek(key) -> API ctx.GetHeader(key).
- With pre-handle logic and post-handle logic.
func Middleware(params){
// pre-handle logic
Next() // nextMiddleware() or bizLogic()
// after-handle logic
}
- Next()
- Without Next(): Initialization logic AND does not need to be on the same call stack.
- With Next(): Post-handle logic OR need to be in the same call stack
-
Routing can register multiple middleware.
-
Guaranteed to increment index at all times.
func (ctx *RequestContext) Next(){
ctx.index++
for ctx.index < int8(len(ctx.handler)){
ctx.handlers[ctx.index]()
ctx.index++
}
}
- Exception handler
func (ctx *RequestContext) Abort(){
ctx.index = IndexMax
}
- Trie
-
Method matching: Map + Method(string) + Tries + Header Node(*node)
-
Multi-handler: add a list for each handler
node struct {
prefix string
parent *node
children children
handlers app.HandlersChain
...
}
-
Do not store Contexts inside a struct type, instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter.
-
Read and write data on the connection.
type Server interface{
Serve(c context.Context, conn network.Conn) error
}
- Blocking I/O (BIO)
// BIO
go func() {
for {
conn, _ := listener.Accept()
go func(){
conn.Read(request)
handler
conn.Write(response)
}()
}
}()
// go net
type Conn interface {
Read(b []byte) (n int, err error) // if fail to read, blocked here
Write(b []byte) (n int, err error) // if fail to write, blocked here
...
}
- New I/O (NIO)
// NIO
go func() {
for {
readableConns, _ := Monitor(conns) // read enough data
for conn := range readableConns {
go func(){
conn.Read(request)
handler
conn.Write(response)
}()
}
}
}()
// netpoll
type Reader interface {
Peek(n int) ([]byte, error)
...
}
type Writer interface {
Malloc(n int) (buf []byte, err error)
Flush() error
}
- go net with bufio.
type Reader interface {
Peek(n int) ([]byte, error) // stop pointer
Discard(n int) (discarded int, err error) // move-forward pointer
Release() error // reclaim memory
Size() int
Read(b []byte) (l int, err error)
...
}
type Writer interface {
Write(p []byte)
Size() int
Flush() error
...
}
- netpoll with nocopy peek: Allocate a sufficiently large buffer and limit the buffer size
-
To find the Terminator \r\n quickly, use SIMD (Single Instruction/Multiple Data).
-
To Parsing Headers body quickly, firstly filter out keys that do not meet the requirements by initials, then parse the corresponding value to an independent field, finally use byte slices to manage corresponding header storage for easy reuse.
switch s.Key[0] | 0x20 {
case 'h': // Filter out keys that do not meet the requirements by initials
if utils.CaseInsensitiveCompare(s.Key, bytestr.StrHost) {
h.setHostBytes(s.Value) // Parse the corresponding value to an independent field
continue
}
}
- Header key normalization, eg aaa-bbb -> Aaa-Bbb.
- Turn to Hertz
- Turn to Microservices