业务描述
先创建两个项目module分别为服务提供者和服务消费者,两个都要注册到NacosServer中(本质上就是一个web服务,端口默认是8848),服务提供者可以为服务消费者提供远程调用服务.
调用方式
说明:
1.RestTemplate方式:定义URL直接调用 ,多个端口则需定义多个URL,使用自定义算法实现负载均衡(如随机).
2.RestTemplate+LoadBalancerClient方式:使用LoadBalancerClient帮助我们获取nacos服务列表,采用轮询方式实现负载均衡
3.RestTemplate+@LoadBalanced方式:采用@LoadBalanced注解,起一个拦截器的作用,拦截之后底层帮我们获取nacos服务列表.(相比方式2就有点耗时)
4.@EnableFeignClients+@FeignClient方式:@EnableFeignClients注解描述启动类时,用于告诉springboot在启动时,扫描启动类所在包或子包中的类,假如接口上有@FeignClient注解描述,则对这样的接口创建其实现类,在实现类内部帮我们进行远程服务调用.
生产者服务的创建
创建服务提供者工程
创建服务提供者工程(module名为sca-provider),继承parent工程(01-sca),添加相关依赖包,其pom.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>01-sca</artifactId>
<groupId>com.jt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sca-provider</artifactId>
<dependencies>
<!--web服务-->
<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>
</project>
创建配置文件application.yml
创建并修改配置文件application.yml(或者application.properties),实现服务注册,关键代码如下:
server:
port: 8082
spring:
application:
name: sca-provider #服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服务注册和发现(默认是注册到这里)
注意这里需要三个端口,分别为8080,8081,8082.服务名不要使用下划线(“_”),应使用横杠(“-”),这是规则。
配置idea同时启动多个服务
需要先进行如下配置,才能启动多个服务.
进入编辑配置界面
创建启动类
创建启动类,并定义处理请求的控制层对象和方法,关键代码如下:
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
@Value("${server.port}")
private String server;
@RestController
public class ProviderController{
@GetMapping(value = "/provider/echo/{msg}")
public String doEcho(@PathVariable String msg){
return server+"say:Hello Nacos Discovery"+msg;
}
}
}
刷新nacos服务
启动启动类,然后刷先nacos服务,检测是否服务注册成功,如图所示:
因为我提供了3个端口,所以这里有3个实例数.注意每次修改端口号之后还需再次运行启动类.
消费者服务的创建
创建服务消费者工程
创建服务消费者(module名为sca-consumer),继承parent工程(01-sca),其pom.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>01-sca</artifactId>
<groupId>com.jt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sca-consumer</artifactId>
<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>
</project>
创建配置文件application.yml
修改配置文件application.yml,关键代码如下:
server:
port: 8090
spring:
application:
name: sca-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848 #从哪里去查找服务
创建启动类
创建启动类并实现服务消费,关键代码如下:
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
/**创建对象,进行远程服务调用*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
@LoadBalanced//这个注解描述RestTemplate对象时,系统底层会对RestTemplate对象的请求进行拦截
public RestTemplate loadBalanceRestTemplate(){
return new RestTemplate();
}
@RestController
public class ConsumerController{
@Value("${spring.application.name}")
private String appName;
//负载均衡客户端对象(基于此对象可以从nacos中获取服务列表,
// 并且可以基于一定的算法从列表中获取一个服务实例)
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/doRestEchol1")
public String doRestEchol01(){
//serviceId要在nacos的服务列表中
ServiceInstance choose = loadBalancerClient.choose("sca-provider");
String ip = choose.getHost();
int port = choose.getPort();
String url = String.format("http://%s:%s/provider/echo/%s",ip,port,appName);
return restTemplate.getForObject(url, String.class);//使用了负载均衡
}
@Autowired//此注解优先按属性类型进行对象的查找,如果相同类型存在多个,则按照属性名与方法名对应查找
@Qualifier("loadBalanceRestTemplate")//可以使用此注解指定要注入的具体对象
private RestTemplate loadBalanceRT;
@GetMapping("/consumer/doRestEchol2")
public String doRestEchol02(){
String url = String.format("http://sca-provider/provider/echo/%s",appName);
return loadBalanceRT.getForObject(url, String.class);//使用了负载均衡
}
}
}
消费者服务调用生产者服务
启动消费者服务,并在浏览器输入http://localhost:8090/consumer/doRestEchol2地址进行访问,假如访问成功会出现,如图所示效果:
Feign方式请求
添加依赖
使用feign进行服务调用时就需要添加如下依赖:
<!--当基于 feign方法进行服务调用时就需要此依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
创建远程调用接口(service层)
创建远程调用接口,接口使用@FeignClient注解。还需要在启动类上面添加@EnableFeignClients注解。
@FeignClient(name = "sca-provider",contextId = "remoteProviderService")
public interface RemoteProviderService {
//@GetMapping表示以get请求方式访问远程服务
//"/provider/echo/{msg}"为远程服务的URL
@GetMapping("/provider/echo/{msg}")
String echoMsg(@PathVariable("msg") String msg);
}
@EnableFeignClients注解说明:
@EnableFeignClients注解描述启动类时,用于告诉springboot在启动时,扫描启动类所在包或子包中的类,假如接口上有@FeignClient注解描述,则对这样的接口创建其实现类,在实现类内部帮我们进行远程服务调用。
@FeignClient注解说明:
@FeignClient注解用于描述远程调用接口,这个接口不需要你写实现类,你只需要定义访问规则即可(例如请求方式,请求URL,请求参数)。
@FeignClient 注解描述的接口的实现类对象会默认交给spring管理,这个bean对象的名字默认就是name属性指定的值,这个name还有一个层面的含义,就是你远程调用的服务名。
假如@FeignClient注解中添加了contextId属性,则这个属性值默认会作为当前bean对象的名字,此时name的值仅仅作为要调用的服务名对待,一般推荐contextId的值默认为@FeignClient注解描述的接口的名字(首字母小写)。
创建业务控制层
@RestController
@RequestMapping("/consumer/")
public class FeignConsumerController {
@Autowired
private RemoteProviderService remoteProviderService;
@Value("${server.port}")
private String server;
//基于feign方式的服务调用
@GetMapping("/echo/{msg}")
public String doFeignEcho(@PathVariable("msg") String msg){
System.out.println(server);
//基于feign方式进行远端调用服务(前提是服务必须存在)
return remoteProviderService.echoMsg(msg+"www");
}
}
访问异常处理
如超时连接、服务提供方断开等现象,用户提供地址访问的时候则会出现500等访问出错提示,影响用户体验。试验方式:我们在服务提供者的方法里面设置了睡眠时间3s.
修改application.yml配置文件
方式1:在application.yml配置文件中设置建立连接超时时间和读取响应资源超时时间.设置的时间需>睡眠时间,注意不要等于(亲测很大可能同样会异常)
ribbon:
#建立连接超时时间
ConnectTimeout: 5000
#建立连接之后,读取响应资源超时时间
ReadTimeout: 5000
方式2:如果请求出错,直接采用feign方式的服务熔断处理,提示用户系统繁忙等信息,提高用户体验.
#feign方式的服务熔断处理
feign:
hystrix: #hystrix 含义是熔断(就相当于服务停止了)或降级
enabled: true
创建异常处理对象
前提是采用feign方式的服务熔断处理.
//此对象可以作为Feign方式的远程调用异常处理对象
@Component
public class ProviderFallbackFactory implements FallbackFactory<RemoteProviderService> {
//当正常远程调用的服务不可用时,系统可以调用此方法进行请求处理
@Override
public RemoteProviderService create(Throwable throwable) {
return new RemoteProviderService() {//匿名内部类
@Override
public String echoMsg(String msg) {
//通知运维人员(发短信,发邮件,电话)
return "服务维护中";
}
};
}
}
整体项目结构
Tomcat请求处理分析
Nacos配置管理
Nacos获取配置信息的方式
采用的是pull长轮询的方式,每隔30秒向nacos配置中心发起请求,如果没有获取到数据,则等待,最长等待29.5秒,因为到30秒时会有新的请求发送过来.
具体流程:客户端会轮询向服务端发出一个长连接请求,这个长连接最多30s就会超时,服务端收到客户端的请求会先判断当前是否有配置更新,有则立即返回,如果没有服务端会将这个请求拿住“hold”29.5s加入队列,最后0.5s再检查配置文件无论有没有更新都正常进行返回,但等待的29.5s期间如果有配置更新则可以提前结束并返回。
对应需要经常变化的数据一般都写到nacos配置中心,如数据库密码、端口号、日志记录等级等信息。
心跳机制
默认情况下,启动服务后,每隔5秒会向nacos发送一个"心跳包",这个心跳包中包含了当前服务的基本信息。
Nacos收到这个"心跳包"如果发现这个服务的信息不在注册列表中,就进行注册,如果这个服务的信息在注册列表中就表明这个服务还是健康的
如果Nacos15秒内没接收到某个服务的心跳包,Nacos会将这个服务标记为不健康的状态
如果30秒内没有接收到这个服务的心跳包,Nacos会将这个服务从注册列表中剔除
这些时间都是可以通过配置修改的
修改application.yml配置文件名称和内容
将application.yml文件名更改为bootstrap.yml.文件的调用顺序的等级为bootstrap.yml>bootstrap.properties>application.yml>application.properties.
注意:如果bootstrap.yml配置文件与nacos配置中心配置了相同内容,则配置中心会覆盖掉配置文件的内容.因为服务启动的时候会先执行配置文件的内容,然后再执行nacos配置中心的内容,后者会覆盖前者.
nacos配置中心文件的配置管理模型应用由namespace,group,service/dataId组成.
- Namespace:命名空间,对不同的环境进⾏隔离,⽐如隔离开发环境和⽣产环境。
- Group:分组,将若⼲个服务或者若⼲个配置集归为⼀组。
- Service/DataId:某⼀个服务或配置集,一般对应一个配置文件。
server:
port: 8082
spring:
application:
name: sca-provider #服务名
cloud:
nacos:
config: #配置中心的配置
server-addr: localhost:8848
#namespace: 362c931e-5b01-4c6f-9423-550195659965 #没有指定namespace的话则默认使用public
file-extension: yml
#group: DEV_GROUP_1111 #没有指定group的话则默认使用DEFAULT_GROUP
discovery: #注册中心
server-addr: localhost:8848 #服务注册和发现(默认是注册到这里)
#经常变化的数据一般都写到配置中心
logging:
level:
com.jt: error
Nacos新建配置
在配置中心设置日志记录等级为warn,bootstrap.yml配置文件的日志记录等级为error,前者会覆盖后者.
查看配置
测试日志记录等级
添加日志打印相关包,日志规范是SLF4J,创建日志打印对象.
//创建java中的日志对象(slf4j是java中的日志规范,是日志对外的窗口,是门面)
//目前市场上对slf4j规范的实现主要有两种:log4j,logback
private static final Logger log =
LoggerFactory.getLogger(ProviderController.class);//成员变量
创建测试方法,输出日志等级.日志的级别是trace<debug<info<warn<error.
@Value("${logging.level.com.jt:trace}")//取不到值则使用默认值trace
private String logLevel;//修改配置中心的等级,此属性值不会改变,因为重新初始化属性需要重新创建对象
@GetMapping(value = "/provider/doGetLogLevel")
public String doGetLogLevel(){
//日志级别trace<debug<info<warn<error
log.trace("--trace--");
log.debug("--debug--");
log.info("--info--");
log.warn("--warn--");
log.error("--error--");
return "log level is "+logLevel;
}
启动服务,地址栏输入地址http://127.0.0.1:8082/provider/doGetLogLevel.查看结果
由测试结果可以看出,虽然bootstrap.yml配置文件设置了日志等级为error,但输出的是warn,即再次说明nacos配置中心会覆盖配置文件内容.
查看控制台打印结果
可以看到,比warn等级高的也打印出来了,说明日志记录会保留比设置的日志等级高的日志.
修改配置中心的日志等级为info,不用再次启动服务,刷新上面网页,查看结果.
刷新网页,查看结果
可以看到网页内容并没有修改,我们再看一下控制台的结果.
可以看到控制台的结果已经有了改变,说明我们不用重新启动服务也可以对配置文件内容进行修改.
但是为什么网页内容没有改变呢?这是因为重新初始化属性需要重新创建对象,如果想要获取修改之后的属性,需要在本类上添加一个注解@RefreshScope.
@RefreshScope 这个注解描述类时,当配置中心数据发生变化会对属性进行重新初始化
添加注解后再次重复以上测试,则可以看到修改之后的结果.
新建命名空间完成配置中心配置测试
创建nacos配置文件,文件内容如下:
查看结果
修改bootstrap.yml配置文件内容
server:
port: 8082
tomcat:
threads:
max: 300
spring:
application:
name: sca-provider #服务名
cloud:
nacos:
config: #配置中心的配置
server-addr: localhost:8848
namespace: 362c931e-5b01-4c6f-9423-550195659965
file-extension: yml
group: DEV_GROUP_1111
discovery: #注册中心
server-addr: localhost:8848 #服务注册和发现(默认是注册到这里)
#经常变化的数据一般都写到配置中心
logging:
level:
com.jt: error
创建测试方法,输出最大线程数和核心线程数.
@Value("${server.tomcat.threads.max:300}")
private Integer maxThread;
@Value("${server.tomcat.threads.min-spare:4}")
private Integer minSpare;
@GetMapping("/provider/doGetMaxThread")
public String doGetMaxThread(){
return "server.threads.max is "+maxThread+" minSpare "+minSpare;
}
暂时不重启服务,地址栏输入地址:http://127.0.0.1:8082/provider/doGetMaxThread,查看结果
可以看到得到的结果是我们设置的默认值,因为我们没有重新启动服务,所以拿不到真实的结果.
重启服务后,查看结果
可以看到,已经拿到nacos配置中心的内容,并且也覆盖掉bootstrap.yml配置文件定义的内容.