spring cloud Alibaba 2021+ 集成ribbon
由于spring cloud 2021 移除了 ribbon 作为 请求的负载均衡,而是使用了
spring-cloud-loadbalancer
引入 ribbon 的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- openfeign 目前用不到可以不引入 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.5</version>
</dependency>
ribbon 的 客户端 configure RibbonClientConfiguration里面含有客户端里所有的配置项 执行流程 参考

/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.netflix.ribbon;
/**
* ignorance import
*/
/**
* @author Dave Syer
* @author Tim Ysewyn
*/
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
/**
* Ribbon client default connect timeout.
*/
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
/**
* Ribbon client default read timeout.
*/
public static final int DEFAULT_READ_TIMEOUT = 1000;
/**
* Ribbon client default Gzip Payload flag.
*/
public static final boolean DEFAULT_GZIP_PAYLOAD = true;
@RibbonClientName
private String name = "client";
// TODO: maybe re-instate autowired load balancers: identified by name they could be
// associated with ribbon clients
@Autowired
private PropertiesFactory propertiesFactory;
@Autowired
private Environment environment;
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, getProperty(
CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT));
config.set(CommonClientConfigKey.ReadTimeout,
getProperty(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT));
config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
return config;
}
private Integer getProperty(IClientConfigKey<Integer> connectTimeout,
int defaultConnectTimeout) {
return environment.getProperty("ribbon." + connectTimeout, Integer.class,
defaultConnectTimeout);
}
// 默认的负载均衡规则
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, name)) {
return this.propertiesFactory.get(IRule.class, config, name);
}
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
// 在后台运行的确保服务可用性的组件
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, name)) {
return this.propertiesFactory.get(IPing.class, config, name);
}
return new DummyPing();
}
// 服务列表,它可以是静态的也可以是动态的,如果是动态的(DynamicServer-ListLoadBalancer),将会启动一个后台线程定期刷新和过滤服务列表
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
}
// 服务列表的更新策略
@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
return new PollingServerListUpdater(config);
}
// 负载均衡器
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
// 服务列表过滤器
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
return this.propertiesFactory.get(ServerListFilter.class, config, name);
}
ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
filter.initWithNiwsConfig(config);
return filter;
}
@Bean
@ConditionalOnMissingBean
public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
IClientConfig config, RetryHandler retryHandler) {
return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
}
@Bean
@ConditionalOnMissingBean
public RetryHandler retryHandler(IClientConfig config) {
return new DefaultLoadBalancerRetryHandler(config);
}
@Bean
@ConditionalOnMissingBean
public ServerIntrospector serverIntrospector() {
return new DefaultServerIntrospector();
}
@PostConstruct
public void preprocess() {
setRibbonProperty(name, DeploymentContextBasedVipAddresses.key(), name);
}
static class OverrideRestClient extends RestClient {
private IClientConfig config;
private ServerIntrospector serverIntrospector;
protected OverrideRestClient(IClientConfig config,
ServerIntrospector serverIntrospector) {
super();
this.config = config;
this.serverIntrospector = serverIntrospector;
initWithNiwsConfig(this.config);
}
@Override
public URI reconstructURIWithServer(Server server, URI original) {
URI uri = updateToSecureConnectionIfNeeded(original, this.config,
this.serverIntrospector, server);
return super.reconstructURIWithServer(server, uri);
}
@Override
protected Client apacheHttpClientSpecificInitialization() {
ApacheHttpClient4 apache = (ApacheHttpClient4) super.apacheHttpClientSpecificInitialization();
apache.getClientHandler().getHttpClient().getParams().setParameter(
ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
return apache;
}
}
}
我们主要需要重写 ServerList<Server> ribbonServerList的逻辑 先看一下 ServerList<Server>需要实现什么
/**
* Interface that defines the methods sed to obtain the List of Servers
* @author stonse
*
* @param <T>
*/
public interface ServerList<T extends Server> {
public List<T> getInitialListOfServers();
/**
* Return updated list of servers. This is called say every 30 secs
* (configurable) by the Loadbalancer's Ping cycle
*
*/
public List<T> getUpdatedListOfServers();
}
其实就两个方法 一个用于初始的服务列表 , 一个是更新的服务列表,知道了这个,直接注册 bean来覆盖掉它原先的 bean
首先 实现ServerList接口 ,下面我实现的可以作为参考
@Slf4j
public class NaCosServerListOnRibbon implements ServerList<Server> {
private NacosServiceManager nacosServiceManager;
private String serviceName ;
/**
* spring auto
* @param nacosServiceManager
*/
public NaCosServerListOnRibbon(NacosServiceManager nacosServiceManager) {
this.nacosServiceManager = nacosServiceManager;
}
public void bindServiceName(String serviceName) {
this.serviceName = serviceName;
}
private NamingService getNamingService (){
return nacosServiceManager.getNamingService();
}
@Override
public List<Server> getInitialListOfServers() {
try {
log.info("serviceName:{} --- {}" ,this.serviceName , getNamingService().getAllInstances(this.serviceName));
return getNamingService().getAllInstances(this.serviceName)
.stream().map(instance -> {
Server server = new Server(instance.getIp(), instance.getPort());
log.warn("11111-{}" , Optional.fromNullable(instance.getInstanceId()).or(String.valueOf(instance.hashCode())) );
// 注意这里的 Id 不要为空 RibbonClientConfiguration.ServerListUpdater:ribbonServerListUpdater 更新服务实例时会调用这个方法 Server:equals()
// 如果通过其他的构造方法实例化了一个 server 那id不用设置 他会自动生成一个ID
//server.setId( Optional.fromNullable(instance.getInstanceId()).or(String.valueOf(instance.hashCode())) );
//是否为临时实例
//instance.isEphemeral()
server.setAlive(instance.isEnabled());
log.warn("generate:{} , id:{}" , server , server.getId());
return server;
}).collect(Collectors.toList());
}catch (NacosException nacosException){
log.error("{}" , nacosException);
throw new RuntimeException(nacosException);
}
}
@Override
public List<Server> getUpdatedListOfServers() {
return getInitialListOfServers();
}
}
有个坑点 如果通过 host,ip构造的server不用设置 id它会自己生成一个 , 如果设置了 id他会根据 id生成 host , ip
然后写一个 configure 来注册bean
@Configuration(proxyBeanMethods = false)
public class RibbonClientNacosConfig implements ApplicationContextAware {
@Resource
private NacosServiceManager nacosServiceManager;
private ApplicationContext applicationContext;
/**
* Ribbon client default connect timeout.
*/
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
/**
* Ribbon client default read timeout.
*/
public static final int DEFAULT_READ_TIMEOUT = 1000;
/**
* Ribbon client default Gzip Payload flag.
*/
public static final boolean DEFAULT_GZIP_PAYLOAD = true;
// TODO: maybe re-instate autowired load balancers: identified by name they could be
// associated with ribbon clients
@Autowired
private Environment environment;
@RibbonClientName
private String name = "client";
private Integer getProperty(IClientConfigKey<Integer> connectTimeout,
int defaultConnectTimeout) {
return environment.getProperty("ribbon." + connectTimeout, Integer.class,
defaultConnectTimeout);
}
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, getProperty(
CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT));
config.set(CommonClientConfigKey.ReadTimeout,
getProperty(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT));
config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
return config;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 获取namingService 具体功能
* @see @link https://nacos.io/zh-cn/docs/open-api.html
* @return
*/
private NamingService getNamingService(){
return this.nacosServiceManager.getNamingService();
}
@Bean
public ServerList<Server> ribbonServerList(IClientConfig config) {
String clientName = config.getClientName();
Object autowire = applicationContext.getAutowireCapableBeanFactory()
.autowire(NaCosServerListOnRibbon.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
Preconditions.checkState(autowire instanceof NaCosServerListOnRibbon
, "NaCosServerListOnRibbon class Except");
NaCosServerListOnRibbon naCosServerListOnRibbon = (NaCosServerListOnRibbon) autowire;
naCosServerListOnRibbon.bindServiceName(clientName);
return naCosServerListOnRibbon;
}
//@Bean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
return new NewNacosZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
}
然后 通过 @RibbonClients注解的 defaultConfiguration属性来复写掉原来的configure
@RibbonClients(value = {
@RibbonClient(name = "store-nacosa" , configuration = RibbonRandomRuleConfig.class)
}, defaultConfiguration = {
RibbonClientNacosConfig.class
})
需要注意的是 ribbon 的所有的配置都要从 spring 中排除掉 可以将配置类写在 spring 的包扫描范围外 可以用 @ComponentScan : excludeFilters属性来排除扫描的范围
@EnableDiscoveryClient
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) ,
/**
* 设置排除 包扫描
*/
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE , classes = RibbonRandomRuleConfig.class),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE , classes = RibbonClientNacosConfig.class)
})
@RibbonClients(value = {
@RibbonClient(name = "store-nacosa" , configuration = RibbonRandomRuleConfig.class)
}, defaultConfiguration = {
RibbonClientNacosConfig.class
})
附上nacos的配置项 之前调程序掉过沟里记录一下
| 配置项 | key | 默认值 | 说明 |
|---|---|---|---|
| 服务端地址 | spring.cloud.nacos.discovery.server-addr | ||
| 服务名 | spring.cloud.nacos.discovery.service | $ | 注册到Nacos上的服务名称,默认值为应用名称 |
| 权重 | spring.cloud.nacos.discovery.weight | 1 | 取值范围 1 到 100,数值越大,权重越大 |
| 网卡名 | spring.cloud.nacos.discovery.network-interface | 当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址 | |
| 注册的IP地址 | spring.cloud.nacos.discovery.ip | 优先级最高 | |
| 注册的IP地址类型 | spring.cloud.nacos.discovery.ip-type | IPv4 | 可以配置IPv4和IPv6两种类型,如果网卡同类型IP地址存在多个,希望制定特定网段地址,可使用spring.cloud.inetutils.preferred-networks配置筛选地址 |
| 注册的端口 | spring.cloud.nacos.discovery.port | -1 | 默认情况下不用配置,会自动探测 |
| 命名空间 | spring.cloud.nacos.discovery.namespace | 常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。 | |
| AccessKey | spring.cloud.nacos.discovery.access-key | ||
| SecretKey | spring.cloud.nacos.discovery.secret-key | ||
| Metadata | spring.cloud.nacos.discovery.metadata | 使用Map格式配置 | |
| 日志文件名 | spring.cloud.nacos.discovery.log-name | ||
| 集群 | spring.cloud.nacos.discovery.cluster-name | DEFAULT | Nacos集群名称 |
| 接入点 | spring.cloud.nacos.discovery.endpoint | 地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址 | |
| 是否集成LoadBalancer | spring.cloud.loadbalancer.nacos.enabled | false | |
| 是否开启Nacos Watch | spring.cloud.nacos.discovery.watch.enabled | false | 可以设置成true来开启 watch |
| 是否启用Nacos | spring.cloud.nacos.discovery.enabled | true | 默认启动,设置为false时会关闭自动向Nacos注册的功能 |

浙公网安备 33010602011771号