技術架構在向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