Repository: go-kratos/kratos
Pull request #1504 (2021-09-23 01:49:02)

feat:edit readme and roadmap
<!--
🎉 Thanks for sending a pull request to Kratos! Here are some tips for you:

1. If this is your first time contributing to Kratos, please read our contribution guide: https://go-kratos.dev/en/docs/community/contribution/
2. Ensure you have added or ran the appropriate tests and lint for your PR, please use `make lint` and `make test` before filing your PR, use `make clean` to tidy your go mod.
3. If the PR is unfinished, you may need mark it as a WIP(Work In Progress) PR or draft PR
4. Please use a conventional commits format title: `<type>[optional scope]: <description>`
    
    Some suggestion on <type>:

    fix: A bug fix
    feat: A new feature
    test: Adding missing tests or correcting existing tests
    refactor: A code change that neither fixes a bug nor adds a feature
    break: Changes has break change

    docs: Documentation only changes
    deps: Changes external dependencies
    style: Changes that do not affect the meaning of the code (white-space, formatting, etc)
    chore Daily work, examples, etc.
    ci: Changes to our CI configuration files and scripts
-->

#### Description (what this PR does / why we need it):
<!--
* The description should include the motivation for this PR or contrast this with previous behavior
-->


#### Which issue(s) this PR fixes (resolves / be part of):
<!--
* Automatically closes linked issue when PR is merged.
* If you PR is not fully resolved issue, please use `part of #<issue number>` instead.

Usage: `fixes/resolves #<issue number>`, or `fixes/resolves (paste link of issue)`.
-->
fixes #

#### Other special notes for reviewer:
<!--
* Somethings that need extra attention for reviewer
* Some additional notes, TODO list, etc.
-->


codecov-commenter: # [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/1504?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) Report
> Merging [#1504](https://codecov.io/gh/go-kratos/kratos/pull/1504?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (734a501) into [main](https://codecov.io/gh/go-kratos/kratos/commit/25f448794d910594adf337d968d298c61862e692?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (25f4487) will **not change** coverage.
> The diff coverage is `n/a`.

> :exclamation: Current head 734a501 differs from pull request most recent head ec3d1d3. Consider uploading reports for the commit ec3d1d3 to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/go-kratos/kratos/pull/1504/graphs/tree.svg?width=650&height=150&src=pr&token=XwnmBTz78x&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)](https://codecov.io/gh/go-kratos/kratos/pull/1504?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)

```diff
@@           Coverage Diff           @@
##             main    #1504   +/-   ##
=======================================
  Coverage   69.51%   69.51%           
=======================================
  Files          66       66           
  Lines        3028     3028           
=======================================
  Hits         2105     2105           
  Misses        743      743           
  Partials      180      180           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/go-kratos/kratos/pull/1504?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/1504?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Last update [25f4487...ec3d1d3](https://codecov.io/gh/go-kratos/kratos/pull/1504?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).


Patch:
diff --git a/README.md b/README.md
index 4bbf29b1..9b863bcd 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
 <p align="center">
 <a href="https://github.com/go-kratos/kratos/actions"><img src="https://github.com/go-kratos/kratos/workflows/Go/badge.svg" alt="Build Status"></a>
 <a href="https://pkg.go.dev/github.com/go-kratos/kratos/v2"><img src="https://pkg.go.dev/badge/github.com/go-kratos/kratos/v2" alt="GoDoc"></a>
+<a href="https://codecov.io/gh/go-kratos/kratos"><img src="https://codecov.io/gh/go-kratos/kratos/master/graph/badge.svg" alt="codeCov"></a>
 <a href="https://goreportcard.com/report/github.com/go-kratos/kratos"><img src="https://goreportcard.com/badge/github.com/go-kratos/kratos" alt="Go Report Card" /a>
 <a href="https://github.com/go-kratos/kratos/blob/main/LICENSE"><img src="https://img.shields.io/github/license/go-kratos/kratos" alt="License"></a>
 <a href="https://discord.gg/BWzJsUJ"><img src="https://img.shields.io/discord/766619759214854164?label=chat&logo=discord" alt="Discord"></a>
diff --git a/README_zh.md b/README_zh.md
index a28f934d..b0a058a1 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -2,6 +2,7 @@
 
 [![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/)
 [![Build Status](https://github.com/go-kratos/kratos/workflows/Go/badge.svg)](https://github.com/go-kratos/kratos/actions)
+[![codecov](https://codecov.io/gh/go-kratos/kratos/master/graph/badge.svg)](https://codecov.io/gh/go-kratos/kratos)
 [![GoDoc](https://pkg.go.dev/badge/github.com/go-kratos/kratos/v2)](https://pkg.go.dev/github.com/go-kratos/kratos/v2)
 [![Go Report Card](https://goreportcard.com/badge/github.com/go-kratos/kratos)](https://goreportcard.com/report/github.com/go-kratos/kratos)
 [![Discord](https://img.shields.io/discord/766619759214854164?label=chat&logo=discord)](https://discord.gg/BWzJsUJ)
diff --git a/ROADMAP.md b/ROADMAP.md
index 8498b2cd..da9872a8 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -27,30 +27,30 @@ This document defines the roadmap for Kratos development.
     - [x] gRPC status
     - [x] transport tracing
     - [x] Validator
-    - [ ] Authentication
-    - [ ] Ratelimit
-    - [ ] CircuitBreaker
+    - [x] Authentication
+    - [x] Ratelimit
+    - [x] CircuitBreaker
 - [x] Metrics
     - [x] Prometheus
     - [x] DataDog
 - [x] Tracing
     - [x] HTTP
-        - [ ] TLS
+        - [x] TLS
         - [x] Client
         - [x] Service Registrar
         - [ ] javascript/typescript clients
     - [x] gRPC
-        - [ ] TLS
+        - [x] TLS
         - [x] Uarry Handler
-        - [ ] Streaming Handler
+        - [x] Streaming Handler
 - [ ] Cache
     - [ ] go-redis
 - [x] Event
     - [x] Pub/Sub
     - [x] Kafka
     - [ ] Nats
-- [ ] Database
-    - [ ] Ent
+- [x] Database
+    - [x] Ent
     - [ ] Gorm
 
 ## Platform
diff --git a/examples/selector/client/main.go b/examples/selector/client/main.go
new file mode 100644
index 00000000..3641c632
--- /dev/null
+++ b/examples/selector/client/main.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+	"context"
+	"log"
+	"time"
+
+	"github.com/go-kratos/kratos/contrib/registry/consul/v2"
+	"github.com/go-kratos/kratos/examples/helloworld/helloworld"
+	"github.com/go-kratos/kratos/v2/middleware/recovery"
+	"github.com/go-kratos/kratos/v2/selector/filter"
+	"github.com/go-kratos/kratos/v2/selector/p2c"
+	"github.com/go-kratos/kratos/v2/selector/wrr"
+	"github.com/go-kratos/kratos/v2/transport/grpc"
+	"github.com/go-kratos/kratos/v2/transport/http"
+	"github.com/hashicorp/consul/api"
+)
+
+func main() {
+	consulCli, err := api.NewClient(api.DefaultConfig())
+	if err != nil {
+		panic(err)
+	}
+	r := consul.New(consulCli)
+
+	// new grpc client
+	conn, err := grpc.DialInsecure(
+		context.Background(),
+		grpc.WithEndpoint("discovery:///helloworld"),
+		grpc.WithDiscovery(r),
+		// 由于gRPC框架的限制只能使用全局balancer+filter的方式来实现selector
+		// 这里使用weighted round robin算法的balancer+静态version=1.0.0的Filter
+		grpc.WithBalancerName(wrr.Name),
+		grpc.WithSelectFilter(filter.Version("1.0.0")),
+	)
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer conn.Close()
+	gClient := helloworld.NewGreeterClient(conn)
+
+	// new http client
+	hConn, err := http.NewClient(
+		context.Background(),
+		http.WithMiddleware(
+			recovery.Recovery(),
+		),
+		http.WithEndpoint("discovery:///helloworld"),
+		http.WithDiscovery(r),
+		// 这里使用p2c算法的balancer+静态version=2.0.0的Filter组成一个selector
+		http.WithSelector(
+			p2c.New(p2c.WithFilter(filter.Version("2.0.0"))),
+		),
+	)
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer hConn.Close()
+	hClient := helloworld.NewGreeterHTTPClient(hConn)
+
+	for {
+		time.Sleep(time.Second)
+		callGRPC(gClient)
+		callHTTP(hClient)
+	}
+}
+
+func callGRPC(client helloworld.GreeterClient) {
+	reply, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "kratos"})
+	if err != nil {
+		log.Fatal(err)
+	}
+	log.Printf("[grpc] SayHello %+v\n", reply)
+}
+
+func callHTTP(client helloworld.GreeterHTTPClient) {
+	reply, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: "kratos"})
+	if err != nil {
+		log.Fatal(err)
+	}
+	log.Printf("[http] SayHello %s\n", reply.Message)
+}
diff --git a/examples/selector/server/main.go b/examples/selector/server/main.go
new file mode 100644
index 00000000..247bdc4c
--- /dev/null
+++ b/examples/selector/server/main.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/go-kratos/kratos/contrib/registry/consul/v2"
+	"github.com/go-kratos/kratos/examples/helloworld/helloworld"
+	"github.com/go-kratos/kratos/v2"
+	"github.com/go-kratos/kratos/v2/log"
+	"github.com/go-kratos/kratos/v2/middleware/logging"
+	"github.com/go-kratos/kratos/v2/middleware/recovery"
+	"github.com/go-kratos/kratos/v2/transport/grpc"
+	"github.com/go-kratos/kratos/v2/transport/http"
+	"github.com/hashicorp/consul/api"
+)
+
+// server is used to implement helloworld.GreeterServer.
+type server struct {
+	helloworld.UnimplementedGreeterServer
+}
+
+// SayHello implements helloworld.GreeterServer
+func (s *server) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) {
+	return &helloworld.HelloReply{Message: fmt.Sprintf("Welcome %+v!", in.Name)}, nil
+}
+
+func main() {
+	logger := log.NewStdLogger(os.Stdout)
+
+	consulClient, err := api.NewClient(api.DefaultConfig())
+	if err != nil {
+		log.NewHelper(logger).Fatal(err)
+	}
+	go runServer("1.0.0", logger, consulClient, 8000)
+	go runServer("1.0.0", logger, consulClient, 8010)
+
+	runServer("2.0.0", logger, consulClient, 8020)
+}
+
+func runServer(version string, logger log.Logger, client *api.Client, port int) {
+	logger = log.With(logger, "version", version, "port:", port)
+	log := log.NewHelper(logger)
+
+	httpSrv := http.NewServer(
+		http.Address(fmt.Sprintf(":%d", port)),
+		http.Middleware(
+			recovery.Recovery(),
+			logging.Server(logger),
+		),
+	)
+	grpcSrv := grpc.NewServer(
+		grpc.Address(fmt.Sprintf(":%d", port+1000)),
+		grpc.Middleware(
+			recovery.Recovery(),
+			logging.Server(logger),
+		),
+	)
+
+	s := &server{}
+	helloworld.RegisterGreeterServer(grpcSrv, s)
+	helloworld.RegisterGreeterHTTPServer(httpSrv, s)
+
+	r := consul.New(client)
+	app := kratos.New(
+		kratos.Name("helloworld"),
+		kratos.Server(
+			grpcSrv,
+			httpSrv,
+		),
+		kratos.Version(version),
+		kratos.Registrar(r),
+	)
+
+	if err := app.Run(); err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/selector/default.go b/selector/default.go
index 63a19bb4..25698e71 100644
--- a/selector/default.go
+++ b/selector/default.go
@@ -9,6 +9,7 @@ import (
 type Default struct {
 	NodeBuilder WeightedNodeBuilder
 	Balancer    Balancer
+	Filters     []Filter
 
 	lk            sync.RWMutex
 	weightedNodes []Node
@@ -19,6 +20,10 @@ func (d *Default) Select(ctx context.Context, opts ...SelectOption) (selected No
 	d.lk.RLock()
 	weightedNodes := d.weightedNodes
 	d.lk.RUnlock()
+
+	for _, f := range d.Filters {
+		weightedNodes = f(ctx, weightedNodes)
+	}
 	var options SelectOptions
 	for _, o := range opts {
 		o(&options)
diff --git a/selector/p2c/p2c.go b/selector/p2c/p2c.go
index 88cd3a7b..817bccd1 100644
--- a/selector/p2c/p2c.go
+++ b/selector/p2c/p2c.go
@@ -18,13 +18,33 @@ const (
 
 var _ selector.Balancer = &Balancer{}
 
+// WithFilter with select filters
+func WithFilter(filters ...selector.Filter) Option {
+	return func(o *options) {
+		o.filters = filters
+	}
+}
+
+// Option is random builder option.
+type Option func(o *options)
+
+// options is random builder options
+type options struct {
+	filters []selector.Filter
+}
+
 // New creates a p2c selector.
-func New() selector.Selector {
+func New(opts ...Option) selector.Selector {
+	var option options
+	for _, opt := range opts {
+		opt(&option)
+	}
 	return &selector.Default{
 		NodeBuilder: &ewma.Builder{},
 		Balancer: &Balancer{
 			r: rand.New(rand.NewSource(time.Now().UnixNano())),
 		},
+		Filters: option.filters,
 	}
 }
 
diff --git a/selector/random/random.go b/selector/random/random.go
index 30e33d4d..14a194df 100644
--- a/selector/random/random.go
+++ b/selector/random/random.go
@@ -8,21 +8,42 @@ import (
 	"github.com/go-kratos/kratos/v2/selector/node/direct"
 )
 
-var (
-	_ selector.Balancer = &Balancer{}
-
-	// Name is balancer name
+const (
+	// Name is random balancer name
 	Name = "random"
 )
 
+var _ selector.Balancer = &Balancer{} // Name is balancer name
+
+// WithFilter with select filters
+func WithFilter(filters ...selector.Filter) Option {
+	return func(o *options) {
+		o.filters = filters
+	}
+}
+
+// Option is random builder option.
+type Option func(o *options)
+
+// options is random builder options
+type options struct {
+	filters []selector.Filter
+}
+
 // Balancer is a random balancer.
 type Balancer struct{}
 
 // New random a selector.
-func New() selector.Selector {
+func New(opts ...Option) selector.Selector {
+	var option options
+	for _, opt := range opts {
+		opt(&option)
+	}
+
 	return &selector.Default{
 		Balancer:    &Balancer{},
 		NodeBuilder: &direct.Builder{},
+		Filters:     option.filters,
 	}
 }
 
diff --git a/selector/wrr/wrr.go b/selector/wrr/wrr.go
new file mode 100644
index 00000000..e5898daf
--- /dev/null
+++ b/selector/wrr/wrr.go
@@ -0,0 +1,82 @@
+package wrr
+
+import (
+	"context"
+	"sync"
+
+	"github.com/go-kratos/kratos/v2/selector"
+	"github.com/go-kratos/kratos/v2/selector/node/direct"
+)
+
+const (
+	// Name is wrr balancer name
+	Name = "wrr"
+)
+
+var _ selector.Balancer = &Balancer{} // Name is balancer name
+
+// WithFilter with select filters
+func WithFilter(filters ...selector.Filter) Option {
+	return func(o *options) {
+		o.filters = filters
+	}
+}
+
+// Option is random builder option.
+type Option func(o *options)
+
+// options is random builder options
+type options struct {
+	filters []selector.Filter
+}
+
+// Balancer is a random balancer.
+type Balancer struct {
+	mu            sync.Mutex
+	currentWeight map[string]float64
+}
+
+// New random a selector.
+func New(opts ...Option) selector.Selector {
+	var option options
+	for _, opt := range opts {
+		opt(&option)
+	}
+
+	return &selector.Default{
+		Balancer: &Balancer{
+			currentWeight: make(map[string]float64),
+		},
+		NodeBuilder: &direct.Builder{},
+		Filters:     option.filters,
+	}
+}
+
+// Pick pick a weighted node.
+func (p *Balancer) Pick(_ context.Context, nodes []selector.WeightedNode) (selector.WeightedNode, selector.DoneFunc, error) {
+	if len(nodes) == 0 {
+		return nil, nil, selector.ErrNoAvailable
+	}
+	var totalWeight float64
+	var selected selector.WeightedNode
+	var selectWeight float64
+
+	// nginx wrr load balancing algorithm: http://blog.csdn.net/zhangskd/article/details/50194069
+	p.mu.Lock()
+	for _, node := range nodes {
+		totalWeight += node.Weight()
+		cwt := p.currentWeight[node.Address()]
+		// current += effectiveWeight
+		cwt += node.Weight()
+		p.currentWeight[node.Address()] = cwt
+		if selected == nil || selectWeight < cwt {
+			selectWeight = cwt
+			selected = node
+		}
+	}
+	p.currentWeight[selected.Address()] = selectWeight - totalWeight
+	p.mu.Unlock()
+
+	d := selected.Pick()
+	return selected, d, nil
+}
diff --git a/transport/grpc/balancer/balancer.go b/transport/grpc/balancer.go
similarity index 85%
rename from transport/grpc/balancer/balancer.go
rename to transport/grpc/balancer.go
index 573f9b7a..3814742f 100644
--- a/transport/grpc/balancer/balancer.go
+++ b/transport/grpc/balancer.go
@@ -1,4 +1,4 @@
-package balancer
+package grpc
 
 import (
 	"sync"
@@ -8,6 +8,8 @@ import (
 	"github.com/go-kratos/kratos/v2/selector/node"
 	"github.com/go-kratos/kratos/v2/selector/p2c"
 	"github.com/go-kratos/kratos/v2/selector/random"
+	"github.com/go-kratos/kratos/v2/selector/wrr"
+	"github.com/go-kratos/kratos/v2/transport"
 
 	gBalancer "google.golang.org/grpc/balancer"
 	"google.golang.org/grpc/balancer/base"
@@ -24,6 +26,7 @@ var (
 func init() {
 	// inject global grpc balancer
 	SetGlobalBalancer(random.Name, random.New())
+	SetGlobalBalancer(wrr.Name, wrr.New())
 	SetGlobalBalancer(p2c.Name, p2c.New())
 }
 
@@ -74,7 +77,14 @@ type Picker struct {
 
 // Pick pick instances.
 func (p *Picker) Pick(info gBalancer.PickInfo) (gBalancer.PickResult, error) {
-	n, done, err := p.selector.Select(info.Ctx)
+	var filters []selector.Filter
+	if tr, ok := transport.FromClientContext(info.Ctx); ok {
+		if gtr, ok := tr.(*Transport); ok {
+			filters = gtr.Filters()
+		}
+	}
+
+	n, done, err := p.selector.Select(info.Ctx, selector.WithFilter(filters...))
 	if err != nil {
 		return gBalancer.PickResult{}, err
 	}
diff --git a/transport/grpc/client.go b/transport/grpc/client.go
index fa33c707..dd1a614b 100644
--- a/transport/grpc/client.go
+++ b/transport/grpc/client.go
@@ -8,14 +8,13 @@ import (
 
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/registry"
-	"github.com/go-kratos/kratos/v2/selector/random"
+	"github.com/go-kratos/kratos/v2/selector"
+	"github.com/go-kratos/kratos/v2/selector/wrr"
 	"github.com/go-kratos/kratos/v2/transport"
 	"github.com/go-kratos/kratos/v2/transport/grpc/resolver/discovery"
 
 	// init resolver
 	_ "github.com/go-kratos/kratos/v2/transport/grpc/resolver/direct"
-	// init balancer
-	_ "github.com/go-kratos/kratos/v2/transport/grpc/balancer"
 
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
@@ -74,15 +73,31 @@ func WithOptions(opts ...grpc.DialOption) ClientOption {
 	}
 }
 
+// WithBalancerName with balancer name
+func WithBalancerName(name string) ClientOption {
+	return func(o *clientOptions) {
+		o.balancerName = name
+	}
+}
+
+// WithSelectFilter with select filters
+func WithSelectFilter(filters ...selector.Filter) ClientOption {
+	return func(o *clientOptions) {
+		o.filters = filters
+	}
+}
+
 // clientOptions is gRPC Client
 type clientOptions struct {
-	endpoint   string
-	tlsConf    *tls.Config
-	timeout    time.Duration
-	discovery  registry.Discovery
-	middleware []middleware.Middleware
-	ints       []grpc.UnaryClientInterceptor
-	grpcOpts   []grpc.DialOption
+	endpoint     string
+	tlsConf      *tls.Config
+	timeout      time.Duration
+	discovery    registry.Discovery
+	middleware   []middleware.Middleware
+	ints         []grpc.UnaryClientInterceptor
+	grpcOpts     []grpc.DialOption
+	balancerName string
+	filters      []selector.Filter
 }
 
 // Dial returns a GRPC connection.
@@ -97,19 +112,20 @@ func DialInsecure(ctx context.Context, opts ...ClientOption) (*grpc.ClientConn,
 
 func dial(ctx context.Context, insecure bool, opts ...ClientOption) (*grpc.ClientConn, error) {
 	options := clientOptions{
-		timeout: 2000 * time.Millisecond,
+		timeout:      2000 * time.Millisecond,
+		balancerName: wrr.Name,
 	}
 	for _, o := range opts {
 		o(&options)
 	}
 	ints := []grpc.UnaryClientInterceptor{
-		unaryClientInterceptor(options.middleware, options.timeout),
+		unaryClientInterceptor(options.middleware, options.timeout, options.filters),
 	}
 	if len(options.ints) > 0 {
 		ints = append(ints, options.ints...)
 	}
 	grpcOpts := []grpc.DialOption{
-		grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, random.Name)),
+		grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, options.balancerName)),
 		grpc.WithChainUnaryInterceptor(ints...),
 	}
 	if options.discovery != nil {
@@ -127,12 +143,13 @@ func dial(ctx context.Context, insecure bool, opts ...ClientOption) (*grpc.Clien
 	return grpc.DialContext(ctx, options.endpoint, grpcOpts...)
 }
 
-func unaryClientInterceptor(ms []middleware.Middleware, timeout time.Duration) grpc.UnaryClientInterceptor {
+func unaryClientInterceptor(ms []middleware.Middleware, timeout time.Duration, filters []selector.Filter) grpc.UnaryClientInterceptor {
 	return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
 		ctx = transport.NewClientContext(ctx, &Transport{
 			endpoint:  cc.Target(),
 			operation: method,
 			reqHeader: headerCarrier{},
+			filters:   filters,
 		})
 		if timeout > 0 {
 			var cancel context.CancelFunc
diff --git a/transport/grpc/transport.go b/transport/grpc/transport.go
index 3f07e744..eb8c5127 100644
--- a/transport/grpc/transport.go
+++ b/transport/grpc/transport.go
@@ -1,6 +1,7 @@
 package grpc
 
 import (
+	"github.com/go-kratos/kratos/v2/selector"
 	"github.com/go-kratos/kratos/v2/transport"
 	"google.golang.org/grpc/metadata"
 )
@@ -13,6 +14,7 @@ type Transport struct {
 	operation   string
 	reqHeader   headerCarrier
 	replyHeader headerCarrier
+	filters     []selector.Filter
 }
 
 // Kind returns the transport kind.
@@ -40,6 +42,11 @@ func (tr *Transport) ReplyHeader() transport.Header {
 	return tr.replyHeader
 }
 
+// Filters returns the client select filters.
+func (tr *Transport) Filters() []selector.Filter {
+	return tr.filters
+}
+
 type headerCarrier metadata.MD
 
 // Get returns the value associated with the passed key.
diff --git a/transport/http/client.go b/transport/http/client.go
index 02542498..fcd29d2c 100644
--- a/transport/http/client.go
+++ b/transport/http/client.go
@@ -17,7 +17,7 @@ import (
 	"github.com/go-kratos/kratos/v2/middleware"
 	"github.com/go-kratos/kratos/v2/registry"
 	"github.com/go-kratos/kratos/v2/selector"
-	"github.com/go-kratos/kratos/v2/selector/random"
+	"github.com/go-kratos/kratos/v2/selector/wrr"
 	"github.com/go-kratos/kratos/v2/transport"
 )
 
@@ -152,7 +152,7 @@ func NewClient(ctx context.Context, opts ...ClientOption) (*Client, error) {
 		decoder:      DefaultResponseDecoder,
 		errorDecoder: DefaultErrorDecoder,
 		transport:    http.DefaultTransport,
-		selector:     random.New(),
+		selector:     wrr.New(),
 	}
 	for _, o := range opts {
 		o(&options)


Test patch:
diff --git a/transport/grpc/client_test.go b/transport/grpc/client_test.go
index f18b6183..f618d9bf 100644
--- a/transport/grpc/client_test.go
+++ b/transport/grpc/client_test.go
@@ -68,7 +68,7 @@ func EmptyMiddleware() middleware.Middleware {
 }
 
 func TestUnaryClientInterceptor(t *testing.T) {
-	f := unaryClientInterceptor([]middleware.Middleware{EmptyMiddleware()}, time.Duration(100))
+	f := unaryClientInterceptor([]middleware.Middleware{EmptyMiddleware()}, time.Duration(100), nil)
 	req := &struct{}{}
 	resp := &struct{}{}