Ribbon
The Ribbon is a client-side IPC library and Netflix’s open source client-side load balancer. It provides the following capabilities:
Load balancing
Fault tolerance
Support for asynchrony and multiple protocols (HTTP, TCP, UDP) in the corresponding model
Caching and batching
Why use the Ribbon
In our system (distributed system), there is process communication between services; Before using the Ribbon, you need to manually implement the load balancer. With the Ribbon, we can automatically get service lists from nacOS services and use user-specified load balancing algorithms to calculate the instances that need to be invoked. This enables us to achieve load balancing easily and efficiently. The Ribbon is scalable enough to meet 99.9999% of production scenarios.
There are two load balancing modes
Server-side load balancing
Load balancing on the client
Manually implement the load balancer on the client side
No load balancer is used
@Override
public ShareDTO findById(Integer id) {
// Get share details
Share share = baseMapper.selectById(id);
// Get the publisher ID
Integer userId = share.getUserId();
List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
String url = instances.stream().map(instance -> instance.getUri().toString() + "/users/{id}").findFirst().orElse("");
// Remotely invoke the user center service interface
UserDTO userDTO = restTemplate.getForObject(url, UserDTO.class, userId);
// Message assembly
ShareDTO shareDTO = ShareDTO.builder()
.wxNickname(userDTO.getWxNickname())
.build();
BeanUtils.copyProperties(share, shareDTO);
return shareDTO;
}
Copy the code
Manual Load balancer implementation (random)
@Override
public ShareDTO findById(Integer id) {
// Get share details
Share share = baseMapper.selectById(id);
// Get the publisher ID
Integer userId = share.getUserId();
List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
List<String> urls = instances.stream().map(instance -> instance.getUri().toString() + "/users/{id}").collect(Collectors.toList());
// Random algorithm
int i = ThreadLocalRandom.current().nextInt(urls.size());
// Remotely invoke the user center service interface
UserDTO userDTO = restTemplate.getForObject(urls.get(i), UserDTO.class, userId);
// Message assembly
ShareDTO shareDTO = ShareDTO.builder()
.wxNickname(userDTO.getWxNickname())
.build();
BeanUtils.copyProperties(share, shareDTO);
return shareDTO;
}
Copy the code
The Ribbon implements load balancing
- Add dependencies (if you are using NacOS Discovery, you do not need to introduce ribbon dependencies separately)
-
Write notes
// If you use RestTemplate to integrate the Ribbon, annotate the RestTemplate @Bean @LoadBalanced RestTemplate restTemplate(a) { return new RestTemplate(); } Copy the code
-
Write configuration (not configured)
Of Ribbon
interface | role | The default value |
---|---|---|
IClientConfig | Reading configuration | DefaultClientConfigImpl |
IRule | Load balancing rule, select instances | ZoneAvoidanceRule |
IPing | Filter instances that cannot ping | DummyPing |
ServerList | Give the Ribbon a list of instances | Ribbon: ConfigurantionBasedServerList.Spring Cloud Alibaba: NacosServerList |
ServerListFilter | Filter out instances that do not meet the criteria | ZonePreferenceServerListFilter |
ILoadBalancer | The entry of the Ribbon | ZoneAwareLoadBalancer |
ServerListUpdater | Update the List strategy given to the Ribbon | PollingServerListUpdater |
We can implement these interfaces and customize our requirements.
Load balancing rules built into the Ribbon
Rule name | The characteristics of |
---|---|
AvailabilityFilterRule | Filter out back-end servers tagged with circuit tripped that repeatedly failed to connect and filter out those with high concurrency or use an AvailabilityPredicate that includes the logic to filter servers, Check the running status of each Server recorded in status. |
BestAvailableRule | Pick a Server with minimal concurrent requests, inspect servers one by one, and skip them if they tripped. |
RandomRule | Select a Server at random. |
ResponseTimeWeightedRule | Have been abandoned. Function as WeightedResponseTimeRule. |
RetryRule | For the on-machine retry mechanism of the selected load balancing policy, if the Server selection fails during a configuration period, the system tries to use subRule to select an available Server. |
RoundRobinRule | Poll Select, poll index, and select the Server corresponding to index. |
WeightedResponseTimeRule | According to the corresponding time weighting, the longer the response time, the smaller the weight, the lower the probability of being selected. |
ZoneAvoidanceRule | Default rules. Compound judge the performance of the Server Zone and the availability of the Server select the Server,In an environment without zones, similar to RoundRobinRule. |
Fine-grained configuration
Java code approach
Note: RibbonConfiguration must be outside the Springboot package scan. (Parent-child context explanation)
-
Create the Ribbon configuration class for a service
package com.samir.contentcenter.configuration; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.context.annotation.Configuration; import ribbonconfiguration.RibbonConfiguration; @Configuration @RibbonClient(name = "user-center", configuration = RibbonConfiguration.class) public class UserCenterRibbonConfiguration {}Copy the code
-
Create a load balancing rule for the current service
package ribbonconfiguration; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RibbonConfiguration { @Bean public IRule ribbonRule(a) { return newRandomRule(); }}Copy the code
Parent and child context
-
The @Configuration annotation is also a special Component annotation, and the @SpringBootApplication on top of the bootstrap class is an aggregate annotation that contains the @ComponentScan annotation.
-
All Component annotations will be scanned (the package and subpackages of the currently launched class).
-
The Component annotation scanned by @SpringBootApplication is the parent context.
-
The Ribbon also scans the Component annotation as a subcontext.
-
Packages of load balancing rules can only be placed outside. Overlapping parent and child contexts can lead to a series of strange problems such as transaction invalidity.
The Ribbon explains that the @Configuration annotation must be used to configure load balancing rules for clients. Note that this annotation cannot be scanned by @ComponentScan. Otherwise, it will be shared by all @RibbonClients. It becomes a global configuration.
Configuring Attribute Mode
The full path. Ribbon. NFLoadBalancerRuleClassName = rules
The name of the service in the registry
user-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule Rule full path
Copy the code
Two ways to compare
Configuration mode | advantages | disadvantages |
---|---|---|
Code configuration | Code based, more flexible | Pay attention to parent-child context issues; Online changes need to be repackaged for distribution |
The configuration properties | Easy-to-use; Simple and intuitive configuration; Online modification does not need to be repackaged and published (configuration center); Higher priority | Extreme scenarios are less flexible than code configuration |
The best implementation
- Use attribute configuration as much as possible, and consider code configuration when attribute mode is not possible.
- Try to keep the same microservice simple, do not mix the two ways, increase the complexity of the location code. Simple is beautiful.
Global configuration
-
Method 1: Make ComponentSacn parent-child context overlap (strongly not recommended). (See parent context or official documentation)
-
Mode 2: @RibbonClients(defaultConfiguration= XXX.class)
package com.samir.contentcenter.configuration; import org.springframework.cloud.netflix.ribbon.RibbonClients; import org.springframework.context.annotation.Configuration; import ribbonconfiguration.RibbonConfiguration; @Configuration @RibbonClients(defaultConfiguration = RibbonConfiguration.class) // Notice here public class UserCenterRibbonConfiguration {}Copy the code
Supported configuration items
Code way
-
Create the Ribbon configuration class for a service
package com.samir.contentcenter.configuration; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.context.annotation.Configuration; import ribbonconfiguration.RibbonConfiguration; @Configuration @RibbonClient(name = "user-center", configuration = RibbonConfiguration.class) public class UserCenterRibbonConfiguration {}Copy the code
-
Create a load balancing rule for the current service
package ribbonconfiguration; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RibbonConfiguration { @Bean public IRule ribbonRule(a) { return new RandomRule(); } @Bean public IPing ping(a) { return new PingUrl(); } // The Ribbon provides the corresponding interface } Copy the code
Configuring Attribute Mode
Use the **.ribbon. Property **. The following attributes:
- NFLoadBalancerRuleClassName: ILoadBalancer implementation class
- NFLoadBalancerRuleClassClassName: IRule implementation class
- NFLoadBalancerPingClassClassName: IPing implementation class
- NIWSServerListClassName: Implementation class of ServerList
- NIWSServerListFilterClassName: ServerListFilter implementation class
Hunger is loaded
The following solutions can solve the problem that causes the first request to be slow
# ribbon enables hungry loading (default is off)
ribbon:
eager-load:
enabled: true
clients: user-center Configure which services to enable hungry loading (multiple, split)
Copy the code
Extend the Ribbon
Nacos weights are supported
- Weight information can be configured for each instance on the NACOS console. The diagram below:
The larger the threshold value is, the greater the probability of being requested is. The use of thresholds is useful in distributed scenarios, where the performance of our servers is inconsistent, and we can use thresholds to get more requests from good servers.
-
The Ribbon’s built-in load balancing rules do not support thresholds, so we need to extend the Ribbon with nacOS (implement the IRule interface and inherit AbstractLoadBalancerRule). The following is a partial configuration as a configuration property.
-
Edit custom load balancing rules
package com.samir.contentcenter.configuration; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.Server; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @Slf4j public class NacosWeightedRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { // Read the configuration file and initialize it } @Override public Server choose(Object o) { try { // The ribbon entry BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); // Get the name of the microservice you want to request String name = loadBalancer.getName(); // Get the API for service discovery NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); // The Nacos client automatically selects an instance for me through a weight-based load balancing algorithm Instance instance = namingService.selectOneHealthyInstance(name); log.info("The selected instance is: port = {}, instance = {}", instance.getPort(), instance); return new NacosServer(instance); } catch (NacosException e) { e.printStackTrace(); return null; }}}Copy the code
-
Editing a Configuration File
The name of the service in the registry user-center: ribbon: NFLoadBalancerRuleClassName: com.samir.contentcenter.configuration.NacosWeightedRule Rule full path Copy the code
-
The same cluster is preferentially loaded
-
Edit custom load balancing rules
package com.samir.contentcenter.configuration; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.client.naming.core.Balancer; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.Server; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @Slf4j public class NacosSameClusterWeigthedRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) {}@Override public Server choose(Object o) { try { / / get the current application (spring configuration file. Cloud. Nacos. Discovery. The cluster - name: BJ) the name of the cluster String clusterName = nacosDiscoveryProperties.getClusterName(); BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); // Get the name of the microservice you want to request String name = loadBalancer.getName(); // Get the API for service discovery NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); // 1. Find all instances A of the specified service List<Instance> instances = namingService.selectInstances(name, true);// true gets only healthy instances // 2. Filter out all instance B in the same cluster List<Instance> instanceList = instances.stream().filter(instance -> Objects.equals(instance.getClusterName(), clusterName)).collect(Collectors.toList()); // 3. If B is empty, use A List<Instance> instancesToBeChosen = new ArrayList<>(); if (CollectionUtils.isEmpty(instanceList)) { instancesToBeChosen = instances; log.warn(Name = {}, clusterName = {}, instances = {}", name, clusterName, instancesToBeChosen); } else { instancesToBeChosen = instanceList; } // 4. Return an instance based on the weighted non-load balancing algorithm Instance instance = MyBalancer.myGetHostByRandomWeight(instancesToBeChosen); return new NacosServer(instance); } catch (NacosException e) { log.error("Something abnormal has happened.", e); return null; }}}/ * * * this method is to inherit the Balancer class to use his protected type of method by namingService. * * selectOneHealthyInstance method of tracking, Public Instance selectOneHealthyInstance(String serviceName, String groupName, List
Clusters, boolean subscribe) throws NacosException { * return subscribe ? RandomByWeight.selectHost(this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","))) : RandomByWeight.selectHost(this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","))); Public static Instance selectHost(ServiceInfo dom) {* List class MyBalancer extends Balancer { public static Instance myGetHostByRandomWeight(List<Instance> hosts) { returngetHostByRandomWeight(hosts); }}Copy the codehosts = selectAll(dom); * if (CollectionUtils.isEmpty(hosts)) { * throw new IllegalStateException("no host to srv for service: " + dom.getName()); * } else { * return Balancer.getHostByRandomWeight(hosts); // This static method is the key to implementation *} *} */ -
Editing a Configuration File
The name of the service in the registry user-center: ribbon: NFLoadBalancerRuleClassName: com.samir.contentcenter.configuration.NacosSameClusterWeigthedRule Rule full path Copy the code
Versioning based on metadata
-
Edit custom load balancing rules
@Slf4j public class NacosFinalRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public Server choose(Object key) { // Load balancing rule: Select instances that meet metadata requirements in the same cluster // If not, select all instances that match metadata in the cluster // 1. Query all instance A // 2. Filter instance B that matches metadata // 3. Select instance C that matches the metadata in cluster // 4. If C is empty, use B // 5. Randomly select instances / / the metadata information (spring configuration file. Cloud. Nacos. Metadata. Version: v1) your application version here / / the metadata information (spring configuration file. Cloud. Nacos. Metadata. The target - version: v1) allows the provider's version of the call try { String clusterName = this.nacosDiscoveryProperties.getClusterName(); String targetVersion = this.nacosDiscoveryProperties.getMetadata().get("target-version"); DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer(); String name = loadBalancer.getName(); NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance(); // All instances List<Instance> instances = namingService.selectInstances(name, true); List<Instance> metadataMatchInstances = instances; // If version mapping is configured, only instances of metadata matching are called if (StringUtils.isNotBlank(targetVersion)) { metadataMatchInstances = instances.stream() .filter(instance -> Objects.equals(targetVersion, instance.getMetadata().get("version"))) .collect(Collectors.toList()); if (CollectionUtils.isEmpty(metadataMatchInstances)) { log.warn("No metadata matching target instance found! Check the configuration. targetVersion = {}, instance = {}", targetVersion, instances); return null; } } List<Instance> clusterMetadataMatchInstances = metadataMatchInstances; // If the cluster name is specified, filter the instances whose metadata matches the cluster if(StringUtils.isNotBlank(clusterName)) { clusterMetadataMatchInstances = metadataMatchInstances.stream() .filter(instance -> Objects.equals(clusterName, instance.getClusterName())) .collect(Collectors.toList());if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) { clusterMetadataMatchInstances = metadataMatchInstances; log.warn("A cross-cluster call occurred. clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances); } } Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances); return new NacosServer(instance); } catch (Exception e) { log.warn("Abnormal", e); return null; }}@Override public void initWithNiwsConfig(IClientConfig iClientConfig) {}}class ExtendBalancer extends Balancer { /** * select instance ** randomly according to weight@paramInstances List of instances *@returnThe selected instance */ public static Instance getHostByRandomWeight2(List<Instance> instances) { returngetHostByRandomWeight(instances); }}Copy the code
-
Editing a Configuration File
The name of the service in the registry user-center: ribbon: NFLoadBalancerRuleClassName: com.samir.contentcenter.configuration.NacosFinalRule Rule full path Copy the code