傳統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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM