Compare commits

...

43 Commits

Author SHA1 Message Date
Zhang Hao
17a0908a84 add test (#95) 2020-09-22 19:15:30 +08:00
Keson
9f9c24cce9 fix bug: release empty struct limit (#96) 2020-09-22 19:13:46 +08:00
kingxt
b628bc0086 goctl support import api file (#94)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl support import

* goctl support import

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-22 18:32:26 +08:00
kevin
be9c48da7f add tracing logs in server side and client side 2020-09-22 17:34:39 +08:00
kevin
797a90ae7d remove unnecessary tag 2020-09-21 22:41:14 +08:00
kevin
92e60a5777 use options instead of opts in error message 2020-09-21 22:37:07 +08:00
miaogaolin
46995a4d7d 修改不能编辑代码注释 (#92)
* rename file and function name

* update comments of "code generate"
2020-09-21 18:27:35 +08:00
kingxt
5e6dcac734 feature: goctl jwt (#91)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-21 16:38:23 +08:00
dylanNew
3e7e466526 fix redis error (#88)
Co-authored-by: dylan <wangdi@xiaoheiban.cn>
2020-09-21 16:37:40 +08:00
kingxt
b6b8941a18 update doc (#90)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* update jwt doc

* update jwt doc

* update jwt doc

* update jwt doc

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-21 16:09:02 +08:00
kingxt
878fd14739 remove no need (#87)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* add jwt doc

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-21 14:29:12 +08:00
kevin
5e99f2b85d add trace/span in http logs 2020-09-20 22:02:45 +08:00
Howie
9c23399c33 chore: fix typos (#85)
* chore: fix typos

Signed-off-by: lihaowei <haoweili35@gmail.com>

* chore: fix 2 typos
2020-09-20 14:00:31 +08:00
kevin
86d3de4c89 use package level defined contextKey as context key 2020-09-20 12:46:35 +08:00
kevin
dc17855367 printing context key friendly 2020-09-20 12:08:30 +08:00
kevin
1606a92c6e use contextType as string type 2020-09-20 12:04:49 +08:00
mlboy
029fd3ea35 fix: golint: context.WithValue should should not use basic type as key (#83)
* fix: golint: context.WithValue should should not use basic type as key

* optimiz
2020-09-20 12:01:43 +08:00
kevin
57299a7597 rename ngin to rest in goctl 2020-09-20 09:15:19 +08:00
Changkun Ou
762af9dda2 optimize AtomicError (#82)
This commit optimize AtomicError using atomic.Value. Benchmarks:

name               old time/op  new time/op  delta
AtomicError/Load-6   305ns ±11%    12ns ± 6%  -96.18%  (p=0.000 n=10+10)
AtomicError/Set-6   314ns ±16%    14ns ± 2%  -95.61%  (p=0.000 n=10+9)
2020-09-18 22:45:01 +08:00
kevin
eccfaba614 update doc 2020-09-18 22:33:40 +08:00
kevin
974c19d6d3 update rpc example 2020-09-18 18:15:39 +08:00
Zhang Hao
0f8140031a fix rpc client examle (#81) 2020-09-18 18:07:08 +08:00
kevin
0b1ee79d3a rename rpcx to zrpc 2020-09-18 11:41:52 +08:00
Zhang Hao
26e16107ce fix example tracing edge config (#76) 2020-09-18 08:53:06 +08:00
kevin
1e5e9d63bd update wechat qrcode 2020-09-17 10:28:33 +08:00
kevin
f994e1df1a add more tests 2020-09-16 20:03:30 +08:00
kevin
b5dcadda78 remove markdown linter temporarily 2020-09-16 16:53:38 +08:00
kevin
df37597ac3 simplify mapreduce code 2020-09-16 16:48:59 +08:00
miaogaolin
68335ada54 rename file and function name (#74) 2020-09-16 13:30:47 +08:00
bittoy
ecdae2477e add mapping readme (#75) 2020-09-16 13:30:13 +08:00
kevin
a561884fcf print message when starting api server 2020-09-16 13:27:16 +08:00
kevin
a50bcb90a6 rename function 2020-09-14 21:13:19 +08:00
Keson
e6f8e0e8c3 optimize: api generating for idea plugin (#68)
* add flag: force to generate api

* add flag: force to generate api

* format api template

* Revert "format api template"
2020-09-14 17:12:31 +08:00
kingxt
598ff6d0fc api support empty request or empty response (#72)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* api support empty request or empty response

* update readme

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-14 17:10:45 +08:00
miaogaolin
9a57993e83 fix goctl api (#71) 2020-09-14 14:51:22 +08:00
kingxt
ee45b0a459 optimize route parse (#70)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* optimized route parser

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-14 11:44:53 +08:00
masonchen2014
2896ef1a49 Sharedcalls.md (#69)
* sharedcalls.md

* markdownlint

Co-authored-by: chenmusheng <chenmusheng@laoyuegou.com>
2020-09-14 10:13:33 +08:00
kingxt
05df86436f optimized api new with absolute path like: goctl api new $PWD/xxxx (#67)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* optimized api new with absolute path like: goctl api new $PWD/xxxx

* optimized api new with absolute path like: goctl api new $PWD/xxxx

* optimized api new with absolute path like: goctl api new $PWD/xxxx

* optimized api new with absolute path like: goctl api new $PWD/xxxx

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-13 16:17:21 +08:00
kevin
fb22589cf5 update doc, add metric link 2020-09-12 22:50:37 +08:00
kevin
a8fb010333 drain pipe if reducer not drained 2020-09-12 17:13:32 +08:00
zhoushuguang
8cc09244a0 Metric (#65)
* doc for service metric

* doc for service metric

* doc for service metric

* doc for service metric

Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-09-12 13:08:10 +08:00
Sergey Cheung
21e811887c Markdown lint (#58)
* markdown linter

* format markdown docs

* format exiting markdown docs
2020-09-11 19:42:58 +08:00
kevin
7f0ec14704 update goctl makefile 2020-09-11 18:17:07 +08:00
192 changed files with 1946 additions and 868 deletions

View File

@@ -7,7 +7,6 @@ on:
branches: [ master ]
jobs:
build:
name: Build
runs-on: ubuntu-latest

6
.markdownlint.json Normal file
View File

@@ -0,0 +1,6 @@
{
"MD010": false,
"MD013": false,
"MD033": false,
"MD034": false
}

View File

@@ -13,15 +13,13 @@ const (
// maps as k in the error rate table
maps = 14
setScript = `
local key = KEYS[1]
for _, offset in ipairs(ARGV) do
redis.call("setbit", key, offset, 1)
redis.call("setbit", KEYS[1], offset, 1)
end
`
testScript = `
local key = KEYS[1]
for _, offset in ipairs(ARGV) do
if tonumber(redis.call("getbit", key, offset)) == 0 then
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
return false
end
end

View File

@@ -5,12 +5,12 @@ import (
"fmt"
"strings"
"sync"
"time"
"github.com/tal-tech/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/proc"
"github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/core/timex"
)
const (
@@ -195,23 +195,23 @@ type errorWindow struct {
func (ew *errorWindow) add(reason string) {
ew.lock.Lock()
ew.reasons[ew.index] = fmt.Sprintf("%s %s", time.Now().Format(timeFormat), reason)
ew.reasons[ew.index] = fmt.Sprintf("%s %s", timex.Time().Format(timeFormat), reason)
ew.index = (ew.index + 1) % numHistoryReasons
ew.count = mathx.MinInt(ew.count+1, numHistoryReasons)
ew.lock.Unlock()
}
func (ew *errorWindow) String() string {
var builder strings.Builder
var reasons []string
ew.lock.Lock()
for i := ew.index + ew.count - 1; i >= ew.index; i-- {
builder.WriteString(ew.reasons[i%numHistoryReasons])
builder.WriteByte('\n')
// reverse order
for i := ew.index - 1; i >= ew.index-ew.count; i-- {
reasons = append(reasons, ew.reasons[(i+numHistoryReasons)%numHistoryReasons])
}
ew.lock.Unlock()
return builder.String()
return strings.Join(reasons, "\n")
}
type promiseWithReason struct {

View File

@@ -2,7 +2,9 @@ package breaker
import (
"errors"
"fmt"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -33,6 +35,84 @@ func TestLogReason(t *testing.T) {
assert.Equal(t, numHistoryReasons, errs.count)
}
func TestErrorWindow(t *testing.T) {
tests := []struct {
name string
reasons []string
}{
{
name: "no error",
},
{
name: "one error",
reasons: []string{"foo"},
},
{
name: "two errors",
reasons: []string{"foo", "bar"},
},
{
name: "five errors",
reasons: []string{"first", "second", "third", "fourth", "fifth"},
},
{
name: "six errors",
reasons: []string{"first", "second", "third", "fourth", "fifth", "sixth"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var ew errorWindow
for _, reason := range test.reasons {
ew.add(reason)
}
var reasons []string
if len(test.reasons) > numHistoryReasons {
reasons = test.reasons[len(test.reasons)-numHistoryReasons:]
} else {
reasons = test.reasons
}
for _, reason := range reasons {
assert.True(t, strings.Contains(ew.String(), reason), fmt.Sprintf("actual: %s", ew.String()))
}
})
}
}
func TestPromiseWithReason(t *testing.T) {
tests := []struct {
name string
reason string
expect string
}{
{
name: "success",
},
{
name: "success",
reason: "fail",
expect: "fail",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
promise := promiseWithReason{
promise: new(mockedPromise),
errWin: new(errorWindow),
}
if len(test.reason) == 0 {
promise.Accept()
} else {
promise.Reject(test.reason)
}
assert.True(t, strings.Contains(promise.errWin.String(), test.expect))
})
}
}
func BenchmarkGoogleBreaker(b *testing.B) {
br := NewBreaker()
for i := 0; i < b.N; i++ {
@@ -41,3 +121,12 @@ func BenchmarkGoogleBreaker(b *testing.B) {
})
}
}
type mockedPromise struct {
}
func (m *mockedPromise) Accept() {
}
func (m *mockedPromise) Reject() {
}

View File

@@ -1,21 +1,18 @@
package errorx
import "sync"
import "sync/atomic"
type AtomicError struct {
err error
lock sync.Mutex
err atomic.Value // error
}
func (ae *AtomicError) Set(err error) {
ae.lock.Lock()
ae.err = err
ae.lock.Unlock()
ae.err.Store(err)
}
func (ae *AtomicError) Load() error {
ae.lock.Lock()
err := ae.err
ae.lock.Unlock()
return err
if v := ae.err.Load(); v != nil {
return v.(error)
}
return nil
}

View File

@@ -2,6 +2,8 @@ package errorx
import (
"errors"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
@@ -19,3 +21,53 @@ func TestAtomicErrorNil(t *testing.T) {
var err AtomicError
assert.Nil(t, err.Load())
}
func BenchmarkAtomicError(b *testing.B) {
var aerr AtomicError
wg := sync.WaitGroup{}
b.Run("Load", func(b *testing.B) {
var done uint32
go func() {
for {
if atomic.LoadUint32(&done) != 0 {
break
}
wg.Add(1)
go func() {
aerr.Set(errDummy)
wg.Done()
}()
}
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = aerr.Load()
}
b.StopTimer()
atomic.StoreUint32(&done, 1)
wg.Wait()
})
b.Run("Set", func(b *testing.B) {
var done uint32
go func() {
for {
if atomic.LoadUint32(&done) != 0 {
break
}
wg.Add(1)
go func() {
_ = aerr.Load()
wg.Done()
}()
}
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
aerr.Set(errDummy)
}
b.StopTimer()
atomic.StoreUint32(&done, 1)
wg.Wait()
})
}

View File

@@ -8,55 +8,60 @@ import (
"github.com/tal-tech/go-zero/core/timex"
)
const customCallerDepth = 3
const durationCallerDepth = 3
type customLog logEntry
type durationLogger logEntry
func WithDuration(d time.Duration) Logger {
return customLog{
return &durationLogger{
Duration: timex.ReprOfDuration(d),
}
}
func (l customLog) Error(v ...interface{}) {
func (l *durationLogger) Error(v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), customCallerDepth))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
}
}
func (l customLog) Errorf(format string, v ...interface{}) {
func (l *durationLogger) Errorf(format string, v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), customCallerDepth))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
}
}
func (l customLog) Info(v ...interface{}) {
func (l *durationLogger) Info(v ...interface{}) {
if shouldLog(InfoLevel) {
l.write(infoLog, levelInfo, fmt.Sprint(v...))
}
}
func (l customLog) Infof(format string, v ...interface{}) {
func (l *durationLogger) Infof(format string, v ...interface{}) {
if shouldLog(InfoLevel) {
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
}
}
func (l customLog) Slow(v ...interface{}) {
func (l *durationLogger) Slow(v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(slowLog, levelSlow, fmt.Sprint(v...))
}
}
func (l customLog) Slowf(format string, v ...interface{}) {
func (l *durationLogger) Slowf(format string, v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
}
}
func (l customLog) write(writer io.Writer, level, content string) {
func (l *durationLogger) WithDuration(duration time.Duration) Logger {
l.Duration = timex.ReprOfDuration(duration)
return l
}
func (l *durationLogger) write(writer io.Writer, level, content string) {
l.Timestamp = getTimestamp()
l.Level = level
l.Content = content
outputJson(writer, logEntry(l))
outputJson(writer, logEntry(*l))
}

View File

@@ -0,0 +1,52 @@
package logx
import (
"log"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestWithDurationError(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).Error("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}
func TestWithDurationErrorf(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).Errorf("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}
func TestWithDurationInfo(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).Info("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}
func TestWithDurationInfof(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).Infof("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}
func TestWithDurationSlow(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).Slow("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}
func TestWithDurationSlowf(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).WithDuration(time.Hour).Slowf("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}

View File

@@ -15,6 +15,7 @@ import (
"strings"
"sync"
"sync/atomic"
"time"
"github.com/tal-tech/go-zero/core/iox"
"github.com/tal-tech/go-zero/core/sysx"
@@ -96,6 +97,7 @@ type (
Infof(string, ...interface{})
Slow(...interface{})
Slowf(string, ...interface{})
WithDuration(time.Duration) Logger
}
)

View File

@@ -4,54 +4,61 @@ import (
"context"
"fmt"
"io"
"time"
"github.com/tal-tech/go-zero/core/timex"
"github.com/tal-tech/go-zero/core/trace/tracespec"
)
type tracingEntry struct {
type traceLogger struct {
logEntry
Trace string `json:"trace,omitempty"`
Span string `json:"span,omitempty"`
ctx context.Context `json:"-"`
Trace string `json:"trace,omitempty"`
Span string `json:"span,omitempty"`
ctx context.Context
}
func (l tracingEntry) Error(v ...interface{}) {
func (l *traceLogger) Error(v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), customCallerDepth))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
}
}
func (l tracingEntry) Errorf(format string, v ...interface{}) {
func (l *traceLogger) Errorf(format string, v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), customCallerDepth))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
}
}
func (l tracingEntry) Info(v ...interface{}) {
func (l *traceLogger) Info(v ...interface{}) {
if shouldLog(InfoLevel) {
l.write(infoLog, levelInfo, fmt.Sprint(v...))
}
}
func (l tracingEntry) Infof(format string, v ...interface{}) {
func (l *traceLogger) Infof(format string, v ...interface{}) {
if shouldLog(InfoLevel) {
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
}
}
func (l tracingEntry) Slow(v ...interface{}) {
func (l *traceLogger) Slow(v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(slowLog, levelSlow, fmt.Sprint(v...))
}
}
func (l tracingEntry) Slowf(format string, v ...interface{}) {
func (l *traceLogger) Slowf(format string, v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
}
}
func (l tracingEntry) write(writer io.Writer, level, content string) {
func (l *traceLogger) WithDuration(duration time.Duration) Logger {
l.Duration = timex.ReprOfDuration(duration)
return l
}
func (l *traceLogger) write(writer io.Writer, level, content string) {
l.Timestamp = getTimestamp()
l.Level = level
l.Content = content
@@ -61,7 +68,7 @@ func (l tracingEntry) write(writer io.Writer, level, content string) {
}
func WithContext(ctx context.Context) Logger {
return tracingEntry{
return &traceLogger{
ctx: ctx,
}
}

View File

@@ -19,7 +19,7 @@ var mock tracespec.Trace = new(mockTrace)
func TestTraceLog(t *testing.T) {
var buf strings.Builder
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
WithContext(ctx).(tracingEntry).write(&buf, levelInfo, testlog)
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId))
assert.True(t, strings.Contains(buf.String(), mockSpanId))
}

View File

@@ -345,7 +345,7 @@ func (u *Unmarshaler) processNamedFieldWithValue(field reflect.StructField, valu
options := opts.options()
if len(options) > 0 {
if !stringx.Contains(options, mapValue.(string)) {
return fmt.Errorf(`error: value "%s" for field "%s" is not defined in opts "%v"`,
return fmt.Errorf(`error: value "%s" for field "%s" is not defined in options "%v"`,
mapValue, key, options)
}
}

View File

@@ -79,7 +79,7 @@ func Map(generate GenerateFunc, mapper MapFunc, opts ...Option) chan interface{}
collector := make(chan interface{}, options.workers)
done := syncx.NewDoneChan()
go mapDispatcher(mapper, source, collector, done.Done(), options.workers)
go executeMappers(mapper, source, collector, done.Done(), options.workers)
return collector
}
@@ -124,8 +124,12 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
}
}()
reducer(collector, writer, cancel)
drain(collector)
}()
go mapperDispatcher(mapper, source, collector, done.Done(), cancel, options.workers)
go executeMappers(func(item interface{}, w Writer) {
mapper(item, w, cancel)
}, source, collector, done.Done(), options.workers)
value, ok := <-output
if err := retErr.Load(); err != nil {
@@ -140,6 +144,7 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
func MapReduceVoid(generator GenerateFunc, mapper MapperFunc, reducer VoidReducerFunc, opts ...Option) error {
_, err := MapReduce(generator, mapper, func(input <-chan interface{}, writer Writer, cancel func(error)) {
reducer(input, cancel)
drain(input)
// We need to write a placeholder to let MapReduce to continue on reducer done,
// otherwise, all goroutines are waiting. The placeholder will be discarded by MapReduce.
writer.Write(lang.Placeholder)
@@ -224,20 +229,6 @@ func executeMappers(mapper MapFunc, input <-chan interface{}, collector chan<- i
}
}
func mapDispatcher(mapper MapFunc, input <-chan interface{}, collector chan<- interface{},
done <-chan lang.PlaceholderType, workers int) {
executeMappers(func(item interface{}, writer Writer) {
mapper(item, writer)
}, input, collector, done, workers)
}
func mapperDispatcher(mapper MapperFunc, input <-chan interface{}, collector chan<- interface{},
done <-chan lang.PlaceholderType, cancel func(error), workers int) {
executeMappers(func(item interface{}, writer Writer) {
mapper(item, writer, cancel)
}, input, collector, done, workers)
}
func newOptions() *mapReduceOptions {
return &mapReduceOptions{
workers: defaultWorkers,

View File

@@ -32,7 +32,7 @@ func AddWrapUpListener(fn func()) (waitForCalled func()) {
return wrapUpListeners.addListener(fn)
}
func SetTimeoutToForceQuit(duration time.Duration) {
func SetTimeToForceQuit(duration time.Duration) {
delayTimeBeforeForceQuit = duration
}

View File

@@ -68,6 +68,38 @@ func TestExclusiveCallDoDupSuppress(t *testing.T) {
}
}
func TestExclusiveCallDoDiffDupSuppress(t *testing.T) {
g := NewSharedCalls()
broadcast := make(chan struct{})
var calls int32
tests := []string{"e", "a", "e", "a", "b", "c", "b", "a", "c", "d", "b", "c", "d"}
var wg sync.WaitGroup
for _, key := range tests {
wg.Add(1)
go func(k string) {
<-broadcast // get all goroutines ready
_, err := g.Do(k, func() (interface{}, error) {
atomic.AddInt32(&calls, 1)
time.Sleep(10 * time.Millisecond)
return nil, nil
})
if err != nil {
t.Errorf("Do error: %v", err)
}
wg.Done()
}(key)
}
time.Sleep(100 * time.Millisecond) // let goroutines above block
close(broadcast)
wg.Wait()
if got := atomic.LoadInt32(&calls); got != 5 { // five letters
t.Errorf("number of calls = %d; want 5", got)
}
}
func TestExclusiveCallDoExDupSuppress(t *testing.T) {
g := NewSharedCalls()
c := make(chan string)

View File

@@ -0,0 +1,12 @@
package tracespec
// TracingKey is tracing key for context
var TracingKey = contextKey("X-Trace")
// contextKey a type for context key
type contextKey string
// Printing a context will reveal a fair amount of information about it.
func (c contextKey) String() string {
return "trace/tracespec context key " + string(c)
}

View File

@@ -1,3 +0,0 @@
package tracespec
const TracingKey = "X-Trace"

View File

@@ -1,8 +1,8 @@
English | [简体中文](bookstore.md)
# Rapid development of microservices - multiple RPCs
## 0. Why building microservices are so difficult?
English | [简体中文](bookstore.md)
## 0. Why building microservices are so difficult
To build a well working microservice, we need lots of knowledges from different aspects.
@@ -25,7 +25,7 @@ As well, we always adhere to the idea that **prefer tools over conventions and d
Lets take the shorturl microservice as a quick example to demonstrate how to quickly create microservices by using [go-zero](https://github.com/tal-tech/go-zero). After finishing this tutorial, youll find that its so easy to write microservices!
## 1. What is a bookstore service?
## 1. What is a bookstore service
For simplicity, the bookstore service only contains two functionalities, adding books and quering prices.
@@ -57,6 +57,12 @@ And now, lets walk through the complete flow of quickly create a microservice
* install etcd, mysql, redis
* install protoc-gen-go
```shell
go get -u github.com/golang/protobuf/protoc-gen-go
```
* install goctl
```shell
@@ -71,7 +77,7 @@ And now, lets walk through the complete flow of quickly create a microservice
* use goctl to generate `api/bookstore.api`
```
```Plain Text
goctl api -o bookstore.api
```
@@ -128,7 +134,7 @@ And now, lets walk through the complete flow of quickly create a microservice
the generated file structure looks like:
```
```Plain Text
api
├── bookstore.api // api definition
├── bookstore.go // main entrance
@@ -188,13 +194,13 @@ And now, lets walk through the complete flow of quickly create a microservice
* under directory `rpc/add` create `add.proto` file
```shell
goctl rpc template -o add.proto
goctl rpc template -o add.proto
```
edit the file and make the code looks like:
```protobuf
syntax = "proto3";
syntax = "proto3";
package add;
@@ -220,7 +226,7 @@ syntax = "proto3";
the generated file structure looks like:
```
```Plain Text
rpc/add
├── add.go // rpc main entrance
├── add.proto // rpc definition
@@ -242,7 +248,6 @@ syntax = "proto3";
└── pb
└── add.pb.go
```
just run it, looks like:
@@ -258,13 +263,13 @@ you can change the listening port in file `etc/add.yaml`.
* under directory `rpc/check` create `check.proto` file
```shell
goctl rpc template -o check.proto
goctl rpc template -o check.proto
```
edit the file and make the code looks like:
```protobuf
syntax = "proto3";
syntax = "proto3";
package check;
@@ -290,7 +295,7 @@ syntax = "proto3";
the generated file structure looks like:
```
```Plain Text
rpc/check
├── check.go // rpc main entrance
├── check.proto // rpc definition
@@ -315,7 +320,7 @@ syntax = "proto3";
you can change the listening port in `etc/check.yaml`.
we need to change the port in `etc/check.yaml` to `8081`, because `8080 ` is used by `add` service.
we need to change the port in `etc/check.yaml` to `8081`, because `8080` is used by `add` service.
just run it, looks like:
@@ -348,8 +353,8 @@ syntax = "proto3";
```go
type Config struct {
rest.RestConf
Add rpcx.RpcClientConf // manual code
Check rpcx.RpcClientConf // manual code
Add zrpc.RpcClientConf // manual code
Check zrpc.RpcClientConf // manual code
}
```
@@ -365,8 +370,8 @@ syntax = "proto3";
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Adder: adder.NewAdder(rpcx.MustNewClient(c.Add)), // manual code
Checker: checker.NewChecker(rpcx.MustNewClient(c.Check)), // manual code
Adder: adder.NewAdder(zrpc.MustNewClient(c.Add)), // manual code
Checker: checker.NewChecker(zrpc.MustNewClient(c.Check)), // manual code
}
}
```
@@ -444,7 +449,7 @@ Till now, weve done the modification of API Gateway. All the manually added c
source book.sql;
```
* under the directory `rpc/model execute the following command to genrate CRUD+cache code, `-c` means using `redis cache`
* under the directory `rpc/model` execute the following command to genrate CRUD+cache code, `-c` means using `redis cache`
```shell
goctl model mysql ddl -c -src book.sql -dir .
@@ -454,7 +459,7 @@ Till now, weve done the modification of API Gateway. All the manually added c
the generated file structure looks like:
```
```Plain Text
rpc/model
├── bookstore.sql
├── bookstoremodel.go // CRUD+cache code
@@ -478,7 +483,7 @@ Till now, weve done the modification of API Gateway. All the manually added c
```go
type Config struct {
rpcx.RpcServerConf
zrpc.RpcServerConf
DataSource string // manual code
Table string // manual code
Cache cache.CacheConf // manual code
@@ -614,4 +619,4 @@ We not only keep the framework simple, but also encapsulate the complexity into
For the generated code by goctl, lots of microservice components are included, like concurrency control, adaptive circuit breaker, adaptive load shedding, auto cache control etc. And its easy to deal with the busy sites.
If you have any ideas that can help us to improve the productivity, tell me any time! 👏
If you have any ideas that can help us to improve the productivity, tell me any time! 👏

View File

@@ -1,8 +1,8 @@
[English](bookstore-en.md) | 简体中文
# 快速构建微服务-多RPC版
## 0. 为什么说做好微服务很难?
[English](bookstore-en.md) | 简体中文
## 0. 为什么说做好微服务很难
要想做好微服务,我们需要理解和掌握的知识点非常多,从几个维度上来说:
@@ -57,6 +57,12 @@
* 安装etcd, mysql, redis
* 安装`protoc-gen-go`
```shell
go get -u github.com/golang/protobuf/protoc-gen-go
```
* 安装goctl工具
```shell
@@ -71,7 +77,7 @@
* 在`bookstore/api`目录下通过goctl生成`api/bookstore.api`
```
```bash
goctl api -o bookstore.api
```
@@ -128,7 +134,7 @@
生成的文件结构如下:
```
```Plain Text
api
├── bookstore.api // api定义
├── bookstore.go // main入口定义
@@ -222,7 +228,7 @@
文件结构如下:
```
```Plain Text
rpc/add
├── add.go // rpc服务main函数
├── add.proto // rpc接口定义
@@ -244,7 +250,6 @@
└── pb
└── add.pb.go
```
直接可以运行,如下:
@@ -294,7 +299,7 @@
文件结构如下:
```
```Plain Text
rpc/check
├── check.go // rpc服务main函数
├── check.proto // rpc接口定义
@@ -350,8 +355,8 @@
```go
type Config struct {
rest.RestConf
Add rpcx.RpcClientConf // 手动代码
Check rpcx.RpcClientConf // 手动代码
Add zrpc.RpcClientConf // 手动代码
Check zrpc.RpcClientConf // 手动代码
}
```
@@ -367,8 +372,8 @@
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Adder: adder.NewAdder(rpcx.MustNewClient(c.Add)), // 手动代码
Checker: checker.NewChecker(rpcx.MustNewClient(c.Check)), // 手动代码
Adder: adder.NewAdder(zrpc.MustNewClient(c.Add)), // 手动代码
Checker: checker.NewChecker(zrpc.MustNewClient(c.Check)), // 手动代码
}
}
```
@@ -454,7 +459,7 @@
生成后的文件结构如下:
```
```Plain Text
rpc/model
├── bookstore.sql
├── bookstoremodel.go // CRUD+cache代码
@@ -478,7 +483,7 @@
```go
type Config struct {
rpcx.RpcServerConf
zrpc.RpcServerConf
DataSource string // 手动代码
Table string // 手动代码
Cache cache.CacheConf // 手动代码
@@ -615,4 +620,3 @@ go-zero不只是一个框架更是一个建立在框架+工具基础上的,
通过go-zero+goctl生成的代码包含了微服务治理的各种组件包括并发控制、自适应熔断、自适应降载、自动缓存控制等可以轻松部署以承载巨大访问量。
有任何好的提升工程效率的想法,随时欢迎交流!👏

View File

@@ -1,4 +1,5 @@
# 熔断机制设计
## 设计目的
* 依赖的服务出现大规模故障时,调用方应该尽可能少调用,降低故障服务的压力,使之尽快恢复服务
* 依赖的服务出现大规模故障时,调用方应该尽可能少调用,降低故障服务的压力,使之尽快恢复服务

View File

@@ -29,6 +29,7 @@ v, err := c.Take("key", func() (interface{}, error) {
```
cache 实现的建的功能包括
* 缓存自动失效,可以指定过期时间
* 缓存大小限制,可以指定缓存个数
* 缓存增删改
@@ -38,6 +39,7 @@ cache 实现的建的功能包括
实现原理:
Cache 自动失效,是采用 TimingWheel(https://github.com/tal-tech/go-zero/blob/master/core/collection/timingwheel.go) 进行管理的
``` go
timingWheel, err := NewTimingWheel(time.Second, slots, func(k, v interface{}) {
key, ok := k.(string)
@@ -50,6 +52,7 @@ timingWheel, err := NewTimingWheel(time.Second, slots, func(k, v interface{}) {
```
Cache 大小限制,是采用 LRU 淘汰策略,在新增缓存的时候会去检查是否已经超出过限制,具体代码在 keyLru 中实现
``` go
func (klru *keyLru) add(key string) {
if elem, ok := klru.elements[key]; ok {
@@ -71,6 +74,7 @@ func (klru *keyLru) add(key string) {
Cache 的命中率统计,是在代码中实现 cacheStat,在缓存命中丢失的时候自动统计,并且会定时打印使用的命中率, qps 等状态.
打印的具体效果如下
```go
cache(proc) - qpm: 2, hit_ratio: 50.0%, elements: 0, hit: 1, miss: 1
```

View File

@@ -2,63 +2,63 @@
goctl model 为go-zero下的工具模块中的组件之一目前支持识别mysql ddl进行model层代码生成通过命令行或者idea插件即将支持可以有选择地生成带redis cache或者不带redis cache的代码逻辑。
# 快速开始
## 快速开始
* 通过ddl生成
```shell script
$ goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true
goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c=true
```
执行上述命令后即可快速生成CURD代码。
```
```Plain Text
model
│   ├── error.go
│   └── usermodel.go
```
* 通过datasource生成
```shell script
$ goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
```
* 生成代码示例
``` go
package model
import (
"database/sql"
"fmt"
"strings"
"time"
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/sqlc"
"github.com/tal-tech/go-zero/core/stores/sqlx"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
)
var (
userFieldNames = builderx.FieldNames(&User{})
userRows = strings.Join(userFieldNames, ",")
userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), ",")
userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "id", "create_time", "update_time"), "=?,") + "=?"
cacheUserMobilePrefix = "cache#User#mobile#"
cacheUserIdPrefix = "cache#User#id#"
cacheUserNamePrefix = "cache#User#name#"
)
type (
UserModel struct {
sqlc.CachedConn
table string
}
User struct {
Id int64 `db:"id"`
Name string `db:"name"` // 用户名称
@@ -70,19 +70,19 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
UpdateTime time.Time `db:"update_time"`
}
)
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserModel {
return &UserModel{
CachedConn: sqlc.NewConn(conn, c),
table: table,
}
}
func (m *UserModel) Insert(data User) (sql.Result, error) {
query := `insert into ` + m.table + `(` + userRowsExpectAutoSet + `) value (?, ?, ?, ?, ?)`
return m.ExecNoCache(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname)
}
func (m *UserModel) FindOne(id int64) (*User, error) {
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
var resp User
@@ -99,7 +99,7 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
return nil, err
}
}
func (m *UserModel) FindOneByName(name string) (*User, error) {
userNameKey := fmt.Sprintf("%s%v", cacheUserNamePrefix, name)
var resp User
@@ -124,7 +124,7 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
return nil, err
}
}
func (m *UserModel) FindOneByMobile(mobile string) (*User, error) {
userMobileKey := fmt.Sprintf("%s%v", cacheUserMobilePrefix, mobile)
var resp User
@@ -149,7 +149,7 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
return nil, err
}
}
func (m *UserModel) Update(data User) error {
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
@@ -158,7 +158,7 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
}, userIdKey)
return err
}
func (m *UserModel) Delete(id int64) error {
data, err := m.FindOne(id)
if err != nil {
@@ -175,13 +175,13 @@ goctl model 为go-zero下的工具模块中的组件之一目前支持识别m
}
```
# 用法
### 用法
```
$ goctl model mysql -h
```Plain Text
goctl model mysql -h
```
```
```Plain Text
NAME:
goctl model mysql - generate mysql model"
@@ -196,23 +196,24 @@ OPTIONS:
--help, -h show help
```
# 生成规则
## 生成规则
* 默认规则
我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为`CURRENT_TIMESTAMP`而updateTime支持`ON UPDATE CURRENT_TIMESTAMP`,对于这两个字段生成`insert`、`update`时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。
* 带缓存模式
* ddl
```shell script
$ goctl model mysql -src={filename} -dir={dir} -cache=true
```
* datasource
```shell script
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true
```
* ddl
```shell script
goctl model mysql -src={filename} -dir={dir} -cache=true
```
* datasource
```shell script
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=true
```
目前仅支持redis缓存如果选择带缓存模式即生成的`FindOne(ByXxx)`&`Delete`代码会生成带缓存逻辑的代码目前仅支持单索引字段除全文索引外对于联合索引我们默认认为不需要带缓存且不属于通用型代码因此没有放在代码生成行列如example中user表中的`id`、`name`、`mobile`字段均属于单字段索引。
* 不带缓存模式
@@ -220,28 +221,31 @@ OPTIONS:
* ddl
```shell script
$ goctl model -src={filename} -dir={dir}
goctl model -src={filename} -dir={dir}
```
* datasource
```shell script
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir}
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir}
```
or
* ddl
```shell script
$ goctl model -src={filename} -dir={dir} -cache=false
goctl model -src={filename} -dir={dir} -cache=false
```
* datasource
```shell script
$ goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false
goctl model mysql datasource -url={datasource} -table={tables} -dir={dir} -cache=false
```
生成代码仅基本的CURD结构。
# 缓存
## 缓存
对于缓存这一块我选择用一问一答的形式进行罗列。我想这样能够更清晰的描述model中缓存的功能。
@@ -261,14 +265,8 @@ OPTIONS:
目前我认为除了基本的CURD外其他的代码均属于<i>业务型</i>代码,这个我觉得开发人员根据业务需要进行编写更好。
# QA
## QA
* goctl model除了命令行模式支持插件模式吗
很快支持idea插件。

View File

@@ -1,12 +1,14 @@
# Rpc Generation
Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块支持proto模板生成和rpc服务代码生成通过此工具生成代码你只需要关注业务逻辑编写而不用去编写一些重复性的代码。这使得我们把精力重心放在业务上从而加快了开发效率且降低了代码出错率。
# 特性
## 特性
* 简单易用
* 快速提升开发效率
* 出错率低
# 快速开始
## 快速开始
### 方式一快速生成greet服务
@@ -15,7 +17,7 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块支持prot
如生成greet rpc服务
```shell script
$ goctl rpc new greet
goctl rpc new greet
```
执行后代码结构如下:
@@ -46,12 +48,13 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块支持prot
```
rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
### 方式二通过指定proto生成rpc服务
* 生成proto模板
```shell script
$ goctl rpc template -o=user.proto
goctl rpc template -o=user.proto
```
```golang
@@ -78,15 +81,16 @@ rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题
rpc Login(Request)returns(Response);
}
```
* 生成rpc服务代码
```
$ goctl rpc proto -src=user.proto
```shell script
goctl rpc proto -src=user.proto
```
代码tree
```
```Plain Text
user
├── etc
│   └── user.json
@@ -109,19 +113,20 @@ rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题
└── user.proto
```
# 准备工作
## 准备工作
* 安装了go环境
* 安装了protoc&protoc-gen-go并且已经设置环境变量
* mockgen(可选,将移除)
* 更多问题请见 <a href="#注意事项">注意事项</a>
# 用法
## 用法
### rpc服务生成用法
```shell script
$ goctl rpc proto -h
goctl rpc proto -h
```
```shell script
@@ -145,44 +150,53 @@ OPTIONS:
* --src 必填proto数据源目前暂时支持单个proto文件生成这里不支持不建议外部依赖
* --dir 非必填默认为proto文件所在目录生成代码的目标目录
* --service 服务名称非必填默认为proto文件所在目录名称但是如果proto所在目录为一下结构
```shell script
user
├── cmd
│   └── rpc
│   └── user.proto
```
则服务名称亦为user而非proto所在文件夹名称了这里推荐使用这种结构可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。
* --shared[⚠️已废弃] 非必填,默认为$dir(xxx.proto)/sharedrpc client逻辑代码存放目录。
> 注意这里的shared文件夹名称将会是代码中的package名称。
* --idea 非必填是否为idea插件中执行保留字段终端执行可以忽略
# 开发人员需要做什么
## 开发人员需要做什么
关注业务代码编写将重复性、与业务无关的工作交给goctl生成好rpc服务代码后开饭人员仅需要修改
* 服务中的配置文件编写(etc/xx.json、internal/config/config.go)
* 服务中业务逻辑编写(internal/logic/xxlogic.go)
* 服务中资源上下文的编写(internal/svc/servicecontext.go)
# 扩展
## 扩展
对于需要进行rpc mock的开发人员在安装了`mockgen`工具的前提下可以在rpc的shared文件中生成好对应的mock文件。
# 注意事项
## 注意事项
* `google.golang.org/grpc`需要降级到v1.26.0,且protoc-gen-go版本不能高于v1.3.2see [https://github.com/grpc/grpc-go/issues/3347](https://github.com/grpc/grpc-go/issues/3347))即
```
```shell script
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
```
* proto不支持暂多文件同时生成
* proto不支持外部依赖包引入message不支持inline
* 目前main文件、shared文件、handler文件会被强制覆盖而和开发人员手动需要编写的则不会覆盖生成这一类在代码头部均有
```shell script
// Code generated by goctl. DO NOT EDIT!
// Source: xxx.proto
```
的标识,请注意不要将也写业务性代码写在里面。
# 常见问题解决(go mod工程)
## 常见问题解决(go mod工程)
* 错误一:
@@ -192,6 +206,7 @@ OPTIONS:
pb/xx.pb.go:234:5: undefined: grpc.ClientConnInterface
pb/xx.pb.go:237:24: undefined: grpc.ClientConnInterface
```
解决方法:请将`protoc-gen-go`版本降至v1.3.2及一下
* 错误二:
@@ -206,11 +221,11 @@ OPTIONS:
*rrBalanced does not implement Picker (wrong type for Pick method)
have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error)
want Pick(balancer.PickInfo) (balancer.PickResult, error)
#github.com/tal-tech/go-zero/rpcx/internal/balancer/p2c
../../../go/pkg/mod/github.com/tal-tech/go-zero@v1.0.12/rpcx/internal/balancer/p2c/p2c.go:41:32: not enough arguments in call to base.NewBalancerBuilder
#github.com/tal-tech/go-zero/zrpc/internal/balancer/p2c
../../../go/pkg/mod/github.com/tal-tech/go-zero@v1.0.12/zrpc/internal/balancer/p2c/p2c.go:41:32: not enough arguments in call to base.NewBalancerBuilder
have (string, *p2cPickerBuilder)
want (string, base.PickerBuilder, base.Config)
../../../go/pkg/mod/github.com/tal-tech/go-zero@v1.0.12/rpcx/internal/balancer/p2c/p2c.go:58:9: cannot use &p2cPicker literal (type *p2cPicker) as type balancer.Picker in return argument:
../../../go/pkg/mod/github.com/tal-tech/go-zero@v1.0.12/zrpc/internal/balancer/p2c/p2c.go:58:9: cannot use &p2cPicker literal (type *p2cPicker) as type balancer.Picker in return argument:
*p2cPicker does not implement balancer.Picker (wrong type for Pick method)
have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error)
want Pick(balancer.PickInfo) (balancer.PickResult, error)

View File

@@ -1,31 +1,40 @@
# goctl使用说明
# goctl使用
## goctl用途
* 定义api请求
* 根据定义的api自动生成golang(后端), java(iOS & Android), typescript(web & 晓程序)dart(flutter)
* 生成MySQL CURD+Cache
* 生成MongoDB CURD+Cache
* 生成MongoDB CURD+Cache
## goctl使用说明
#### 快速生成服务
### 快速生成服务
* api: goctl api new xxxx
* rpc: goctl rpc new xxxx
* rpc: goctl rpc new xxxx
#### goctl参数说明
`goctl api [go/java/ts] [-api user/user.api] [-dir ./src]`
> api 后面接生成的语言现支持go/java/typescript
>
> -api 自定义api所在路径
>
> -dir 自定义生成目录
#### 保持goctl总是最新版
第一次运行会在~/.goctl里增加下面两行
```Plain Text
url = http://47.97.184.41:7777/
```
#### API 语法说明
``` golang
info(
title: doc title
desc: >
@@ -132,6 +141,7 @@ service user-api {
请求getRequest里面的属性赋值getResponse为返回的结构体这两个类型都定义在2描述的类型中。
#### api vscode插件
开发者可以在vscode中搜索goctl的api插件它提供了api语法高亮语法检测和格式化相关功能。
1. 支持语法高亮和类型导航。
@@ -143,7 +153,7 @@ service user-api {
命令如下:
`goctl api go -api user/user.api -dir user`
```
```Plain Text
.
├── internal
@@ -173,17 +183,20 @@ service user-api {
└── user.go
```
生成的代码可以直接跑,有几个地方需要改:
* 在`servicecontext.go`里面增加需要传递给logic的一些资源比如mysql, redisrpc等
* 在定义的get/post/put/delete等请求的handler和logic里增加处理业务逻辑的代码
* 在`servicecontext.go`里面增加需要传递给logic的一些资源比如mysql, redisrpc等
* 在定义的get/post/put/delete等请求的handler和logic里增加处理业务逻辑的代码
#### 根据定义好的api文件生成java代码
```shell
goctl api java -api user/user.api -dir ./src
```
#### 根据定义好的api文件生成typescript代码
```shell
goctl api ts -api user/user.api -dir ./src -webapi ***
@@ -191,6 +204,7 @@ ts需要指定webapi所在目录
```
#### 根据定义好的api文件生成Dart代码
```shell
goctl api dart -api user/user.api -dir ./src
```
@@ -198,32 +212,37 @@ goctl api dart -api user/user.api -dir ./src
## 根据mysql ddl或者datasource生成model文件
```shell script
$ goctl model mysql -src={filename} -dir={dir} -cache={true|false}
goctl model mysql -src={filename} -dir={dir} -cache={true|false}
```
详情参考[model文档](https://github.com/tal-tech/go-zero/blob/master/tools/goctl/model/sql/README.MD)
## 根据定义好的简单go文件生成mongo代码文件(仅限golang使用)
```shell
goctl model mongo -src {{yourDir}}/xiao/service/xhb/user/model/usermodel.go -cache yes
-src需要提供简单的usermodel.go文件里面只需要提供一个结构体即可
-cache 控制是否需要缓存 yes=需要 no=不需要
src 示例代码如下
```
* src需要提供简单的usermodel.go文件里面只需要提供一个结构体即可
* cache 控制是否需要缓存 yes=需要 no=不需要
src 示例代码如下
```go
package model
type User struct {
Name string `o:"find,get,set" c:"姓名"`
Age int `o:"find,get,set" c:"年纪"`
School string `c:"学校"`
}
package model
type User struct {
Name string `o:"find,get,set" c:"姓名"`
Age int `o:"find,get,set" c:"年纪"`
School string `c:"学校"`
}
```
结构体中不需要提供Id,CreateTime,UpdateTime三个字段会自动生成
结构体中每个tag有两个可选标签 c 和 o
c是改字段的注释
o是改字段需要生产的操作函数 可以取得get,find,set 分别表示生成返回单个对象的查询方法,返回多个对象的查询方法,设置该字段方法
生成的目标文件会覆盖该简单go文件
结构体中不需要提供Id,CreateTime,UpdateTime三个字段会自动生成
结构体中每个tag有两个可选标签 c 和 o
c 是改字段的注释
o 是改字段需要生产的操作函数 可以取得get,find,set 分别表示生成返回单个对象的查询方法,返回多个对象的查询方法,设置该字段方法
生成的目标文件会覆盖该简单go文件
## goctl rpc生成业务剥离中暂未开放
@@ -232,15 +251,15 @@ type User struct {
参数说明:
- ${proto}: proto文件
- ${serviceName}: rpc服务名称
- ${projectName}: 所属项目如xjy,xhb,crm,hera具体查看help主要为了根据不同项目服务往redis注册key可选
- ${directory}: 输出目录
- ${shared}: shared文件生成目录可选默认为${pwd}/shared
* ${proto}: proto文件
* ${serviceName}: rpc服务名称
* ${projectName}: 所属项目如xjy,xhb,crm,hera具体查看help主要为了根据不同项目服务往redis注册key可选
* ${directory}: 输出目录
* ${shared}: shared文件生成目录可选默认为${pwd}/shared
生成目录结构示例:
生成目录结构示例:
``` go
```Plain Text
.
├── shared [示例目录,可自己指定,强制覆盖更新]
│   └── contentservicemodel.go
@@ -276,4 +295,5 @@ type User struct {
│   └── test.go [强制覆盖更新]
└── test.proto
```
- 注意 目前rpc目录生成的proto文件暂不支持import外部proto文件
注意 目前rpc目录生成的proto文件暂不支持import外部proto文件

BIN
doc/images/datasource.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
doc/images/panel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

BIN
doc/images/prom_up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
doc/images/prometheus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
doc/images/qps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
doc/images/qps_panel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
doc/images/variables.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 141 KiB

140
doc/jwt.md Normal file
View File

@@ -0,0 +1,140 @@
# 基于go-zero实现JWT认证
关于JWT是什么大家可以看看[官网](https://jwt.io/),一句话介绍下:是可以实现服务器无状态的鉴权认证方案,也是目前最流行的跨域认证解决方案。
要实现JWT认证我们需要分成如下两个步骤
* 客户端获取JWT token。
* 服务器对客户端带来的JWT token认证。
## 1. 客户端获取JWT Token
我们定义一个协议供客户端调用获取JWT token我们新建一个目录jwt然后在目录中执行 `goctl api -o jwt.api`将生成的jwt.api改成如下
````go
type JwtTokenRequest struct {
}
type JwtTokenResponse struct {
AccessToken string `json:"access_token"`
AccessExpire int64 `json:"access_expire"`
RefreshAfter int64 `json:"refresh_after"` // 建议客户端刷新token的绝对时间
}
type GetUserRequest struct {
UserId string `json:"userId"`
}
type GetUserResponse struct {
Name string `json:"name"`
}
service jwt-api {
@server(
handler: JwtHandler
)
post /user/token(JwtTokenRequest) returns (JwtTokenResponse)
}
@server(
jwt: JwtAuth
)
service jwt-api {
@server(
handler: GetUserHandler
)
post /user/info(GetUserRequest) returns (GetUserResponse)
}
````
在服务jwt目录中执行`goctl api go -api jwt.api -dir .`
打开jwtlogic.go文件修改 `func (l *JwtLogic) Jwt(req types.JwtTokenRequest) (*types.JwtTokenResponse, error) {` 方法如下:
```go
func (l *JwtLogic) Jwt(req types.JwtTokenRequest) (*types.JwtTokenResponse, error) {
var accessExpire = l.svcCtx.Config.JwtAuth.AccessExpire
now := time.Now().Unix()
accessToken, err := l.GenToken(now, l.svcCtx.Config.JwtAuth.AccessSecret, nil, accessExpire)
if err != nil {
return nil, err
}
return &types.JwtTokenResponse{
AccessToken: accessToken,
AccessExpire: now + accessExpire,
RefreshAfter: now + accessExpire/2,
}, nil
}
func (l *JwtLogic) GenToken(iat int64, secretKey string, payloads map[string]interface{}, seconds int64) (string, error) {
claims := make(jwt.MapClaims)
claims["exp"] = iat + seconds
claims["iat"] = iat
for k, v := range payloads {
claims[k] = v
}
token := jwt.New(jwt.SigningMethodHS256)
token.Claims = claims
return token.SignedString([]byte(secretKey))
}
```
在启动服务之前我们需要修改etc/jwt-api.yaml文件如下
```yaml
Name: jwt-api
Host: 0.0.0.0
Port: 8888
JwtAuth:
AccessSecret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AccessExpire: 604800
```
启动服务器然后测试下获取到的token。
```sh
➜ curl --location --request POST '127.0.0.1:8888/user/token'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDEyNjE0MjksImlhdCI6MTYwMDY1NjYyOX0.6u_hpE_4m5gcI90taJLZtvfekwUmjrbNJ-5saaDGeQc","access_expire":1601261429,"refresh_after":1600959029}
```
## 2. 服务器验证JWT token
1. 在api文件中通过`jwt: JwtAuth`标记的service表示激活了jwt认证。
2. 可以阅读rest/handler/authhandler.go文件了解服务器jwt实现。
3. 修改getuserlogic.go如下
```go
func (l *GetUserLogic) GetUser(req types.GetUserRequest) (*types.GetUserResponse, error) {
return &types.GetUserResponse{Name: "kim"}, nil
}
```
* 我们先不带JWT Authorization header请求头测试下返回http status code是401符合预期。
```sh
➜ curl -w "\nhttp: %{http_code} \n" --location --request POST '127.0.0.1:8888/user/info' \
--header 'Content-Type: application/json' \
--data-raw '{
"userId": "a"
}'
http: 401
```
* 加上Authorization header请求头测试。
```sh
➜ curl -w "\nhttp: %{http_code} \n" --location --request POST '127.0.0.1:8888/user/info' \
--header 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDEyNjE0MjksImlhdCI6MTYwMDY1NjYyOX0.6u_hpE_4m5gcI90taJLZtvfekwUmjrbNJ-5saaDGeQc' \
--header 'Content-Type: application/json' \
--data-raw '{
"userId": "a"
}'
{"name":"kim"}
http: 200
```
综上所述基于go-zero的JWT认证完成在真实生产环境部署时候AccessSecret, AccessExpire, RefreshAfter根据业务场景通过配置文件配置RefreshAfter 是告诉客户端什么时候该刷新JWT token了一般都需要设置过期时间前几天。

View File

@@ -22,7 +22,8 @@ fmt.Println(replacer.Replace("日本的首都是东京"))
```
可以得到:
```
```Plain Text
东京是日本的首都
```
@@ -46,7 +47,7 @@ fmt.Println(keywords)
可以得到:
```
```Plain Text
[苍井空 日本AV女优 AV演员色情 AV AV演员]
```
@@ -70,7 +71,7 @@ fmt.Println(found)
可以得到:
```
```Plain Text
日本????兼电视、电影演员。?????女优是xx出道, ??????们最精彩的表演是??????表演
[苍井空 日本AV女优 AV演员色情 AV AV演员]
true
@@ -83,4 +84,3 @@ true
| Sentences | Keywords | Regex | go-zero |
| --------- | -------- | -------- | ------- |
| 10000 | 10000 | 16min10s | 27.2ms |

View File

@@ -1,23 +1,26 @@
# 服务自适应降载保护设计
## 设计目的
* 保证系统不被过量请求拖垮
* 在保证系统稳定的前提下,尽可能提供更高的吞吐量
## 设计考虑因素
* 如何衡量系统负载
* 是否处于虚机或容器内需要读取cgroup相关负载
* 用1000m表示100%CPU推荐使用800m表示系统高负载
* 是否处于虚机或容器内需要读取cgroup相关负载
* 用1000m表示100%CPU推荐使用800m表示系统高负载
* 尽可能小的Overhead不显著增加RT
* 不考虑服务本身所依赖的DB或者缓存系统问题这类问题通过熔断机制来解决
## 机制设计
* 计算CPU负载时使用滑动平均来降低CPU负载抖动带来的不稳定关于滑动平均见参考资料
* 滑动平均就是取之前连续N次值的近似平均N取值可以通过超参beta来决定
* 当CPU负载大于指定值时触发降载保护机制
* 滑动平均就是取之前连续N次值的近似平均N取值可以通过超参beta来决定
* 当CPU负载大于指定值时触发降载保护机制
* 时间窗口机制用滑动窗口机制来记录之前时间窗口内的QPS和RT(response time)
* 滑动窗口使用5秒钟50个桶的方式每个桶保存100ms时间内的请求循环利用最新的覆盖最老的
* 计算maxQPS和minRT时需要过滤掉最新的时间没有用完的桶防止此桶内只有极少数请求并且RT处于低概率的极小值所以计算maxQPS和minRT时按照上面的50个桶的参数只会算49个
* 滑动窗口使用5秒钟50个桶的方式每个桶保存100ms时间内的请求循环利用最新的覆盖最老的
* 计算maxQPS和minRT时需要过滤掉最新的时间没有用完的桶防止此桶内只有极少数请求并且RT处于低概率的极小值所以计算maxQPS和minRT时按照上面的50个桶的参数只会算49个
* 满足以下所有条件则拒绝该请求
1. 当前CPU负载超过预设阈值或者上次拒绝时间到现在不超过1秒(冷却期)。冷却期是为了不能让负载刚下来就马上增加压力导致立马又上去的来回抖动
2. `averageFlying > max(1, QPS*minRT/1e3)`
@@ -27,7 +30,7 @@
1. 请求增加后更新一次averageFlying见图中橙色曲线
2. 请求结束后更新一次averageFlying见图中绿色曲线
3. 请求增加后更新一次averageFlying请求结束后更新一次averageFlying
我们使用的是第二种,这样可以更好的防止抖动,如图:
![flying策略对比](images/shedding_flying.jpg)
* QPS = maxPass * bucketsPerSecond
@@ -36,11 +39,13 @@
* 1e3表示1000毫秒minRT单位也是毫秒QPS*minRT/1e3得到的就是平均每个时间点有多少并发请求
## 降载的使用
* 已经在ngin和rpcx框架里增加了可选激活配置
* CpuThreshold如果把值设置为大于0的值则激活该服务的自动降载机制
* 如果请求被drop那么错误日志里会有`dropreq`关键字
* CpuThreshold如果把值设置为大于0的值则激活该服务的自动降载机制
* 如果请求被drop那么错误日志里会有`dropreq`关键字
## 参考资料
* [滑动平均](https://www.cnblogs.com/wuliytTaotao/p/9479958.html)
* [Sentinel自适应限流](https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81)
* [Kratos自适应限流保护](https://github.com/bilibili/kratos/blob/master/doc/wiki-cn/ratelimit.md)
* [Kratos自适应限流保护](https://github.com/bilibili/kratos/blob/master/doc/wiki-cn/ratelimit.md)

131
doc/mapping.md Normal file
View File

@@ -0,0 +1,131 @@
# 文本序列化和反序列化
go-zero针对文本的序列化和反序列化主要在三个地方使用
* http api请求体的反序列化
* http api返回体的序列化
* 配置文件的反序列化
本文假定读者已经定义过api文件以及修改过配置文件如不熟悉可参照
* [快速构建高并发微服务](shorturl.md)
* [快速构建高并发微服务](bookstore.md)
## 1. http api请求体的反序列化
在反序列化的过程中的针对请求数据的`数据格式`以及`数据校验`需求go-zero实现了自己的一套反序列化机制
### 1.1 `数据格式`以订单order.api文件为例
```go
type (
createOrderReq struct {
token string `path:"token"` // 用户token
productId string `json:"productId"` // 商品ID
num int `json:"num"` // 商品数量
}
createOrderRes struct {
success bool `json:"success"` // 是否成功
}
findOrderReq struct {
token string `path:"token"` // 用户token
page int `form:"page"` // 页数
pageSize int8 `form:"pageSize"` // 页大小
}
findOrderRes struct {
orderInfo []orderInfo `json:"orderInfo"` // 商品ID
}
orderInfo struct {
productId string `json:"productId"` // 商品ID
productName string `json:"productName"` // 商品名称
num int `json:"num"` // 商品数量
}
deleteOrderReq struct {
id string `path:"id"`
}
deleteOrderRes struct {
success bool `json:"success"` // 是否成功
}
)
service order {
@doc(
summary: 创建订单
)
@server(
handler: CreateOrderHandler
)
post /order/add/:token(createOrderReq) returns(createOrderRes)
@doc(
summary: 获取订单
)
@server(
handler: FindOrderHandler
)
get /order/find/:token(findOrderReq) returns(findOrderRes)
@doc(
summary: 删除订单
)
@server(
handler: DeleteOrderHandler
)
delete /order/:id(deleteOrderReq) returns(deleteOrderRes)
}
```
http api请求体的反序列化的tag有三种
* `path`http url 路径中参数反序列化
* `/order/add/1234567`会解析出来token为1234567
* `form`http form表单反序列化需要 header头添加 Content-Type: multipart/form-data
* `/order/find/1234567?page=1&pageSize=20`会解析出来token为1234567page为1pageSize为20
* `json`http request json body反序列化需要 header头添加 Content-Type: application/json
* `{"productId":"321","num":1}`会解析出来productId为321num为1
### 1.2 `数据校验`以用户user.api文件为例
```go
type (
createUserReq struct {
age int8 `json:"age,default=20,range=(12:100]"` // 年龄
name string `json:"name"` // 名字
alias string `json:"alias,optional"` // 别名
sex string `json:"sex,options=male|female"` // 性别
avatar string `json:"avatar,default=default.png"` // 头像
}
createUserRes struct {
success bool `json:"success"` // 是否成功
}
)
service user {
@doc(
summary: 创建订单
)
@server(
handler: CreateUserHandler
)
post /user/add(createUserReq) returns(createUserRes)
}
```
数据校验有很多种方式,包括以下但不限:
* `age`默认不输入为20输入则取值范围为(12:100],前开后闭
* `name`:必填,不可为空
* `alias`:选填,可为空
* `sex`:必填,取值为`male``female`
* `avatar`:选填,默认为`default.png`
更多详情参见[unmarshaler_test.go](../core/mapping/unmarshaler_test.go)
## 2. http api返回体的序列化
* 使用官方默认的`encoding/json`包序列化,在此不再累赘
## 3. 配置文件的反序列化
* `配置文件的反序列化``http api请求体的反序列化`使用同一套解析规则,可参照`http api请求体的反序列化`

View File

@@ -24,7 +24,7 @@ MapReduce主要有三个参数第一个参数为generate用以生产数据
场景一: 某些功能的结果往往需要依赖多个服务比如商品详情的结果往往会依赖用户服务、库存服务、订单服务等等一般被依赖的服务都是以rpc的形式对外提供为了降低依赖的耗时我们往往需要对依赖做并行处理
```
```go
func productDetail(uid, pid int64) (*ProductDetail, error) {
var pd ProductDetail
err := mr.Finish(func() (err error) {
@@ -42,15 +42,16 @@ func productDetail(uid, pid int64) (*ProductDetail, error) {
log.Printf("product detail error: %v", err)
return nil, err
}
return &pd, nil
}
```
该示例中返回商品详情依赖了多个服务获取数据,因此做并发的依赖处理,对接口的性能有很大的提升
场景二: 很多时候我们需要对一批数据进行处理比如对一批用户id效验每个用户的合法性并且效验过程中有一个出错就认为效验失败返回的结果为效验合法的用户id
```
```go
func checkLegal(uids []int64) ([]int64, error) {
r, err := mr.MapReduce(func(source chan<- interface{}) {
for _, uid := range uids {
@@ -85,9 +86,11 @@ func check(uid int64) (bool, error) {
return true, nil
}
```
该示例中如果check过程出现错误则通过cancel方法结束效验过程并返回error整个效验过程结束如果某个uid效验结果为false则最终结果不返回该uid
**MapReduce使用注意事项**
***MapReduce使用注意事项***
* mapper和reducer中都可以调用cancel参数为error调用后立即返回返回结果为nil, error
* mapper中如果不调用writer.Write则item最终不会被reducer聚合
* reducer中如果不调用writer.Wirte则返回结果为nil, ErrReduceNoOutput
@@ -96,12 +99,13 @@ func check(uid int64) (bool, error) {
***实现原理分析:***
MapReduce中首先通过buildSource方法通过执行generate(参数为无缓冲channel)产生数据并返回无缓冲的channelmapper会从该channel中读取数据
```
```go
func buildSource(generate GenerateFunc) chan interface{} {
source := make(chan interface{})
go func() {
defer close(source)
generate(source)
generate(source)
}()
return source
@@ -109,7 +113,8 @@ func buildSource(generate GenerateFunc) chan interface{} {
```
在MapReduceWithSource方法中定义了cancel方法mapper和reducer中都可以调用该方法调用后主线程收到close信号会立马返回
```
```go
cancel := once(func(err error) {
if err != nil {
retErr.Set(err)
@@ -125,7 +130,8 @@ cancel := once(func(err error) {
```
在mapperDispatcher方法中调用了executeMappersexecuteMappers消费buildSource产生的数据每一个item都会起一个goroutine单独处理默认最大并发数为16可以通过WithWorkers进行设置
```
```go
var wg sync.WaitGroup
defer func() {
wg.Wait() // 保证所有的item都处理完成
@@ -159,7 +165,8 @@ for {
```
reducer单goroutine对数mapper写入collector的数据进行处理如果reducer中没有手动调用writer.Write则最终会执行finish方法对output进行close避免死锁
```
```go
go func() {
defer func() {
if r := recover(); r != nil {
@@ -172,13 +179,12 @@ go func() {
}()
```
在该工具包中还提供了许多针对不同业务场景的方法实现原理与MapReduce大同小异感兴趣的同学可以查看源码学习
在该工具包中还提供了许多针对不同业务场景的方法实现原理与MapReduce大同小异感兴趣的同学可以查看源码学习
* MapReduceVoid 功能和MapReduce类似但没有结果返回只返回error
* Finish 处理固定数量的依赖返回error有一个error立即返回
* FinishVoid 和Finish方法功能类似没有返回值
* Map 只做generate和mapper处理返回channel
* MapVoid 和Map功能类似无返回
本文主要介绍了go-zero框架中的MapReduce工具在实际的项目中非常实用。用好工具对于提升服务性能和开发效率都有很大的帮助希望本篇文章能给大家带来一些收获。

112
doc/metric.md Normal file
View File

@@ -0,0 +1,112 @@
# 基于prometheus的微服务指标监控
服务上线后我们往往需要对服务进行监控,以便能及早发现问题并做针对性的优化,监控又可分为多种形式,比如日志监控,调用链监控,指标监控等等。而通过指标监控能清晰的观察出服务指标的变化趋势,了解服务的运行状态,对于保证服务稳定起着非常重要的作用
[prometheus](https://prometheus.io/)是一个开源的系统监控和告警工具支持强大的查询语言PromQL允许用户实时选择和汇聚时间序列数据时间序列数据是服务端通过HTTP协议主动拉取获得也可以通过中间网关来推送时间序列数据可以通过静态配置文件或服务发现来获取监控目标
## Prometheus 的架构
Prometheus 的整体架构以及生态系统组件如下图所示:
![prometheus](./images/prometheus.png)
Prometheus Server直接从监控目标中或者间接通过推送网关来拉取监控指标它在本地存储所有抓取到样本数据并对此数据执行一系列规则以汇总和记录现有数据的新时间序列或生成告警。可以通过 [Grafana](https://grafana.com/) 或者其他工具来实现监控数据的可视化
## go-zero基于prometheus的服务指标监控
[go-zero](https://github.com/tal-tech/go-zero) 框架中集成了基于prometheus的服务指标监控下面我们通过go-zero官方的示例[shorturl](https://github.com/tal-tech/go-zero/blob/master/doc/shorturl.md)来演示是如何对服务指标进行收集监控的:
- 第一步需要先安装Prometheus安装步骤请参考[官方文档](https://prometheus.io/)
- go-zero默认不开启prometheus监控开启方式很简单只需要在shorturl-api.yaml文件中增加配置如下其中Host为Prometheus Server地址为必填配置Port端口不填默认9091Path为用来拉取指标的路径默认为/metrics
```go
Prometheus:
Host: 127.0.0.1
Port: 9091
Path: /metrics
```
- 编辑prometheus的配置文件prometheus.yml添加如下配置并创建targets.json
```go
- job_name: 'file_ds'
file_sd_configs:
- files:
- targets.json
```
- 编辑targets.json文件其中targets为shorturl配置的目标地址并添加了几个默认的标签
```go
[
{
"targets": ["127.0.0.1:9091"],
"labels": {
"job": "shorturl-api",
"app": "shorturl-api",
"env": "test",
"instance": "127.0.0.1:8888"
}
}
]
```
- 启动prometheus服务默认侦听在9090端口
```go
prometheus --config.file=prometheus.yml
```
- 在浏览器输入http://127.0.0.1:9090/然后点击Status -> Targets即可看到状态为Up的Job并且Lables栏可以看到我们配置的默认的标签
![job状态为up](./images/prom_up.png)
通过以上几个步骤我们完成了prometheus对shorturl服务的指标监控收集的配置工作为了演示简单我们进行了手动的配置在实际的生产环境中一般采用定时更新配置文件或者服务发现的方式来配置监控目标篇幅有限这里不展开讲解感兴趣的同学请自行查看相关文档
## go-zero监控的指标类型
go-zero中目前在http的中间件和rpc的拦截器中添加了对请求指标的监控。
主要从请求耗时和请求错误两个维度请求耗时采用了Histogram指标类型定义了多个Buckets方便进行分位统计请求错误采用了Counter类型并在http metric中添加了path标签rpc metric中添加了method标签以便进行细分监控。
接下来演示如何查看监控指标:
首先在命令行多次执行如下命令
```go
curl -i "http://localhost:8888/shorten?url=http://www.xiaoheiban.cn"
```
打开Prometheus切换到Graph界面在输入框中输入{path="/shorten"}指令,即可查看监控指标,如下图
![查询面板](./images/panel.png)
我们通过PromQL语法查询过滤path为/shorten的指标结果中显示了指标名以及指标数值其中http_server_requests_code_total指标中code值为http的状态码200表明请求成功http_server_requests_duration_ms_bucket中对不同bucket结果分别进行了统计还可以看到所有的指标中都添加了我们配置的默认指标
Console界面主要展示了查询的指标结果Graph界面为我们提供了简单的图形化的展示界面在实际的生产环境中我们一般使用Grafana做图形化的展示
## grafana可视化界面
[grafana](https://grafana.com/)是一款可视化工具功能强大支持多种数据来源Prometheus、Elasticsearch、Graphite等安装比较简单请参考[官方文档](https://grafana.com/docs/grafana/latest/)grafana默认端口3000安装好后再浏览器输入http://localhost:3000/默认账号和密码都为admin
下面演示如何基于以上指标进行可视化界面的绘制:
- 点击左侧边栏Configuration->Data Source->Add data source进行数据源添加其中HTTP的URL为数据源的地址
![datasource](./images/datasource.png)
- 点击左侧边栏添加dashboard然后添加Variables方便针对不同的标签进行过滤筛选比如添加app变量用来过滤不同的服务
![variables](./images/variables.png)
- 进入dashboard点击右上角Add panel添加面板以path维度统计接口的qps
![qps](./images/qps.png)
- 最终的效果如下所示可以通过服务名称过滤不同的服务面板展示了path为/shorten的qps变化趋势
![qps panel](./images/qps_panel.png)
## 总结
以上演示了go-zero中基于prometheus+grafana服务指标监控的简单流程生产环境中可以根据实际的场景做不同维度的监控分析。现在go-zero的监控指标主要还是针对http和rpc这对于服务的整体监控显然还是不足的比如容器资源的监控依赖的mysql、redis等资源的监控以及自定义的指标监控等等go-zero在这方面后续还会持续优化。希望这篇文章能够给您带来帮助

View File

@@ -1,6 +1,6 @@
# PeriodicalExecutor设计
# 添加任务
## 添加任务
* 当前没有未执行的任务
* 添加并启动定时器
@@ -9,7 +9,7 @@
* 如到,执行所有缓存任务
* 未到,只添加
# 定时器到期
## 定时器到期
* 清除并执行所有缓存任务
* 再等待N个定时周期如果等待过程中一直没有新任务则退出
* 再等待N个定时周期如果等待过程中一直没有新任务则退出

167
doc/sharedcalls.md Normal file
View File

@@ -0,0 +1,167 @@
# 防止缓存击穿之进程内共享调用
go-zero微服务框架中提供了许多开箱即用的工具好的工具不仅能提升服务的性能而且还能提升代码的鲁棒性避免出错实现代码风格的统一方便他人阅读等等。
本文主要讲述进程内共享调用神器[SharedCalls](https://github.com/tal-tech/go-zero/blob/master/core/syncx/sharedcalls.go)。
## 使用场景
并发场景下可能会有多个线程协程同时请求同一份资源如果每个请求都要走一遍资源的请求过程除了比较低效之外还会对资源服务造成并发的压力。举一个具体例子比如缓存失效多个请求同时到达某服务请求某资源该资源在缓存中已经失效此时这些请求会继续访问DB做查询会引起数据库压力瞬间增大。而使用SharedCalls可以使得同时多个请求只需要发起一次拿结果的调用其他请求"坐享其成",这种设计有效减少了资源服务的并发压力,可以有效防止缓存击穿。
高并发场景下当某个热点key缓存失效后多个请求会同时从数据库加载该资源并保存到缓存如果不做防范可能会导致数据库被直接打死。针对这种场景go-zero框架中已经提供了实现具体可参看[sqlc](https://github.com/tal-tech/go-zero/blob/master/core/stores/sqlc/cachedsql.go)和[mongoc](https://github.com/tal-tech/go-zero/blob/master/core/stores/mongoc/cachedcollection.go)等实现代码。
为了简化演示代码我们通过多个线程同时去获取一个id来模拟缓存的场景。如下
```go
func main() {
const round = 5
var wg sync.WaitGroup
barrier := syncx.NewSharedCalls()
wg.Add(round)
for i := 0; i < round; i++ {
// 多个线程同时执行
go func() {
defer wg.Done()
// 可以看到多个线程在同一个key上去请求资源获取资源的实际函数只会被调用一次
val, err := barrier.Do("once", func() (interface{}, error) {
// sleep 1秒为了让多个线程同时取once这个key上的数据
time.Sleep(time.Second)
// 生成了一个随机的id
return stringx.RandId(), nil
})
if err != nil {
fmt.Println(err)
} else {
fmt.Println(val)
}
}()
}
wg.Wait()
}
```
运行,打印结果为:
```
837c577b1008a0db
837c577b1008a0db
837c577b1008a0db
837c577b1008a0db
837c577b1008a0db
```
可以看出只要是同一个key上的同时发起的请求都会共享同一个结果对获取DB数据进缓存等场景特别有用可以有效防止缓存击穿。
## 关键源码分析
- SharedCalls interface提供了Do和DoEx两种方法的抽象
```go
// SharedCalls接口提供了Do和DoEx两种方法
type SharedCalls interface {
Do(key string, fn func() (interface{}, error)) (interface{}, error)
DoEx(key string, fn func() (interface{}, error)) (interface{}, bool, error)
}
```
- SharedCalls interface的具体实现sharedGroup
```go
// call代表对指定资源的一次请求
type call struct {
wg sync.WaitGroup // 用于协调各个请求goroutine之间的资源共享
val interface{} // 用于保存请求的返回值
err error // 用于保存请求过程中发生的错误
}
type sharedGroup struct {
calls map[string]*call
lock sync.Mutex
}
```
- sharedGroup的Do方法
- key参数可以理解为资源的唯一标识。
- fn参数真正获取资源的方法。
- 处理过程分析:
```go
// 当多个请求同时使用Do方法请求资源时
func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
// 先申请加锁
g.lock.Lock()
// 根据key获取对应的call结果,并用变量c保存
if c, ok := g.calls[key]; ok {
// 拿到call以后释放锁此处call可能还没有实际数据只是一个空的内存占位
g.lock.Unlock()
// 调用wg.Wait判断是否有其他goroutine正在申请资源如果阻塞说明有其他goroutine正在获取资源
c.wg.Wait()
// 当wg.Wait不再阻塞表示资源获取已经结束可以直接返回结果
return c.val, c.err
}
// 没有拿到结果则调用makeCall方法去获取资源注意此处仍然是锁住的可以保证只有一个goroutine可以调用makecall
c := g.makeCall(key, fn)
// 返回调用结果
return c.val, c.err
}
```
- sharedGroup的DoEx方法
- 和Do方法类似只是返回值中增加了布尔值表示值是调用makeCall方法直接获取的还是取的共享成果
```go
func (g *sharedGroup) DoEx(key string, fn func() (interface{}, error)) (val interface{}, fresh bool, err error) {
g.lock.Lock()
if c, ok := g.calls[key]; ok {
g.lock.Unlock()
c.wg.Wait()
return c.val, false, c.err
}
c := g.makeCall(key, fn)
return c.val, true, c.err
}
```
- sharedGroup的makeCall方法
- 该方法由Do和DoEx方法调用是真正发起资源请求的方法。
```go
// 进入makeCall的一定只有一个goroutine因为要拿锁锁住的
func (g *sharedGroup) makeCall(key string, fn func() (interface{}, error)) *call {
// 创建call结构用于保存本次请求的结果
c := new(call)
// wg加1用于通知其他请求资源的goroutine等待本次资源获取的结束
c.wg.Add(1)
// 将用于保存结果的call放入map中以供其他goroutine获取
g.calls[key] = c
// 释放锁这样其他请求的goroutine才能获取call的内存占位
g.lock.Unlock()
defer func() {
// delete key first, done later. can't reverse the order, because if reverse,
// another Do call might wg.Wait() without get notified with wg.Done()
g.lock.Lock()
delete(g.calls, key)
g.lock.Unlock()
// 调用wg.Done通知其他goroutine可以返回结果这样本批次所有请求完成结果的共享
c.wg.Done()
}()
// 调用fn方法将结果填入变量c中
c.val, c.err = fn()
return c
}
```
## 最后
本文主要介绍了go-zero框架中的 SharedCalls工具对其应用场景和关键代码做了简单的梳理希望本篇文章能给大家带来一些收获。

View File

@@ -1,8 +1,8 @@
English | [简体中文](shorturl.md)
# Rapid development of microservices
## 0. Why building microservices are so difficult?
English | [简体中文](shorturl.md)
## 0. Why building microservices are so difficult
To build a well working microservice, we need lots of knowledges from different aspects.
@@ -25,7 +25,7 @@ As well, we always adhere to the idea that **prefer tools over conventions and d
Lets take the shorturl microservice as a quick example to demonstrate how to quickly create microservices by using [go-zero](https://github.com/tal-tech/go-zero). After finishing this tutorial, youll find that its so easy to write microservices!
## 1. What is a shorturl service?
## 1. What is a shorturl service
A shorturl service is that it converts a long url into a short one, by well designed algorithms.
@@ -60,6 +60,12 @@ And now, lets walk through the complete flow of quickly create a microservice
* install etcd, mysql, redis
* install protoc-gen-go
```
go get -u github.com/golang/protobuf/protoc-gen-go
```
* install goctl
```shell
@@ -129,7 +135,7 @@ And now, lets walk through the complete flow of quickly create a microservice
the generated file structure looks like:
```
```Plain Text
.
├── api
│   ├── etc
@@ -232,7 +238,7 @@ And now, lets walk through the complete flow of quickly create a microservice
the generated file structure looks like:
```
```Plain Text
rpc/transform
├── etc
│   └── transform.yaml // configuration file
@@ -284,7 +290,7 @@ And now, lets walk through the complete flow of quickly create a microservice
```go
type Config struct {
rest.RestConf
Transform rpcx.RpcClientConf // manual code
Transform zrpc.RpcClientConf // manual code
}
```
@@ -299,7 +305,7 @@ And now, lets walk through the complete flow of quickly create a microservice
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Transformer: transformer.NewTransformer(rpcx.MustNewClient(c.Transform)), // manual code
Transformer: transformer.NewTransformer(zrpc.MustNewClient(c.Transform)), // manual code
}
}
```
@@ -375,7 +381,7 @@ Till now, weve done the modification of API Gateway. All the manually added c
source shorturl.sql;
```
* under the directory `rpc/transform/model execute the following command to genrate CRUD+cache code, `-c` means using `redis cache`
* under the directory `rpc/transform/model` execute the following command to genrate CRUD+cache code, `-c` means using `redis cache`
```shell
goctl model mysql ddl -c -src shorturl.sql -dir .
@@ -385,7 +391,7 @@ Till now, weve done the modification of API Gateway. All the manually added c
the generated file structure looks like:
```
```Plain Text
rpc/transform/model
├── shorturl.sql
├── shorturlmodel.go // CRUD+cache code
@@ -409,7 +415,7 @@ Till now, weve done the modification of API Gateway. All the manually added c
```go
type Config struct {
rpcx.RpcServerConf
zrpc.RpcServerConf
DataSource string // manual code
Table string // manual code
Cache cache.CacheConf // manual code
@@ -533,4 +539,3 @@ We not only keep the framework simple, but also encapsulate the complexity into
For the generated code by goctl, lots of microservice components are included, like concurrency control, adaptive circuit breaker, adaptive load shedding, auto cache control etc. And its easy to deal with the busy sites.
If you have any ideas that can help us to improve the productivity, tell me any time! 👏

View File

@@ -1,8 +1,8 @@
[English](shorturl-en.md) | 简体中文
# 快速构建高并发微服务
## 0. 为什么说做好微服务很难?
[English](shorturl-en.md) | 简体中文
## 0. 为什么说做好微服务很难
要想做好微服务,我们需要理解和掌握的知识点非常多,从几个维度上来说:
@@ -25,7 +25,7 @@
下面我通过短链微服务来演示通过[go-zero](https://github.com/tal-tech/go-zero)快速的创建微服务的流程,走完一遍,你就会发现:原来编写微服务如此简单!
## 1. 什么是短链服务
## 1. 什么是短链服务
短链服务就是将长的URL网址通过程序计算等方式转换为简短的网址字符串。
@@ -60,6 +60,12 @@
* 安装etcd, mysql, redis
* 安装`protoc-gen-go`
```shell
go get -u github.com/golang/protobuf/protoc-gen-go
```
* 安装goctl工具
```shell
@@ -123,7 +129,7 @@
生成的文件结构如下:
```
```Plain Text
.
├── api
│   ├── etc
@@ -228,7 +234,7 @@
文件结构如下:
```
```Plain Text
rpc/transform
├── etc
│   └── transform.yaml // 配置文件
@@ -280,7 +286,7 @@
```go
type Config struct {
rest.RestConf
Transform rpcx.RpcClientConf // 手动代码
Transform zrpc.RpcClientConf // 手动代码
}
```
@@ -295,7 +301,7 @@
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Transformer: transformer.NewTransformer(rpcx.MustNewClient(c.Transform)), // 手动代码
Transformer: transformer.NewTransformer(zrpc.MustNewClient(c.Transform)), // 手动代码
}
}
```
@@ -320,7 +326,6 @@
// 手动代码结束
}
```
通过调用`transformer`的`Expand`方法实现短链恢复到url
@@ -342,7 +347,6 @@
// 手动代码结束
}
```
通过调用`transformer`的`Shorten`方法实现url到短链的变换
@@ -383,7 +387,7 @@
生成后的文件结构如下:
```
```Plain Text
rpc/transform/model
├── shorturl.sql
├── shorturlmodel.go // CRUD+cache代码
@@ -407,7 +411,7 @@
```go
type Config struct {
rpcx.RpcServerConf
zrpc.RpcServerConf
DataSource string // 手动代码
Table string // 手动代码
Cache cache.CacheConf // 手动代码
@@ -531,4 +535,3 @@ go-zero不只是一个框架更是一个建立在框架+工具基础上的,
通过go-zero+goctl生成的代码包含了微服务治理的各种组件包括并发控制、自适应熔断、自适应降载、自动缓存控制等可以轻松部署以承载巨大访问量。
有任何好的提升工程效率的想法,随时欢迎交流!👏

View File

@@ -21,4 +21,3 @@
* 通过Primary到DB查询行记录并写入缓存
* 有Primary到行记录的缓存
* 直接返回缓存结果

View File

@@ -1,10 +1,12 @@
package main
import (
"flag"
"fmt"
"bookstore/api/internal/config"
"bookstore/api/internal/handler"
"bookstore/api/internal/svc"
"flag"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rest"
@@ -23,5 +25,7 @@ func main() {
defer server.Stop()
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}

View File

@@ -2,11 +2,11 @@ package config
import (
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
rest.RestConf
Add rpcx.RpcClientConf
Check rpcx.RpcClientConf
}
Add zrpc.RpcClientConf
Check zrpc.RpcClientConf
}

View File

@@ -1,4 +1,4 @@
// DO NOT EDIT, generated by goctl
// Code generated by goctl. DO NOT EDIT.
package handler
import (

View File

@@ -5,7 +5,7 @@ import (
"bookstore/rpc/add/adder"
"bookstore/rpc/check/checker"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type ServiceContext struct {
@@ -17,7 +17,7 @@ type ServiceContext struct {
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Adder: adder.NewAdder(rpcx.MustNewClient(c.Add)),
Checker: checker.NewChecker(rpcx.MustNewClient(c.Check)),
Adder: adder.NewAdder(zrpc.MustNewClient(c.Add)),
Checker: checker.NewChecker(zrpc.MustNewClient(c.Check)),
}
}
}

View File

@@ -1,4 +1,4 @@
// DO NOT EDIT, generated by goctl
// Code generated by goctl. DO NOT EDIT.
package types
type AddReq struct {

View File

@@ -4,16 +4,17 @@
package main
import (
"flag"
"fmt"
"bookstore/rpc/add/internal/config"
"bookstore/rpc/add/internal/server"
"bookstore/rpc/add/internal/svc"
add "bookstore/rpc/add/pb"
"flag"
"fmt"
"log"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
@@ -27,12 +28,10 @@ func main() {
ctx := svc.NewServiceContext(c)
adderSrv := server.NewAdderServer(ctx)
s, err := rpcx.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
s, err := zrpc.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
add.RegisterAdderServer(grpcServer, adderSrv)
})
if err != nil {
log.Fatal(err)
}
logx.Must(err)
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()

View File

@@ -10,7 +10,7 @@ import (
"context"
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type (
@@ -19,11 +19,11 @@ type (
}
defaultAdder struct {
cli rpcx.Client
cli zrpc.Client
}
)
func NewAdder(cli rpcx.Client) Adder {
func NewAdder(cli zrpc.Client) Adder {
return &defaultAdder{
cli: cli,
}

View File

@@ -2,12 +2,12 @@ package config
import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
rpcx.RpcServerConf
zrpc.RpcServerConf
DataSource string
Table string
Cache cache.CacheConf
}
}

View File

@@ -4,16 +4,17 @@
package main
import (
"flag"
"fmt"
"bookstore/rpc/check/internal/config"
"bookstore/rpc/check/internal/server"
"bookstore/rpc/check/internal/svc"
check "bookstore/rpc/check/pb"
"flag"
"fmt"
"log"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
@@ -27,12 +28,10 @@ func main() {
ctx := svc.NewServiceContext(c)
checkerSrv := server.NewCheckerServer(ctx)
s, err := rpcx.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
s, err := zrpc.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
check.RegisterCheckerServer(grpcServer, checkerSrv)
})
if err != nil {
log.Fatal(err)
}
logx.Must(err)
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()

View File

@@ -10,7 +10,7 @@ import (
"context"
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type (
@@ -19,11 +19,11 @@ type (
}
defaultChecker struct {
cli rpcx.Client
cli zrpc.Client
}
)
func NewChecker(cli rpcx.Client) Checker {
func NewChecker(cli zrpc.Client) Checker {
return &defaultChecker{
cli: cli,
}

View File

@@ -2,12 +2,12 @@ package config
import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
rpcx.RpcServerConf
zrpc.RpcServerConf
DataSource string
Table string
Cache cache.CacheConf
}
}

View File

@@ -2,10 +2,10 @@ package config
import (
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
rest.RestConf
Rpc rpcx.RpcClientConf
Rpc zrpc.RpcClientConf
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/tal-tech/go-zero/example/graceful/dns/api/handler"
"github.com/tal-tech/go-zero/example/graceful/dns/api/svc"
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
var configFile = flag.String("f", "etc/graceful-api.json", "the config file")
@@ -19,7 +19,7 @@ func main() {
var c config.Config
conf.MustLoad(*configFile, &c)
client := rpcx.MustNewClient(c.Rpc)
client := zrpc.MustNewClient(c.Rpc)
ctx := &svc.ServiceContext{
Client: client,
}

View File

@@ -1,4 +1,4 @@
// DO NOT EDIT, generated by goctl
// Code generated by goctl. DO NOT EDIT.
package handler
import (

View File

@@ -1,7 +1,7 @@
package svc
import "github.com/tal-tech/go-zero/rpcx"
import "github.com/tal-tech/go-zero/zrpc"
type ServiceContext struct {
Client rpcx.Client
Client zrpc.Client
}

View File

@@ -1,4 +1,4 @@
// DO NOT EDIT, generated by goctl
// Code generated by goctl. DO NOT EDIT.
package types
type Response struct {

View File

@@ -9,7 +9,7 @@ import (
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/example/graceful/dns/rpc/graceful"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
@@ -39,10 +39,10 @@ func (gs *GracefulServer) Grace(ctx context.Context, req *graceful.Request) (*gr
func main() {
flag.Parse()
var c rpcx.RpcServerConf
var c zrpc.RpcServerConf
conf.MustLoad(*configFile, &c)
server := rpcx.MustNewServer(c, func(grpcServer *grpc.Server) {
server := zrpc.MustNewServer(c, func(grpcServer *grpc.Server) {
graceful.RegisterGraceServiceServer(grpcServer, NewGracefulServer())
})
defer server.Stop()

View File

@@ -2,10 +2,10 @@ package config
import (
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
rest.RestConf
Rpc rpcx.RpcClientConf
Rpc zrpc.RpcClientConf
}

View File

@@ -6,7 +6,7 @@
"Rpc": {
"Etcd": {
"Hosts": ["etcd.discov:2379"],
"Key": "rpcx"
"Key": "zrpc"
}
}
}
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/tal-tech/go-zero/example/graceful/etcd/api/handler"
"github.com/tal-tech/go-zero/example/graceful/etcd/api/svc"
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
var configFile = flag.String("f", "etc/graceful-api.json", "the config file")
@@ -19,7 +19,7 @@ func main() {
var c config.Config
conf.MustLoad(*configFile, &c)
client := rpcx.MustNewClient(c.Rpc)
client := zrpc.MustNewClient(c.Rpc)
ctx := &svc.ServiceContext{
Client: client,
}

View File

@@ -1,4 +1,4 @@
// DO NOT EDIT, generated by goctl
// Code generated by goctl. DO NOT EDIT.
package handler
import (

View File

@@ -1,7 +1,7 @@
package svc
import "github.com/tal-tech/go-zero/rpcx"
import "github.com/tal-tech/go-zero/zrpc"
type ServiceContext struct {
Client rpcx.Client
Client zrpc.Client
}

View File

@@ -1,4 +1,4 @@
// DO NOT EDIT, generated by goctl
// Code generated by goctl. DO NOT EDIT.
package types
type Response struct {

View File

@@ -3,6 +3,6 @@
"ListenOn": "0.0.0.0:3456",
"Etcd": {
"Hosts": ["etcd.discov:2379"],
"Key": "rpcx"
"Key": "zrpc"
}
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/example/graceful/etcd/rpc/graceful"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
@@ -39,10 +39,10 @@ func (gs *GracefulServer) Grace(ctx context.Context, req *graceful.Request) (*gr
func main() {
flag.Parse()
var c rpcx.RpcServerConf
var c zrpc.RpcServerConf
conf.MustLoad(*configFile, &c)
server := rpcx.MustNewServer(c, func(grpcServer *grpc.Server) {
server := zrpc.MustNewServer(c, func(grpcServer *grpc.Server) {
graceful.RegisterGraceServiceServer(grpcServer, NewGracefulServer())
})
defer server.Stop()

View File

@@ -25,4 +25,3 @@
`watch -n 5 kubectl top pod -n adhoc`
可以看到`kubectl`报告的`CPU`使用率,两者进行对比,即可知道是否准确

View File

@@ -8,7 +8,7 @@ import (
"github.com/tal-tech/go-zero/core/discov"
"github.com/tal-tech/go-zero/example/rpc/remote/unary"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
const timeFormat = "15:04:05"
@@ -16,10 +16,10 @@ const timeFormat = "15:04:05"
func main() {
flag.Parse()
client := rpcx.MustNewClient(rpcx.RpcClientConf{
client := zrpc.MustNewClient(zrpc.RpcClientConf{
Etcd: discov.EtcdConf{
Hosts: []string{"localhost:2379"},
Key: "rpcx",
Key: "zrpc",
},
})

View File

@@ -9,7 +9,7 @@ import (
"github.com/tal-tech/go-zero/core/discov"
"github.com/tal-tech/go-zero/example/rpc/remote/unary"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
var lb = flag.String("t", "direct", "the load balancer type")
@@ -17,20 +17,20 @@ var lb = flag.String("t", "direct", "the load balancer type")
func main() {
flag.Parse()
var cli rpcx.Client
var cli zrpc.Client
switch *lb {
case "direct":
cli = rpcx.MustNewClient(rpcx.RpcClientConf{
cli = zrpc.MustNewClient(zrpc.RpcClientConf{
Endpoints: []string{
"localhost:3456",
"localhost:3457",
},
})
case "discov":
cli = rpcx.MustNewClient(rpcx.RpcClientConf{
cli = zrpc.MustNewClient(zrpc.RpcClientConf{
Etcd: discov.EtcdConf{
Hosts: []string{"localhost:2379"},
Key: "rpcx",
Key: "zrpc",
},
})
default:

View File

@@ -5,20 +5,21 @@ import (
"flag"
"fmt"
"log"
"sync"
"github.com/tal-tech/go-zero/core/discov"
"github.com/tal-tech/go-zero/example/rpc/remote/stream"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
const name = "kevin"
var key = flag.String("key", "rpcx", "the key on etcd")
var key = flag.String("key", "zrpc", "the key on etcd")
func main() {
flag.Parse()
client, err := rpcx.NewClientNoAuth(discov.EtcdConf{
client, err := zrpc.NewClientNoAuth(discov.EtcdConf{
Hosts: []string{"localhost:2379"},
Key: *key,
})
@@ -33,6 +34,7 @@ func main() {
log.Fatal(err)
}
var wg sync.WaitGroup
go func() {
for {
resp, err := stm.Recv()
@@ -41,10 +43,12 @@ func main() {
}
fmt.Println("=>", resp.Greet)
wg.Done()
}
}()
for i := 0; i < 3; i++ {
wg.Add(1)
fmt.Println("<=", name)
if err = stm.Send(&stream.StreamReq{
Name: name,
@@ -52,4 +56,6 @@ func main() {
log.Fatal(err)
}
}
wg.Wait()
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/example/rpc/remote/unary"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
var configFile = flag.String("f", "config.json", "the config file")
@@ -16,9 +16,9 @@ var configFile = flag.String("f", "config.json", "the config file")
func main() {
flag.Parse()
var c rpcx.RpcClientConf
var c zrpc.RpcClientConf
conf.MustLoad(*configFile, &c)
client := rpcx.MustNewClient(c)
client := zrpc.MustNewClient(c)
ticker := time.NewTicker(time.Millisecond * 500)
defer ticker.Stop()
for {

View File

@@ -7,7 +7,7 @@ import (
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/service"
"github.com/tal-tech/go-zero/example/rpc/remote/unary"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
@@ -17,7 +17,7 @@ var (
)
type GreetServer struct {
*rpcx.RpcProxy
*zrpc.RpcProxy
}
func (s *GreetServer) Greet(ctx context.Context, req *unary.Request) (*unary.Response, error) {
@@ -33,7 +33,7 @@ func (s *GreetServer) Greet(ctx context.Context, req *unary.Request) (*unary.Res
func main() {
flag.Parse()
proxy := rpcx.MustNewServer(rpcx.RpcServerConf{
proxy := zrpc.MustNewServer(zrpc.RpcServerConf{
ServiceConf: service.ServiceConf{
Log: logx.LogConf{
Mode: "console",
@@ -42,7 +42,7 @@ func main() {
ListenOn: *listen,
}, func(grpcServer *grpc.Server) {
unary.RegisterGreeterServer(grpcServer, &GreetServer{
RpcProxy: rpcx.NewProxy(*server),
RpcProxy: zrpc.NewProxy(*server),
})
})
proxy.Start()

View File

@@ -6,7 +6,7 @@
"Hosts": [
"localhost:2379"
],
"Key": "rpcx"
"Key": "zrpc"
},
"Redis": {
"Host": "localhost:6379",

View File

@@ -6,7 +6,7 @@ import (
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/example/rpc/remote/stream"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
@@ -39,10 +39,10 @@ func (gs StreamGreetServer) Greet(s stream.StreamGreeter_GreetServer) error {
}
func main() {
var c rpcx.RpcServerConf
var c zrpc.RpcServerConf
conf.MustLoad("etc/config.json", &c)
server := rpcx.MustNewServer(c, func(grpcServer *grpc.Server) {
server := zrpc.MustNewServer(c, func(grpcServer *grpc.Server) {
stream.RegisterStreamGreeterServer(grpcServer, StreamGreetServer(0))
})
server.Start()

View File

@@ -8,6 +8,6 @@
"Hosts": [
"localhost:2379"
],
"Key": "rpcx"
"Key": "zrpc"
}
}

View File

@@ -8,6 +8,6 @@
"Hosts": [
"localhost:2379"
],
"Key": "rpcx"
"Key": "zrpc"
}
}

View File

@@ -6,7 +6,7 @@
"Hosts": [
"etcd.discov:2379"
],
"Key": "rpcx"
"Key": "zrpc"
},
"Timeout": 500
}

View File

@@ -10,7 +10,7 @@ import (
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/example/rpc/remote/unary"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
@@ -44,10 +44,10 @@ func (gs *GreetServer) Greet(ctx context.Context, req *unary.Request) (*unary.Re
func main() {
flag.Parse()
var c rpcx.RpcServerConf
var c zrpc.RpcServerConf
conf.MustLoad(*configFile, &c)
server := rpcx.MustNewServer(c, func(grpcServer *grpc.Server) {
server := zrpc.MustNewServer(c, func(grpcServer *grpc.Server) {
unary.RegisterGreeterServer(grpcServer, NewGreetServer())
})
server.Start()

View File

@@ -2,10 +2,10 @@ package config
import (
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
rest.RestConf
Transform rpcx.RpcClientConf
Transform zrpc.RpcClientConf
}

View File

@@ -1,4 +1,4 @@
// DO NOT EDIT, generated by goctl
// Code generated by goctl. DO NOT EDIT.
package handler
import (

View File

@@ -4,7 +4,7 @@ import (
"shorturl/api/internal/config"
"shorturl/rpc/transform/transformer"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type ServiceContext struct {
@@ -15,6 +15,6 @@ type ServiceContext struct {
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Transformer: transformer.NewTransformer(rpcx.MustNewClient(c.Transform)),
Transformer: transformer.NewTransformer(zrpc.MustNewClient(c.Transform)),
}
}

View File

@@ -1,4 +1,4 @@
// DO NOT EDIT, generated by goctl
// Code generated by goctl. DO NOT EDIT.
package types
type ExpandReq struct {

View File

@@ -2,11 +2,11 @@ package config
import (
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
rpcx.RpcServerConf
zrpc.RpcServerConf
DataSource string
Table string
Cache cache.CacheConf

View File

@@ -14,7 +14,7 @@ import (
transform "shorturl/rpc/transform/pb"
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
@@ -28,7 +28,7 @@ func main() {
ctx := svc.NewServiceContext(c)
transformerSrv := server.NewTransformerServer(ctx)
s, err := rpcx.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
s, err := zrpc.NewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
transform.RegisterTransformerServer(grpcServer, transformerSrv)
})
if err != nil {

View File

@@ -11,7 +11,7 @@ import (
transform "shorturl/rpc/transform/pb"
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
type (
@@ -21,11 +21,11 @@ type (
}
defaultTransformer struct {
cli rpcx.Client
cli zrpc.Client
}
)
func NewTransformer(cli rpcx.Client) Transformer {
func NewTransformer(cli zrpc.Client) Transformer {
return &defaultTransformer{
cli: cli,
}

View File

@@ -0,0 +1,34 @@
package main
import (
"fmt"
"sync"
"time"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/core/syncx"
)
func main() {
const round = 5
var wg sync.WaitGroup
barrier := syncx.NewSharedCalls()
wg.Add(round)
for i := 0; i < round; i++ {
go func() {
defer wg.Done()
val, err := barrier.Do("once", func() (interface{}, error) {
time.Sleep(time.Second)
return stringx.RandId(), nil
})
if err != nil {
fmt.Println(err)
} else {
fmt.Println(val)
}
}()
}
wg.Wait()
}

View File

@@ -1,3 +1,13 @@
{
"Server": "localhost:3456"
"Name": "edge-api",
"Host": "0.0.0.0",
"Port": 3456,
"Portal": {
"Etcd": {
"Hosts": [
"localhost:2379"
],
"Key": "portal"
}
}
}

View File

@@ -10,14 +10,19 @@ import (
"github.com/tal-tech/go-zero/example/tracing/remote/portal"
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/rest/httpx"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
)
var (
configFile = flag.String("f", "config.json", "the config file")
client rpcx.Client
client zrpc.Client
)
type Config struct {
rest.RestConf
Portal zrpc.RpcClientConf
}
func handle(w http.ResponseWriter, r *http.Request) {
conn := client.Conn()
greet := portal.NewPortalClient(conn)
@@ -34,16 +39,16 @@ func handle(w http.ResponseWriter, r *http.Request) {
func main() {
flag.Parse()
var c rpcx.RpcClientConf
var c Config
conf.MustLoad(*configFile, &c)
client = rpcx.MustNewClient(c)
client = zrpc.MustNewClient(c.Portal)
engine := rest.MustNewServer(rest.RestConf{
ServiceConf: service.ServiceConf{
Log: logx.LogConf{
Mode: "console",
},
},
Port: 3333,
Port: c.Port,
})
defer engine.Stop()

View File

@@ -7,7 +7,7 @@ import (
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/example/tracing/remote/portal"
"github.com/tal-tech/go-zero/example/tracing/remote/user"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
@@ -15,16 +15,16 @@ var configFile = flag.String("f", "etc/config.json", "the config file")
type (
Config struct {
rpcx.RpcServerConf
UserRpc rpcx.RpcClientConf
zrpc.RpcServerConf
UserRpc zrpc.RpcClientConf
}
PortalServer struct {
userRpc rpcx.Client
userRpc zrpc.Client
}
)
func NewPortalServer(client rpcx.Client) *PortalServer {
func NewPortalServer(client zrpc.Client) *PortalServer {
return &PortalServer{
userRpc: client,
}
@@ -53,8 +53,8 @@ func main() {
var c Config
conf.MustLoad(*configFile, &c)
client := rpcx.MustNewClient(c.UserRpc)
server := rpcx.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
client := zrpc.MustNewClient(c.UserRpc)
server := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
portal.RegisterPortalServer(grpcServer, NewPortalServer(client))
})
server.Start()

View File

@@ -10,7 +10,7 @@ import (
"github.com/tal-tech/go-zero/core/conf"
"github.com/tal-tech/go-zero/example/tracing/remote/user"
"github.com/tal-tech/go-zero/rpcx"
"github.com/tal-tech/go-zero/zrpc"
"google.golang.org/grpc"
)
@@ -44,10 +44,10 @@ func (gs *UserServer) GetGrade(ctx context.Context, req *user.UserRequest) (*use
func main() {
flag.Parse()
var c rpcx.RpcServerConf
var c zrpc.RpcServerConf
conf.MustLoad(*configFile, &c)
server := rpcx.MustNewServer(c, func(grpcServer *grpc.Server) {
server := zrpc.MustNewServer(c, func(grpcServer *grpc.Server) {
user.RegisterUserServer(grpcServer, NewUserServer())
})
server.Start()

View File

@@ -1,14 +1,14 @@
English | [简体中文](readme.md)
# go-zero
English | [简体中文](readme.md)
[![Go](https://github.com/tal-tech/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/tal-tech/go-zero/actions)
[![codecov](https://codecov.io/gh/tal-tech/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/tal-tech/go-zero)
[![Go Report Card](https://goreportcard.com/badge/github.com/tal-tech/go-zero)](https://goreportcard.com/report/github.com/tal-tech/go-zero)
[![Release](https://img.shields.io/github/v/release/tal-tech/go-zero.svg?style=flat-square)](https://github.com/tal-tech/go-zero)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
## 0. what is go-zero?
## 0. what is go-zero
go-zero is a web and rpc framework that with lots of engineering practices builtin. Its born to ensure the stability of the busy services with resilience design, and has been serving sites with tens of millions users for years.
@@ -19,7 +19,7 @@ Advantages of go-zero:
* improve the stability of the services with tens of millions of daily active users
* builtin chained timeout control, concurrency control, rate limit, adaptive circuit breaker, adaptive load shedding, even no configuration needed
* builtin middlewares also can be integrated into your frameworks
* simple API syntax, one command to generate couple different languages
* simple API syntax, one command to generate couple of different languages
* auto validate the request parameters from clients
* plenty of builtin microservice management and concurrent toolkits
@@ -78,7 +78,7 @@ As below, go-zero protects the system with couple layers and mechanisms:
## 4. Future development plans of go-zero
* auto generate API mock server, make the client debugging eaisier
* auto generate API mock server, make the client debugging easier
* auto generate the simple integration test for the server side just from the .api files
## 5. Installation
@@ -99,7 +99,7 @@ go get -u github.com/tal-tech/go-zero
1. install goctl
`goctl`can be read as `go control`. `goctl means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve the productivity, and make our lives easier.
`goctl`can be read as `go control`. `goctl` means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve the productivity, and make our lives easier.
```shell
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
@@ -113,11 +113,11 @@ go get -u github.com/tal-tech/go-zero
type Request struct {
Name string `path:"name,options=you|me"` // parameters are auto validated
}
type Response struct {
Message string `json:"message"`
}
service greet-api {
@server(
handler: GreetHandler
@@ -140,7 +140,7 @@ go get -u github.com/tal-tech/go-zero
the generated files look like:
```
```Plain Text
├── greet
│   ├── etc
│   │   └── greet-api.yaml // configuration file
@@ -159,6 +159,7 @@ go get -u github.com/tal-tech/go-zero
│   └── types.go // request/response defined here
└── greet.api // api description file
```
the generated code can be run directly:
```shell
@@ -184,8 +185,9 @@ go get -u github.com/tal-tech/go-zero
4. Write the business logic code
* the dependencies can be passed into the logic within servicecontext.go, like mysql, reds etc.
* add the logic code in logic package according to .api file
* the dependencies can be passed into the logic within servicecontext.go, like mysql, reds etc.
* add the logic code in logic package according to .api file
5. Generate code like Java, TypeScript, Dart, JavaScript etc. just from the api file
```shell

View File

@@ -1,7 +1,7 @@
[English](readme-en.md) | 简体中文
# go-zero
[English](readme-en.md) | 简体中文
[![Go](https://github.com/tal-tech/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/tal-tech/go-zero/actions)
[![codecov](https://codecov.io/gh/tal-tech/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/tal-tech/go-zero)
[![Go Report Card](https://goreportcard.com/badge/github.com/tal-tech/go-zero)](https://goreportcard.com/report/github.com/tal-tech/go-zero)
@@ -108,41 +108,41 @@ go get -u github.com/tal-tech/go-zero
确保goctl可执行
2. 快速生成api服务
```shell
goctl api new greet
cd greet
go run greet.go -f etc/greet-api.yaml
```
默认侦听在8888端口可以在配置文件里修改可以通过curl请求
```shell
curl -i http://localhost:8888/greet/from/you
```
返回如下:
```http
HTTP/1.1 200 OK
Date: Sun, 30 Aug 2020 15:32:35 GMT
Content-Length: 0
```
编写业务代码:
* api文件定义了服务对外暴露的路由可参考[api规范](https://github.com/tal-tech/go-zero/blob/master/doc/goctl.md)
* 可以在servicecontext.go里面传递依赖给logic比如mysql, redis等
* 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑
3. 可以根据api文件生成前端需要的Java, TypeScript, Dart, JavaScript代码
```shell
goctl api java -api greet.api -dir greet
goctl api dart -api greet.api -dir greet
...
goctl api new greet
cd greet
go run greet.go -f etc/greet-api.yaml
```
默认侦听在8888端口可以在配置文件里修改可以通过curl请求
```shell
curl -i http://localhost:8888/greet/from/you
```
返回如下:
```http
HTTP/1.1 200 OK
Date: Sun, 30 Aug 2020 15:32:35 GMT
Content-Length: 0
```
编写业务代码:
* api文件定义了服务对外暴露的路由可参考[api规范](https://github.com/tal-tech/go-zero/blob/master/doc/goctl.md)
* 可以在servicecontext.go里面传递依赖给logic比如mysql, redis等
* 在api定义的get/post/put/delete等请求对应的logic里增加业务处理逻辑
3. 可以根据api文件生成前端需要的Java, TypeScript, Dart, JavaScript代码
```shell
goctl api java -api greet.api -dir greet
goctl api dart -api greet.api -dir greet
...
```
## 7. Benchmark
![benchmark](doc/images/benchmark.png)
@@ -157,6 +157,10 @@ Content-Length: 0
* [通过MapReduce降低服务响应时间](doc/mapreduce.md)
* [关键字替换和敏感词过滤工具](doc/keywords.md)
* [进程内缓存使用方法](doc/collection.md)
* [防止缓存击穿之进程内共享调用](doc/sharedcalls.md)
* [基于prometheus的微服务指标监控](doc/metric.md)
* [文本序列化和反序列化](doc/mapping.md)
* [快速构建jwt鉴权认证](doc/jwt.md)
## 9. 微信交流群

View File

@@ -20,7 +20,7 @@ type (
// why not name it as Conf, because we need to consider usage like:
// type Config struct {
// rpcx.RpcConf
// zrpc.RpcConf
// rest.RestConf
// }
// if with the name Conf, there will be two Conf inside Config.

View File

@@ -115,7 +115,7 @@ func logBrief(r *http.Request, code int, timer *utils.ElapsedTimer, logs *intern
buf.WriteString(fmt.Sprintf("%d - %s - %s - %s - %s",
code, r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(), timex.ReprOfDuration(duration)))
if duration > slowThreshold {
logx.Slowf("[HTTP] %d - %s - %s - %s - slowcall(%s)",
logx.WithContext(r.Context()).Slowf("[HTTP] %d - %s - %s - %s - slowcall(%s)",
code, r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(), timex.ReprOfDuration(duration))
}
@@ -130,9 +130,9 @@ func logBrief(r *http.Request, code int, timer *utils.ElapsedTimer, logs *intern
}
if ok {
logx.Info(buf.String())
logx.WithContext(r.Context()).Info(buf.String())
} else {
logx.Error(buf.String())
logx.WithContext(r.Context()).Error(buf.String())
}
}
@@ -143,7 +143,7 @@ func logDetails(r *http.Request, response *DetailLoggedResponseWriter, timer *ut
buf.WriteString(fmt.Sprintf("%d - %s - %s\n=> %s\n",
response.writer.code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r)))
if duration > slowThreshold {
logx.Slowf("[HTTP] %d - %s - slowcall(%s)\n=> %s\n",
logx.WithContext(r.Context()).Slowf("[HTTP] %d - %s - slowcall(%s)\n=> %s\n",
response.writer.code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r))
}
@@ -157,7 +157,7 @@ func logDetails(r *http.Request, response *DetailLoggedResponseWriter, timer *ut
buf.WriteString(fmt.Sprintf("<= %s", respBuf))
}
logx.Info(buf.String())
logx.WithContext(r.Context()).Info(buf.String())
}
func isOkResponse(code int) bool {

View File

@@ -109,6 +109,18 @@ func TestParseRequired(t *testing.T) {
assert.NotNil(t, err)
}
func TestParseOptions(t *testing.T) {
v := struct {
Position int8 `form:"pos,options=1|2"`
}{}
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?pos=4", nil)
assert.Nil(t, err)
err = Parse(r, &v)
assert.NotNil(t, err)
}
func BenchmarkParseRaw(b *testing.B) {
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", nil)
if err != nil {

View File

@@ -5,7 +5,7 @@ import (
"net/http"
)
const pathVars = "pathVars"
var pathVars = contextKey("pathVars")
func Vars(r *http.Request) map[string]string {
vars, ok := r.Context().Value(pathVars).(map[string]string)
@@ -19,3 +19,9 @@ func Vars(r *http.Request) map[string]string {
func WithPathVars(r *http.Request, params map[string]string) *http.Request {
return r.WithContext(context.WithValue(r.Context(), pathVars, params))
}
type contextKey string
func (c contextKey) String() string {
return "rest/internal/context context key" + string(c)
}

View File

@@ -10,7 +10,7 @@ import (
"github.com/tal-tech/go-zero/rest/httpx"
)
const LogContext = "request_logs"
var LogContext = contextKey("request_logs")
type LogCollector struct {
Messages []string
@@ -82,3 +82,9 @@ func formatf(r *http.Request, format string, v ...interface{}) string {
func formatWithReq(r *http.Request, v string) string {
return fmt.Sprintf("(%s - %s) %s", r.RequestURI, httpx.GetRemoteAddr(r), v)
}
type contextKey string
func (c contextKey) String() string {
return "rest/internal context key " + string(c)
}

Some files were not shown because too many files have changed in this diff Show More