Nacos
Nacos的安装
在父工程中加载springcloudalibaba依赖用来管理版本信息
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2.2.5.RELEASE</version><type>pom</type><scope>import</scope></dependency>
在子工程中添加nacos的依赖:
<!-- Nacos客户端依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
更改Nacos的注册端口:
在yml中添加如下配置:
spring: cloud:nacos:server-addr: localhost:8848 #nacos服务地址
就可以完成Nacos注册
Nacos集群属性
集群是指一个服务中运行的多个端口集合在一起构成一个集群,例如将userservice的8080和8081端口设置在上海集群,再将8083和8084设置在杭州集群等。
集群的配置(修改yml文件):
spring:cloud:nacos:discovery: cluster-name: XX # 集群名称(自定义)
设置负载均衡规则(对于配置的写法与Eureka一样,但传入的包名不同):
userservice:ribbon:NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #定义针对于userservice的负载均衡规则
注意这种情况下负载均衡策略为优先在和自己同集群的端口中发送请求,若找不到再去其他端口找,但这样子会在控制台提出一个非同集群的警告
可以在Nacos的控制台中点击Edit来编辑权重信息,权重数值在0-1之间,对应的比例代表着其被随机访问的概率的比例,例如一个1一个0.1,被访问的概率之比就是10比1
将服务的权重调整为0则代表不会有服务进行访问,这种情况适用于服务器升级维护。
环境隔离(命名空间)
在Nacos服务器的命名空间内容中加载新的命名空间,右上角创建命名空间,填写名称和描述,Id自动生成。
在yml配置文件中进行命名空间的配置:
spring:cloud:nacos:discovery:namespace: d9e54854-e561-4b09-ad00-4624f5b6397d #创建的命名空间的Id
处在不同命名空间的服务不能相互访问
Nacos原理
服务消费者会定时从Nacos服务器中拉取服务列表并缓存到Dynamic中。
服务提供者:
- 临时实例会心跳检测传递给Nacos,若检测到其无法访问则会直接将其从服务列表中删除(慢)
- 永久实例则会由Nacos定时进行主动询问,若其无法访问则会将其标注为不健康
另外,若Nacos检测到服务信息有服务无法访问则会立刻将这个信息推送给服务消费者(快)
在yml文件中进行是否为临时实例的配置:
spring:cloud:nacos:discovery:ephemeral: false #配置为非临时实例,默认为临时实例
Nacos VS Eureka
同:
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康监测、
异:
- Nacos支持服务端主动对服务提供者进行检测,对于临时实例采用心跳检测,对于非临时实例则进行主动监测
- 临时实力不正常会被剔除,而非临时实例不正常只会被标记
- Nacos支持服务列表变更的消息推送模式,服务列表更新及时
- Nacos集群默认采用AP
Nacos实现配置管理
配置管理需要实现热更新、及时读取的需求,使用Nacos配置管理进行处理:
在Nacos服务器的配置中心、配置管理中添加:
配置id为:服务名称-dev.yaml:userservice-dev.yaml
勾选对应的格式(yaml):
编写配置文件(Nacos中只需要配置有可能会经常改变的代码,不经常改变的代码不需要部署到Nacos中):
Nacos配置的实现
添加依赖:
<!-- Nacos配置管理依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>
创建bootstrap.yml文件(bootstrap.yml文件的读取优先级要高于application.yml):
因为bootstrap的优先级较高,所以在bootstrap定义过的配置就不需要再application中配置了
spring:application:name: userserviceprofiles:active: devcloud:nacos:server-addr: localhost:8848config:file-extension: yaml
Nacos配置实现热更新
@Refresh方式实现热更新
使用@Value和@RefreshScope方式实现热更新
在Controller类上添加@RefreshScope注解
@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
热更新原理:会对配置文件进行检测,如果检测发现配置文件有变化则会重新读取配置文件并执行相应的变化。
测试配置获取(在UserController中进行):
@Value("${pattern.dateformat}")private String dateformat;@GetMapping("now")public String now() {return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));}
使用@ConfigurationProperties注解实现热更新
创建配置类(config/PatternProperties):
@ConfigurationProperties(prefix = "pattern") //获取配置,获取配置文件中的属性,所有以prefix为前缀,以变量名为后缀的配置都会被获取给对应的属性
@Component //注入为Java的Bean
@Data
public class PatternProperties {private String dateformat; //获取到pattern.dateformat属性
}
在Controller中通过获取容器中的类进行配置的使用。
@Autowiredpublic PatternProperties patternProperties;@GetMapping("now")public String now() {return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));}
多环境配置共享
有些配置信息在许多环境的搭建、生产和应用过程中都是相同的、我们没必要在所有的配置文件中都进行定义,所以可以定义一个一致的配置文件对这个信息进行定义:
微服务在启动时,Nacos会读取以下两个配置:userservice-dev.yml、userservice.yml
即:服务名-环境名.后缀
其中-dev在开发时会引入,-test在测试时会引入,而普通的userservice.yml会在任意的环境下都被读取进来,所以公共的配置文件可以写在【服务名.yml】文件中。
注意,这个文件是从Nacos服务器中配置的,且这里的Id一定要写.yaml尽量不要简写。
# 测试
pattern:envSharedValue: 多环境共享属性值
故现有的两个配置文件:userservice.yaml、userservice-dev.yaml中,-dev的配置文件只在开发文件中生效,userservice.yaml在所有的环境中生效(注意在bootstrap中的配置active:dev配置了配置文件的环境名,这个名称必须与Nacos服务器中创建的配置文件的环境名一致)。
同时,文件的环境也可以在服务器右键 -> Edit Configuration -> Active profiles中对环境进行配置:(dev(开发)、test(测试))
注意:如果在多个配置文件中都有针对于同一个属性的配置,那么他们的优先级:服务名-profile.yaml > 服务名.yaml > 本地配置
Nacos集群搭建
Nacos在实际应用中都是有多个结点的,这个结点的集合称作Nacos集群,而具体的服务要选取其中之一进行请求处理(负载均衡),这里的负载均衡一般由Nginx实现,而众多的Nacos结点共同访问一整个Mysql集群,这样来实现Nacos的集群,以提高系统的并发响应能力。
由于只有一台电脑,所以集群的ip地址相同,但是具有不同的端口号
Nacos配置
配置Nacos节点的IP地址:
-
打开文件:Nacos -> conf -> cluster.conf.example(将其重命名,去掉.example)并编辑(空文件夹、加入如下例配置,这个ip地址必须是你Nacos配置的Ip地址,这里的192.168.0.108是主机的地址,相当于127.0.0.1但是不要写127.0.0.1,将ip地址固定(网络配置中)并写固定的地址)
192.168.0.108:8845 192.168.0.108:8846 192.168.0.108:8847
-
编辑conf文件夹下的application.properties
-
修改端口为三个端口之一并且在后边的复制时将三个不同的Nacos的端口号对应修改
-
打开spring.datasource.platform属性,以标注数据库
-
打开db.num属性以标注数据库的数量
-
打开db.url、db.user.0、db.password.0以标注数据库的连接信息以及账号密码
-
-
将nacos重命名为三个结点名并进行复制,移动到需要的文件夹位置。
-
集群启动,在bin文件夹下使用cmd命令:
startup.cmd
注意之前带有-m的命令为单点启动的命令 -
配置Nginx,进入Nginx的conf文件夹内并编辑Nigix.conf
-
在http标签内进行如下定义:
upstream nacos-cluster标签定义了nacos的ip地址入口
server标签定义了监听的端口号(以80开头的请求都会被监听)
location /nacos定义了将请求中/nacos的请求都将其拦截,注意尽量只有一个server标签
upstream nacos-cluster {server 127.0.0.1:8845;server 127.0.0.1:8846;server 127.0.0.1:8847;}server {listen 80;server_name localhost;location /nacos {proxy_pass http://nacos-cluster;}}
-
-
在nginx-1.18.0下通过命令启动
start niginx.exe
在userservcie的bootstrap.yaml中修改端口号配置,不再直接访问Nacos的地址,而是访问Nginx负载均衡过的Nacos地址,将端口地址修改为:localhost:80
Feign
我们之前采用RestTemplate进行服务远程调用时,代码可读性很差,另外,手动插入url在url路径复杂时会十分不方便,而Feign是一种可以解决上述问题的服务远程调用方式。
Feign是一种声明式的Http客户端,可以帮助我们优雅的实现请求的调用
Feign的装配
- 添加依赖信息
这个依赖与Feign的其他配置信息都是要在发送服务调用请求的模块中进行的。例如这个例子中就是放在orderservice模块中进行的。
<!-- fegign依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
-
添加自动装配Feign的注解:
在启动类上添加注解:
@EnableFeignClients public class OrderApplication {
该注解可以完成开启Feign的AutoConfigutration的功能
-
添加Feign的接口
创建clients/UserClient接口
//FeignClient注解用来标注这是一个Feign发送请求的注解 //里面的参数代表Nacos注册中心中的服务名,代表要请求哪里的服务 //注意@FeignClient注解也有依赖注入的功能 @FeignClient("userservice") public interface UserClient {//这里方法的返回值应该是请求的返回类型@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id); }
-
测试:在Service中添加测试进行调用:
@Autowiredprivate UserClient userClient;public Order queryOrderById(Long orderId) {Order order = orderMapper.findById(orderId);//调用Feign请求并获取到需要的数据User user = userClient.findById(orderId);order.setUser(user);return order;}
Feign的配置信息
日志信息配置
在application.yml文件
` 中添加日志信息配置如下可以修改Feign的日志级别:NONE、BASIC、HEADERS、FULL
# Feign的日志级别
feign:client:config:default:logger-level: FULL
另外、也可以使用配置类的方式实现日志级别的装配,若使用配置类的方式实现日志修改,则需要在对应的Feign的Clients请求上标注注解(只在这个服务请求中生效)或者在启动类的Feign注解中添加参数(在这个模块的全局生效)
public class DefaultFeignConfiguration {@Beanpublic Logger.Level logLevel() {return Logger.Level.BASIC;}
}
局部生效注解:
//FeignClient注解用来标注这是一个Feign发送请求的注解
//里面的参数代表Nacos注册中心中的服务名,代表要请求哪里的服务
//configuration中标注了日志级别信息
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class)
public interface UserClient {//这里方法的返回值应该是请求的返回类型@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id);
}
全模块生效注解:
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
//全局生效注解
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);}
Feign的性能优化
Feign的性能优化主要在于两个方面:
-
Http请求的发送方式
在Feign中,发送Http请求的默认方式为URLConnection方式,而这种方式不支持连接池,如果能够使用连接池会有更好的性能表现(Apache HttpClient、OKHttp)
-
降低日志级别
Headers以及Full的日志级别会使客户端生产出更多日志(进行更多监听)故降低日志级别以提升系统整体性能。
将Feign默认Http请求发送方式更改为Apache HttpClient方式:
添加HttpClient依赖:
<!-- HttpClient依赖--><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId></dependency>
在配置文件中打开HttpClient(默认为true,但需要引入依赖)
#打开Feign的HttpClient
feign:httpclient:enabled: truemax-connections: 200 #连接池最大连接数max-connections-per-route: 50 #单个模块的最大连接数
同时,连接池中最大连接数以及单个模块的最大连接数都需要使用JMeter进行压测之后找到最合适的数值
Feign的实践
Fegin有两种实践方式:
- 创建一个API并令服务提供者和Client接口都继承于这个API,这样的问题是会极大的提高耦合度,但这样开发也可以大大简化Feign接口以及Controller类的开发,是一种可以选择的方案
- 创建一个Feign-api模块对Feign进行统一管理,将POJO信息、Feign信息(clients)、配置信息全部放在这个模块中,在其他模块中引入这个模块的依赖,并配置好包的引入,令Feign在一个模块中统一实现,统一管理,但这样的问题在于依赖必须全部引入进模块中,这是不必要的。
Feign的统一管理方式实现:
引入Feign依赖
创建一个Feign-api模块,并将Clients包、config包以及实体类(pojo文件夹下)导入到这个包中,已导入的模块可以删除。
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
向需要Feign的地方引入自己创建的API
<!-- 引入自己创建的API--><dependency><groupId>cn.itcast.demo</groupId><artifactId>feign-api</artifactId><version>1.0</version></dependency>
之后将需要的类进行导入后,还要将Feign的导入加入到OrderService中,其有两种实现方式:
-
将整个Feign的包进行指定,但这样会将所有的Feign组件全部导入,会导入不需要的类
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
-
指定FeignClient的字节码,这样就只会导入需要的Feign(建议方式)
@EnableFeignClients(clients = {UserClient.class})
实践效果:
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class, clients = {UserClient.class})
public class OrderApplication {