技术架构在向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= # 控制是否注册自身到eureka中
eureka.registration.enabled=false # eureka相关配置
eureka.preferSameZone=true
eureka.shouldUseDns=false
eureka.serviceUrl.default=http://x.x.x.x:/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的流行说明Javaweb正在向着简单化、轻量化的方向发展,说不定以后可能会彻底淘汰Servlet容器,转而使用Netty这样的通讯框架代替。

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

传统Java Web(非Spring Boot)、非Java语言项目接入Spring Cloud方案的更多相关文章

  1. 传统Java Web(非Spring Boot)、非Java语言项目接入Spring Cloud方案--temp

    技术架构在向spring Cloud转型时,一定会有一些年代较久远的项目,代码已变成天书,这时就希望能在不大规模重构的前提下将这些传统应用接入到Spring Cloud架构体系中作为一个服务以供其它项 ...

  2. ASP.NET CORE(C#)与Spring Boot MVC(JAVA)

    干货分享:ASP.NET CORE(C#)与Spring Boot MVC(JAVA)异曲同工的编程方式总结   目录 C# VS JAVA 基础语法类比篇: 一.匿名类 二.类型初始化 三.委托(方 ...

  3. 利用spring boot创建java app

    利用spring boot创建java app 背景 在使用spring框架开发的过程中,随着功能以及业务逻辑的日益复杂,应用伴随着大量的XML配置和复杂的bean依赖关系,特别是在使用mvc的时候各 ...

  4. 一起学JAVA之《spring boot》03 - 开始spring boot基本配置及项目结构(转)

    <div class="markdown_views"> <h3 id="一导航"><a name="t0"& ...

  5. Java Spring Boot VS .NetCore (九) Spring Security vs .NetCore Security

    Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...

  6. Spring Boot 获取 java resources 下文件

    Spring Boot 获取 java resources 下文件 Spring Boot 获取 resources 目录下的目录(例:获取 resources 目录下的 template 目录): ...

  7. 曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  8. Spring Boot 2.X(三):使用 Spring MVC + MyBatis + Thymeleaf 开发 web 应用

    前言 Spring MVC 是构建在 Servlet API 上的原生框架,并从一开始就包含在 Spring 框架中.本文主要通过简述 Spring MVC 的架构及分析,并用 Spring Boot ...

  9. Spring Boot入门教程1、使用Spring Boot构建第一个Web应用程序

    一.前言 什么是Spring Boot?Spring Boot就是一个让你使用Spring构建应用时减少配置的一个框架.约定优于配置,一定程度上提高了开发效率.https://zhuanlan.zhi ...

随机推荐

  1. WEBapi在IIS发布注意事项-发布错误

    发布报错:403.14-Forbidden Web 服务器被配置为不列出此目录的内容 解决方法: 1)打开IIS管理器 2)找到功能视图的目录浏览 3)双击进入后,点击右侧操作栏-启用

  2. Linux命令速查手册

    Others make 通过外部编译器的,比如linux中的gcc集来编译源码 获取Makefile文件的命令触发编译 curl -X GET/POST -I 获取head curl有cache 查看 ...

  3. Spring 注解bean默认名称规则

    在使用@Component.@Repository.@Service.@Controller等注解创建bean时,如果不指定bean名称,bean名称的默认规则是类名的首字母小写,如SysConfig ...

  4. 【转载】Win10桌面图标有小箭头怎么去掉?Win10去掉桌面图标小箭头的方法

    以下文章转载至系统之家 网址:http://www.xitongzhijia.net/xtjc/20190104/146560.html Win10桌面图标有小箭头怎么去掉?Win10去掉桌面图标小箭 ...

  5. Android之根布局动态载入子布局时边距设置无效问题

    Android大部分的控件都会有padding和layout_margin两个属性,一般来说它们的差别是: padding:控件中的内容离控件边缘的距离. margin:  控件离它的父控件边缘的距离 ...

  6. JSON字符串转C#实体Class类

    在项目开发过程中,经常需要和不同部门或者不同的组员一起协同工作,但对方写的json返回的结果集,我们需要用,那么如何来生成对应的类代码和实体对象呢?于是参考了网上的做法,做一个简单的字符串转实体类的功 ...

  7. day6大纲

    01 昨日内容回顾 字典: 增: setdefault() 有责不变,无责添加 dic['key'] = value 删: pop 按照key pop('key') pop('key',None) p ...

  8. windows下搭建voip服务器

    软件: yate-6.0.0-1-setup.exe 服务端,里面也有个客户端 eyeBeam.exe 客户端 步骤: 失败....

  9. base64 base64urlsafe

    1. base64 不算是加密算法,只能说是一种转码.使用64 个可见的字符来代替 ASCII码 中的256 个字符. 2. ASCII码占用一个字节,可以有0-255共256个取值.前128个为常用 ...

  10. hiho一下 第165周#1327 : 分隔相同字符

    题目要求: 时间限制:10000ms单点时限:1000ms内存限制:256MB 描述给定一个只包含小写字母'a'-'z'的字符串 S ,你需要将 S 中的字符重新排序,使得任意两个相同的字符不连在一起 ...