目录
首先我们要知道Nacos版本与Spring-Cloud-Alibaba对应的版本以及Spring-Clound对应的版本,如果版本对应的不一致的话,有时候会导致不必要的麻烦
这里我们采用的2.0.3的版本,下载后解压缩后三步走就可以使用。
1.打开nacos-》conf-》nacos-mysql.sql文件将这个文件导入我们的数据库里面。
2. 打开nacos-》conf-》application.properties进入将33 36 39 40 41 44 45 46 47行解开应用,在39 40 41行
db.url.0=jdbc:mysql://本地数据库的名字:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=数据库账号
db.password.0=数据库密码
3. 打开nacos-》bin-》startup.cmd右键打开查看里面的配置
这个是打开的方式,我们将他修改为单机模式,就可以双击运行了
standalone:单机模式
cluster:集群模式
运行成功界面
在浏览器输入localhost:8848/nacos进入nacos界面,账号密码都是:nacos
创建idea的Spring Initializr选择
创建成功后进入pom文件修改版本,将画住的地方修改。
修改配置文件,我使用的.yml样式
- server:
- port: 8081
- spring:
- application:
- name: nacos-client-a
- cloud:
- nacos:
- discovery:
- server-addr: localhost:8848
- username: nacos
- password: nacos
在启动类上面加入一个注解@EnableDiscoveryClient ,就可以启动了
server:
port: 8081 端口号
spring:
application:
name: nacos-client-a 服务注册的名字
cloud:
nacos:
discovery:
server-addr: localhost:8848 nacos的地址
username: nacos nacos账号
password: nacos nacos密码
完成上面的启动,我们重新创建一个一摸一样,我们添加一个controller
- @RestController
- public class AController {
- //打招呼的接口
- @GetMapping("hello")
- public String hello(String name) {
- return "hello" + name;
- }
- }
运行就完成了服务注册,我们可以在网址上查看一下时候运行成功。
网址 localhost:端口号/hello?name
重复上面的步骤到添加一个controller,这里要注意的是配置文件里面的端口号要修改,不然会存在端口号被占用。
- @RestController
- public class BController {
- // 注册服务发现组件
- @Autowired
- private DiscoveryClient discoveryClient;
- //服务发现接口
- @GetMapping("discovery")
- public String dicoveryService(String serviceId){
- //根据实例名称拿到实例集合
- List
instances = discoveryClient.getInstances(serviceId); - //根据实例集合拿到一个实例对象
- ServiceInstance serviceInstance = instances.get(0);
- System.out.println(serviceInstance.getHost()+":"+serviceInstance.getPort());
- return serviceInstance.getHost()+":"+serviceInstance.getPort();
- }
- }
运行就可以完成服务发现,我们可以在网址上查看一下时候运行成功。
网址 localhost:端口号/discovery?serviceId=服务注册的名字
nacos-》conf-》application.properties ,创建三个nacos修改21行分别是8848,8849,8850
nacos-》conf-》新建一个文件cluster.conf,记事本打开加入
端口号:8848
端口号:8849
端口号:8850
nacos-》bin-》startup.cmd修改为集群模式
连着启动三个,就会打开集群模式
创建完成后第一步就是去修改对应的版本,加入一个lombok依赖
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- <version>1.18.24version>
- dependency>
我们打开配置会发现多了一个配置
bootstrap是提前加载,可以看出图标有一个云是加载云上的配置,也就是加载nacos的配置
- server:
- port: 8888
- spring:
- application:
- name: nacos-config
- #项目启动时去哪里找他的配置文件
- cloud:
- nacos:
- config:
- server-addr: localhost:8848
- username: root
- password: root
- #这里开始就是开始寻找他的配置文件
- prefix: nacos-config #读那个配置文件
- file-extension: yml #文件类型,不写默认properties
- # namespace: #不写默认public
- # group: #不写默认DEFAULT_GROUP
-
在Nacos里面建立一个配置文件
建立一个controller
- @RestController
- public class NacosConfigConfiguration {
- @Value("${hero.name}")
- private String name;
- @Value("${hero.age}")
- private Integer age;
- @Value("${hero.address}")
- private String address;
-
- @GetMapping("info")
- public String info(){
- return name+":"+age+":"+address;
- }
- }
运行,只要是不报错就是调用成功,进入网址确认
网址 localhost:端口号/info
当我们修改Nacos里面的配置文件,对应调用配置文件的项目如何进行同步更新。
如果是项目下线重新运行的话,一个两个还可以,如果是成百上千个工作量就大的离谱。这时候我们就需要一种修改配置文件的时候,项目里面的文件也能同步更新的方法。
优化上面的代码
新建一个实体类Hero
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- @Component
- @RefreshScope //给这个类 添加一个刷新的作用域
- public class Hero {
- @Value("${hero.name}")
- private String name;
- @Value("${hero.age}")
- private Integer age;
- @Value("${hero.address}")
- private String address;
-
- }
@RefreshScope 加入这个注解就可以实时进行刷新
对应的controller也要进行优化
- @RestController
- public class NacosConfigConfiguration {
- @Autowired
- private Hero hero;
-
- @GetMapping("info")
- public String info() {
-
- return hero.getName() + ":" + hero.getAge() + ":" + hero.getAddress();
- }
- }
Nacos的隔离级别有两种一种是命名空间一种是Group
新建命名空间
命名空间ID如果不写就会自动生成一个uuid
命名空间别名就是起一个名字
描述 就是描述命名空间
Nacos创建新的配置,这里的配置的后缀得加上时什么文件,不然会扫描不上
在配置类加入
profiles:
active:后缀名
- server:
- port: 8082
- spring:
- application:
- name: nacos-client-b
- cloud:
- nacos:
- discovery:
- server-addr: localhost:8848
- username: nacos
- password: nacos
- prefix: nacos-config
- file-extension: yml
- profiles:
- active: dev
比如订单系统,用户要下订单,但是订单的相关代码在user-service里面没有,在order-service里面有,这时候我们就要使用user-service去调用order-service里面接口来完成用户下单的操作。
建立一个user
按照上面的操作修改pom文件里面的版本
建立起yml配置文件
- server:
- port: 9001
- spring:
- application:
- name: nacos-openfeign-user
- cloud:
- nacos:
- discovery:
- server-addr: localhost:8848
- username: nacos
- password: nacos
建立controller
- @RestController
- public class UserController {
-
- @GetMapping("user")
- public String user(){
- return "这是user类";
- }
-
- }
启动类加入Nacos的启动注解
@SpringBootApplication
@EnableDiscoveryClient
public class NacosOpenfeignUserApplication {
public static void main(String[] args) {
SpringApplication.run(NacosOpenfeignUserApplication.class, args);
}
}
建立一个order,选中openfeign
修改pom的文件的版本
加入yml配置信息
- server:
- port: 9002
- spring:
- application:
- name: nacos-openfeign-service
- cloud:
- nacos:
- discovery:
- server-addr: localhost:8848
- username: nacos
- password: nacos
根据openfeign的简介我们可以看出,openfeign建立主要分为三步
1.声明性(注解)web服务客户端
加入启动类注解
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class NacosOpenfeignServiceApplication { public static void main(String[] args) { SpringApplication.run(NacosOpenfeignServiceApplication.class, args); } }@EnableFeignClients:开启feign的客户端功能 才可以帮助我们发起调用
2.要使用Feign,创立一个接口并对其进行注解
建立一个feign包,里面创建feign类 注:起名规范:比如是user调用order类的起名为UserOrderFeign
建立feign类
@FeignClient(value = "nacos-openfeign-user") public interface UserOrderOpenfeign { @GetMapping("user") public String user(); }@FeignClient(value = "nacos-openfeign-user") :这是feign注解
nacos-openfeign-user:这是提供者提供的nacos的服务名
提供者:就是被调用的一方;
@GetMapping("user")
public String user();
这是一个完整的方法签名
方法签名就是一个方法的全部——修饰符 返回值 方法名以及方法的注解
3.建立controller进行调用
@RestController public class ServiceController { @Autowired private UserOrderOpenfeign userOrderOpenfeign; @GetMapping("service") public String service() { String user = userOrderOpenfeign.user(); return user; } }
两个都得运行,首先得运行user,然后运行service,进入网址确认
网址:localhost:9002/service
如果提供者延时了两秒就会报错,因为feign的默认等待时间是1S;我们可以使用睡眠让user睡眠两秒,在用网址测的时候就会发现报500错误,在控制台上显示连接超时;
解决方法是加入配置信息,由于feign只是帮你封装了远程调用的功能,底层还是ribbon,所以超时的配置还是ribbon的配置
ribbon: ReadTimeout: 3000 ConnectTimeout: 3000ReadTimeout: 3000 :3S超时时间
ConnectTimeout: 3000 :链接服务的超时时间
-
- @SpringBootTest
- class ApplicationTests {
- @Autowired
- private RestTemplate restTemplate;
-
- /**
- * 手写feign的核心步骤
- */
- @Test
- void contextLoads() {
- UserOrderFeign o = (UserOrderFeign) Proxy.newProxyInstance(UserController.class.getClassLoader(), new Class[]{UserOrderFeign.class}, new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // 能去拿到对方的ip和port 并且拿到这个方法上面的注解里面的值 那么就完事了
- GetMapping annotation = method.getAnnotation(GetMapping.class);
- String[] paths = annotation.value();
- String path = paths[0];
- Class> aClass = method.getDeclaringClass();
- FeignClient annotation1 = aClass.getAnnotation(FeignClient.class);
- String applicationName = annotation1.value();
- String url = "http://" + applicationName + "/" + path;
- String forObject = restTemplate.getForObject(url, String.class);
- return forObject;
- }
- });
- String s = o.doOrder();
- System.out.println(s);
- }
-
- }
提供端建立实体类
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- @Builder
- public class Order {
-
- private Integer id;
- private String name;
- private Double price;
- private Date time;
-
- }
提供端controller
-
- /**
- * url /doOrder/热干面/add/油条/aaa
- * get传递一个参数
- * get传递多个参数
- * post传递一个对象
- * post传递一个对象+一个基本参数
- */
- @RestController
- public class ParamController {
-
- @GetMapping("testUrl/{name}/and/{age}")
- public String testUrl(@PathVariable("name") String name, @PathVariable("age") Integer age) {
- System.out.println(name + ":" + age);
- return "ok";
- }
-
- @GetMapping("oneParam")
- public String oneParam(@RequestParam(required = false) String name) {
- System.out.println(name);
- return "ok";
- }
-
-
- @GetMapping("twoParam")
- public String twoParam(@RequestParam(required = false) String name, @RequestParam(required = false) Integer age) {
- System.out.println(name);
- System.out.println(age);
- return "ok";
- }
-
- @PostMapping("oneObj")
- public String oneObj(@RequestBody Order order) {
- System.out.println(order);
- return "ok";
- }
-
-
- @PostMapping("oneObjOneParam")
- public String oneObjOneParam(@RequestBody Order order,@RequestParam("name") String name) {
- System.out.println(name);
- System.out.println(order);
- return "ok";
- }
-
-
- // 单独传递时间对象
-
- @GetMapping("testTime")
- public String testTime(@RequestParam Date date){
- System.out.println(date);
- return "ok";
- }
- }
接收端feign类里面的接口
-
- /**
- * @FeignClient(value = "order-service")
- * value 就是提供者的应用名称
- */
- @FeignClient(value = "order-service")
- public interface UserOrderFeign {
-
- /**
- * 你需要调用哪个controller 就写它的方法签名
- * 方法签名(就是包含一个方法的所有的属性)
- *
- * @return
- */
- @GetMapping("doOrder")
- String doOrder();
-
- @GetMapping("testUrl/{name}/and/{age}")
- public String testUrl(@PathVariable("name") String name, @PathVariable("age") Integer age);
-
- @GetMapping("oneParam")
- public String oneParam(@RequestParam(required = false) String name);
-
- @GetMapping("twoParam")
- public String twoParam(@RequestParam(required = false) String name, @RequestParam(required = false) Integer age);
-
- @PostMapping("oneObj")
- public String oneObj(@RequestBody Order order);
-
-
- @PostMapping("oneObjOneParam")
- public String oneObjOneParam(@RequestBody Order order, @RequestParam("name") String name);
-
-
-
- @GetMapping("testTime")
- public String testTime(@RequestParam Date date);
- }
接收端的实体类
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- @Builder
- public class Order {
-
- private Integer id;
- private String name;
- private Double price;
- private Date time;
-
- }
接收端controller
-
- @RestController
- public class UserController {
-
- /**
- * 接口是不能做事情的
- * 如果想做事 必须要有对象
- * 那么这个接口肯定是被创建出代理对象的
- * 动态代理 jdk(java interface 接口 $Proxy ) cglib(subClass 子类)
- * jdk动态代理 只要是代理对象调用的方法必须走 java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
- */
- @Autowired
- public UserOrderFeign userOrderFeign;
-
- /**
- * 总结
- * 浏览器(前端)-------> user-service(/userDoOrder)-----RPC(feign)--->order-service(/doOrder)
- * feign的默认等待时间时1s
- * 超过1s就在直接报错超时
- *
- * @return
- */
- @GetMapping("userDoOrder")
- public String userDoOrder() {
- System.out.println("有用户进来了");
- // 这里需要发起远程调用
- String s = userOrderFeign.doOrder();
- return s;
- }
-
-
-
- @GetMapping("testParam")
- public String testParam(){
- String cxs = userOrderFeign.testUrl("cxs", 18);
- System.out.println(cxs);
-
- String t = userOrderFeign.oneParam("老唐");
- System.out.println(t);
-
- String lg = userOrderFeign.twoParam("雷哥", 31);
- System.out.println(lg);
-
- Order order = Order.builder()
- .name("牛排")
- .price(188D)
- .time(new Date())
- .id(1)
- .build();
-
- String s = userOrderFeign.oneObj(order);
- System.out.println(s);
-
- String param = userOrderFeign.oneObjOneParam(order, "稽哥");
- System.out.println(param);
- return "ok";
- }
-
- /**
- * Sun Mar 20 10:24:13 CST 2022
- * Mon Mar 21 00:24:13 CST 2022 +- 14个小时
- * 1.不建议单独传递时间参数
- * 2.转成字符串 2022-03-20 10:25:55:213 因为字符串不会改变
- * 3.jdk LocalDate 年月日 LocalDateTime 会丢失s
- * 4.改feign的源码
- *
- * @return
- */
- @GetMapping("time")
- public String time(){
- Date date = new Date();
- System.out.println(date);
- String s = userOrderFeign.testTime(date);
-
- LocalDate now = LocalDate.now();
- LocalDateTime now1 = LocalDateTime.now();
-
- return s;
- }
-
-
-
- }
服务A调用服务B,服务B调用服务C,服务C挂掉了,服务A和服务B一直在等待服务C。占据线程不放掉。就是用熔断器。
调用的一方加入依赖
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
- dependency>
在feign包里面建立hystrix包,新建一个类继承feign接口
- @Component
- public class ConsumerProviderHystrix implements ConsumerProviderFeign {
- @Override
- public String provider() {
- return "null";
- }
- }
在feign接口的注解里面加入fallback指向熔断后的路径也就是刚才建立的hystrix包里新建的类
@FeignClient(value = "hystrix-provinder", fallback =ConsumerProviderHystrix.class)
public interface ConsumerProviderFeign {
@GetMapping("provider")
public String provider();
}
启动类加入hystrix注解 @EnableHystrix
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
public class HystrixConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixConsumerApplication.class, args);
}
}
在配置中打开hystrix,默认是关闭的
server: port: 9022 spring: application: name: hystrix-consumer cloud: nacos: discovery: username: nacos password: nacos server-addr: localhost:8848 feign: hystrix: enabled: true
拦截器状态有三种
关:默认情况下,断路器是关闭的,正常使用
开:当在一个时间窗口内(10S)访问失败的次数到达一个阈值,断路器开
半开:让少许流量去尝试调用,如果正常了,就关掉断路器
hystrix: #hystrix的全局控制
command:
default: #default是全局控制,也可以换成单个方法控制,把default换成方法名即可
circuitBreaker:
enabled: true #开启断路器
requestVolumeThreshold: 3 #失败次数(阀值) 10次
sleepWindowInMilliseconds: 20000 #窗口时间
errorThresholdPercentage: 60 #失败率
execution:
isolation:
Strategy: thread #隔离方式 thread线程隔离集合和semaphore信号量隔离级别
thread:
timeoutInMilliseconds: 3000 #调用超时时长
fallback:
isolation:
semaphore:
maxConcurrentRequests: 1000 #信号量隔离级别最大并发数
ribbon:
ReadTimeout: 5000 #要结合feign的底层ribbon调用的时长
ConnectTimeout: 5000
#隔离方式 两种隔离方式 thread线程池 按照group(10个线程)划分服务提供者,用户请求的线程和做远程的线程不一样
# 好处 当B服务调用失败了 或者请求B服务的量太大了 不会对C服务造成影响 用户访问比较大的情况下使用比较好 异步的方式
# 缺点 线程间切换开销大,对机器性能影响
# 应用场景 调用第三方服务 并发量大的情况下
# SEMAPHORE信号量隔离 每次请进来 有一个原子计数器 做请求次数的++ 当请求完成以后 --
# 好处 对cpu开销小
# 缺点 并发请求不易太多 当请求过多 就会拒绝请求 做一个保护机制
# 场景 使用内部调用 ,并发小的情况下
# 源码入门 HystrixCommand AbstractCommand HystrixThreadPool