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注册的功能


posted @ 2023-01-07 15:33  Epiphanyi  阅读(321)  评论(0)    收藏  举报