Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ratelimit interceptor #181

Merged
merged 8 commits into from
Jun 3, 2019

Conversation

ceshihao
Copy link
Contributor

Add ratelimit interceptor, and implement a token bucket limiter using github.com/juju/ratelimit

Fix #134

@andyxning
Copy link

/sub

@jsindy
Copy link

jsindy commented Mar 18, 2019

Why are checkdocs failing?

@codecov-io
Copy link

codecov-io commented Mar 18, 2019

Codecov Report

Merging #181 into master will increase coverage by 0.19%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #181      +/-   ##
==========================================
+ Coverage   73.09%   73.28%   +0.19%     
==========================================
  Files          36       37       +1     
  Lines        1349     1359      +10     
==========================================
+ Hits          986      996      +10     
  Misses        314      314              
  Partials       49       49
Impacted Files Coverage Δ
ratelimit/ratelimit.go 100% <100%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cfaf568...821b156. Read the comment docs.

@ceshihao
Copy link
Contributor Author

I rebase the changes on latest master.
CI pass :)

@makkalot
Copy link

Any plans to merge this ?

@EngHabu
Copy link

EngHabu commented May 20, 2019

ping :) @domgreen ?

@domgreen domgreen self-requested a review May 20, 2019 18:28
Copy link
Contributor

@domgreen domgreen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation feels very focused on the specific implementation of the github.com/juju/ratelimit token package.
I think that if we are to have a rate limiting interceptor we should look to have it much more generic so that other implementations can easily be added to the library. Maybe add a second implementation to tease out the details?

ratelimit/DOC.md Outdated
### Server Side Ratelimit Middleware
It allows to use your own rate limiter (e.g. token bucket, leaky bucket, etc.) to do grpc rate limit.

`ratelimit/tokenbucket`provides an implementation based on token bucket `github.com/juju/ratelimit`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double space on token

ratelimit/DOC.md Outdated
`grpc_ratelimit` a generic server-side ratelimit middleware for gRPC.

### Server Side Ratelimit Middleware
It allows to use your own rate limiter (e.g. token bucket, leaky bucket, etc.) to do grpc rate limit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we tidy up this sentence?

ratelimit/doc.go Outdated

Please see examples for simple examples of use.
*/
package grpc_ratelimit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's not continue this anti-pattern grpc_ratelimit > ratelimit

ratelimit/doc.go Outdated
@@ -0,0 +1,15 @@
// Copyright 2018 Zheng Dayu. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove :)

)

// Simple example of server initialization code.
func Example_initialization() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this just be Example?
I'm really not keen on the way examples are done in this project but think this at least follows the current pattern.

}
}

// StreamServerInterceptor returns a new stream server interceptors that performs request rate limit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// StreamServerInterceptor returns a new stream server interceptor that performs rate limiting on the request.

if ratelimiter.Wait() {
return handler(srv, stream)
}
return status.Errorf(codes.ResourceExhausted, "%s is rejected by grpc_ratelimit middleare, please retry later.", info.FullMethod)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again invert this logic to have the happy path non inverted

}
var ctx context.Context
var req interface{}
var info *grpc.UnaryServerInfo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the onces that can define them in a var block

var (
 ...
 ...
)


const errMsgFake = "fake error"

func TestEmptyUnaryServerInterceptor(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestUnaryServerInterceptor_EmptyCall

assert.EqualError(t, err, errMsgFake)
}

func TestRateLimitUnaryServerInterceptor(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

@domgreen
Copy link
Contributor

@ceshihao sorry I didnt pick this up earlier lots of other stuff going on with the world of work and open source so great to see some more people adding keeping the project going 👍 and @EngHabu Thanks for the ping.

An alternative way to do this could be to pass in the interceptor to the middleware eg.

ratelimit.UnaryServerInterceptor(WithRateLimiter(limiter))

and limiter just conforms to the limiter interface with a Limit method so that people can pass any type of limiter and the grpc_middleware does not have to depend on all the custom providers of that logic.

Thoughts?

@ceshihao
Copy link
Contributor Author

@domgreen Thanks for review. I refactor the codes according to your comments.

I change the Limiter interface to

type Limiter interface {
	Limit() bool
}

It makes everyone can pass his own limiter to the interceptor.

Copy link
Contributor

@domgreen domgreen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates ... I think this is getting there the main thing I want us to try and move away from is taking dependencies on all rate limit packages so if we can pull that outside and instead allow the user to specify their own we can simplify the code massively.
Thanks again. 👍

@@ -0,0 +1,33 @@
// Copyright 2018 Zheng Dayu. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove

@@ -0,0 +1,16 @@
// See LICENSE for licensing terms.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may as well remove all of this and let people view the LICENSE file ... when i get time will clean the rest of the codebase


type Option func(*rateLimiter)

// WithRateLimiter customizes your limiter in the middleware
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be a full sentence ... how does it customize? It does give much info to the consumer.

}
}

// UnaryServerInterceptor returns a new unary server interceptors that performs request rate limit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

limit > limiting

for _, opt := range opts {
opt(ratelimiter)
}
fmt.Println(ratelimiter.limiter.Limit())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove Println

}
fmt.Println(ratelimiter.limiter.Limit())
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
fmt.Println(ratelimiter.limiter.Limit())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

@@ -0,0 +1,28 @@
// Copyright 2018 Zheng Dayu. All Rights Reserved.
// See LICENSE for licensing terms.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above

fmt.Println(ratelimiter.limiter.Limit())
for _, opt := range opts {
opt(ratelimiter)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could get rid of the opts here ... from what i can see there is only a single opt ... the ratelimiter that the user is passing in.
Lets change it to be something like:

func UnaryServerInterceptor(limiter Limiter) grpc.UnaryServerInterceptor {

@@ -0,0 +1,22 @@
package tokenbucket
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are getting the end user to pass in the rate limiter we can remove the whole tokenbucket package from this codebase and not depoend on juju ourselves.
The user would then wrap it using the Limiter interface so that we allow them to use any rate limit algorithm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the package can be kept in order to give user an easy implementation of Limiter.

And also user can implement his own limiter if it is not enough.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can give an example without needing to import the dependency, the thinking behind passing in the rate limiter is so that we do not have to maintain the rate limiters but just wrap them to meet the Limiter interface that has been defined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌remove it from repo

)

type Limiter interface {
Limit() bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add documentation on the the type and func :)

@domgreen
Copy link
Contributor

Just done a round of review @andyxning @makkalot @EngHabu feel free to add your own comments. 👍

Copy link
Contributor

@domgreen domgreen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small amount of clean up needed

ratelimit/doc.go Outdated

It allows to do grpc rate limit by your own rate limiter (e.g. token bucket, leaky bucket, etc.)

`ratelimit/tokenbucket`provides an implementation based on token bucket `github.com/juju/ratelimit`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update ... I dont think this is true anymore :)

import (
"time"

"github.com/ceshihao/ratelimiter/tokenbucket"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer in the project.

Tio do the example can we create a test rate limiter that meets the interface 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to add a fake limiter (e.g. allwaysPassLimiter) in the example instead of the one based on juju/ratelimit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, lets just add a fake that returns true (or get creative) but one that is not exported ... dont really want the dep just for an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@domgreen domgreen merged commit 27f3801 into grpc-ecosystem:master Jun 3, 2019
@ceshihao ceshihao deleted the ratelimit_interceptor branch July 11, 2019 04:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rate Limit Interceptor
7 participants