赞
踩
我们知道,除了代码之外,软件还有一些配置信息,比如数据库的用户名和密码,还有一些我们不想写死在代码里的东西,例如像线程池大小、队列长度等运行参数,以及日志级别、算法策略等, 还有一些是软件运行环境的参数,如Java 的内存大小,应用启动的参数,包括操作系统的一些 参数配置…… 所有这些东西,我们都叫做软件配置。以前,我们把软件配置写在一个配置文件中,就像 Windows 下的 ini 文件,或是 Linux 下的 conf 文件。然而,在分布式系统下,这样的方式就变得非常不好管理,并容易出错。假如生产环境下,项目现在正在运行,此时修改了配置文件,我们需要让这些配置生效,通常的做法是不是要重启服务。但重启是不是会带来系统服务短时间的暂停,从而影响用户体验呢,还有可能会带来经济上的很大损失(例如双11重启下服务)。基于这样的背景,配置中心诞生了。
配置中心最基础的功能就是存储一个键值对,用户发布一个配置(configKey),然后客户端获取这个配置项(configValue);进阶的功能就是当某个配置项发生变更时,不停机就可以动态刷新服务内部的配置项,例如,在生产环境上我们可能把我们的日志级别调整为 error 级别,但是,在系统出问题我们希望对它 debug 的时候,我们需要动态的调整系统的行为的能力,把日志级别调整为 debug 级别。还有,当你设计一个电商系统时,设计大促预案一定会考虑,同时涌进来超过一亿人并发访问的时候,假如系统是扛不住的,你会怎么办,在这个过程中我们一般会采用限流,降级。系统的限流和降级本质上来讲就是从日常的运行态切换到大促态的一个行为的动态调整,这个本身天然就是配置起到作用的一个相应的场景。
在面向分布式的微服务系统中,如何通过更高效的配置管理方式,实现微服务系统架构持续“无痛”的演进,并动态调整和控制系统的运行时态,配置中心的选型和设计起着举足轻重的作用。市场上主流配置中心有Apollo(携程开源),nacos(阿里开源),Spring Cloud Config(Spring Cloud 全家桶成员)。我们在对这些配置中心进行选型时重点要从产品功能、使用体验、实施过程和性能等方面进行综合考量。本次课程我们选择nacos,此组件不仅提供了注册中心,还具备配置中心的功能
在sca-provider项目中添加一个Controller对象,例如ProviderLogController,基于此Controller中的方法演示日志级别的配置。
第一步:创建ProviderLogController对象,例如:
package com.jt.provider.controller; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j //lombok中的这个注解用于告诉lombok,为我们的类中创建一个log = org.slf4j.Logger的对象 @RestController public class ProviderLogController { /* slf4j是基于门面模式设计的一套API,(Java中的日志API规范,基于这个规范有Log4J,Logback等日志库) import org.slf4j.Logger; import org.slf4j.LoggerFactory; 构建日志对象 记住:以后只要Java中使用日志对象,你就采用下面之中方式创建即可 假如在log对象所在的类上使用了@Slf4j注解,log不再需要我们手动创建,lombok会帮我们创建*/ // private static Logger log = LoggerFactory.getLogger("com.jt.provider.controller.ProviderLogController"); // private static final Logger log = LoggerFactory.getLogger(ProviderLogController.class); //http://localhost:8081/provider/log/doLog01 @GetMapping("/provider/log/doLog01") public String doLog01(){ //trace<debug<info<warn<error log.trace("==trace=="); log.debug("==debug=="); log.info("==info=="); log.warn("==warn=="); log.error("==error=="); return "log config test"; } }
补充点:控制台默认是info级别的,如果想要在本地修改,可在配置文件application.yml中修改,如下:
#服务的端口 server: port: 8081 #服务名 spring: application: name: sca-provider #服务注册地址(服务启动时向这个位置发送心跳包,一般是每隔5秒发送一次) #服务端在15秒内每隔5秒检测一次心跳,如果15秒内没有心跳,将当前服务健康实例数变为0; #再过15秒还没有检测到,那么nacos会将当前服务移除 cloud: nacos: discovery: server-addr: localhost:8848 #将来nacos会在一台独立的机器上进行部署 logging: level: com.jt: debug
第二步:在已有的sca-provider项目中添加如配置依赖,例如:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
第三步: 将项目sca-provider的application.yml的名字修改为bootstrap.yml(启动优先级最高),并添加配置中心配置,代码如下:
#服务的端口 server: port: 8081 #服务名 spring: application: name: sca-provider #服务注册地址(服务启动时向这个位置发送心跳包,一般是每隔5秒发送一次) #服务端在15秒内每隔5秒检测一次心跳,如果15秒内没有心跳,将当前服务健康实例数变为0; #再过15秒还没有检测到,那么nacos会将当前服务移除 cloud: nacos: discovery: server-addr: localhost:8848 #将来nacos会在一台独立的机器上进行部署 config: server-addr: localhost:8848 file-extension: yml #当配置文件和配置中心同时写了日志输出记录时,配置文件的不生效, #因为启动时是先读取配置文件,后加载配置中心,配置中心的将配置文件的配置覆盖了 #logging: # level: # com.jt: debug
打开nacos配置中心,新建配置,如图所示:
其中,Data ID的值要与bootstrap.yml中定义的spring.application.name的值相同(服务名-假如有多个服务一般会创建多个配置实例,不同服务对应不同的配置实例)。配置发布以后,会在配置列表中,显示我们的配置,例如:
配置创建好以后,启动sca-provider服务,然后打开浏览器,输入http://localhost:8081/provider/log/doLog01,检测idea控制台日志输出。然后再打开nacos控制台动态更新日志级别,再访问资源并检测后台日志输出.
对于nacos配置中心而言,有系统内部对配置变化的感知,还有外部系统对配置的感知,假如我们系统在浏览器中能看到日志级别的变化,该如何实现呢?我们现在来实现一个案例.
第一步:在ProviderLogController类的上面添加一个@RefreshScope注解:
其中,@RefreshScope的作用是在配置中心的相关配置发生变化以后,能够及时看到类中属性值的更新(底层是通过重新创建Controller对象的方式,对属性进行了重新初始化)。
第二步:添加ProviderLogController中添加一个获取日志级别(debug<info<warn<error)的的属性和方法,代码如下:
package com.jt.provider.controller; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j //lombok中的这个注解用于告诉lombok,为我们的类中创建一个log = org.slf4j.Logger的对象 @RestController @RefreshScope//此注解描述类时,用于告诉系统底层,当配置中心内容发生变化时候,此对象要重新创建,重新初始话属性 public class ProviderLogController { /* slf4j是基于门面模式设计的一套API,(Java中的日志API规范,基于这个规范有Log4J,Logback等日志库) import org.slf4j.Logger; import org.slf4j.LoggerFactory; 构建日志对象 记住:以后只要Java中使用日志对象,你就采用下面之中方式创建即可 假如在log对象所在的类上使用了@Slf4j注解,log不再需要我们手动创建,lombok会帮我们创建*/ // private static Logger log = LoggerFactory.getLogger("com.jt.provider.controller.ProviderLogController"); // private static final Logger log = LoggerFactory.getLogger(ProviderLogController.class); //http://localhost:8081/provider/log/doLog01 @GetMapping("/provider/log/doLog01") public String doLog01(){ //trace<debug<info<warn<error log.trace("==trace=="); log.debug("==debug=="); log.info("==info=="); log.warn("==warn=="); log.error("==error=="); return "log config test"; } /* 如果没有@RefreshScope注解时候,只会拿到第一次创建对象的配置信息 配置中心更新配置信息后,此属性的值还是更新前的*/ @Value("${logging.level.com.jt:debug}") private String logLevel; @GetMapping("/provider/log/doGetLogLevel") public String doGetLogLevel(){ log.info("log level is {}",logLevel);//内部感知 return "log level is "+logLevel;//外部感知 } }
第三步:启动sca-provider服务,然后打开浏览器并输入http://localhost:8081/provider/log/doGetLogLevel进行访问测试。
说明,假如对配置的信息访问不到,请检测项目配置文件的名字是否为bootstrap.yml,检查配置文件中spring.application.name属性的值是否与配置中心的data-id名相同,还有你读取的配置信息缩进以及空格写的格式是否正确.
Nacos 配置管理模型由三部分构成,如图所示:
其中:
Nacos中的命名空间一般用于配置隔离,这种命名空间的定义一般会按照环境(开发,生产等环境)进行设计和实现.我们默认创建的配置都存储到了public命名空间,如图所示:
创建新的开发环境并定义其配置,然后从开发环境的配置中读取配置信息,该如何实现呢?
第一步:创建新命名空间,如图所示:
命名空间成功创建以后,会在如下列表进行呈现。
在指定命名空间下添加配置,也可以直接取配置列表中克隆,例如:
克隆成功以后,我们会发现在指定的命名空间中有了我们克隆的配置,如图所示:
此时我们修改dev命名空间中Data Id的sca-provider配置,如图所示:
修改项目module中的配置文件bootstrap.yml,添加如下配置,关键代码如下:
#服务的端口 server: port: 8081 #服务名 spring: application: name: sca-provider #服务注册地址(服务启动时向这个位置发送心跳包,一般是每隔5秒发送一次) #服务端在15秒内每隔5秒检测一次心跳,如果15秒内没有心跳,将当前服务健康实例数变为0; #再过15秒还没有检测到,那么nacos会将当前服务移除 cloud: nacos: discovery: server-addr: localhost:8848 #将来nacos会在一台独立的机器上进行部署 config: server-addr: localhost:8848 file-extension: yml namespace: f9b86daa-ae97-41b9-9b70-7f0f6db4656f #不写的话,默认是public #当配置文件和配置中心同时写了日志输出记录时,配置文件的不生效, #因为启动时是先读取配置文件,后加载配置中心,配置中心的将配置文件的配置覆盖了 #logging: # level: # com.jt: debug
其中,namespace后面的字符串为命名空间的id,可直接从命名空间列表中进行拷贝.然后重启服务,继续刷新http://localhost:8081/provider/log/doGetLogLevel地址。检测输出,看看输出的内容是什么,是否为dev命名空间下配置的内容。
当我们在指定命名空间下,按环境或服务做好了配置以后,有时还需要基于服务做分组配置,例如,一个服务在不同时间节点(节假日,活动等)切换不同的配置,可以在新建配置时指定分组名称,如图所示:
其中,这里的useLocalCache为自己定义的配置值,表示是否使用本地缓存.
配置发布以后,修改boostrap.yml配置类,在其内部指定我们刚刚创建的分组,代码如下:
#服务的端口 server: port: 8081 #服务名 spring: application: name: sca-provider #服务注册地址(服务启动时向这个位置发送心跳包,一般是每隔5秒发送一次) #服务端在15秒内每隔5秒检测一次心跳,如果15秒内没有心跳,将当前服务健康实例数变为0; #再过15秒还没有检测到,那么nacos会将当前服务移除 cloud: nacos: discovery: server-addr: localhost:8848 #将来nacos会在一台独立的机器上进行部署 config: server-addr: localhost:8848 file-extension: yml namespace: f9b86daa-ae97-41b9-9b70-7f0f6db4656f #不写的话,默认是public group: DEFAULT_GROUP_51 #当配置文件和配置中心同时写了日志输出记录时,配置文件的不生效, #因为启动时是先读取配置文件,后加载配置中心,配置中心的将配置文件的配置覆盖了 #logging: # level: # com.jt: debug
在指定的Controller类中添加属性和方法用于获取和输出DEFAULT_GROUP_51中的useLocalCache的值,代码如下:
package com.jt.provider.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RefreshScope public class ProviderCacheController { @Value("${useLocalCache:false}") private boolean useLocalCache; @RequestMapping("/provider/cache01") public String doUseLocalCache01() { return "useLocalCache'value is " + useLocalCache; } }
然后重启服务,进行访问测试,检测内容输出。
当同一个namespace的多个配置文件中都有相同配置时,可以对这些配置进行提取,然后存储到nacos配置中心的一个或多个指定配置文件,哪个微服务需要,就在服务的配置中设置读取即可。例如:
第一步:在nacos中创建一个共享配置文件,例如:
其中,这里的secret可以理解为一个密钥。
第二步:在指定的微服务配置文件(bootstrap.yml)中设置对共享配置文件的读取,例如:
#服务的端口 server: port: 8081 #服务名 spring: application: name: sca-provider #服务注册地址(服务启动时向这个位置发送心跳包,一般是每隔5秒发送一次) #服务端在15秒内每隔5秒检测一次心跳,如果15秒内没有心跳,将当前服务健康实例数变为0; #再过15秒还没有检测到,那么nacos会将当前服务移除 cloud: nacos: discovery: server-addr: localhost:8848 #将来nacos会在一台独立的机器上进行部署 config: server-addr: localhost:8848 # 配置中心文件扩展名 file-extension: yml #sca-provider.yml # 命名空间 namespace: f9b86daa-ae97-41b9-9b70-7f0f6db4656f #不写的话,默认是public # 分组名 group: DEFAULT_GROUP_51 #不写的话,默认是DEFAULT_GROUP # 共享配置 shared-configs[0]: data-id: app-public.yml refresh: true #默认false,共享配置更新,引用此配置的地方是否要更新 #当配置文件和配置中心同时写了日志输出记录时,配置文件的不生效, #因为启动时是先读取配置文件,后加载配置中心,配置中心的将配置文件的配置覆盖了 #logging: # level: # com.jt: debug
第三步:在指定的Controller类中读取和应用共享配置即可,例如:
package com.jt.provider.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RefreshScope @RestController public class ProviderSecretController { @Value("${app.secret:123456}") private String secret; @GetMapping("/provider/secret") public String doGetSecret(){ // return "The Secret is "+secret; return String.format("The Secret is %s", secret); } }
第四步:启动服务,然后打开浏览器进行访问测试。
个人笔记:
补充点:@RefreshScope必须和refresh: true共同配合才会实现感知共享更新后的数据,不然没有注解的话,一直使用刚开始创建创建对象的属性,或者就是新创建对象但是本地配置文件不更新,一致还是启动时候的数据
单独的refresh: true,配置中心更新数据后,其实本地已经更新好了,并且应用在了程序中,但是因为当前感知内没有@RefreshScope去重新创建对象获取新值,所以回显的还是以前的值.
前提条件:确保你本地配置文件选择的配置中心的配置有useLocalCache这个配置
package com.jt.provider.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @RestController @RefreshScope public class ProviderCacheController { @Value("${useLocalCache:false}") private boolean useLocalCache; @RequestMapping("/provider/cache01") public String doUseLocalCache01() { return "useLocalCache'value is " + useLocalCache; } /* * CopyOnWriteArrayList<>()基于乐观锁去实现并发处理,vector是悲观锁 * 在你操作之前把值拿出来,然后准备把原有的值改为新值,但是在改为新值之前会比较之前拿出来的值 * 和集合中现有的值比较,如果相同,说明没人更新,则执行更新,否则不执行更新,应对高并发 * */ /* * 定义一个cache,我们要将从数据库中取到的数据存储到cache中,下次取数据从cache中取 * 1.要保证数据的正确,线程安全 * 2.要保证效率 * */ /* * 补充点:在本次测试方法过程中,既然前面已经加锁了,后面的CopyOnWriteArrayList就可以换成ArrayList * 但是建议不要换,因为将来在实际工作中,可能不止一个方法在调用这个cache,有可能有update数据的方法,这个时候 * 同时有查询缓存和更新缓存或者清空缓存的操作存在,所以还是需要这种线程安全的集合的 * */ private List<String> cache = new CopyOnWriteArrayList<>(); @RequestMapping("/provider/cache02") public List<String> doUseLocalCache02() { //1.配置中心useLocalCache未开启(值为false),则直接从数据库中取数据 if (!useLocalCache) { System.out.println("本地缓存未打开,所以查询数据库"); List<String> list = Arrays.asList("A", "B", "C");//假设这是从数据库中取到的数据 return list; } //2.配置中心useLocalCache开启了(值为true),则执行如下步骤 //2.1.从cache中取数据,如果有则直接返回 //双重校验机制 if (cache.isEmpty()) { synchronized (this) { if (cache.isEmpty()) { System.out.println("本地缓存打开,但没有数据,所以查询数据库"); //查数据库(模拟),并将查询到的数据添加到缓存中 cache.addAll(Arrays.asList("A", "B", "C")); return cache; } } } //2.2.cache中没有数据,则从数据库中取数据,并将数据存到cache中,然后返回数据 return cache; } }
package com.jt.common.net; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class Tomcat { public static void main(String[] args) throws IOException { //1.构建一个Java中的服务端对象(ServerSocket),并在只当端口上进行监听(例如9999) ServerSocket server= new ServerSocket(9999); System.out.println("server start success"); //2.等待客户端的连接,处理客户端的请求 while (true){ //等待客户端(Socket)的连接,accept方法用于接收客户端请求 Socket client = server.accept();//阻塞式方法(没有连接请求,线程休眠,所以会让出CPU) //通过浏览器访问服务端,会经历三次握手,客户端hello,服务端ok,客户端又发送一次请求;所以在启动后第一次访问会打印两次 System.out.println("client"+client); //获取输出流对象,基于此对象向客户端响应hello client OutputStream outputStream = client.getOutputStream(); //\n\r表示换行回车 outputStream.write(("HTTP/1.1 200 OK \n\r"+//响应行 "Content-Type: text/html;charset=utf-8 \n\r"+//响应头 "\n\r"+//空行 "hello client"/*响应体*/).getBytes()); outputStream.flush(); } } }
什么是配置中心?(存储项目配置信息的一个服务,这个服务可以实现配置的动态发布和更新)
为什么要使用配置中心?(集中管理配置信息,动态发布配置信息,服务自动感知配置)
市场上有哪些主流的配置中心?(Apollo,nacos,……)
配置中心一般都会配置什么内容?(可能会经常变化的配置信息,例如连接池,日志、线程池、限流熔断规则)
什么信息一般不会写到配置中心?(服务端口,服务名,服务的注册地址,配置中心地址)
项目中为什么要定义bootstrap.yml文件?(此文件被读取的优先级比较高,可以在服务启动时读取配置中心的数据)
Nacos配置中心宕机了,我们的服务还可以读取到配置信息吗?(可以从服务的本地内存读取,但是有时间限制)
微服务应用中客户端如何感知配置中心数据变化?(1.4.x版本的nacos客户端会基于长轮询机制(每隔30秒发一次请求)从nacos获取配置信息(来更新本地的配置信息))当客户端访问nacos时,假如没有更新时,请求会等待,29.5s.
服务启动后没有从配置中心获取我们的配置数据是什么原因?(依赖,bootstrap.yml,配置单词,格式,配置模型)
你项目中使用的日志规范是什么?(SLF4J~门面模式)
你了解项目中的日志级别吗?(debug,info,warn,error可以基于日志级别控制日志的输出)
Nacos配置管理模型的背景?(环境不同配置不同)
Nacos配置中的管理模型是怎样的?(namespace>group>service/data-id)
Nacos客户端(微服务)是否可以读取共享配置?(可以)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。