搭建服务测试环境
测试中,服务发现层采用Netflix的Eureka搭建。
主要步骤如下:
1.引入Eureka所需依赖
<!--eureka服务端-->
<dependency>
<groupId>org.springframework.cloud
</groupId>
<artifactId>spring-cloud-starter-eureka-server
</artifactId>
</dependency>
<!--客户端-->
<dependency>
<groupId>org.springframework.cloud
</groupId>
<artifactId>spring-cloud-starter-eureka
</artifactId>
</dependency>
2.修改配置文件
服务端:
eureka:
instance:
hostname:
eureka9001.com
#eureka服务端的实例名称
instance-id:
eureka9001
client:
register-with-eureka:
false
#false表示不向注册中心注册自己
fetch-registry:
false
# #false 表示自己就是注册中心,职责就是维护服务实例,并不需要去检索服务
service-url:
defaulteZone:
http://127.0.0.1:9001
客户端1:
server:
port:
8002
spring:
application:
name:
licensingservice
eureka:
instance:
instance-id:
licensing-service-8002
prefer-ip-address:
true
client:
register-with-eureka:
true
fetch-registry:
true
service-url:
defaultZone:
http://127.0.0.1:9001/eureka/,
客户端2:
server:
port:
8002
spring:
application:
name:
licensingservice
eureka:
instance:
instance-id:
licensing-service-8002
prefer-ip-address:
true
client:
register-with-eureka:
true
fetch-registry:
true
service-url:
defaultZone:
http://127.0.0.1:9001/eureka/,
一组微服务的不同实例采服务名相同,不同的实例Id区分,分别对应,spring.application.name 和eureka.instance.instance-id。
3.启动服务
服务端:
@SpringBootApplication
@EnableEurekaServer
public
class EurekaServerPort9001_App {
public static void main(String[] args) {
SpringApplication.run(EurekaServerPort9001_App
.class,args);
}
}
客户端:
@SpringBootApplication
@RefreshScope
@EnableEurekaClient
public
class LicenseApplication_8002 {
public static void main(String[] args) {
SpringApplication.run(LicenseApplication_8002
.class, args);
}
}
4.测试
进入到Eureka服务端地址,我这是127.0.0.1:9001,可以查看注册到注册中心的服务。
如图:
注意事项:Eureka通过三次心跳检测均通过,服务才会成功注册到注册中心,默认每次间隔10s,及初次启动服务需等待30s才能在Eureka中看到注册服务。
消费者搭建
1.Discover Client方式
微服务中服务既是消费者也可以是调用者,因此消费者配置和上面服务配置大体一致,依赖及配置参考上面服务端搭建方式。启动主类添加EnableEurekaClient注释:
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public
class ConsumerApplication_7002 {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication_7002
.class, args);
}
}
核心配置类:
@Component
public
class ConsumerDiscoveryClient {
@Autowired
private DiscoveryClient discoveryClient;
public ServiceInstance getServiceInstance() {
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(
"licensingservice");
if (serviceInstances.size() ==
0) {
return
null;
}
return serviceInstances.get(
0);
}
public String getUrl(String url) {
ServiceInstance serviceInstance=
this.getServiceInstance();
if (serviceInstance==
null)
throw
new RuntimeException(
"404 ,NOT FOUND");
String urlR=String.format(url,serviceInstance.getUri().toString());
return urlR;
}
}
通过DiscoveryClient从Eureka中获取licensingservice服务的实例数组,并返回第一个实例。
测试Controller
@RestController
@RequestMapping(
"test")
public
class TestController {
//注意必须new,否则会被ribbon拦截器拦截,改变URL行为
private RestTemplate restTemplate=
new RestTemplate();
@Autowired
private ConsumerDiscoveryClient consumerDiscoveryClient;
@RequestMapping(
"/getAllEmp")
public List<Emp> getAllLicense(){
String url=consumerDiscoveryClient.getUrl(
"%s/test/getAllEmp");
return restTemplate.getForObject(url,List
.class);
}
}
测试:
- 调试信息
从该图可以直观看到licensingservice,拥有两个服务实例,并可以查看实例信息。
- 页面返回信息
成功查询到数据库存储信息。
2.Ribbon功能的Spring RestTemplate方式
依赖同上。
核心配置类:
//指明负载均衡算法
@Bean
public IRule iRule() {
return new RoundRobinRule();
}
@Bean
@LoadBalanced //告诉Spring创建一个支持Ribbon负载均衡的RestTemplate
public RestTemplate restTemplate() {
return new RestTemplate();
}
设置负载均衡方式为轮询方式。
测试类:
@RestController
@RequestMapping( "rest")
public class ConsumerRestController {
@Autowired
private RestTemplate restTemplate;
private final
static String SERVICE_URL_PREFIX =
"http://LICENSINGSERVICE";
@RequestMapping( "/getById")
public Emp getById(Long id) {
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add( "id", id);
return restTemplate.postForObject(SERVICE_URL_PREFIX + "/test/getById", paramMap, Emp
.class);
}
}
测试结果:
- 第一次:
- 第二次:
- 第三次:
因为采用轮询负载均衡方式分别调用不同服务实例,未区别,将name做出了一定更改。
以上两种方式对比,Ribbon方式是对第一种方式的封装且内置不同的负载算法,支持自定义。使用更加简单,但此两次均需编写RestTemplate的请求方法,较为繁琐且容易出错,第三种方式Feign客户端则极大的降低了开发难度和提升速度。
3.feign客户端方式
引入依赖:
<dependency>
<groupId>org.springframework.cloud </groupId>
<artifactId>spring-cloud-starter-feign </artifactId>
</dependency>
主要代码:
@FeignClient(value = "LICENSINGSERVICE",fallbackFactory = ServiceImp .class)
public interface ServiceInterface {
@RequestMapping( "/test/getById")
Emp getById(@RequestParam("id") Long id);
@RequestMapping( "/test/getLicenseById")
License getLicenseById(@RequestParam("id") Long id);
@RequestMapping( "/test/getAllEmp")
List<Emp> getAllLicense();
}
@Component
public class ServiceImp implements FallbackFactory<ServiceInterface> {
@Override
public ServiceInterface create(Throwable throwable) {
return new ServiceInterface() {
@Override
public Emp getById(Long id) {
Emp emp = new Emp();
emp.setName( "i am feign fallback create");
return emp;
}
@Override
public License getLicenseById(Long id) {
return null;
}
@Override
public List<Emp> getAllLicense() {
return null;
}
};
}
}
采用接口模式开发,通过注解指明服务名以及后备方法,在服务表现不佳时,方便返回默认的结果,而不是一个不友好的错误。
主启动类:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class Consumer_feign_Application_7004 {
public static void main(String[] args) {
SpringApplication.run(Consumer_feign_Application_7004 .class, args);
}
}
测试类:
@RestController
@RequestMapping( "rest")
public class ConsumerRestController {
@Autowired
private ServiceInterface serviceInterface;
@Autowired
private RestTemplate restTemplate;
@RequestMapping( "/getById")
public Emp getById(Long id) {
return serviceInterface.getById(id);
}
}
测试结果:
- 正常测试:
- 关闭两个服务实例,模拟服务实例死亡:
Feign除了能简化服务调用,也可以实现当调用的服务失败时,友好的反馈信息。
此三种调用方式,由低至上,从不同层次实现了SpringCloud中的微服务互相调用。