服务注册与调用
本篇博客接"微服务架构入门"~
我们已经将聚合结构创建好了,一个父级工程三个子集工程,创建好后,我们来看一下父级工程中的pom文件悄悄的发生了什么变化
在没有创建子集工程前,这一部分是没有的,通过这个图片可以得出结论,父级默认打包方式为pom文件,每构建一个子工程,自动会在modules标签下生成该子工程.
业务描述
我们已经将父子工程创建完毕,接下来我们将sca-qty-provider以及sca-qty-consumer两个子工程注册到Nacos注册中心,实现服务提供者可以为服务消费者提供远程调用.
生产者服务创建及注册
1.配置pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
2.创建并配置application.yml文件
server:
port: 8081
spring:
application:
name: sca-qty-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
注意:name的属性不要用"_"连接,同时注意缩进问题
3.创建启动类
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class,args);
}
}
4.启动启动类查看Nacos管理界面是否有相应的服务被注册
如果显示以上图片效果,服务恭喜注册成功
停掉该服务并尝试刷新Nacos页面
大概过了15秒左右的样子,Nacos检测到该服务已经不正常了~
在第30秒左右的样子,该服务消失了~
服务检测机制:
每5秒进行检测,俗称检测"心跳"停掉服务15秒,检测时,“心跳"还不跳,标明该服务即将挂掉,在第三十秒,还没有"心跳”,则该服务停止.
消费者服务创建及注册
1.配置pom文件
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
2.创建并配置application.yml文件
server:
port: 8090
spring:
application:
name: sca-qty-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
3.在服务提供工程下创建controller包,创建服务提供方独享,基于此对象向外提供服务
@RestController
public class ProviderController {
@Value("${server.port:8080}")
private String serverPort;
@GetMapping("/provider/echo/{msg}")
public String doRestEcho1(@PathVariable String msg){
return String.format("%s hello %s", serverPort, msg);
}
}
4.创建消费者启动类
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
5.在启动类中创建RestTemplate对象,此对象用于远程服务调用
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
此对象可以创建在启动类中,也可以创建在配置类中,因为启动类中包含了配置类注解@Configuration.
6.创建consumer的controller对象,基于此类中的方法,进行远端服务调用
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Value("${server.port:8080}")
private String serverPort;
@GetMapping("/doRestEcho1")
public String doRestEcho1(){
String url = "http://localhost:8081/provider/echo/{msg}";
return restTemplate.getForObject(url, String.class,serverPort);
}
}
服务负载均衡设计以及实践
在实际应用场景中,几乎都是多个服务实例并发的状态,这时,我们就需要考虑,服务的负载均衡策略,比如轮询,随机,哈希,权重等策略,LoadBalancerClient对象可以从nacos中基于服务名获取服务实例,然后根据算法实现负载均衡方式的调用.
修改ConsumerController
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/doRestEcho2")
public String doRestEcho2(){
ServiceInstance serviceInstance =
loadBalancerClient.choose("sca-qty-provider");
int port = serviceInstance.getPort();
String id = serviceInstance.getHost();
String url = "http://%s:%s/provider/echo/{msg}";
url = String.format(url,id,port);
return restTemplate.getForObject(url, String.class,serverPort);
}
根据LoadBalancerClient对象可以获取到服务列表中的服务名,通过服务名可以获取该服务的id以及port,这样就额能动态并且,多服务并发调用了.可以修改provider的端口,同时启动多个服务,进行调用.
为了测试方便,可以这样做,在resources目录下创建http目录,在http目录下创建consumer-api.http文件
GET http://localhost:8090/consumer/doRestEcho2
测试后发现可以访问多个服务了
@LoadBalanced
假如在调用服务时,需要负载均衡,可以创建LoadBalanced对象来实现,修改Consumer启动类
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
return new RestTemplate();
}
修改controller类
@Autowired
private RestTemplate loadBalancedRestTemplate;
@GetMapping("/doRestEcho3")
public String doRestEcho3(){
String url = "http://sca-qty-provider/provider/echo/{msg}";
return loadBalancedRestTemplate.getForObject(url, String.class, serverPort);
}
修改http测试文件
GET http://localhost:8090/consumer/doRestEcho2
###
GET http://localhost:8090/consumer/doRestEcho
注意两个请求之间要用"###"隔开,否则,会被而那位是同一个请求路径
上述其实都是基于ReastTemplate对象完成远程服务调用的,LoadBalancerClient可以获取服务列表中的服务名,这样就可以动态获取端口号以及ip,其中@LoadBalanced可以将多个服务访问进行负载均衡
接下来我们再来学习一种远程调用方法—Feign
基于Feign的远程服务调用
为什么会出现Feign
上述远程服务调用都是基于RestTemlate方法,这种方法,一般都是自己拼接url,一不小心就会出现错误,每次调用都会进行拼接,不宜维护,此时Feign出现了
Feign是什么
Feign是一种声明式的web服务客户端,底层封装了rest,通过Feign可以简化服务调用服务的操作,如图所示
Feign的使用
1.配置消费方的pom文件
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Feign是由Netfilx开发的,但是后来不进行维护了,现在由SpringCloud进行维护,叫做openFeign
2.在启动类中添加注解@EnableFeignClients
package com.qty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
return new RestTemplate();
}
}
@EnableFeignClients:用于扫描配置类或启动类,启动类启动,就会创建一个FeignStart,为注解@Feign的类生成Feign对象.
3.在consumer的controller类中创建一个类
@RestController
@RequestMapping("/consumer")
public class FeignConsumerController {
}
4.创建一个service包,在包下创建一个接口
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient(name = "sca-qty-provider")
public interface RemoteProviderService {
}
在接口中添加注解@FeignClient(name=“服务名”),加了这个注解汇报扫描到,底层就会通过动态代理生成一个实例对象,所以这也就是只声明接口不创建实现类的原因.
5.修改
@RestController
@RequestMapping("/consumer")
public class FeignConsumerController {
@Autowired
private RemoteProviderService remoteProviderService;
@GetMapping("/doFeignEcho/{msg}")
public String doFeignEcho(@PathVariable String msg){
return remoteProviderService.echoMessage(msg);
}
}
@FeignClient(name = "sca-qty-provider")
public interface RemoteProviderService {
@GetMapping("/provider/echo/{msg}")
String echoMessage(@PathVariable("msg") String msg);
}
假如同时由多个消费方调用同一个服务提供方,这时就会报错,继续优化
@FeignClient(name = "sca-qty-provider", contextId = "remoteProviderService")
public interface RemoteProviderService {
@GetMapping("/provider/echo/{msg}")
String echoMessage(@PathVariable("msg") String msg);
}
我们通过给这个添加一个专属的名字就可以了,但是假如出现500,404等错误,现在会直接让用户看到,很不友好,现在我们来继续优化
在service包下创建此类
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ProviderError implements FallbackFactory<RemoteProviderService> {
@Override
public RemoteProviderService create(Throwable throwable) {
return msg -> {
// 这里还可以向运维人员发送报警短信,电话等
log.error("error: {}","provider服务调用失败");
return "出错啦";
};
}
}
修改RemoteProviderService接口
@FeignClient(name = "sca-qty-provider",
contextId = "remoteProviderService",
fallbackFactory = ProviderError.class)
public interface RemoteProviderService {
@GetMapping("/provider/echo/{msg}")
String echoMessage(@PathVariable("msg") String msg);
}
修改application.yml配置文件
feign:
hystrix:
enabled: true
这样即使调用出错,一会很友好的进行提示,而不是直接俄报404,500等,后台也可以根据日志看到报错信息
手动调节负载均衡策略
第一种方法:
/**
* 假如默认的负载均衡不满足需求怎么办
* 创建一个IRule接口实现类对象
* 创建随机的负载均衡
* */
@Bean
public IRule iRule(){
return new RoundRobinRule();
}
这种方法有个弊端,假如,需要调换策略,那么就会改动源码,所以一般使用第二种方法,修改application.yml配置文件
#直接在配置文件中的配置,其优势是,可以将配置提取到配置中心
sca-provider: #基于服务名指定负载均衡策略
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule