1. What is micro service
- A set of small services is used to develop a single application. Each service runs in an independent process. It is generally interconnected by lightweight communication mechanism, and they can be deployed in an automatic way
Micro service is a design idea, not a reflection of quantity
- Specific function
- A lot of code
- Complex architecture
2. What are the characteristics
- Specific responsibilities, such as focusing on rights management
- Lightweight communication, which is independent of platform and language. For example, http is lightweight
- Data isolation
- Have their own data
- Technical diversity
image
3. Advantages of microservice architecture
- Independence
- Easy for users to understand
- Flexible technology stack
- Efficient team
4. Deficiencies of microservice architecture
- Additional work, service splitting
- Ensure data consistency
- Increased communication costs
Micro service ecology
1. Hardware layer
- Use docker+k8s to solve it
2. Communication layer
- Network transmission, using RPC (remote procedure call)
* **HTTP transmission** ,GET POST PUT DELETE * **be based on TCP** ,Closer to the bottom, RPC be based on TCP,Dubbo(18 Change to support various languages at the end of the year), Grpc,Thrift You need to know who to call, register and discover with the service
* Distributed data synchronization required: etcd,consul,zk Data transmission may be various languages, technologies and transmission
Suggestions on data transmission protocol selection
1. For inter company system calls, if the performance requirements are more than 100ms, XML based SOAP protocol is a scheme worthy of consideration.
2. For scenes with bad debugging environment, using JSON or XML can greatly improve debugging efficiency and reduce system development cost.
3. When there are high requirements for performance and simplicity, Protobuf, Thrift and Avro have a certain competitive relationship.
4. Protobuf and Avro are the first choice for the T-level data persistence application scenario
. If the persistent data is stored in the Hadoop subproject, Avro will be a better choice.
5. If you need to provide a complete RPC solution, Thrift is a good choice.
6. If different transport layer protocols need to be supported after serialization, or high-performance scenarios requiring cross firewall access, Protobuf can be given priority.
RPC mechanism and implementation process
1. RPC mechanism
Lightweight remote procedure calls are used between services. Generally, HTTP and RPC are used
- HTTP calls the application layer protocol, and the structure is relatively fixed
- The network protocol of RPC is relatively flexible and can be customized
RPC remote procedure call generally adopts C/S
Mode, client server mode, client process, the program that calls the server process. The execution result of the server process is returned to the client. The client is awakened from the blocking state, receives data and extracts data.
In the above process, the client calls the functions of the server to execute the task. It does not know whether the operation is carried out in the local operating system or through remote procedure calls. It has no sense in the whole process.
The basic communication of RPC is as follows:
image
For RPC remote procedure calls, the following four points should be considered:
- Parameter transfer
- Communication protocol mechanism
- Error handling
- timeout handler
2. Parameter transfer
- pass by value
Generally, the default is value transfer. You only need to copy the value in the parameter to the data in the network message
- Reference passing
It's difficult. It's meaningless to simply pass a reference to a parameter
, because the referenced address is given to the remote server, the memory address on the server is not the data that the client wants at all. If this is not the case, the client must also pass copies of the data to the remote server and put them into the memory of the remote server. After the server copies the referenced address, the data can be read.
However, the above method is troublesome and error prone. Generally, RPC does not support direct reference passing
- Data format unification
There needs to be a standard to encode and decode all data types. Data formats can have implicit types and explicit types
- Implicit type
Pass only the value, not the name or type of the variable
- Explicit type
Pass the type and value of the field
Common transmission data formats are:
- ASN.1 of ISO standard
- JSON
- PROTOBUF
- XML
3. Communication protocol mechanism
In a broad sense, the protocol stack is divided into common protocol and private protocol
- Mutual agreement
For example, HTTP, SMPP and WEBSERVICE are common protocols, which have the advantages of universal type and public network transmission capability
- Private agreement
The agreement formed by internal agreement has many disadvantages, but it can be highly customized, improve performance, reduce cost, and improve flexibility and efficiency. Private protocol development is often used within enterprises
For the formulation of the agreement, the following five aspects need to be considered:
- Protocol design
What issues need to be considered
image
- Encoding and decoding of private protocols
Business specific encoding and decoding methods are required, as shown in the following examples
- Definition of command and selection of command processor
There are generally two kinds of protocol processes
- Load command
Transmit service specific data, such as request parameters and commands in response to results
- control command
It is generally a function management command, such as heartbeat command
- Command protocol
Serialization protocols are generally used. Different protocols have different coding efficiency and transmission efficiency, such as
image
- Communication mode
- oneway -- don't care about the response, and the request thread won't be blocked
- sync -- the call will be blocked until the result is returned
- future -- the county thread will not be blocked when calling, and the thread will be blocked when obtaining the result
- callback -- an asynchronous call that does not block threads
Error handling and timeout handling
Remote procedure calls have a higher probability of error than local procedure calls. Therefore, various scenarios of call failure need to be considered:
- What should I do if there is an error on the server
- An error or timeout occurs when the client requests a service. You need to set an appropriate retry mechanism
image
4. Simple GO language native RPC
It is roughly divided into the following four steps:
- Design data structure and method
- Implementation method
- Registration service
- The client connects to the server and calls the method of the server
Let's look at the case of how golang uses native rpc
rpc invocation and service monitoring
- RPC related content
- Data transmission: JSON Protobuf thrift
- Load: random algorithm polling consistency hash weighting
- Abnormal fault tolerance: health detection fuse current limiting
- Service monitoring
- Log collection
- Dot sampling
1. Introduction to RPC
- Remote Procedure Call (RPC) is a computer communication protocol
- The protocol allows a program running on one computer to call a subroutine of another computer without extra programming for this interaction
- If the software involved adopts object-oriented programming, remote procedure call can also be called remote call or remote method call
2. RPC call process
In general, we will call the function code directly locally. In the microservice architecture, we need to run this function as a separate service, and the client calls it through the network
- Under the microservice architecture, data interaction is generally internal RPC and external REST
- Splitting the business into micro services according to functional modules has the following advantages
- Improve project collaboration efficiency
- Reduce module coupling
- Improve system availability
- It has the following disadvantages:
- The development threshold is relatively high, such as the use of RPC framework and later service monitoring
3.rpc golang native processing method
The simplest use of golang native rpc
The official net/rpc Library of golang uses encoding/gob for encoding and decoding, and supports tcp and http data transmission
server1.go
package main
import (
"log"
"net/http"
"net/rpc"
)
type Happy struct{}
// Calculate happy
func (r *Happy) CalHappy(num int, ret *int) error {
*ret = num * 10
return nil
}
// Main function
func main() {
// new a service
ha := new(Happy)
// Register a Happy service
rpc.Register(ha)
// Service processing is bound to the http protocol
rpc.HandleHTTP()
// Listener Service
err := http.ListenAndServe(":9999", nil)
if err != nil {
log.Panicln(err)
}
}
client1.go
package main
import (
"fmt"
"log"
"net/rpc"
)
// Main function
func main() {
//Connect to remote rpc service
conn, err := rpc.DialHTTP("tcp", ":9999")
if err != nil {
log.Fatal(err)
}
// Call server method
ret := 0
err2 := conn.Call("Happy.CalHappy", 10, &ret)
if err2 != nil {
log.Fatal(err2)
}
fmt.Println("Happy index:", ret)
}
result
image
golang uses jsonrpc
Jsonrpc uses JSON for data encoding and decoding and supports cross language calls. The jsonrpc library is implemented based on tcp protocol and does not support http transmission temporarily
server2.go
package main
import (
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
type Happy struct{}
// Calculate happy
func (r *Happy) CalHappy(num int, ret *int) error {
*ret = num * 10
return nil
}
// Main function
func main() {
// new a service
ha := new(Happy)
// Register a Happy service
rpc.Register(ha)
// Listener Service
listen, err := net.Listen("tcp", ":9999")
if err != nil {
log.Panicln(err)
}
// Processing requests
for {
con, err := listen.Accept()
if err != nil {
continue
}
// A special collaboration process is opened to handle the corresponding request
go func(con net.Conn) {
fmt.Println("process new client")
jsonrpc.ServeConn(con)
}(con)
}
}
client2.go
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
)
// Main function
func main() {
//Connect to remote rpc service
conn, err := jsonrpc.Dial("tcp", ":9999")
if err != nil {
log.Fatal(err)
}
// Call server method
ret := 0
err2 := conn.Call("Happy.CalHappy", 10, &ret)
if err2 != nil {
log.Fatal(err2)
}
fmt.Println("Happy index:", ret)
}
golang native rpc custom protocol
For example, we customize the protocol. For a piece of data, the first two bytes are the data header, and the following is the real data, such as:
image
- Since the protocol is customized, we need to abide by our protocol when sending and reading data, otherwise there will be problems
- Then when we do data transmission, coding and decoding will be involved. We also need to encapsulate the functions of coding and decoding
Function encapsulation for writing data and reading data
// Write data
func MyWriteData(con net.Conn, data []byte) (int, error) {
if con == nil {
log.Fatal("con is nil")
}
buf := make([]byte, 2+len(data))
// Write the header first, and write the length of the real data into the header
binary.BigEndian.PutUint16(buf[:2], uint16(len(data)))
// Re write data
copy(buf[2:], data)
n, err := con.Write(buf)
if err != nil {
log.Fatal("Write error", err)
}
return n, nil
}
//Read data
func MyReadData(con net.Conn) ([]byte, error) {
if con == nil {
log.Fatal("con is nil")
}
// Protocol header 2 bytes
myheader := make([]byte, 2)
// Read 2-byte protocol header
_, err := io.ReadFull(con, myheader)
if err != nil {
log.Fatal("ReadFull error", err)
}
//Read real data
// Read the length of real data from the inside
len := binary.BigEndian.Uint16(myheader)
data := make([]byte, len)
_, err = io.ReadFull(con, data)
if err != nil {
log.Fatal("ReadFull error", err)
}
return data, nil
}
Write function encapsulation for encoding and decoding
We designed the binding method between the string command and the specific called function, so as to lay a good foundation for the implementation of server3.go rpc
// Specific data structure
type MyData struct {
Name string
MyArgs []interface{} // parameter list
}
// encryption
func MyEncode(data *MyData) ([]byte, error) {
if data == nil {
log.Fatal("con is nil")
}
var bb bytes.Buffer
buf := gob.NewEncoder(&bb)
if err := buf.Encode(data); err != nil {
log.Fatal("Encode error ", err)
}
return bb.Bytes(), nil
}
// decrypt
func MyDecode(data []byte) (MyData, error) {
if data == nil {
log.Fatal("con is nil")
}
buf := bytes.NewBuffer(data)
myDe := gob.NewDecoder(buf)
var res MyData
if err := myDe.Decode(&res); err != nil {
log.Fatal("Decode error ", err)
}
return res, nil
}
Integrating the above functions, the implementation of the server side_ server.go:
package main
import (
"bytes"
"encoding/binary"
"encoding/gob"
"fmt"
"io"
"log"
"net"
"reflect"
)
// Write data
func MyWriteData(con net.Conn, data []byte) (int, error) {
if con == nil {
log.Fatal("con is nil")
}
buf := make([]byte, 2+len(data))
// Write the header first, and write the length of the real data into the header
binary.BigEndian.PutUint16(buf[:2], uint16(len(data)))
// Re write data
copy(buf[2:], data)
n, err := con.Write(buf)
if err != nil {
log.Fatal("Write error", err)
}
return n, nil
}
//Read data
func MyReadData(con net.Conn) ([]byte, error) {
if con == nil {
log.Fatal("con is nil")
}
// Protocol header 2 bytes
myheader := make([]byte, 2)
// Read 2-byte protocol header
_, err := io.ReadFull(con, myheader)
if err != nil {
log.Fatal("ReadFull error", err)
}
//Read real data
// Read the length of real data from the inside
len := binary.BigEndian.Uint16(myheader)
data := make([]byte, len)
_, err = io.ReadFull(con, data)
if err != nil {
log.Fatal("ReadFull error", err)
}
return data, nil
}
// Specific data structure
type MyData struct {
Name string
MyArgs []interface{} // parameter list
}
// encryption
func MyEncode(data *MyData) ([]byte, error) {
if data == nil {
log.Fatal("con is nil")
}
var bb bytes.Buffer
buf := gob.NewEncoder(&bb)
if err := buf.Encode(data); err != nil {
log.Fatal("Encode error ", err)
}
return bb.Bytes(), nil
}
// decrypt
func MyDecode(data []byte) (MyData, error) {
if data == nil {
log.Fatal("con is nil")
}
buf := bytes.NewBuffer(data)
myDe := gob.NewDecoder(buf)
var res MyData
if err := myDe.Decode(&res); err != nil {
log.Fatal("Decode error ", err)
}
return res, nil
}
// A global map, corresponding to commands and functions
var myFun = make(map[string]reflect.Value)
// Register command binding functions
func MyRegister(name string, fn interface{}) {
if _, ok := myFun[name]; ok { // This indicates that the command has been bound to a function
return
}
myFun[name] = reflect.ValueOf(fn)
log.Println("reflect.ValueOf(fn) == ", myFun[name])
}
// Method of server execution
func MyRun(addr string) {
listen, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal("Listen is nil")
}
log.Println("Start the server....")
// Start blocking connections waiting for clients
for {
con, err := listen.Accept()
if err != nil {
log.Println("Accept is nil")
return
}
// Read data
b, err := MyReadData(con)
if err != nil {
log.Println("MyReadData error ", err)
return
}
log.Println("MyReadData =============== ")
// Parse data
my, err := MyDecode(b)
if err != nil {
log.Println("MyDecode =============== ")
log.Println("MyDecode error ", err)
return
}
f, ok := myFun[my.Name]
if !ok {
fmt.Printf("command %s No binding function\n", my.Name)
return
}
// Get parameters
args := make([]reflect.Value, 0, len(my.MyArgs))
for _, arg := range my.MyArgs {
args = append(args, reflect.ValueOf(arg))
log.Println("reflect.ValueOf(arg) - ", reflect.ValueOf(arg))
}
//reflex
res := f.Call(args)
log.Println("f.Call(args) == ", res)
// Packaging result data to the client
out := make([]interface{}, 0, len(res))
for _, arg := range res {
log.Println("arg == ", arg)
out = append(out, arg.Interface())
}
log.Println("out == ", out)
// Coded data
bb, err := MyEncode(&MyData{Name: my.Name, MyArgs: out})
if err != nil {
log.Println("MyEncode error ", err)
return
}
// Write data to client
_, err = MyWriteData(con, bb)
if err != nil {
log.Println("MyWriteData ======== ")
log.Println("MyWriteData error ", err)
return
}
}
}
// The client calls the function through the command
func CallRPCFun(con net.Conn, rpcName string, args interface{}) {
// Get the uninitialized function prototype of args through reflection
fn := reflect.ValueOf(args).Elem()
log.Println("fn == ", fn)
// Another function is required to operate on the first function parameter
f := func(args []reflect.Value) []reflect.Value {
// Processing parameters
inArgs := make([]interface{}, 0, len(args))
for _, arg := range args {
inArgs = append(inArgs, arg.Interface())
}
// connect
// Coded data
reqRPC := &MyData{Name: rpcName, MyArgs: inArgs}
b, err := MyEncode(reqRPC)
if err != nil {
log.Println("MyEncode =============== ")
log.Println("MyEncode error ", err)
}
// Write data
_, err = MyWriteData(con, b)
if err != nil {
log.Println("MyWriteData =============== ")
log.Fatal("MyWriteData error ", err)
}
// The return value sent by the server should be read and parsed at this time
respBytes, err := MyReadData(con)
if err != nil {
log.Fatal("MyReadData error ", err)
}
// decode
res, err := MyDecode(respBytes)
if err != nil {
log.Println("MyDecode =============== ")
log.Fatal("MyDecode error ", err)
}
// Process the data returned by the server
outArgs := make([]reflect.Value, 0, len(res.MyArgs))
for i, arg := range res.MyArgs {
// nil conversion is required
if arg == nil {
// reflect.Zero() returns the value of the zero value of the type
// . out() returns the parameter type of the function output
outArgs = append(outArgs, reflect.Zero(fn.Type().Out(i)))
continue
}
outArgs = append(outArgs, reflect.ValueOf(arg))
}
return outArgs
}
v := reflect.MakeFunc(fn.Type(), f)
// Assign value to function f
fn.Set(v)
}
// Define user objects
type Data struct {
CmdName string
Param string
}
// Method for testing user queries
func GetData(id int) (Data, error) {
data := make(map[int]Data)
// False data
data[0] = Data{"PullInfo", "xiaoxiong"}
data[1] = Data{"PutInfo", "daxiong"}
// query
if u, ok := data[id]; ok {
return u, nil
}
return Data{}, fmt.Errorf("%d err", id)
}
// Main function
func main() {
// Simply set the log parameter
log.SetFlags(log.Lshortfile | log.LstdFlags)
// rpc server
// Register when one of the fields in the code is interface {}
gob.Register(Data{})
addr := "127.0.0.1:9999"
// Create server
// Register the server method
MyRegister("GetData", GetData)
// Server waiting for call
go MyRun(addr)
//-------------I am the dividing line-----------
// rpc client get connection
conn, err := net.Dial("tcp", addr)
if err != nil {
fmt.Println("Dial err")
return
}
log.Println("The client succeeded in dialing and started calling the function...")
// Create client object
// A function prototype needs to be declared
var getdata func(int) (Data, error)
CallRPCFun(conn, "GetData", &getdata)
// Get query results
u, err := getdata(1)
if err != nil {
fmt.Println("getdata err")
return
}
log.Println(u)
select {}
}