Ribbon load balancing strategy
It can be seen Ribbon
that a lot of selection strategies are implemented in. Let’s explain IRule
each implementation of the interface in detail.
AbstractLoadBalancerRule
The abstract class of load balancing strategy, in which the load balancer object is defined ILoadBalancer
. This object can obtain some information maintained in the load balancer as the basis for allocation when selecting a service strategy in specific implementation, and is designed accordingly. Some algorithms to implement efficient strategies for specific scenarios.
/**
* Class that provides a default implementation for setting and getting load balancer
* 该类提供用户设置和获取负载均衡器的默认实现
* @author stonse
*
*/
public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {
private ILoadBalancer lb;
@Override
public void setLoadBalancer(ILoadBalancer lb){
this.lb = lb;
}
@Override
public ILoadBalancer getLoadBalancer(){
return lb;
}
}
RandomRule
This strategy implements the function of randomly selecting a service instance from the service strength list.
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
//可用实例集合
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
int index = rand.nextInt(serverCount);
server = upList.get(index);
if (server == null) {
/*
* 服务器列表在被维护的情况下,可能会出现为null,释放CPU资源再重试
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
You can see that the function implementation IRole
of the interface choose(Object key)
is delegated to this class choose(ILoadBalancer lb, Object key)
. This method adds a parameter of the load balancer object. From the specific implementation point of view, he will use the incoming load balancer to obtain the available instance list upList
and all instance lists allList
, and rand.nextInt(serverCount)
obtain a random number through the function, and use the random number as upList
the index to return the specific implementation. At the same time, the specific selection logic is within a while(server == null)
loop. According to the implementation of the selection logic, under normal circumstances, a service instance should be selected for each selection. If an infinite loop occurs and the service instance cannot be obtained, there is a high possibility of concurrency. of Bug
.
RoundRobinRule
This strategy implements the function of selecting each service instance at once in a linear polling manner.
Its detailed structure is RandomRule
very similar to except that the loop condition is different, which is the so-called logical difference obtained from the possible list. From the loop condition, we can see that a count variable has been added count
, which will be accumulated after each loop value. That is to say, if the selection is less than server
10 times, the attempt will end and a warning message will be printed. No up servers available from load balancer: ...
. The implementation of linear polling is AtomicInteger nextServerCyclicCounter
implemented through objects, and the increment is achieved by calling the function each time an instance is selected incrementAndGetModulo
.
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
while (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
RetryRule
This strategy implements an instance selection function with a retry mechanism. From the implementation below, we can see that an object is also defined internally IRule
, and an instance is used by default RoundRobinRule
. In choose
the method, the strategy of repeatedly trying the internally defined strategy is implemented. If a specific service instance can be selected during the period, it will be returned. If it cannot be selected, the set attempt end time will be the threshold ( ). When the threshold is maxRetryMillis 参数定义的值 + choose方法开始中的时间戳
exceeded Then return null
.
public class RetryRule extends AbstractLoadBalancerRule {
IRule subRule = new RoundRobinRule();
long maxRetryMillis = 500;
...
/*
* Loop if necessary. Note that the time CAN be exceeded depending on the
* subRule, because we're not spawning additional threads and returning
* early.
*/
public Server choose(ILoadBalancer lb, Object key) {
long requestTime = System.currentTimeMillis();
long deadline = requestTime + maxRetryMillis;
Server answer = null;
answer = subRule.choose(key);
if (((answer == null) || (!answer.isAlive()))
&& (System.currentTimeMillis() < deadline)) {
InterruptTask task = new InterruptTask(deadline
- System.currentTimeMillis());
while (!Thread.interrupted()) {
answer = subRule.choose(key);
if (((answer == null) || (!answer.isAlive()))
&& (System.currentTimeMillis() < deadline)) {
/* pause and retry hoping it's transient */
Thread.yield();
} else {
break;
}
}
task.cancel();
}
if ((answer == null) || (!answer.isAlive())) {
return null;
} else {
return answer;
}
}
...
}
WeightedResponseTimeRule
This strategy is an RoundRobinRule
extension of , adding the weight calculation based on the running status of the instance, and selecting instances based on the weight to achieve better allocation effects. Its implementation mainly has three core contents.
scheduled tasks
WeightedResponseTimeRule
When the policy is initialized serverWeightTimer.schedule(new DynamicServerWeightTask(), 0, serverWeightTaskTimerInterval)
, a scheduled task will be started to calculate the weight for each service instance. This task will be executed every 30 seconds by default.
class DynamicServerWeightTask extends TimerTask {
public void run() {
ServerWeight serverWeight = new ServerWeight();
try {
serverWeight.maintainWeights();
} catch (Throwable t) {
logger.error(
"Throwable caught while running DynamicServerWeightTask for "
+ name, t);
}
}
}
Weight calculation
We can easily find the object used to store weights in the source code List<Double> accumulatedWeights = new ArrayList<Double>()
. The List
position of each weight value in the corresponds to the position of all examples in the list of service instances maintained by the load balancer.
The calculation process of maintaining instance weight is through maintainWeight
function matters, as shown in the following code:
public void maintainWeights() {
ILoadBalancer lb = getLoadBalancer();
if (lb == null) {
return;
}
if (serverWeightAssignmentInProgress.get()) {
return; // Ping in progress - nothing to do
} else {
serverWeightAssignmentInProgress.set(true);
}
try {
logger.info("Weight adjusting job started");
AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
LoadBalancerStats stats = nlb.getLoadBalancerStats();
if (stats == null) {
// no statistics, nothing to do
return;
}
//所有示例的平均响应时间总和
double totalResponseTime = 0;
for (Server server : nlb.getAllServers()) {
// 如果服务实例的状态快照不在缓存汇总,这里会进行自动加载
ServerStats ss = stats.getSingleServerStat(server);
totalResponseTime += ss.getResponseTimeAvg();
}
// weight for each server is (sum of responseTime of all servers - responseTime)
// so that the longer the response time, the less the weight and the less likely to be chosen
Double weightSoFar = 0.0;
// create new list and hot swap the reference
List<Double> finalWeights = new ArrayList<Double>();
for (Server server : nlb.getAllServers()) {
ServerStats ss = stats.getSingleServerStat(server);
double weight = totalResponseTime - ss.getResponseTimeAvg();
weightSoFar += weight;
finalWeights.add(weightSoFar);
}
setWeights(finalWeights);
} catch (Throwable t) {
logger.error("Exception while dynamically calculating server weights", t);
} finally {
serverWeightAssignmentInProgress.set(false);
}
}
The implementation of this function is mainly divided into two steps:
- According to
LoadBalancerStats
the statistics of each instance recorded in , the platform response time of all examples is accumulated to obtain the total average response timetotalResponseTime
, which will be used in subsequent calculations. - Calculate the weight one by one (starting from the first one) for the instance list maintained in the load balancer. The calculation rule is
weightSoFar + totalResponseTime - 实例的平均响应时间
, whereweightSoFar
is initialized to zero, and each calculated weight needs to be accumulated toweightSoFar
for the next calculation.
Let's take a simple example to understand this calculation process. Suppose there are 4 instances A, B, C, and D. Their average response times are 10, 40, 60, 80, and 100, so the total average response time is, each
10 + 40 + 80 + 100 = 230
instance The weight of is the cumulative difference between the total response time and the average response time of the instance itself, so the weights of instances A, B, C, and D are as follows.
- Example A:
230 - 10 = 220
- Example B:
220 + (230 - 40) = 410
- Example C:
410 + (230 - 80) = 560
- Example D:
60 + (230 - 100) = 690
It should be noted that the weight value here only represents the upper limit of the weight range of each instance, not the priority of a certain instance, so it does not mean that the larger the data, the greater the probability of being selected. So what is the weight interval? Taking the calculation above as an example, it actually constructs 4 different intervals for these 4 instances. The lower limit of the interval for each instance is the upper limit of the interval of the previous instance, and the upper limit of the interval for each instance is the upper limit of our interval.
List accumulatedWeights
The weight value is calculated and stored in , where the lower bound for the first instance defaults to zero. Therefore, based on the weight calculation results of the above instances, we can get the weight range of each instance.
- Example A:
[0, 220]
- Example B:
(220, 410]
- Example C:
(410, 560]
- Example D:
(560, 690)
It is not difficult to find that the width of each interval on the instance is: total average response time - average response time of the instance, so the shorter the average response time of the instance, the greater the width of the weight interval, and the greater the width of the weight interval, the selected the higher the probability. How are the opening and closing of these interval boundaries determined? Why not so regular? This will be explained through the example selection algorithm below.
Instance selection
WeightedResponseTimeRule
The implementation of the selected instance is similar to the algorithm structure introduced before. The following is the algorithm of its theme (the loop body and some judgments and other processing are omitted):
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
// get hold of the current reference in case it is changed from the other thread
List<Double> currentWeights = accumulatedWeights;
if (Thread.interrupted()) {
return null;
}
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
int serverIndex = 0;
// 获取最后一个实例的权重
double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1);
// 如果最后一个实例的权重值小于0.001,则采用父类实现的线型轮询的策略
if (maxTotalWeight < 0.001d) {
server = super.choose(getLoadBalancer(), key);
if(server == null) {
return server;
}
} else {
// 如果最后一个实例的权重值大于等于0.001,就产生一个[0, maxTotalWeight)的随机数
double randomWeight = random.nextDouble() * maxTotalWeight;
int n = 0;
for (Double d : currentWeights) {
// 遍历维护的权重清单,若权重大于等于随机得到的数值,就选择这个实例
if (d >= randomWeight) {
serverIndex = n;
break;
} else {
n++;
}
}
server = allList.get(serverIndex);
}
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Next.
server = null;
}
return server;
}
From the source code, we can see that the core process of selecting an instance is two steps:
-
Generate a
[0, 最大权重值)
random number within a range. -
Traverse the weight list and compare the weight value with the random number. If the weight value is greater than or equal to the random number, use the index value of the current weight list to obtain the specific instance in the service instance list.
This is the principle that the service instances mentioned in the previous section will be selected based on the weight interval, and the opening and closing principle of the weight interval boundary is based on the algorithm. Normally each interval is in the form of, but why are the first instance and the last instance different
(x, y]
? Woolen cloth? Since the minimum value of the random number can be 0, the lower limit of the first instance is a closed interval. At the same time, the maximum value of the random number cannot reach the maximum weight value, so the upper limit of the last instance is an open interval.
If you continue to use the above data as an example to select a service instance, this method will [0, 690)
select a random number from the interval. For example, the selected random number is 230. Since this value is in the second interval, it will Select instance B to make the request.
ClientConfigEnabledRoundRobinRule
This strategy is quite special. We generally do not use it directly because it does not implement any special processing logic. As shown in the source code below, a strategy is defined inside it, and the implementation of the choose function also RoundRobinRule
uses RoundRobinRule
’s linear polling mechanism, so the function it implements is actually the RoundRobinRule
same as .
choose
Although we will not use this strategy directly, by inheriting this strategy , the thread polling mechanism is implemented by default. When doing some advanced strategies in subclasses, there may often be some situations that cannot be implemented, so you can use the parent Class implementation as an alternative. In the following article, we will continue to introduce the advanced strategic military ClientConfigEnabledRoundRobinRule
expansion based on .
public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule {
RoundRobinRule roundRobinRule = new RoundRobinRule();
...
@Override
public Server choose(Object key) {
if (roundRobinRule != null) {
return roundRobinRule.choose(key);
} else {
throw new IllegalArgumentException(
"This class has not been initialized with the RoundRobinRule class");
}
}
}
BestAvailableRule
This strategy is inherited from ClientConfigEnabledRoundRobinRule
. In the implementation, it injects the statistical object of the load balancer , and uses the saved instance statistics LoadBalancerStats
in the specific choose
algorithm to select instances that meet the requirements. From the following source code, we can see that it traverses the load LoadBalancerStats
All service instances maintained in the balancer will filter out failed instances and find the one with the smallest number of concurrent requests. Therefore, the characteristic of this strategy is to select the idlest instance.
public Server choose(Object key) {
if (loadBalancerStats == null) {
return super.choose(key);
}
List<Server> serverList = getLoadBalancer().getAllServers();
int minimalConcurrentConnections = Integer.MAX_VALUE;
long currentTime = System.currentTimeMillis();
Server chosen = null;
for (Server server: serverList) {
ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
if (!serverStats.isCircuitBreakerTripped(currentTime)) {
int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
}
if (chosen == null) {
return super.choose(key);
} else {
return chosen;
}
}
At the same time, since the core basis of the algorithm is the statistical object loadBalancerStats
, when it is empty, the strategy cannot be executed. So we can see from the source code that when loadBalancerStats
is empty, it will adopt the linear polling strategy in the parent class. As we introduced ClientConfigEnabledRoundRobinRule
, its subclasses can use it when they cannot meet the requirements for implementing advanced strategies. Characteristics of the linear polling strategy. The strategies that will be introduced later should all inherit from ClientConfigEnabledRoundRobinRule
, so they will all have such characteristics.
PredicateBasedRule
This is an abstract strategy, which it also inherits ClientConfigEnabledRoundRobinRule
. From its name, you can guess that this is a Predicate
strategy based on implementation, Predicate
which is Google Guava Collection
a conditional interface for tools to filter collections.
As shown in the source code below, it defines an abstract function getPredicate
to obtain AbstractServerPredicate
the implementation of the object, and in choose
the function, the specific service instance is selected through the function AbstractServerPredicate
. chooseRoundRobinAfterFiltering
From the naming of this function, we can roughly guess its basic logic: first filter some service instances through the logic implemented in the subclass Predicate
, and then select one from the filtered instance list in a linear polling manner.
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
public abstract AbstractServerPredicate getPredicate();
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}
The following AbstractServerPredicate
source code snippet can confirm the guess we made above. choose
The method called in the above function first obtains the list of candidate instances (filtering is implemented) through the chooseRoundRobinAfterFiltering
internally defined function. If the returned list is empty, it is used to indicate that it does not exist. Otherwise, linear polling is used from Get an instance from the shortlist.getEligibleServers
Optional.absent()
public abstract class AbstractServerPredicate implements Predicate<PredicateKey> {
...
public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
if (loadBalancerKey == null) {
return ImmutableList.copyOf(Iterables.filter(servers, this.getServerOnlyPredicate()));
} else {
List<Server> results = Lists.newArrayList();
for (Server server: servers) {
if (this.apply(new PredicateKey(loadBalancerKey, server))) {
results.add(server);
}
}
return results;
}
}
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
return Optional.of(eligible.get(nextIndex.getAndIncrement() % eligible.size()));
}
...
}
After understanding the overall logic, let's take a detailed look at getEligibleServers
the functions that implement the filtering function. From the source code, its implementation structure is simple and clear. By traversing the service list, use this.apply
the method to determine whether the instance needs to be retained, and if so, add it to the result list.
Friends who are not familiar with Google Guava Collections
collection tools may be confused. This apply
definition cannot be found in AbstractServerPredicate, so how does it implement filtering? In fact, AbstractServerPredicate implements com.google.common.base.Predicate
the interface, and the apply method is defined in this interface. It is mainly used to implement the judgment logic of filter conditions. The parameters it inputs are some information (such as those in the source code) that the filter conditions need to use new PredicateKey(loadBalancerKey, server)
. Statistical information about the instance and the load balancer selection algorithm are passed in, so the function here chooseRoundRobinAfterFiltering
just defines a template strategy: "filter the list first, then poll for selection." As for how to filter, we need to AbstractServerPredicate
implement the method in the subclass apply
to determine the specific filtering strategy.
The two strategies we will introduce later are implemented based on this abstract strategy, but they use different Predicate
implementations to complete the filtering logic to achieve different instance selection effects.
Google Guava Collections
is anJava Collections Framework
open source project that enhances and extends . AlthoughJava Collections Framework
it can meet our requirements for using collections in most cases, our code will be verbose and error-prone when encountering some special situations.Guava Collections
It can help us make the collection operation code shorter and more concise and greatly enhance the readability of the code.
AvailabilityFilteringRule
This strategy inherits from the abstract strategy introduced above PredicateBasedRule
, so it also inherits the basic processing logic of "filter the list first, then poll for selection", in which the filter conditions are used AvailabilityPredicate
:
public class AvailabilityPredicate extends AbstractServerPredicate {
...
@Override
public boolean apply(@Nullable PredicateKey input) {
LoadBalancerStats stats = getLBStats();
if (stats == null) {
return true;
}
return !shouldSkipServer(stats.getSingleServerStat(input.getServer()));
}
private boolean shouldSkipServer(ServerStats stats) {
if ((CIRCUIT_BREAKER_FILTERING.get() && stats.isCircuitBreakerTripped())
|| stats.getActiveRequestsCount() >= activeConnectionsLimit.get()) {
return true;
}
return false;
}
}
From the above source code, we can know that its main filtering logic is located shouldSkipServer
in the method, and it mainly determines two contents of the service instance:
- It is a fault, that is, whether the circuit breaker is in effect and has been disconnected.
- The number of concurrent requests in the example is greater than the threshold. The default is
2的32次幂 - 1
. This configuration can<clientName>.<nameSpace>.ActiveConnectionsLimit
be modified through parameters.
As long as one of these two items is satisfied, apply
it will be returned false
(indicating that the node may be faulty or the load is too high). If neither is satisfied, it will return true.
In this strategy, in addition to implementing the above filtering method, choose
some improvements and optimizations have also been made to the strategy, so the implementation of the parent class is only a backup option for it, and its specific implementation is as follows:
public Server choose(Object key) {
int count = 0;
Server server = roundRobinRule.choose(key);
while (count++ <= 10) {
if (predicate.apply(new PredicateKey(server))) {
return server;
}
server = roundRobinRule.choose(key);
}
return super.choose(key);
}
As you can see, it does not first traverse all nodes to filter, and then select instances in the filtered collection like in the parent class. Instead, select an instance linearly first. Then use the filtering conditions to determine whether the instance meets the requirements. If it is slowly grouped, use the instance directly. If it does not meet the requirements, select the next instance and check whether it meets the requirements. This cycle continues. When this process is repeated 10 times If an instance that meets the requirements is still not found, the implementation plan of the parent class is adopted.
Simply put, this strategy directly tries to find available and idle instances to use through linear abstraction, optimizing the overhead of the parent class traversing all examples every time.
ZoneAvoidanceRule
ZoneAvoidanceRule
We have already mentioned this strategy when introducing the complex equalizer , and it is also PredicateBasedRule
a specific implementation class of. The previous introduction mainly focused on some static functions used to select Zone regional ZoneAvoidanceRule
strategies, such as createSnapshot
.getAvailableZones
Here we will take a closer look at ZoneAvoidanceRule
how this is implemented as a filter condition for service instances. As you can see from the source code judgment below ZoneAvoidanceRule
, it uses CompositePredicate
to filter the service instance list. This is a combined filter condition. In its constructor, it uses as ZoneAvoidancePredicate
the main filter condition and AvailabilityPredicate
initializes an instance of the combined filter condition for the secondary filter condition.
public class ZoneAvoidanceRule extends PredicateBasedRule {
...
private CompositePredicate compositePredicate;
public ZoneAvoidanceRule() {
super();
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}
...
}
When implemented, ZoneAvoidanceRule did not AvailabilityFilteringRule
rewrite choose
the function for optimization like that, so it completely followed the main filtering logic of the parent class: "filter light first, then poll for selection." Among them, the condition for filtering lightness is the combined filtering condition we mentioned above, with is ZoneAvoidancePredicate
the main filtering condition and the secondary filtering condition . From the source code snippet, we can see that it defines a main filter condition and a set of secondary filter condition lists , so it can have multiple secondary filter lists, and because it uses storage, the secondary filter conditions are based on executed sequentially.AvailabilityPredicate
CompositePredicate
CompositePredicate
AbstractServerPredicate delegate
List<AbstractServerPredicate> fallbacks
List
public class CompositePredicate extends AbstractServerPredicate {
private AbstractServerPredicate delegate;
private List<AbstractServerPredicate> fallbacks = Lists.newArrayList();
private int minimalFilteredServers = 1;
private float minimalFilteredPercentage = 0;
@Override
public boolean apply(@Nullable PredicateKey input) {
return delegate.apply(input);
}
...
/**
* 从主过滤条件获取筛选过的服务器,如果筛选过的服务器达不到指定要求,则用次过滤条件进行筛选
*/
@Override
public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
List<Server> result = super.getEligibleServers(servers, loadBalancerKey);
Iterator<AbstractServerPredicate> i = fallbacks.iterator();
while (!(result.size() >= minimalFilteredServers && result.size() > (int) (servers.size() * minimalFilteredPercentage))
&& i.hasNext()) {
AbstractServerPredicate predicate = i.next();
result = predicate.getEligibleServers(servers, loadBalancerKey);
}
return result;
}
}
In the implementation function for obtaining filtering results getEligibleServers
, its processing logic is as follows:
- Filter all instances using the main filter and return the filtered instance list.
- Filter all instances using the filter conditions in the filter list in sequence.
- After each filtering (including primary filtering conditions and secondary filtering conditions), the following two conditions need to be judged. As long as one requirement is not met, continue to use the next filtering condition for filtering, and the final result that meets the two conditions will be returned to linear Polling algorithm selection:
- Total number of instances after filtering >= Minimum filtering is membership (
minimalFilteredSevers
, default 1). - Filtered instance proportion > minimum filtered percentage (
minimalFilteredPercentage
, default 0).
- Total number of instances after filtering >= Minimum filtering is membership (