рд╣рдорд╛рд░реА рдЯреАрдо рдХреЗ рднреАрддрд░ рдЬреНрдЮрд╛рди рд╣рд╕реНрддрд╛рдВрддрд░рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдиреАрдЪреЗ рджрд┐рдпрд╛ рдЧрдпрд╛ рдЧреЛрд▓рд╛рдВрдЧ рд╕рд░реНрд╡рд░ рдЯреЗрдореНрдкреНрд▓реЗрдЯ рддреИрдпрд╛рд░ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред рдЯреЗрдореНрдкрд▓реЗрдЯ рдХрд╛ рдореБрдЦреНрдп рд▓рдХреНрд╖реНрдп, рдкреНрд░рд╢рд┐рдХреНрд╖рдг рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдЧреЛ рдкрд░ рдЫреЛрдЯреЗ рд╕рд░реНрд╡рд░ рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рдкреНрд░реЛрдЯреЛрдЯрд╛рдЗрдк рдХреЗ рд▓рд┐рдП рд╕рдордп рдХрдо рдХрд░рдирд╛ рд╣реИред
рдЯреЗрдореНрдкрд▓реЗрдЯ рдХрд╛ рдкрд╣рд▓рд╛ рднрд╛рдЧ HTTP рд╕рд░реНрд╡рд░ рдХреЗ рд▓рд┐рдП рд╕рдорд░реНрдкрд┐рдд рдерд╛:
- рдХрдорд╛рдВрдб рд▓рд╛рдЗрди рдФрд░ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдлрд╝рд╛рдЗрд▓ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдПрдХ HTTP рд╕рд░реНрд╡рд░ рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдирд╛
- TLS HTTP рд╕рд░реНрд╡рд░ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░реЗрдВ
- рд░рд╛рдЙрдЯрд░ рд╕реЗрдЯрдЕрдк рдФрд░ HTTP рдФрд░ рдкреНрд░реЛрдлреЗрд╕рд░-рд╣реИрдВрдбрд▓рд░реНрд╕ рдХрд╛ рдкрдВрдЬреАрдХрд░рдг
- HTTP рдЯреНрд░реИрдлрд┐рдХ рд▓реЙрдЧрд┐рдВрдЧ, HTTP рдПрд░рд░ рд▓реЙрдЧрд┐рдВрдЧ рд╕реЗрдЯ рдХрд░рдирд╛
- HTTP рдмреЗрд╕рд┐рдХ рдФрд░ рдПрдордПрд╕ рд╡рд┐рдЬреНрдЮрд╛рдкрди рдкреНрд░рдорд╛рдгреАрдХрд░рдг, JSON рд╡реЗрдм рдЯреЛрдХрди
- рддреНрд░реБрдЯрд┐ рдЪреИрдирд▓ рдкрд░ рд▓реМрдЯрдиреЗ рдХреА рдЙрдореНрдореАрдж рдХреЗ рд╕рд╛рде рд╕рд░реНрд╡рд░ рд╢реБрд░реВ рдХрд░рдирд╛
- рд╕рд░реНрд╡рд░ рдФрд░ рд╕рдВрдмрдВрдзрд┐рдд рд╕реЗрд╡рд╛рдУрдВ рдХреЛ рд╕рд╣реА рдврдВрдЧ рд╕реЗ рдмрдВрдж рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рдВрджрд░реНрдн рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛
- рдХрд╕реНрдЯрдо рдПрд░рд░ рд╣реИрдВрдбрд▓рд┐рдВрдЧ рдФрд░ рдХрд╕реНрдЯрдо рд▓реЙрдЧрд┐рдВрдЧ
- рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рд╕рд╛рде рдХреЛрдб рдЕрд╕реЗрдВрдмрд▓реА, рджрд┐рдирд╛рдВрдХ рдФрд░ рдкреНрд░рддрд┐рдмрджреНрдз рдмрдирд╛рдПрдБ
рдЯреЗрдореНрдкрд▓реЗрдЯ рдХрд╛ рджреВрд╕рд░рд╛ рднрд╛рдЧ REST API рдХреЗ рдкреНрд░реЛрдЯреЛрдЯрд╛рдЗрдк рдХреЗ рд▓рд┐рдП рд╕рдорд░реНрдкрд┐рдд рд╣реИред рдкреНрд░реЛрдЬреЗрдХреНрдЯ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА
рдХрд╛ рд▓рд┐рдВрдХ рд╡рд╣реА рд░рд╣рддрд╛ рд╣реИред

рд╕реНрдЯреИрдВрдб рдкрд░ рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЗ рдкрд░реАрдХреНрд╖рдг рдХреЗ рджреМрд░рд╛рди , рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдкрд░рд┐рдгрд╛рдо рдкреНрд░рд╛рдкреНрдд рдХрд┐рдП рдЧрдП рдереЗ ред
- PostgreSQL рд╕реЗ рд╕реАрдзреЗ рд░реАрдбрд┐рдВрдЧ рдореЛрдб рдореЗрдВ - 16,000 рддрдХ [рдкреНрд░рд╛рдкреНрдд / рд╕реЗрдХ], рд╕рдВрдХреНрд╖рд┐рдкреНрддрддрд╛ 1024, рдордВрдЭрд▓рд╛ 60 [рдПрдордПрд╕]ред рдкреНрд░рддреНрдпреЗрдХ 360,000,000 рдкрдВрдХреНрддрд┐рдпреЛрдВ рдХреЗ рдХреБрд▓ рдЖрдХрд╛рд░ рдХреЗ рд╕рд╛рде рджреЛ рддрд╛рд▓рд┐рдХрд╛рдУрдВ рд╕реЗ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХрд╛ рдЕрдиреБрд░реЛрдз рдХрд░рддрд╛ рд╣реИред JSON рдХрд╛ рдЖрдХрд╛рд░ 1800 рдмрд╛рдЗрдЯреНрд╕ рд╣реИред
- 100 000 тАФ 120 000 [get/sec], oncurrency 1024, 2 [ms]. Transfer rate 200-250 [MB/sec]
- PostgreSQL тАФ 10 000 [insert/sec].
- ,
- Marshal / Unmarshal JSON
- SQLXX
- DB
- JSON
- Marshal / Unmarshal JSON
- Unmarshal JSON
- JSON
- HTTP
1.
.
- Model :
- Marshal / Unmarshal JSON
- -
- SQLXX :
- DB :
- JSON JSON :
- Marshal / Unmarshal JSON
- JSON
- HTTP HTTP :
- HTTP
- HTTP
- (basic, MS AD, ...)
- token
- (body)
- (header)
- HSTS Strict-Transport-Security
- Content-Type (response)
- HTTP
- (header)
- defer recovery panic
REST API : FaaS, , gRPC.
2 Model
2.1. ,
, "Department" "Employee". .
, JOSN .
(-).
type Dept struct {
Deptno int `db:"deptno" json:"deptNumber" validate:"required"`
Dname string `db:"dname" json:"deptName" validate:"required"`
Loc null.String `db:"loc" json:"deptLocation,nullempty"`
Emps []*Emp `json:"emps,omitempty"`
}
type Emp struct {
Empno int `db:"empno" json:"empNo" validate:"required"`
Ename null.String `db:"ename" json:"empName,nullempty"`
Job null.String `db:"job" json:"job,nullempty"`
Mgr null.Int `db:"mgr" json:"mgr,omitempty"`
Hiredate null.String `db:"hiredate" json:"hiredate,nullempty"`
Sal null.Int `db:"sal" json:"sal,nullempty" validate:"gte=0"`
Comm null.Int `db:"comm" json:"comm,nullempty" validate:"gte=0"`
Deptno null.Int `db:"deptno" json:"deptNumber,nullempty"`
}
.
( ).
, , - (, ). "" .
REST API , : Get, Create, Update.
context.Context тАФ " ".
, , , , , . sync.Pool.
type DeptService interface {
GetDept(ctx context.Context, out *Dept) (bool, error)
GetDeptsPK(ctx context.Context, out *DeptPKs) error
CreateDept(ctx context.Context, in *Dept, out *Dept) error
UpdateDept(ctx context.Context, in *Dept, out *Dept) (bool, error)
}
type EmpService interface {
GetEmp(ctx context.Context, out *Emp) (bool, error)
GetEmpsByDept(ctx context.Context, in *Dept, out *EmpSlice) error
CreateEmp(ctx context.Context, in *Emp, out *Emp) error
UpdateEmp(ctx context.Context, in *Emp, out *Emp) (bool, error)
}
2.1.2
IBM MQ.
type Request struct {
QObject string
QueueName string
QueueOptions int32
QueueWaitInterval int
MsgIdHex string
CorrelIdHex string
MesFormat string
MesSyncpoint int32
MesOptions int32
BrowseOptions int32
Bufsize int
Buf []byte `json:"-"`
}
type Response struct {
QObject string
QueueName string
MsgId []byte
MsgIdHex string
CorrelId []byte
CorrelIdHex string
PutDate string
PutTime string
DataLen int
ErrCode string
ErrMes string
ErrTrace string
CauseErrCode string
CauseErrMes string
Buf []byte
}
IBM MQ.
type QueueService interface {
OpenQueue(ctx context.Context, req *Request, resp *Response) error
CloseQueue(ctx context.Context, req *Request, resp *Response) error
BrowseMessage(ctx context.Context, req *Request, resp *Response) error
BrowseFirstMessage(ctx context.Context, req *Request, resp *Response) error
BrowseNextMessage(ctx context.Context, req *Request, resp *Response) error
BrowseUnderMessage(ctx context.Context, req *Request, resp *Response) error
GetMessage(ctx context.Context, req *Request, resp *Response) error
DeleteUnderMessage(ctx context.Context, req *Request, resp *Response) error
PutMessage(ctx context.Context, req *Request, resp *Response) error
RegisterCallback(ctx context.Context, req *Request, resp *Response) error
DeregisterCallback(ctx context.Context, req *Request, resp *Response) error
}
2.2.
, :
- тАФ , . , "" > 0, "" not null.
- тАФ , " " >= " "
- () тАФ , " " >= " "
- тАФ , (PK, UK), .
- тАФ , (FK), .
- тАФ , .
1, 2 3 . 4, 5, 6 , , .
.
, , , .
- .
- " API". , DML . , API. , Oracle , API.
- (View API), , .
- Instead of Trigger DML API.
, тАФ DML . ( Oracle ). API, .
.
, 1, 2 3 JSON .
3 1, 2 3 gopkg.in/go-playground/validator.
4 (PK, UK).
5 6 .
2.3.
REST API (JSON->Model->DB) (). , , JSON . API, (GC). ( GC).
GC тАФ sync.Pool. . тАФ sync.Pool.
, тАФ sync.Pool, , , , .
sync.Pool :
Dept
var deptsPool = sync.Pool{
New: func() interface{} { return new(Dept) },
}
func GetDept() *Dept {
p := deptsPool.Get().(*Dept)
p.Reset()
return p
}
func PutDept(p *Dept, isCascad bool) {
if p != nil {
PutEmpSlice(p.Emps, isCascad)
p.Emps = nil
deptsPool.Put(p)
}
}
func (p *Dept) Reset() {
p.Deptno = 0
p.Dname = ""
p.Loc.String = ""
p.Loc.Valid = false
if p.Emps != nil {
EmpSlice(p.Emps).Reset()
}
p.Emps = nil
}
type EmpSlice []*Emp
func PutEmpSlice(p EmpSlice) {
if p != nil {
for i := range p {
p[i] = nil
}
p = p[:0]
empSlicePool.Put(p)
}
}
, , 1000000 Get , 500 . .
, sync.Pool CPU, sync.Pool .
dept emp , .
2.4. Marshal / Unmarshal JSON
encoding/json .
:
. .
:
github.com/mailru/easyjson.
, , / JSON .
type Dept struct {
Deptno int `db:"deptno" json:"deptNumber" validate:"required"`
Dname string `db:"dname" json:"deptName" validate:"required"`
Loc null.String `db:"loc" json:"deptLocation,nullempty"`
Emps []*Emp `json:"emps,omitempty"`
}
, easyjson NULL , gopkg.in/guregu/null.v4. , NULL , , "Loc null.String" "Loc sql.NullString".
.
easyjson model.go
Marshal / Unmarshal , JSON.
3. SQLXX
SQLXX :
- , , Listen/notify PostgreSQL (jackc/pgx/stdlib lib/pq)
, .
, , SQL .
httpserver/sqlxx jmoiron/sqlx.
type DB struct {
*sqlx.DB
cfg *Config
sqlStms SQLStms
}
type Tx struct {
*sqlx.Tx
}
, , jmoiron/sqlx, , jackc/pgx.
3.1.
jmoiron/sqlx , : Beginx, Rollback, Commit, Select, Get, Exec.
.
INFO trace. , , , , .
func Commit(reqID uint64, tx *Tx) (myerr error) {
defer func() {
r := recover()
if r != nil {
msg := "PostgreSQL recover from panic: commit transaction: reqID"
switch t := r.(type) {
case string:
myerr = myerror.New("4008", msg, reqID, t).PrintfInfo()
case error:
myerr = myerror.WithCause("4006", msg, t, reqID).PrintfInfo()
default:
myerr = myerror.New("4006", msg, reqID).PrintfInfo()
}
}
}()
if tx == nil {
return myerror.New("4004", "Transaction is not defined: reqID", reqID).PrintfInfo()
}
if err := tx.Commit(); err != nil {
return myerror.WithCause("4008", "Error commit the transaction: reqID", err, reqID).PrintfInfo()
}
return nil
}
interface{}, , , , .
func Select(reqID uint64, tx *Tx, sqlStm *SQLStm, dest interface{}, args ...interface{}) (myerr error) {
if dest != nil && !reflect.ValueOf(dest).IsNil() {
}
return myerror.New("4400", "Incorrect call - nil dest interface{} pointer: reqID", reqID).PrintfInfo()
}
reflect.ValueOf(in).IsNil() , .
3.2.
. .
type Config struct {
ConnectString string
Host string
Port string
Dbname string
SslMode string
User string
Pass string
ConnMaxLifetime int
MaxOpenConns int
MaxIdleConns int
DriverName string
}
3.3. SQL
SQLXX SQL interface{}.
, SQL .
SQLXX SQL . SQL , , ( SQL ).
type SQLStm struct {
Text string
Stmt *sqlx.Stmt
IsPrepare bool
}
type SQLStms map[string]*SQLStm
4. DB
DB :
4.1. , SQL
, (-) :
- (PK) тАФ API. GUID, , BTree .
- (UK), , " ". UK .
- (FK) PK. (FK), FK API . FK.
dept emp, , тАФ .
DB SQL .
SQL : "GetDept", "GetDeptUK", "DeptExists" .. , SQL.
, SQL :
- PK, , "GetDept"
- UK, , "GetDeptUK"
- PK UK, , "DeptExists"
, View API. SQL REST API . .
service.SQLStms = map[string]*sql.SQLStm{
"GetDept": &sql.SQLStm{"SELECT deptno, dname, loc FROM dept WHERE deptno = $1", nil, true},
"GetDeptUK": &sql.SQLStm{"SELECT deptno, dname, loc FROM dept WHERE deptno = $1", nil, true},
"DeptExists": &sql.SQLStm{"SELECT 1 FROM dept WHERE deptno = $1", nil, true},
"GetDepts": &sql.SQLStm{"SELECT deptno, dname, loc FROM dept", nil, true},
"GetDeptsPK": &sql.SQLStm{"SELECT deptno FROM dept", nil, true},
"CreateDept": &sql.SQLStm{"INSERT INTO dept (deptno, dname, loc) VALUES (:deptno, :dname, :loc)", nil, false},
"UpdateDept": &sql.SQLStm{"UPDATE dept SET dname = :dname, loc = :loc WHERE deptno = :deptno", nil, false},
"EmpExists": &sql.SQLStm{"SELECT 1 FROM emp WHERE empno = $1", nil, true},
"GetEmp": &sql.SQLStm{"SELECT empno, ename, job, mgr, hiredate, sal, comm, deptno FROM emp WHERE empno = $1", nil, true},
"GetEmpUK": &sql.SQLStm{"SELECT empno, ename, job, mgr, hiredate, sal, comm, deptno FROM emp WHERE empno = $1", nil, true},
"GetEmpsByDept": &sql.SQLStm{"SELECT empno, ename, job, mgr, hiredate, sal, comm, deptno FROM emp WHERE deptno = $1", nil, true},
"GetEmpsPKByDept": &sql.SQLStm{"SELECT empno FROM emp WHERE deptno = $1", nil, true},
"CreateEmp": &sql.SQLStm{"INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno) VALUES (:empno, :ename, :job, :mgr, :hiredate, :sal, :comm, :deptno)", nil, false},
"UpdateEmp": &sql.SQLStm{"UPDATE emp SET empno = :empno, ename = :ename, job = :job, mgr = :mgr, hiredate = :hiredate, sal = :sal, comm = :comm, deptno = :deptno WHERE empno = :empno", nil, false},
}
, SQL "".
4.2.
:
4.2.1 Get
Get . , .
func (s *Service) getDept(ctx context.Context, tx *mysql.Tx, out *model.Dept) (exists bool, myerr error) {
if exists, myerr = s.db.Get(reqID, tx, "GetDept", out, out.Deptno); myerr != nil {
return false, myerr
}
if exists {
outEmps := model.GetEmpSlice()
if myerr = s.getEmpsByDept(ctx, tx, out, &outEmps); myerr != nil {
return false, myerr
}
out.Emps = outEmps
}
return exists, nil
}
Get .
func (s *Service) GetDept(ctx context.Context, out *model.Dept) (exists bool, myerr error) {
return s.getDept(ctx, nil, out)
}
4.2.2 Create
Create :
- newDept, model.GetDept(). , .
- . UK ( Dept ).
- Get(reqID, tx, "DeptExists", &foo, in.Deptno), тАФ UK. args ...interface{}, .
- , тАФ . .
- PK
- . , SQL / 1 . , .
- API, , / PK, , Get(reqID, tx, "GetDeptUK", newDept, in.Deptno) UK.
- , FK newEmp.Deptno = null.Int{sql.NullInt64{int64(newDept.Deptno), true}} createEmp(ctx, tx, newEmp, nil).
- , .
- тАФ PK
- тАФ JSON .
- HTTP .
- , nil out.
func (s *Service) createDept(ctx context.Context, tx *mysql.Tx, in *model.Dept, out *model.Dept) (myerr error) {
newDept := model.GetDept()
defer model.PutDept(newDept, true)
{
var foo int
exists, myerr := s.db.Get(reqID, tx, "DeptExists", &foo, in.Deptno)
if myerr != nil {
return myerr
}
if exists {
return myerror.New("4004", "Error create - row already exists: reqID, Deptno", reqID, in.Deptno).PrintfInfo()
}
}
{
rows, myerr := s.db.Exec(reqID, tx, "CreateDept", in)
if myerr != nil {
return myerr
}
if rows != 1 {
return myerror.New("4004", "Error create: reqID, Deptno, rows", reqID, in.Deptno, rows).PrintfInfo()
}
exists, myerr := s.db.Get(reqID, tx, "GetDeptUK", newDept, in.Deptno)
if myerr != nil {
return myerr
}
if !exists {
return myerror.New("4004", "Row does not exists after creating: reqID, Deptno", reqID, in.Deptno).PrintfInfo()
}
}
{
if in.Emps != nil {
for _, newEmp := range in.Emps {
newEmp.Deptno = null.Int{sql.NullInt64{int64(newDept.Deptno), true}}
if myerr = s.createEmp(ctx, tx, newEmp, nil); myerr != nil {
return myerr
}
}
}
}
if out != nil {
out.Deptno = newDept.Deptno
exists, myerr := s.getDept(ctx, tx, out)
if myerr != nil {
return myerr
}
if !exists {
return myerror.New("4004", "Row does not exists after updating: reqID, PK", reqID, in.Deptno).PrintfInfo()
}
}
return nil
}
Create :
func (s *Service) CreateDept(ctx context.Context, in *model.Dept, out *model.Dept) (myerr error) {
var tx *mysql.Tx
reqID := myctx.FromContextRequestID(ctx)
if tx, myerr = s.db.Beginx(reqID); myerr != nil {
return myerr
}
if myerr = s.createDept(ctx, tx, in, out); myerr != nil {
_ = s.db.Rollback(reqID, tx)
return myerr
}
return s.db.Commit(reqID, tx)
}
4.2.3 Update
Update :
- oldDept newDept, .
- PK.
- . , SQL 1 .
- API, , / , , P Get(reqID, tx, "GetDept", newDept, in.Deptno).
- , FK updateEmp(ctx, tx, inEmp, nil).
- ,
- тАФ PK
func (s *Service) updateDept(ctx context.Context, tx *mysql.Tx, in *model.Dept, out *model.Dept) (exists bool, myerr error) {
oldDept := model.GetDept()
defer model.PutDept(oldDept, true)
newDept := model.GetDept()
defer model.PutDept(newDept, true)
{
oldDept.Deptno = in.Deptno
if exists, myerr = s.getDept(ctx, tx, oldDept); myerr != nil {
return false, myerr
}
if !exists {
mylog.PrintfDebugMsg("Row does not exists: reqID, PK", reqID, in.Deptno)
return false, nil
}
}
{
if oldDept.Deptno != in.Deptno {
var foo int
exists, myerr := s.db.Get(reqID, tx, "DeptExists", &foo, in.Deptno)
if myerr != nil {
return false, myerr
}
if exists {
return false, myerror.New("4004", "Error update - row with UK already exists: reqID, Deptno", reqID, in.Deptno).PrintfInfo()
}
}
}
{
rows, myerr := s.db.Exec(reqID, tx, "UpdateDept", in)
if myerr != nil {
return false, myerr
}
if rows != 1 {
return false, myerror.New("4004", "Error update: reqID, Deptno, rows", reqID, in.Deptno, rows).PrintfInfo()
}
exists, myerr := s.db.Get(reqID, tx, "GetDept", newDept, in.Deptno)
if myerr != nil {
return false, myerr
}
if !exists {
return false, myerror.New("4004", "Row does not exists after creating: reqID, PK", reqID, in.Deptno).PrintfInfo()
}
}
{
if in.Emps != nil {
for _, inEmp := range in.Emps {
inEmp.Deptno = null.Int{sql.NullInt64{int64(in.Deptno), true}}
if exists, myerr = s.updateEmp(ctx, tx, inEmp, nil); myerr != nil {
return false, myerr
}
if !exists {
if myerr = s.createEmp(ctx, tx, inEmp, nil); myerr != nil {
return false, myerr
}
}
}
}
}
if out != nil {
out.Deptno = in.Deptno
if exists, myerr = s.getDept(ctx, tx, out); myerr != nil {
return false, myerr
}
if !exists {
return false, myerror.New("4004", "Row does not exists after updating: reqID, PK", reqID, in.Deptno).PrintfInfo()
}
}
return true, nil
}
Update :
- rollback commit
- , exists = false
func (s *Service) UpdateDept(ctx context.Context, in *model.Dept, out *model.Dept) (exists bool, myerr error) {
var tx *mysql.Tx
reqID := myctx.FromContextRequestID(ctx)
if tx, myerr = s.db.Beginx(reqID); myerr != nil {
return false, myerr
}
if exists, myerr = s.updateDept(ctx, tx, in, out); myerr != nil {
_ = s.db.Rollback(reqID, tx)
return false, myerr
}
if !exists {
_ = s.db.Rollback(reqID, tx)
return false, nil
}
if myerr = s.db.Commit(reqID, tx); myerr != nil {
return false, myerr
}
return exists, nil
}
4.3
, .
. , backend , . / , .
3 dgraph-io/ristretto. PostgreSQL listen/notify.
5. JSON
JSON :
- Marshal / Unmarshal JSON
- JSON
JSON . - ( ).
JSON HTTP. , . []byte, JSON.
5.1. Marshal JSON
encode JSON github.com/mailru/easyjson .
, encode JSON Dept:
- HTTP JSON buf []byte
- , w.Buffer.BuildBytes(buf) ,
- HTTP
func deptMarshal(reqID uint64, v *model.Dept, buf []byte) (outBuf []byte, myerr error) {
w := jwriter.Writer{}
v.MarshalEasyJSON(&w)
if w.Error != nil {
return nil, myerror.WithCause("6001", "Error Marshal: reqID", w.Error, reqID).PrintfInfo(1)
}
return w.Buffer.BuildBytes(buf), nil
}
5.2. Unmarshal JSON
Decode JSON :
if err := vIn.UnmarshalJSON(inBuf); err != nil {
return 0, nil, myerror.WithCause("6001", "Error Unmarshal: reqID, buf", err, reqID, string(inBuf)).PrintfInfo()
}
. mailru/easyjson decode , . make new, "" .
:
- out.Emps = make([]*Emp, 0, 8) out.Emps = []*Emp(GetEmpSlice())
- v1 = new(Emp) v1 = GetEmp()
5.3.
5.3.1. Get
Get :
- : id тАФ PK, , buf тАФ JSON
- : outBuf JSON ( buf)
- JSON . vOut := model.GetDept() ( Dept тАФ , Emp тАФ ) defer model.PutDept(vOut, true). .
func (s *Service) GetDept(ctx context.Context, id int, buf []byte) (outBuf []byte, myerr error) {
vOut := model.GetDept()
defer model.PutDept(vOut, true)
vOut.Deptno = id
exists, myerr := s.deptService.GetDept(ctx, vOut)
if myerr != nil {
return nil, myerr
}
if exists {
return deptMarshal(reqID, vOut, buf)
}
return nil, nil
}
5.3.2. Create
Create :
func (s *Service) CreateDept(ctx context.Context, inBuf []byte, buf []byte) (id int, outBuf []byte, myerr error) {
vIn := model.GetDept()
defer model.PutDept(vIn, true)
vOut := model.GetDept()
defer model.PutDept(vOut, true)
if err := vIn.UnmarshalJSON(inBuf); err != nil {
return 0, nil, myerror.WithCause("6001", "Error Unmarshal: reqID, buf", err, reqID, string(inBuf)).PrintfInfo()
}
if myerr = s.deptService.CreateDept(ctx, vIn, vOut); myerr != nil {
return 0, nil, myerr
}
if outBuf, myerr = deptMarshal(reqID, vOut, buf); myerr != nil {
return 0, nil, myerr
}
return vOut.Deptno, outBuf, myerr
}
5.3.3. Update
Update:
- id тАФ PK .
- outBuf NO_DATA_FOUND
func (s *Service) UpdateDept(ctx context.Context, id int, inBuf []byte, buf []byte) (outBuf []byte, myerr error) {
vIn := model.GetDept()
defer model.PutDept(vIn, true)
vOut := model.GetDept()
defer model.PutDept(vOut, true)
if err := vIn.UnmarshalJSON(inBuf); err != nil {
return nil, myerror.WithCause("6001", "Error Unmarshal: reqID, buf", err, reqID, string(inBuf)).PrintfInfo()
}
if id != vIn.Deptno {
return nil, myerror.New("6001", "Resource ID does not corespond to JSON: resource.id, json.Deptno", id, vIn.Deptno).PrintfInfo()
}
exists, myerr := s.deptService.UpdateDept(ctx, vIn, vOut)
if myerr != nil {
return nil, myerr
}
if exists {
return deptMarshal(reqID, vOut, buf)
}
return nil, nil
}
5.4. JSON
:
3 JSON go.etcd.io/bbolt. PostgreSQL listen/notify.
6. HTTP
HTTP :
- HTTP
- HTTP
- (basic, MS AD, ...)
- token ( )
- (body)
- (header)
- HSTS Strict-Transport-Security
- Content-Type (response)
- HTTP
- (header)
- defer recovery panic
. HTTP HTTP Golang.
, , :
- defer . UML тАФ RecoverWrap.func1, .
- process. HTTP handler. UML process тАФ .
- HTTP handler. UML EchoHandler.func1 тАФ .
. process, REST API.
( ).

6.1.
JSON, []byte http.ResponseWriter . GC. GC .
- JSON http.ResponseWriter
- JSON http.ResponseWriter
.
, sync.Pool:
:
[HTTP_POOL]
UseBufPool = true
BufPooledSize = 512
BufPooledMaxSize = 32768
process .
var buf []byte
if s.cfg.UseBufPool && s.bytesPool != nil {
buf = s.bytesPool.GetBuf()
}
, , encode JSON .
, JSON .
[]byte .
if responseBuf != nil && s.cfg.UseBufPool && s.bytesPool != nil {
if cap(responseBuf) >= s.cfg.BufPooledSize && cap(responseBuf) <= s.cfg.BufPooledMaxSize {
defer s.bytesPool.PutBuf(responseBuf)
}
}
, . Pointer() []byte.
if reflect.ValueOf(buf).Pointer() != reflect.ValueOf(responseBuf).Pointer() {
}
6.2. GET
process, :
- URL ID тАФ PK
- JSON jsonService.GetDept(ctx, id, buf), buf .
- . , header["RequestID"]
func (s *Service) GetDeptHandler(w http.ResponseWriter, r *http.Request) {
_ = s.process("GET", w, r, func(ctx context.Context, requestBuf []byte, buf []byte) ([]byte, Header, int, error) {
reqID := myctx.FromContextRequestID(ctx)
vars := mux.Vars(r)
idStr := vars["id"]
id, err := strconv.Atoi(idStr)
if err != nil {
return nil, nil, http.StatusBadRequest, myerror.WithCause("8001", "Failed to process parameter 'id' invalid number: reqID, id", err, reqID, idStr).PrintfInfo()
}
responseBuf, err := s.jsonService.GetDept(ctx, id, buf)
if err != nil {
return nil, nil, http.StatusInternalServerError, err
}
if responseBuf == nil {
return nil, nil, http.StatusNotFound, nil
}
header := Header{}
header["Content-Type"] = "application/json; charset=utf-8"
header["Errcode"] = "0"
header["RequestID"] = fmt.Sprintf("%v", reqID)
return responseBuf, header, http.StatusOK, nil
})
}
6.3. POST
POST :
func (s *Service) CreateDeptHandler(w http.ResponseWriter, r *http.Request) {
_ = s.process("POST", w, r, func(ctx context.Context, requestBuf []byte, buf []byte) ([]byte, Header, int, error) {
reqID := myctx.FromContextRequestID(ctx)
mylog.PrintfDebugMsg("START: reqID", reqID)
id, responseBuf, err := s.jsonService.CreateDept(ctx, requestBuf, buf)
if err != nil {
return nil, nil, http.StatusInternalServerError, err
}
header := Header{}
header["Content-Type"] = "application/json; charset=utf-8"
header["Errcode"] = "0"
header["Id"] = fmt.Sprintf("%v", id)
header["RequestID"] = fmt.Sprintf("%v", reqID)
return responseBuf, header, http.StatusOK, nil
})
}
6.4. PUT
PUT :
func (s *Service) UpdateDeptHandler(w http.ResponseWriter, r *http.Request) {
_ = s.process("PUT", w, r, func(ctx context.Context, requestBuf []byte, buf []byte) ([]byte, Header, int, error) {
reqID := myctx.FromContextRequestID(ctx)
mylog.PrintfDebugMsg("START: reqID", reqID)
vars := mux.Vars(r)
idStr := vars["id"]
id, err := strconv.Atoi(idStr)
if err != nil {
return nil, nil, http.StatusBadRequest, myerror.WithCause("8001", "Failed to process parameter 'id' invalid number: reqID, id", err, reqID, idStr).PrintfInfo()
}
responseBuf, err := s.jsonService.UpdateDept(ctx, id, requestBuf, buf)
if err != nil {
return nil, nil, http.StatusInternalServerError, err
}
if responseBuf == nil {
return nil, nil, http.StatusNotFound, nil
}
header := Header{}
header["Content-Type"] = "application/json; charset=utf-8"
header["Errcode"] = "0"
header["RequestID"] = fmt.Sprintf("%v", reqID)
return responseBuf, header, http.StatusOK, nil
})
}
6.5.
, httpserver/httpservice Handlers.
type Handler struct {
Path string
HundlerFunc func(http.ResponseWriter, *http.Request)
Method string
}
type Handlers map[string]Handler
httpservice.New. , service.recoverWrap().
service.Handlers = map[string]Handler{
"EchoHandler": Handler{"/echo", service.recoverWrap(service.EchoHandler), "POST"},
"SinginHandler": Handler{"/signin", service.recoverWrap(service.SinginHandler), "POST"},
"JWTRefreshHandler": Handler{"/refresh", service.recoverWrap(service.JWTRefreshHandler), "POST"},
"HTTPLogHandler": Handler{"/httplog", service.recoverWrap(service.HTTPLogHandler), "POST"},
"HTTPErrorLogHandler": Handler{"/httperrlog", service.recoverWrap(service.HTTPErrorLogHandler), "POST"},
"LogLevelHandler": Handler{"/loglevel", service.recoverWrap(service.LogLevelHandler), "POST"},
"CreateDeptHandler": Handler{"/depts", service.recoverWrap(service.CreateDeptHandler), "POST"},
"GetDeptHandler": Handler{"/depts/{id:[0-9]+}", service.recoverWrap(service.GetDeptHandler), "GET"},
"UpdateDeptHandler": Handler{"/depts/{id:[0-9]+}", service.recoverWrap(service.UpdateDeptHandler), "PUT"},
}
7.
7.1.
Oracle 3- :
- VM.DenseIO2.8 тАФ 8 Core Intel (16 virtual core), 120 Memory (GB), Oracle Linux 7.7
- NVMe тАФ 6.4 TB NVMe SSD Storage (1 drives) min 250k IOPS (4k block)
- тАФ 8.2 Network Bandwidth (Gbps)
- тАФ PostgreSQL 12. .
- ApacheBench:
- concurrency level 4 16384
- 10 000 1 000 000 .
- 2 8 ApacheBench ( ApacheBench 1 )

7.2. GET
- JSON тАФ 1800 . Dept 10 Emp.
- Dept тАФ 33 000 000, Emp тАФ 330 000 000 .
:
- GET PostgreSQL
- GET JSON BBolt ( 74 )
- GET ( map)
.
Requests per second Concurrency.

рдкреНрд░рддрд┐ рдирд┐рд░реНрднрд░рддрд╛ рдЧреНрд░рд╛рдл рдкреНрд░рддрд┐ рд╕рдордпред [ms] рдХрдВрд╕реАрдбрд░ рд╕реЗ (рд▓реЙрдЧрд░рд┐рджрдорд┐рдХ рд╕реНрдХреЗрд▓)ред

99% рдкреНрд░рддрд┐рд╢рдд рдХрд╛ рдЧреНрд░рд╛рдл [ms] рдмрдирд╛рдо рдХрдВрдЬреЗрдВрд╕реАред

99% рдкреНрд░рддрд┐рд╢рдд рдХрд╛ рдЧреНрд░рд╛рдл [ms] рдмрдирд╛рдо рдХрдВрдЬреЗрдВрд╕реА (рд▓рдШреБрдЧрдгрдХ рдкреИрдорд╛рдиреЗ)ред

рдпрд╣ рдХреИрд╕реЗ 100,000 [# / sec] рдкрд░ рдмреИрдХрдПрдВрдб рд╕рд░реНрд╡рд░ рд▓реЛрдб рдХрд░ рд░рд╣рд╛ рд╣реИ рдФрд░ BBolt (Concurrency 128) рдореЗрдВ рдХреИрд╢рд┐рдВрдЧ рдбреЗрдЯрд╛ рджрд┐рдЦрддрд╛ рд╣реИред рдПрдХ NVMe рдбреНрд░рд╛рдЗрд╡ рдкрд░ рдФрд╕рдд I / O рд╡рд┐рд▓рдВрдмрддрд╛ 0.02 [ms] (500,000 IOPS) рд╣реИред
