zoukankan      html  css  js  c++  java
  • 传统Java Web(非Spring Boot)、非Java语言项目接入Spring Cloud方案

    技术架构在向spring Cloud转型时,一定会有一些年代较久远的项目,代码已变成天书,这时就希望能在不大规模重构的前提下将这些传统应用接入到Spring Cloud架构体系中作为一个服务以供其它项目调用。我们需要使用原生的Eureka/Ribbon手动完成注册中心、查询服务列表功能。如果是非Java项目,可以使用 Spring Sidecar 项目接入Spring Cloud形成异构系统。

    JDK版本的选择

    强烈建议使用JDK8, 因为Eureka Client的最新版本已经要求JDK8起了,JDK8以下的版本会出现No such method运行时错误。如果不能使用JDK8, 可以选择较早版本的eureka client, 但最低也只能支持到JDK7。对于老项目来说,在不动代码的前提下升级JDK不会有太大的风险,除非你使用了JDK特定版本的功能。风险最大的其实是升级开发框架(如Spring3到Spring4)。

    服务列表查询

    非Spring Cloud要调用Cloud体系内的服务接口,核心问题就是如何获取到目标服务地址。我们可以直接使用原生的eureka, ribbon库实现这一功能:

             <dependency>
                <groupId>com.netflix.ribbon</groupId>
                <artifactId>ribbon</artifactId>
                <version>2.2.2</version>
            </dependency>
            <dependency>
                <groupId>com.netflix.eureka</groupId>
                <artifactId>eureka-client</artifactId>
                <version>1.7.0</version>
            </dependency>
    ServiceAddressSelector.xml
    package com.dfs.pos.gateway.cloud;
    
    import java.io.IOException;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class ServiceAddressSelector {
        /**
         * 默认的ribbon配置文件名, 该文件需要放在classpath目录下
         */
        public static final String RIBBON_CONFIG_FILE_NAME = "ribbon.properties";
        private static final Logger log = LoggerFactory.getLogger(ServiceAddressSelector.class);
        private static RoundRobinRule chooseRule = new RoundRobinRule();
        static {
            log.info("开始初始化ribbon");
            try {
                // 加载ribbon配置文件
                ConfigurationManager.loadPropertiesFromResources(RIBBON_CONFIG_FILE_NAME);
            } catch (IOException e) {
                e.printStackTrace();
                log.error("ribbon初始化失败");
                throw new IllegalStateException("ribbon初始化失败");
            }
            // 初始化Eureka Client
            DiscoveryManager.getInstance().initComponent(new MyDataCenterInstanceConfig(), new DefaultEurekaClientConfig());
            log.info("ribbon初始化完成");
        }
    
        /**
         * 根据轮询策略选择一个地址
         * 
         * @param clientName
         *            ribbon.properties配置文件中配置项的前缀名, 如myclient
         * @return null表示该服务当前没有可用地址
         */
        public static AlanServiceAddress selectOne(String clientName) {
            // ClientFactory.getNamedLoadBalancer会缓存结果, 所以不用担心它每次都会向eureka发起查询
            DynamicServerListLoadBalancer lb = (DynamicServerListLoadBalancer) ClientFactory
                    .getNamedLoadBalancer(clientName);
            Server selected = chooseRule.choose(lb, null);
            if (null == selected) {
                log.warn("服务{}没有可用地址", clientName);
                return null;
            }
            log.debug("服务{}选择结果:{}", clientName, selected);
            return new AlanServiceAddress(selected.getPort(), selected.getHost());
        }
    
        /**
         * 选出该服务所有可用地址
         * 
         * @param clientName
         * @return
         */
        public static List<AlanServiceAddress> selectAvailableServers(String clientName) {
            DynamicServerListLoadBalancer lb = (DynamicServerListLoadBalancer) ClientFactory
                    .getNamedLoadBalancer(clientName);
            List<Server> serverList = lb.getReachableServers();
            if (serverList.isEmpty()) {
                log.warn("服务{}没有可用地址", clientName);
                return Collections.emptyList();
            }
            log.debug("服务{}所有选择结果:{}", clientName, serverList);
            return serverList.stream().map(server -> new AlanServiceAddress(server.getPort(), server.getHost()))
                    .collect(Collectors.toList());
        }
    }

    使用方法很简单:

    // 选择出myclient对应服务全部可用地址
    List<AlanServiceAddress> list = AlanServiceAddressSelector.selectAvailableServers("myclient");
    System.out.println(list);
    
    // 选择出myclient对应服务的一个可用地址(轮询), 返回null表示服务当前没有可用地址
    AlanServiceAddress addr = AlanServiceAddressSelector.selectOne("myclient");
    System.out.println(addr);

    这样就获取到了目标服务的URL,然后可以通过Http Client之类的方式发送HTTP请求完成调用。当然这样远没有Spring Cloud体系中使用Feign组件来的方便,但是对于代码已经变成天书的老项目来说这不算什么了。 
    上面这个类工作的前提是提供ribbon.properties文件,该文件指定了eureka地址和服务名相关信息:

    # myclient对应的微服务名
    myclient.ribbon.DeploymentContextBasedVipAddresses=S3
    
    # 固定写法,myclient使用的ribbon负载均衡器
    myclient.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
    
    # 每分钟更新myclient对应服务的可用地址列表
    myclient.ribbon.ServerListRefreshInterval=60000
    
    
    
    # 控制是否注册自身到eureka中
    eureka.registration.enabled=false
    
    # eureka相关配置
    eureka.preferSameZone=true
    eureka.shouldUseDns=false
    eureka.serviceUrl.default=http://x.x.x.x:8761/eureka
    
    eureka.decoderName=JacksonJson

    另外,DiscoveryManager.getInstance().initComponent()方法已经被标记为@Deprecated了,但是ribbon的DiscoveryEnabledNIWSServerList组件代码中依然是通过DiscoveryManager来获取EurekaClient对象的:

    DiscoveryClient discoveryClient = DiscoveryManager.getInstance()
                    .getDiscoveryClient();

    因此这里只能用过时方法,否则ribbon获取不到Eureka Client,程序跑不通。

    使用原生Feign调用HTTP接口

    如果你的老项目有幸可以使用Feign, 那就能大大简化HTTP调用流程。我们可以使用原生Feign代替Http Client。先定义Feign接口:

    public interface ClientIdRemoteService {
        @RequestLine("POST /client/query")
        @Headers("Content-Type: application/x-www-form-urlencoded")
        @Body("uuid={uuid}")
        String getClientId(@Param("uuid") String uuid);
    }

    下面是Spring配置类:

    @Configuration
    public class NonCloudFeignConfig {
        private static final Logger log = LoggerFactory.getLogger(NonCloudFeignConfig.class);
    
    
        @Autowired
        private ObjectFactory<HttpMessageConverters> messageConverters;
    
        @Bean
        public ClientIdRemoteService clientIdRemoteService() {
            log.info("初始化获取uuid服务的Feign接口");
            return Feign.builder()
                    .encoder(new SpringEncoder(messageConverters))
                    .decoder(new SpringDecoder(messageConverters))
                    .target(ClientIdRemoteService.class, "http://xxxx.com");
        }
    }

    这时在代码中就可以通过

    @Autowired
    private ClientIdRemoteService service;
    
    String result = service.getClientId("uuid");

    的方式调用了。做异常处理的话可以自定义Feign的ErrorDecoder,然后在调用Feign.builder()的时候将你的ErrorDecoder传进去。 
    如果你项目的Spring版本不支持注解式配置,那么也可以通过编程的方式手动将Feign代理对象放到上下文中。

    非Java应用接入Spring Cloud的技术方案

    正是因为Spring Cloud Netflix架构体系中所有的服务都是通过HTTP协议来暴露自身,利用HTTP的语言无关性,我们才有了将老项目甚至非Java应用纳入到该体系中的可能。假如某个使用Node.js实现的项目想将自己变成服务供其它服务调用(或自己去调用别人的服务),可选择的方案有:

    • Spring Sidecar项目 
      原理是启动一个node.js对应的代理应用sidecar, sidecar本身是用spring cloud实现的,会将自身注册到eureka中,此时这个sidecar应用逻辑上就代表使用nodejs实现的服务,并且它同时也集成了ribbon, hystrix, zuul这些组件。 其他服务在调用node.js时,eureka会返回sidecar的地址,因此请求会发到sidecar,sidecar再将你的请求转发到node.js。当node.js想要调用别人的服务时,node.js需要向sidecar发请求, 由sidecar替node.js发HTTP请求,最后再将结果返回给node.js。

    • 直接使用eureka的HTTP接口 
      由于eureka也是通过HTTP协议的接口暴露自身服务的,因此我们可以在node.js中手动发送HTTP请求实现服务的注册、查询和心跳功能。eureka接口描述信息可以在官方github的wiki中找到。

    总结

    通过HTTP协议的语言无关性优势,我们得到了非java应用接入Spring Cloud架构体系中的能力。但带来的其实是性能上的开销,毕竟HTTP是基于字符的协议,它的解析速度不如二进制协议。同时Spring Boot基于Tomcat和SpringMVC也是比较臃肿笨重的。但近几年Spring Boot的流行说明Java web正在向着简单化、轻量化的方向发展,说不定以后可能会彻底淘汰Servlet容器,转而使用Netty这样的通讯框架代替。

    转自:http://blog.csdn.net/neosmith/article/details/70049977

  • 相关阅读:
    Struts2+Spring3+Mybatis3开发环境搭建
    spring+struts2+mybatis
    【LeetCode】Populating Next Right Pointers in Each Node
    【LeetCode】Remove Duplicates from Sorted Array
    【LeetCode】Remove Duplicates from Sorted Array II
    【LeetCode】Binary Tree Inorder Traversal
    【LeetCode】Merge Two Sorted Lists
    【LeetCode】Reverse Integer
    【LeetCode】Same Tree
    【LeetCode】Maximum Depth of Binary Tree
  • 原文地址:https://www.cnblogs.com/duanxz/p/7040929.html
Copyright © 2011-2022 走看看