What is the Ribbon
After looking at how to use native Feign, today we’ll look at Ribbon, another library developed by the Netflix team. The Ribbon and Feign have a lot in common. First, they are both HTTP clients at heart, and second, they both have retry, integrated circuit breakers, and more. The biggest difference is that the Ribbon has a built-in load balancer, which Feign does not.
This article will show you how to use the native Ribbon, which is native, not wrapped in layers of Spring.
Why use the Ribbon
Here we need to answer two questions:
- Why use an HTTP Client?
- Why build load balancer into HTTP Client?
The first of these is already covered in how to use native Feign, so let’s move on to the second without further ado.
We know that the Apache HTTP Client and Feign do not have a built-in load balancer. That is, the HTTP client does not have to have a built-in load balancer. Why does the Ribbon need to be special?
In fact, if you think about it, the Ribbon is mostly used for internal invocation, and this scenario has one big feature — the target service is clustered deployment. Typically, when a target service is invoked, we want requests to be distributed to each instance as evenly as possible. With the built-in load balancer, the Ribbon meets this requirement well, whereas the Apache HTTP Client and Feign do not.
Therefore, the load balancer is built into the HTTP client to provide load balancing support when the target service is deployed in a cluster.
Some people might say, you can just deploy a load balancer, why bother? Sure, you can do that. But it’s important to consider that mid-Tier services are much more demanding than Edge services, so you need a very high performance load balancer. In this sense, the Ribbon solution saves you the cost of deploying a load balancer independently.
How to use Ribbon
Project I use RxNettty wrote a simple HTTP interface (see cn. ZZS. Ribbon. RxUserServer) for the back of the example calls, this interface operation in 8080, 8081, 8082, the interface of the machine, used to simulate three different instances. So, if you want to test the examples in your project, start these three instances first.
http://127.0.0.1:8080/user/getUserById?userId= {username} request: userId = 1 response: the User (id = 1, name = zzs001, age = 18]Copy the code
As a reminder, the Ribbon API uses a lot of RxJava code. If you haven’t seen it before, you should know about it.
Project environment
OS: win 10
The JDK: 1.8.0 comes with _231
Maven: 3.6.3
IDE: Spring Tool Suite 4.6.1.release
Ribbon: 2.7.17
Used as an HTTP client
Like Feign, the Ribbon supports HTTP interface definitions using annotations. In addition, the Ribbon supports HTTP interface definitions using HttpRequestTemplate, HttpClientRequest, and other methods. Interested can go to the project source code.
The list of service instances is set through the ConfigurationManager. Now, when you look at ConfigurationManager, does it look familiar? We’ve covered this in detail in Eureka: Exploring Eureka’s powerful configuration system. Yes, the Ribbon uses the same configuration system. Note that the configuration system developed by the Netflix team provides dynamic configuration support (if you know how to use it, of course). For example, if you change the listOfServers through the ConfigurationManager during the run, The Ribbon senses this change and uses the latest listOfServers for load balancing, with only a slight delay.
// Use annotations to define the HTTP API
@ClientProperties(properties = { @Property(name="ReadTimeout", value="2000"), @Property(name="ConnectTimeout", value="1000"), @Property(name="MaxAutoRetries", value="1"), @Property(name="MaxAutoRetriesNextServer", value="2") }, exportToArchaius = true)
interface UserService {
@TemplateName("getUserById")
@Http( method = HttpMethod.GET, uri = "/user/getUserById? userId={userId}", headers = { @Header(name = "X-Platform-Version", value = "xyz"), @Header(name = "X-Auth-Token", value = "abc") })
RibbonRequest<ByteBuf> getUserById(@Var("userId") String userId);
}
public class RxUserProxyTest {
@Test
public void testBase(a) throws InterruptedException {
// Specify the address of the service instance
/ / key: + service ". Ribbon. "+ name of the configuration items (see com.netflix.client.config.Com monClientConfigKey)
ConfigurationManager.getConfigInstance().setProperty(
"UserService.ribbon.listOfServers"."127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");
UserService userService = Ribbon.from(UserService.class);
userService.getUserById("1")
.toObservable()
.subscribe(new Subscriber<Object>() {
@Override
public void onCompleted(a) {
LOG.info("onCompleted");
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(Object t) {
LOG.info("onNext:{}", t);
if(t ! =null && t instanceofByteBuf) { LOG.info(ByteBuf.class.cast(t).toString(Charset.defaultCharset())); }}});// Since the request HTTP interface is asynchronous, let the test main thread sleep for a while
Thread.sleep(10000); }}Copy the code
Default load balancing rules
To see how multiple requests are distributed across three instances, let’s now change the code and try to make six requests.
@Test
public void test01(a) throws InterruptedException {
ConfigurationManager.getConfigInstance().setProperty(
"UserService.ribbon.listOfServers"."127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");
UserService userService = Ribbon.from(UserService.class);
// Initiate multiple requests
Observable<ByteBuf>[] requestList = new Observable[]{
userService.getUserById("1").toObservable(),
userService.getUserById("2").toObservable(),
userService.getUserById("3").toObservable(),
userService.getUserById("4").toObservable(),
userService.getUserById("5").toObservable(),
userService.getUserById("6").toObservable()
};
Observable.concat(Observable.from(requestList))
.subscribe(subscriber);
Thread.sleep(10000);
}
Copy the code
Running the test, you can see that the six requests are evenly allocated to three instances.
In the log, you can see the default load balancing rules.
As can be seen from the source code, the default rule is essentially a RoundRobinRule. In addition, the Ribbon defines rules such as RandomRule and RetryRule for you to choose from.
public class AvailabilityFilteringRule {
RoundRobinRule roundRobinRule = new RoundRobinRule();
}
Copy the code
User-defined load balancing rules
Custom load balancing rules need to be inherited com.net flix. Loadbalancer. AbstractLoadBalancerRule, and realize the method to choose. The rule I’ve defined here is that no matter how many instances there are, the default access is to the first one.
public class MyLoadBalancerRule extends AbstractLoadBalancerRule {
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
List<Server> allServers = lb.getAllServers();
return allServers.stream().findFirst().orElse(null); }}Copy the code
Then you just need to configure the custom rules through the ConfigurationManager.
@Test
public void test01(a) throws InterruptedException {
ConfigurationManager.getConfigInstance().setProperty(
"UserService.ribbon.listOfServers"."127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");
// Configure a custom rule
ConfigurationManager.getConfigInstance().setProperty(
"UserService.ribbon.NFLoadBalancerRuleClassName"."cn.zzs.ribbon.MyLoadBalancerRule");
UserService userService = Ribbon.from(UserService.class);
Observable<ByteBuf>[] requestList = new Observable[]{
userService.getUserById("1").toObservable(),
userService.getUserById("2").toObservable(),
userService.getUserById("3").toObservable(),
userService.getUserById("1").toObservable(),
userService.getUserById("2").toObservable(),
userService.getUserById("3").toObservable()
};
Observable.concat(Observable.from(requestList))
.subscribe(subscriber);
Thread.sleep(10000);
}
Copy the code
Running the test, you can see that all requests are assigned to the first instance. The user-defined load balancing rule takes effect.
Refresh the listOfServers dynamically
In the previous example, our service instance address, listOfServers, was written dead. However, in a real project, the number of instances and address of the target service would change, so we would need to update the listOfServers dynamically instead of writing them dead.
As mentioned above, the configuration system developed by Netflix supports dynamic configuration, so the simplest solution I can think of is to start a task that periodically pulls the latest list of instances from the registry, etc., and then stuff the new list into the ConfigurationManager.
For eureka Server as the registry of the project, the official provided an implementation scheme. Compared to the scheme I said above, this scheme logic is simple, but the implementation of more complex, source code readability is very high, interested friends can see.
@Test
public void testEureka(a) throws InterruptedException {
// Specify the instance list to be obtained from Eureka
ConfigurationManager.getConfigInstance().setProperty(
"UserService.ribbon.NIWSServerListClassName"."com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList");
ConfigurationManager.getConfigInstance().setProperty(
"UserService.ribbon.DeploymentContextBasedVipAddresses"."UserService");
// Initialize EurekaClient
DiscoveryManager.getInstance().initComponent(new MyDataCenterInstanceConfig(), new DefaultEurekaClientConfig());
UserService userService = Ribbon.from(UserService.class);
userService.getUserById("1")
.toObservable()
.subscribe(subscriber);
Thread.sleep(10000); }}Copy the code
conclusion
The Ribbon has other extendable features, such as circuit breakers and retries. If you’re interested, you can do your own analysis.
Finally, thanks for reading.
The resources
ribbon github
Related source code please move: github.com/ZhangZiShen…
This article original articles, reproduced please attach the original source link: www.cnblogs.com/ZhangZiShen…