[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"chapter-book:Rv8JoXm5":3,"chapter:E784VKZw":62},{"id":4,"created_at":5,"updated_at":5,"code":6,"author_id":7,"title":8,"slug":9,"cover_theme_key":10,"visibility":11,"status":12,"sort_order":4,"favorite_count":13,"view_count":7,"author":14,"chapter_count":26,"word_count":27,"free_count":13,"is_favorite":23,"book_status":12,"unparted":28},6,"2024-01-19T04:32:30.187+08:00","Rv8JoXm5",1,"Spring Cloud微服务","spring-cloud微服务-6","default","public","published",0,{"id":7,"created_at":15,"updated_at":16,"code":17,"username":18,"display_name":19,"role":20,"avatar_url":21,"is_active":22,"is_banned":23,"follower_count":13,"following_count":24,"last_login_at":25},"2023-11-30T23:53:12.952+08:00","2026-10-20T16:16:54.352+08:00","Mg87g8Gk","1701359592888","大苹果","verified_author","\u002Fcuts\u002F17021220625513129.png",true,false,3,"2026-10-20T16:12:35+08:00",4,78396,[29,38,46,54],{"id":30,"created_at":31,"updated_at":32,"code":33,"author_id":7,"book_id":4,"slug":34,"article_id":35,"title":36,"sort_order":30,"content":37,"status":12},25,"2024-01-19T04:32:46.608+08:00","2024-01-19T14:25:46.04+08:00","jMKe26zn","spring-cloud微服务的使用与nacos注册中心-25",173,"Spring Cloud微服务的使用与nacos注册中心","# Spring Cloud微服务\n\n# 1.认识微服务\n\n随着互联网行业的发展，对服务的要求也越来越高，服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢？\n\n## 1.0.学习目标\n\n了解微服务架构的优缺点\n\n## 1.1.单体架构\n\n**单体架构**：将业务的所有功能集中在一个项目中开发，打成一个包部署。\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639016624_image.png)\n\n单体架构的优缺点如下：\n\n**优点：**\n\n- 架构简单\n- 部署成本低\n\n**缺点：**\n\n- 耦合度高（维护困难、升级困难）\n\n## 1.2.分布式架构\n\n**分布式架构**：根据业务功能对系统做拆分，每个业务功能模块作为独立项目开发，称为一个服务。\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639064696_image.png)\n\n分布式架构的优缺点：\n\n**优点：**\n\n- 降低服务耦合\n- 有利于服务升级和拓展\n\n**缺点：**\n\n- 服务调用关系错综复杂\n\n分布式架构虽然降低了服务耦合，但是服务拆分时也有很多问题需要思考：\n\n- 服务拆分的粒度如何界定？\n- 服务之间如何调用？\n- 服务的调用关系如何管理？\n\n人们需要制定一套行之有效的标准来约束分布式架构。\n\n## 1.3.微服务\n\n微服务的架构特征：\n\n- 单一职责：微服务拆分粒度更小，每一个服务都对应唯一的业务能力，做到单一职责\n- 自治：团队独立、技术独立、数据独立，独立部署和交付\n- 面向服务：服务提供统一标准的接口，与语言和技术无关\n- 隔离性强：服务调用做好隔离、容错、降级，避免出现级联问题\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639080159_image.png)\n\n微服务的上述特性其实是在给分布式架构制定一个标准，进一步降低服务之间的耦合度，提供服务的独立性和灵活性。做到高内聚，低耦合。\n\n因此，可以认为**微服务**是一种经过良好架构设计的**分布式架构方案** 。\n\n但方案该怎么落地？选用什么样的技术栈？全球的互联网公司都在积极尝试自己的微服务落地方案。\n\n其中在Java领域最引人注目的就是SpringCloud提供的方案了。\n\n## 1.4.SpringCloud\n\nSpringCloud是目前国内使用最广泛的微服务框架。官网地址：https:\u002F\u002Fspring.io\u002Fprojects\u002Fspring-cloud。\n\nSpringCloud集成了各种微服务功能组件，并基于SpringBoot实现了这些组件的自动装配，从而提供了良好的开箱即用体验。\n\n其中常见的组件包括：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639120992_image.png)\n\n另外，SpringCloud底层是依赖于SpringBoot的，并且有版本的兼容关系，如下：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639139150_image.png)\n\n我们课堂学习的版本是 Hoxton.SR10，因此对应的SpringBoot版本是2.3.x版本。\n\n## 1.5.总结\n\n- 单体架构：简单方便，高度耦合，扩展性差，适合小型项目。例如：学生管理系统\n- 分布式架构：松耦合，扩展性好，但架构复杂，难度大。适合大型互联网项目，例如：京东、淘宝\n- 微服务：一种良好的分布式架构方案\n\n  ①优点：拆分粒度更小、服务更独立、耦合度更低\n\n  ②缺点：架构非常复杂，运维、监控、部署难度提高\n- SpringCloud是微服务架构的一站式解决方案，集成了各种优秀微服务功能组件\n\n# 2.服务拆分和远程调用\n\n任何分布式架构都离不开服务的拆分，微服务也是一样。\n\n## 2.1.服务拆分原则\n\n这里我总结了微服务拆分时的几个原则：\n\n- 不同微服务，不要重复开发相同业务\n- 微服务数据独立，不要访问其它微服务的数据库\n- 微服务可以将自己的业务暴露为接口，供其它微服务调用\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639146808_image.png)\n\n## 2.2.服务拆分示例\n\n以课前资料中的微服务cloud-demo为例，其结构如下：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639154005_image.png)\n\ncloud-demo：父工程，管理依赖\n\n- order-service：订单微服务，负责订单相关业务\n- user-service：用户微服务，负责用户相关业务\n\n要求：\n\n- 订单微服务和用户微服务都必须有各自的数据库，相互独立\n- 订单服务和用户服务都对外暴露Restful的接口\n- 订单服务如果需要查询用户信息，只能调用用户服务的Restful接口，不能查询用户数据库\n\n### 2.2.1.导入Sql语句\n\n首先，将课前资料提供的`cloud-order.sql`和`cloud-user.sql`导入到mysql中：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639171827_image.png)\n\ncloud-user表中初始数据如下：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639183893_image.png)\n\ncloud-order表中初始数据如下：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639190049_image.png)\n\ncloud-order表中持有cloud-user表中的id字段。\n\n### 2.2.2.导入demo工程\n\n用IDEA导入课前资料提供的Demo：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639205431_image.png)\n\n项目结构如下：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639213400_image.png)\n\n导入后，会在IDEA右下角出现弹窗：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639228295_image.png)\n\n点击弹窗，然后按下图选择：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639235003_image.png)\n\n会出现这样的菜单：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639241833_image.png)\n\n配置下项目使用的JDK：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639248264_image.png)\n\n## 2.3.实现远程调用案例\n\n在order-service服务中，有一个根据id查询订单的接口：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639255381_image.png)\n\n根据id查询订单，返回值是Order对象，如图：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639262309_image.png)\n\n其中的user为null\n\n在user-service中有一个根据id查询用户的接口：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639269564_image.png)\n\n查询的结果如图：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639278765_image.png)\n\n### 2.3.1.案例需求：\n\n修改order-service中的根据id查询订单业务，要求在查询订单的同时，根据订单中包含的userId查询出用户信息，一起返回。\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639286335_image.png)\n\n因此，我们需要在order-service中 向user-service发起一个http的请求，调用http:\u002F\u002Flocalhost:8081\u002Fuser\u002F{userId}这个接口。\n\n大概的步骤是这样的：\n\n- 注册一个RestTemplate的实例到Spring容器\n- 修改order-service服务中的OrderService类中的queryOrderById方法，根据Order对象中的userId查询User\n- 将查询的User填充到Order对象，一起返回\n\n### 2.3.2.注册RestTemplate\n\n首先，我们在order-service服务中的OrderApplication启动类中，注册RestTemplate实例：\n\n```java\npackage cn.itcast.order;\n\nimport org.mybatis.spring.annotation.MapperScan;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.web.client.RestTemplate;\n\n@MapperScan(\"cn.itcast.order.mapper\")\n@SpringBootApplication\npublic class OrderApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(OrderApplication.class, args);\n    }\n\n    @Bean\n    public RestTemplate restTemplate() {\n        return new RestTemplate();\n    }\n}\n```\n\n### 2.3.3.实现远程调用\n\n修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639297951_image.png)\n\n## 2.4.提供者与消费者\n\n在服务调用关系中，会有两个不同的角色：\n\n**服务提供者**：一次业务中，被其它微服务调用的服务。（提供接口给其它微服务）\n\n**服务消费者**：一次业务中，调用其它微服务的服务。（调用其它微服务提供的接口）\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639307403_image.png)\n\n但是，服务提供者与服务消费者的角色并不是绝对的，而是相对于业务而言。\n\n如果服务A调用了服务B，而服务B又调用了服务C，服务B的角色是什么？\n\n- 对于A调用B的业务而言：A是服务消费者，B是服务提供者\n- 对于B调用C的业务而言：B是服务消费者，C是服务提供者\n\n因此，服务B既可以是服务提供者，也可以是服务消费者。\n\n# 3.Eureka注册中心\n\n假如我们的服务提供者user-service部署了多个实例，如图：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639314523_image.png)\n\n大家思考几个问题：\n\n- order-service在发起远程调用的时候，该如何得知user-service实例的ip地址和端口？\n- 有多个user-service实例地址，order-service调用时该如何选择？\n- order-service如何得知某个user-service实例是否依然健康，是不是已经宕机？\n\n## 3.1.Eureka的结构和作用\n\n这些问题都需要利用SpringCloud中的注册中心来解决，其中最广为人知的注册中心就是Eureka，其结构如下：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639334426_image.png)\n\n回答之前的各个问题。\n\n问题1：order-service如何得知user-service实例地址？\n\n获取地址信息的流程如下：\n\n- user-service服务实例启动后，将自己的信息注册到eureka-server（Eureka服务端）。这个叫服务注册\n- eureka-server保存服务名称到服务实例地址列表的映射关系\n- order-service根据服务名称，拉取实例地址列表。这个叫服务发现或服务拉取\n\n问题2：order-service如何从多个user-service实例中选择具体的实例？\n\n- order-service从实例列表中利用负载均衡算法选中一个实例地址\n- 向该实例地址发起远程调用\n\n问题3：order-service如何得知某个user-service实例是否依然健康，是不是已经宕机？\n\n- user-service会每隔一段时间（默认30秒）向eureka-server发起请求，报告自己状态，称为心跳\n- 当超过一定时间没有发送心跳时，eureka-server会认为微服务实例故障，将该实例从服务列表中剔除\n- order-service拉取服务时，就能将故障实例排除了\n\n> 注意：一个微服务，既可以是服务提供者，又可以是服务消费者，因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端\n\n因此，接下来我们动手实践的步骤包括：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639347192_image.png)\n\n## 3.2.搭建eureka-server\n\n首先大家注册中心服务端：eureka-server，这必须是一个独立的微服务\n\n### 3.2.1.创建eureka-server服务\n\n在cloud-demo父工程下，创建一个子模块：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639357505_image.png)\n\n填写模块信息：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639371533_image.png)\n\n然后填写服务信息：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639384691_image.png)\n\n### 3.2.2.引入eureka依赖\n\n引入SpringCloud为eureka提供的starter依赖：\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>org.springframework.cloud\u003C\u002FgroupId>\n    \u003CartifactId>spring-cloud-starter-netflix-eureka-server\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n```\n\n### 3.2.3.编写启动类\n\n给eureka-server服务编写一个启动类，一定要添加一个@EnableEurekaServer注解，开启eureka的注册中心功能：\n\n```java\npackage cn.itcast.eureka;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;\n\n@SpringBootApplication\n@EnableEurekaServer\npublic class EurekaApplication {\n    public static void main(String[] args) {\n        SpringApplication.run(EurekaApplication.class, args);\n    }\n}\n```\n\n### 3.2.4.编写配置文件\n\n编写一个application.yml文件，内容如下：\n\n```yaml\nserver:\n  port: 10086\nspring:\n  application:\n    name: eureka-server\neureka:\n  client:\n    service-url: \n      defaultZone: http:\u002F\u002F127.0.0.1:10086\u002Feureka\n```\n\n### 3.2.5.启动服务\n\n启动微服务，然后在浏览器访问：http:\u002F\u002F127.0.0.1:10086\n\n看到下面结果应该是成功了：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639453289_image.png)\n\n## 3.3.服务注册\n\n下面，我们将user-service注册到eureka-server中去。\n\n### 1）引入依赖\n\n在user-service的pom文件中，引入下面的eureka-client依赖：\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>org.springframework.cloud\u003C\u002FgroupId>\n    \u003CartifactId>spring-cloud-starter-netflix-eureka-client\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n```\n\n### 2）配置文件\n\n在user-service中，修改application.yml文件，添加服务名称、eureka地址：\n\n```yaml\nspring:\n  application:\n    name: userservice\neureka:\n  client:\n    service-url:\n      defaultZone: http:\u002F\u002F127.0.0.1:10086\u002Feureka\n```\n\n### 3）启动多个user-service实例\n\n为了演示一个服务有多个实例的场景，我们添加一个SpringBoot的启动配置，再启动一个user-service。\n\n首先，复制原来的user-service启动配置：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639463548_image.png)\n\n然后，在弹出的窗口中，填写信息：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639473249_image.png)\n\n现在，SpringBoot窗口会出现两个user-service启动配置：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639484673_image.png)\n\n不过，第一个是8081端口，第二个是8082端口。\n\n启动两个user-service实例：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639498575_image.png)\n\n查看eureka-server管理页面：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639510157_image.png)\n\n## 3.4.服务发现\n\n下面，我们将order-service的逻辑修改：向eureka-server拉取user-service的信息，实现服务发现。\n\n### 1）引入依赖\n\n之前说过，服务发现、服务注册统一都封装在eureka-client依赖，因此这一步与服务注册时一致。\n\n在order-service的pom文件中，引入下面的eureka-client依赖：\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>org.springframework.cloud\u003C\u002FgroupId>\n    \u003CartifactId>spring-cloud-starter-netflix-eureka-client\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n```\n\n### 2）配置文件\n\n服务发现也需要知道eureka地址，因此第二步与服务注册一致，都是配置eureka信息：\n\n在order-service中，修改application.yml文件，添加服务名称、eureka地址：\n\n```yaml\nspring:\n  application:\n    name: orderservice\neureka:\n  client:\n    service-url:\n      defaultZone: http:\u002F\u002F127.0.0.1:10086\u002Feureka\n```\n\n### 3）服务拉取和负载均衡\n\n最后，我们要去eureka-server中拉取user-service服务的实例列表，并且实现负载均衡。\n\n不过这些动作不用我们去做，只需要添加一些注解即可。\n\n在order-service的OrderApplication中，给RestTemplate这个Bean添加一个@LoadBalanced注解：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639521507_image.png)\n\n修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法。修改访问的url路径，用服务名代替ip、端口：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639531402_image.png)\n\nspring会自动帮助我们从eureka-server端，根据userservice这个服务名称，获取实例列表，而后完成负载均衡。\n\n# 4.Ribbon负载均衡\n\n上一节中，我们添加了@LoadBalanced注解，即可实现负载均衡功能，这是什么原理呢？\n\n## 4.1.负载均衡原理\n\nSpringCloud底层其实是利用了一个名为Ribbon的组件，来实现负载均衡功能的。\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639540398_image.png)\n\n那么我们发出的请求明明是http:\u002F\u002Fuserservice\u002Fuser\u002F1，怎么变成了http:\u002F\u002Flocalhost:8081的呢？\n\n## 4.2.源码跟踪\n\n为什么我们只输入了service名称就可以访问了呢？之前还要获取ip和端口。\n\n显然有人帮我们根据service名称，获取到了服务实例的ip和端口。它就是`LoadBalancerInterceptor`，这个类会在对RestTemplate的请求进行拦截，然后从Eureka根据服务id获取服务列表，随后利用负载均衡算法得到真实的服务地址信息，替换服务id。\n\n我们进行源码跟踪：\n\n### 1）LoadBalancerIntercepor\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639550586_image.png)\n\n可以看到这里的intercept方法，拦截了用户的HttpRequest请求，然后做了几件事：\n\n- `request.getURI()`：获取请求uri，本例中就是 http:\u002F\u002Fuser-service\u002Fuser\u002F8\n- `originalUri.getHost()`：获取uri路径的主机名，其实就是服务id，`user-service`\n- `this.loadBalancer.execute()`：处理服务id，和用户请求。\n\n这里的`this.loadBalancer`是`LoadBalancerClient`类型，我们继续跟入。\n\n### 2）LoadBalancerClient\n\n继续跟入execute方法：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639558385_image.png)\n\n代码是这样的：\n\n- getLoadBalancer(serviceId)：根据服务id获取ILoadBalancer，而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。\n- getServer(loadBalancer)：利用内置的负载均衡算法，从服务列表中选择一个。本例中，可以看到获取了8082端口的服务\n\n放行后，再次访问并跟踪，发现获取的是8081：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639572401_image.png)\n\n果然实现了负载均衡。\n\n### 3）负载均衡策略IRule\n\n在刚才的代码中，可以看到获取服务使通过一个`getServer`方法来做负载均衡:\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639600821_image.png)\n\n我们继续跟入：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639610923_image.png)\n\n继续跟踪源码chooseServer方法，发现这么一段代码：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639637603_image.png)\n\n我们看看这个rule是谁：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639663008_image.png)\n\n这里的rule默认值是一个`RoundRobinRule`，看类的介绍：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639673300_image.png)\n\n这不就是轮询的意思嘛。\n\n到这里，整个负载均衡的流程我们就清楚了。\n\n### 4）总结\n\nSpringCloudRibbon的底层采用了一个拦截器，拦截了RestTemplate发出的请求，对地址做了修改。用一幅图来总结一下：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639681674_image.png)\n\n基本流程如下：\n\n- 拦截我们的RestTemplate请求http:\u002F\u002Fuserservice\u002Fuser\u002F1\n- RibbonLoadBalancerClient会从请求url中获取服务名称，也就是user-service\n- DynamicServerListLoadBalancer根据user-service到eureka拉取服务列表\n- eureka返回列表，localhost:8081、localhost:8082\n- IRule利用内置负载均衡规则，从列表中选择一个，例如localhost:8081\n- RibbonLoadBalancerClient修改请求地址，用localhost:8081替代userservice，得到http:\u002F\u002Flocalhost:8081\u002Fuser\u002F1，发起真实请求\n\n## 4.3.负载均衡策略\n\n### 4.3.1.负载均衡策略\n\n负载均衡的规则都定义在IRule接口中，而IRule有很多不同的实现类：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639692267_image.png)\n\n不同规则的含义如下：\n\n\n| **内置负载均衡规则类**    | **规则描述**                                                                                                                                                                                                                                                                                                                                                                    |\n| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| RoundRobinRule            | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。                                                                                                                                                                                                                                                                                                                    |\n| AvailabilityFilteringRule | 对以下两种服务器进行忽略：   （1）在默认情况下，这台服务器如果3次连接失败，这台服务器就会被设置为“短路”状态。短路状态将持续30秒，如果再次连接失败，短路的持续时间就会几何级地增加。  （2）并发数过高的服务器。如果一个服务器的并发连接数过高，配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限，可以由客户端的..ActiveConnectionsLimit属性进行配置。 |\n| WeightedResponseTimeRule  | 为每一个服务器赋予一个权重值。服务器响应时间越长，这个服务器的权重就越小。这个规则会随机选择服务器，这个权重值会影响服务器的选择。                                                                                                                                                                                                                                              |\n| **ZoneAvoidanceRule**     | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类，这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。                                                                                                                                                                                                                                    |\n| BestAvailableRule         | 忽略那些短路的服务器，并选择并发数较低的服务器。                                                                                                                                                                                                                                                                                                                                |\n| RandomRule                | 随机选择一个可用的服务器。                                                                                                                                                                                                                                                                                                                                                      |\n| RetryRule                 | 重试机制的选择逻辑                                                                                                                                                                                                                                                                                                                                                              |\n\n默认的实现就是ZoneAvoidanceRule，是一种轮询方案\n\n### 4.3.2.自定义负载均衡策略\n\n通过定义IRule实现可以修改负载均衡规则，有两种方式：\n\n1. 代码方式：在order-service中的OrderApplication类中，定义一个新的IRule：\n\n```java\n@Bean\npublic IRule randomRule(){\n    return new RandomRule();\n}\n```\n\n2. 配置文件方式：在order-service的application.yml文件中，添加新的配置也可以修改规则：\n\n```yaml\nuserservice: # 给某个微服务配置负载均衡规则，这里是userservice服务\n  ribbon:\n    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则\n```\n\n> **注意**，一般用默认的负载均衡规则，不做修改。\n\n## 4.4.饥饿加载\n\nRibbon默认是采用懒加载，即第一次访问时才会去创建LoadBalanceClient，请求时间会很长。\n\n而饥饿加载则会在项目启动时创建，降低第一次访问的耗时，通过下面配置开启饥饿加载：\n\n```yaml\nribbon:\n  eager-load:\n    enabled: true\n    clients: userservice\n```\n\n# 5.Nacos注册中心\n\n国内公司一般都推崇阿里巴巴的技术，比如注册中心，SpringCloudAlibaba也推出了一个名为Nacos的注册中心。\n\n## 5.1.认识和安装Nacos\n\n[Nacos](https:\u002F\u002Fnacos.io\u002F)是阿里巴巴的产品，现在是[SpringCloud](https:\u002F\u002Fspring.io\u002Fprojects\u002Fspring-cloud)中的一个组件。相比[Eureka](https:\u002F\u002Fgithub.com\u002FNetflix\u002Feureka)功能更加丰富，在国内受欢迎程度较高。\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639706980_image.png)\n\n安装方式可以参考课前资料《Nacos安装指南.md》\n\n## 5.2.服务注册到nacos\n\nNacos是SpringCloudAlibaba的组件，而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说，并没有太大区别。\n\n主要差异在于：\n\n- 依赖不同\n- 服务地址不同\n\n### 1）引入依赖\n\n在cloud-demo父工程的pom文件中的`\u003CdependencyManagement>`中引入SpringCloudAlibaba的依赖：\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>com.alibaba.cloud\u003C\u002FgroupId>\n    \u003CartifactId>spring-cloud-alibaba-dependencies\u003C\u002FartifactId>\n    \u003Cversion>2.2.6.RELEASE\u003C\u002Fversion>\n    \u003Ctype>pom\u003C\u002Ftype>\n    \u003Cscope>import\u003C\u002Fscope>\n\u003C\u002Fdependency>\n```\n\n然后在user-service和order-service中的pom文件中引入nacos-discovery依赖：\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>com.alibaba.cloud\u003C\u002FgroupId>\n    \u003CartifactId>spring-cloud-starter-alibaba-nacos-discovery\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n```\n\n> **注意**：不要忘了注释掉eureka的依赖。\n\n### 2）配置nacos地址\n\n在user-service和order-service的application.yml中添加nacos地址：\n\n```yaml\nspring:\n  cloud:\n    nacos:\n      server-addr: localhost:8848\n```\n\n> **注意**：不要忘了注释掉eureka的地址\n\n### 3）重启\n\n重启微服务后，登录nacos管理页面，可以看到微服务信息：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639724323_image.png)\n\n## 5.3.服务分级存储模型\n\n一个**服务**可以有多个**实例**，例如我们的user-service，可以有:\n\n- 127.0.0.1:8081\n- 127.0.0.1:8082\n- 127.0.0.1:8083\n\n假如这些实例分布于全国各地的不同机房，例如：\n\n- 127.0.0.1:8081，在上海机房\n- 127.0.0.1:8082，在上海机房\n- 127.0.0.1:8083，在杭州机房\n\nNacos就将同一机房内的实例 划分为一个**集群**。\n\n也就是说，user-service是服务，一个服务可以包含多个集群，如杭州、上海，每个集群下可以有多个实例，形成分级模型，如图：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639732007_image.png)\n\n微服务互相访问时，应该尽可能访问同集群实例，因为本地访问速度更快。当本集群内不可用时，才访问其它集群。例如：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639739546_image.png)\n\n杭州机房内的order-service应该优先访问同机房的user-service。\n\n### 5.3.1.给user-service配置集群\n\n修改user-service的application.yml文件，添加集群配置：\n\n```yaml\nspring:\n  cloud:\n    nacos:\n      server-addr: localhost:8848\n      discovery:\n        cluster-name: HZ # 集群名称\n```\n\n重启两个user-service实例后，我们可以在nacos控制台看到下面结果：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639747387_image.png)\n\n我们再次复制一个user-service启动配置，添加属性：\n\n```sh\n-Dserver.port=8083 -Dspring.cloud.nacos.discovery.cluster-name=SH\n```\n\n配置如图所示：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639798478_image.png)\n\n启动UserApplication3后再次查看nacos控制台：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639807029_image.png)\n\n### 5.3.2.同集群优先的负载均衡\n\n默认的`ZoneAvoidanceRule`并不能实现根据同集群优先来实现负载均衡。\n\n因此Nacos中提供了一个`NacosRule`的实现，可以优先从同集群中挑选实例。\n\n1）给order-service配置集群信息\n\n修改order-service的application.yml文件，添加集群配置：\n\n```sh\nspring:\n  cloud:\n    nacos:\n      server-addr: localhost:8848\n      discovery:\n        cluster-name: HZ # 集群名称\n```\n\n2）修改负载均衡规则\n\n修改order-service的application.yml文件，修改负载均衡规则：\n\n```yaml\nuserservice:\n  ribbon:\n    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则\n```\n\n## 5.4.权重配置\n\n实际部署中会出现这样的场景：\n\n服务器设备性能有差异，部分实例所在机器性能较好，另一些较差，我们希望性能好的机器承担更多的用户请求。\n\n但默认情况下NacosRule是同集群内随机挑选，不会考虑机器的性能问题。\n\n因此，Nacos提供了权重配置来控制访问频率，权重越大则访问频率越高。\n\n在nacos控制台，找到user-service的实例列表，点击编辑，即可修改权重：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639819227_image.png)\n\n在弹出的编辑窗口，修改权重：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639827930_image.png)\n\n> **注意**：如果权重修改为0，则该实例永远不会被访问\n\n## 5.5.环境隔离\n\nNacos提供了namespace来实现环境隔离功能。\n\n- nacos中可以有多个namespace\n- namespace下可以有group、service等\n- 不同namespace之间相互隔离，例如不同namespace的服务互相不可见\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639839052_image.png)\n\n### 5.5.1.创建namespace\n\n默认情况下，所有service、data、group都在同一个namespace，名为public：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639846728_image.png)\n\n我们可以点击页面新增按钮，添加一个namespace：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639860741_image.png)\n\n然后，填写表单：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639871323_image.png)\n\n就能在页面看到一个新的namespace：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639878273_image.png)\n\n### 5.5.2.给微服务配置namespace\n\n给微服务配置namespace只能通过修改配置来实现。\n\n例如，修改order-service的application.yml文件：\n\n```yaml\nspring:\n  cloud:\n    nacos:\n      server-addr: localhost:8848\n      discovery:\n        cluster-name: HZ\n        namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间，填ID\n```\n\n重启order-service后，访问控制台，可以看到下面的结果：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639886264_image.png)\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639892490_image.png)\n\n此时访问order-service，因为namespace不同，会导致找不到userservice，控制台会报错：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639899201_image.png)\n\n## 5.6.Nacos与Eureka的区别\n\nNacos的服务实例分为两种l类型：\n\n- 临时实例：如果实例宕机超过一定时间，会从服务列表剔除，默认的类型。\n- 非临时实例：如果实例宕机，不会从服务列表剔除，也可以叫永久实例。\n\n配置一个服务实例为永久实例：\n\n```yaml\nspring:\n  cloud:\n    nacos:\n      discovery:\n        ephemeral: false # 设置为非临时实例\n```\n\nNacos和Eureka整体结构类似，服务注册、服务拉取、心跳等待，但是也存在一些差异：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705639907503_image.png)\n\n- Nacos与eureka的共同点\n\n  - 都支持服务注册和服务拉取\n  - 都支持服务提供者心跳方式做健康检测\n- Nacos与Eureka的区别\n\n  - Nacos支持服务端主动检测提供者状态：临时实例采用心跳模式，非临时实例采用主动检测模式\n  - 临时实例心跳不正常会被剔除，非临时实例则不会被剔除\n  - Nacos支持服务列表变更的消息推送模式，服务列表更新更及时\n  - Nacos集群默认采用AP方式，当集群中存在非临时实例时，采用CP模式；Eureka采用AP方式\n",{"id":39,"created_at":40,"updated_at":40,"code":41,"author_id":7,"book_id":4,"slug":42,"article_id":43,"title":44,"sort_order":39,"content":45,"status":12},26,"2024-01-19T14:12:51.776+08:00","0V6AgX2D","nacos配置管理-26",174,"Nacos配置管理","# 1.Nacos配置管理\n\nNacos除了可以做注册中心，同样可以做配置管理来使用。\n\n## 1.1.统一配置管理\n\n当微服务部署的实例越来越多，达到数十、数百时，逐个修改微服务配置就会让人抓狂，而且很容易出错。我们需要一种统一配置管理方案，可以集中管理所有实例的配置。\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673695129_image.png)\n\nNacos一方面可以将配置集中管理，另一方可以在配置变更时，及时通知微服务，实现配置的热更新。\n\n### 1.1.1.在nacos中添加配置文件\n\n如何在nacos中管理配置呢？\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673711213_image.png)\n\n然后在弹出的表单中，填写配置信息：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673718361_image.png)\n\n> 注意：项目的核心配置，需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。\n\n### 1.1.2.从微服务拉取配置\n\n微服务要拉取nacos中管理的配置，并且与本地的application.yml配置合并，才能完成项目启动。\n\n但如果尚未读取application.yml，又如何得知nacos地址呢？\n\n因此spring引入了一种新的配置文件：bootstrap.yaml文件，会在application.yml之前被读取，流程如下：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673725871_image.png)\n\n1）引入nacos-config依赖\n\n首先，在user-service服务中，引入nacos-config的客户端依赖：\n\n```xml\n\u003C!--nacos配置管理依赖-->\n\u003Cdependency>\n    \u003CgroupId>com.alibaba.cloud\u003C\u002FgroupId>\n    \u003CartifactId>spring-cloud-starter-alibaba-nacos-config\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n```\n\n2）添加bootstrap.yaml\n\n然后，在user-service中添加一个bootstrap.yaml文件，内容如下：\n\n```yaml\nspring:\n  application:\n    name: userservice # 服务名称\n  profiles:\n    active: dev #开发环境，这里是dev \n  cloud:\n    nacos:\n      server-addr: localhost:8848 # Nacos地址\n      config:\n        file-extension: yaml # 文件后缀名\n```\n\n如果有鉴权,配置如下\n\n```yaml\nspring:\n  application:\n    name: userservice # 服务名称\n  profiles:\n    active: dev #开发环境，这里是dev \n  cloud:\n    nacos:\n      server-addr: localhost:8848 # Nacos地址\n      username: nacos\n      password: nacos\n      config:\n\t\tusername: nacos\n\t\tpassword: nacos\n\t\tnamespace: \"\" # 如果为默认命名空间则填空，否则会一直获取配置，非默认则填写命名空间名字\n\t\tfile-extension: yaml # 文件后缀名\n```\n\n这里会根据spring.cloud.nacos.server-addr获取nacos地址，再根据\n\n`${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}`作为文件id，来读取配置。\n\n本例中，就是去读取`userservice-dev.yaml`：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673736542_image.png)\n\n3）读取nacos配置\n\n在user-service中的UserController中添加业务逻辑，读取pattern.dateformat配置：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673748625_image.png)\n\n完整代码：\n\n```java\npackage cn.itcast.user.web;\n\nimport cn.itcast.user.pojo.User;\nimport cn.itcast.user.service.UserService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\n\n@Slf4j\n@RestController\n@RequestMapping(\"\u002Fuser\")\npublic class UserController {\n\n    @Autowired\n    private UserService userService;\n\n    @Value(\"${pattern.dateformat}\")\n    private String dateformat;\n  \n    @GetMapping(\"now\")\n    public String now(){\n        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));\n    }\n    \u002F\u002F ...略\n}\n```\n\n在页面访问，可以看到效果：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673760630_image.png)\n\n## 1.2.配置热更新\n\n我们最终的目的，是修改nacos中的配置后，微服务中无需重启即可让配置生效，也就是**配置热更新**。\n\n要实现配置热更新，可以使用两种方式：\n\n### 1.2.1.方式一\n\n在@Value注入的变量所在类上添加注解@RefreshScope：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673774528_image.png)\n\n### 1.2.2.方式二\n\n使用@ConfigurationProperties注解代替@Value注解。\n\n在user-service服务中，添加一个类，读取patterrn.dateformat属性：\n\n```java\npackage cn.itcast.user.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.stereotype.Component;\n\n@Component\n@Data\n@ConfigurationProperties(prefix = \"pattern\")\npublic class PatternProperties {\n    private String dateformat;\n}\n```\n\n在UserController中使用这个类代替@Value：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673787692_image.png)\n\n完整代码：\n\n```java\npackage cn.itcast.user.web;\n\nimport cn.itcast.user.config.PatternProperties;\nimport cn.itcast.user.pojo.User;\nimport cn.itcast.user.service.UserService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\n\n@Slf4j\n@RestController\n@RequestMapping(\"\u002Fuser\")\npublic class UserController {\n\n    @Autowired\n    private UserService userService;\n\n    @Autowired\n    private PatternProperties patternProperties;\n\n    @GetMapping(\"now\")\n    public String now(){\n        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));\n    }\n\n    \u002F\u002F 略\n}\n```\n\n## 1.3.配置共享\n\n其实微服务启动时，会去nacos读取多个配置文件，例如：\n\n- `[spring.application.name]-[spring.profiles.active].yaml`，例如：userservice-dev.yaml\n- `[spring.application.name].yaml`，例如：userservice.yaml\n\n而`[spring.application.name].yaml`不包含环境，因此可以被多个环境共享。\n\n下面我们通过案例来测试配置共享\n\n### 1）添加一个环境共享配置\n\n我们在nacos中添加一个userservice.yaml文件：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673800354_image.png)\n\n### 2）在user-service中读取共享配置\n\n在user-service服务中，修改PatternProperties类，读取新添加的属性：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673806899_image.png)\n\n在user-service服务中，修改UserController，添加一个方法：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673817504_image.png)\n\n### 3）运行两个UserApplication，使用不同的profile\n\n修改UserApplication2这个启动项，改变其profile值：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673825514_image.png)\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673831525_image.png)\n\n这样，UserApplication(8081)使用的profile是dev，UserApplication2(8082)使用的profile是test。\n\n启动UserApplication和UserApplication2\n\n访问http:\u002F\u002Flocalhost:8081\u002Fuser\u002Fprop，结果：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673838761_image.png)\n\n访问http:\u002F\u002Flocalhost:8082\u002Fuser\u002Fprop，结果：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673847885_image.png)\n\n可以看出来，不管是dev，还是test环境，都读取到了envSharedValue这个属性的值。\n\n### 4）配置共享的优先级\n\n当nacos、服务本地同时出现相同属性时，优先级有高低之分：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705673855231_image.png)\n\n# Nacos集群搭建\n\n## 1.集群结构图\n\n官方给出的Nacos集群图：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705674714530_image.png)\n\n其中包含3个nacos节点，然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。\n\n我们计划的集群结构：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705674725884_image.png)\n\n三个nacos节点的地址：\n\n\n| 节点   | ip            | port |\n| ------ | ------------- | ---- |\n| nacos1 | 192.168.150.1 | 8845 |\n| nacos2 | 192.168.150.1 | 8846 |\n| nacos3 | 192.168.150.1 | 8847 |\n\n## 2.搭建集群\n\n搭建集群的基本步骤：\n\n- 搭建数据库，初始化数据库表结构\n- 下载nacos安装包\n- 配置nacos\n- 启动nacos集群\n- nginx反向代理\n\n### 2.1.初始化数据库\n\nNacos默认数据存储在内嵌数据库Derby中，不属于生产可用的数据库。\n\n官方推荐的最佳实践是使用带有主从的高可用数据库集群，主从模式的高可用数据库可以参考**传智教育**的后续高手课程。\n\n这里我们以单点的数据库为例来讲解。\n\n首先新建一个数据库，命名为nacos，而后导入下面的SQL：\n\n```sql\nCREATE TABLE `config_info` (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(255) DEFAULT NULL,\n  `content` longtext NOT NULL COMMENT 'content',\n  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  `src_user` text COMMENT 'source user',\n  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',\n  `app_name` varchar(128) DEFAULT NULL,\n  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',\n  `c_desc` varchar(256) DEFAULT NULL,\n  `c_use` varchar(64) DEFAULT NULL,\n  `effect` varchar(64) DEFAULT NULL,\n  `type` varchar(64) DEFAULT NULL,\n  `c_schema` text,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';\n\n\u002F******************************************\u002F\n\u002F*   数据库全名 = nacos_config   *\u002F\n\u002F*   表名称 = config_info_aggr   *\u002F\n\u002F******************************************\u002F\nCREATE TABLE `config_info_aggr` (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(255) NOT NULL COMMENT 'group_id',\n  `datum_id` varchar(255) NOT NULL COMMENT 'datum_id',\n  `content` longtext NOT NULL COMMENT '内容',\n  `gmt_modified` datetime NOT NULL COMMENT '修改时间',\n  `app_name` varchar(128) DEFAULT NULL,\n  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';\n\n\n\u002F******************************************\u002F\n\u002F*   数据库全名 = nacos_config   *\u002F\n\u002F*   表名称 = config_info_beta   *\u002F\n\u002F******************************************\u002F\nCREATE TABLE `config_info_beta` (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) NOT NULL COMMENT 'group_id',\n  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',\n  `content` longtext NOT NULL COMMENT 'content',\n  `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',\n  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  `src_user` text COMMENT 'source user',\n  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',\n  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';\n\n\u002F******************************************\u002F\n\u002F*   数据库全名 = nacos_config   *\u002F\n\u002F*   表名称 = config_info_tag   *\u002F\n\u002F******************************************\u002F\nCREATE TABLE `config_info_tag` (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) NOT NULL COMMENT 'group_id',\n  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',\n  `tag_id` varchar(128) NOT NULL COMMENT 'tag_id',\n  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',\n  `content` longtext NOT NULL COMMENT 'content',\n  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  `src_user` text COMMENT 'source user',\n  `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';\n\n\u002F******************************************\u002F\n\u002F*   数据库全名 = nacos_config   *\u002F\n\u002F*   表名称 = config_tags_relation   *\u002F\n\u002F******************************************\u002F\nCREATE TABLE `config_tags_relation` (\n  `id` bigint(20) NOT NULL COMMENT 'id',\n  `tag_name` varchar(128) NOT NULL COMMENT 'tag_name',\n  `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',\n  `data_id` varchar(255) NOT NULL COMMENT 'data_id',\n  `group_id` varchar(128) NOT NULL COMMENT 'group_id',\n  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',\n  `nid` bigint(20) NOT NULL AUTO_INCREMENT,\n  PRIMARY KEY (`nid`),\n  UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),\n  KEY `idx_tenant_id` (`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';\n\n\u002F******************************************\u002F\n\u002F*   数据库全名 = nacos_config   *\u002F\n\u002F*   表名称 = group_capacity   *\u002F\n\u002F******************************************\u002F\nCREATE TABLE `group_capacity` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',\n  `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID，空字符表示整个集群',\n  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额，0表示使用默认值',\n  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',\n  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限，单位为字节，0表示使用默认值',\n  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数，，0表示使用默认值',\n  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限，单位为字节，0表示使用默认值',\n  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_group_id` (`group_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';\n\n\u002F******************************************\u002F\n\u002F*   数据库全名 = nacos_config   *\u002F\n\u002F*   表名称 = his_config_info   *\u002F\n\u002F******************************************\u002F\nCREATE TABLE `his_config_info` (\n  `id` bigint(64) unsigned NOT NULL,\n  `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `data_id` varchar(255) NOT NULL,\n  `group_id` varchar(128) NOT NULL,\n  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',\n  `content` longtext NOT NULL,\n  `md5` varchar(32) DEFAULT NULL,\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  `src_user` text,\n  `src_ip` varchar(50) DEFAULT NULL,\n  `op_type` char(10) DEFAULT NULL,\n  `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',\n  PRIMARY KEY (`nid`),\n  KEY `idx_gmt_create` (`gmt_create`),\n  KEY `idx_gmt_modified` (`gmt_modified`),\n  KEY `idx_did` (`data_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';\n\n\n\u002F******************************************\u002F\n\u002F*   数据库全名 = nacos_config   *\u002F\n\u002F*   表名称 = tenant_capacity   *\u002F\n\u002F******************************************\u002F\nCREATE TABLE `tenant_capacity` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',\n  `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',\n  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额，0表示使用默认值',\n  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',\n  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限，单位为字节，0表示使用默认值',\n  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',\n  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限，单位为字节，0表示使用默认值',\n  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',\n  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_tenant_id` (`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';\n\n\nCREATE TABLE `tenant_info` (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',\n  `kp` varchar(128) NOT NULL COMMENT 'kp',\n  `tenant_id` varchar(128) default '' COMMENT 'tenant_id',\n  `tenant_name` varchar(128) default '' COMMENT 'tenant_name',\n  `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',\n  `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',\n  `gmt_create` bigint(20) NOT NULL COMMENT '创建时间',\n  `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),\n  KEY `idx_tenant_id` (`tenant_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';\n\nCREATE TABLE `users` (\n\t`username` varchar(50) NOT NULL PRIMARY KEY,\n\t`password` varchar(500) NOT NULL,\n\t`enabled` boolean NOT NULL\n);\n\nCREATE TABLE `roles` (\n\t`username` varchar(50) NOT NULL,\n\t`role` varchar(50) NOT NULL,\n\tUNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE\n);\n\nCREATE TABLE `permissions` (\n    `role` varchar(50) NOT NULL,\n    `resource` varchar(255) NOT NULL,\n    `action` varchar(8) NOT NULL,\n    UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE\n);\n\nINSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);\n\nINSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');\n```\n\n### 2.2.下载nacos\n\nnacos在GitHub上有下载地址：https:\u002F\u002Fgithub.com\u002Falibaba\u002Fnacos\u002Ftags，可以选择任意版本下载。\n\n本例中才用1.4.1版本：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705674754082_image.png)\n\n### 2.3.配置Nacos\n\n将这个包解压到任意非中文目录下，如图：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705674766831_image.png)\n\n目录说明：\n\n- bin：启动脚本\n- conf：配置文件\n\n进入nacos的conf目录，修改配置文件cluster.conf.example，重命名为cluster.conf：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705674778483_image.png)\n\n然后添加内容：\n\n```\n127.0.0.1:8845\n127.0.0.1.8846\n127.0.0.1.8847\n```\n\n然后修改application.properties文件，添加数据库配置\n\n```properties\nspring.datasource.platform=mysql\n\ndb.num=1\n\ndb.url.0=jdbc:mysql:\u002F\u002F127.0.0.1:3306\u002Fnacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC\ndb.user.0=root\ndb.password.0=123\n```\n\n### 2.4.启动\n\n将nacos文件夹复制三份，分别命名为：nacos1、nacos2、nacos3\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705674793238_image.png)\n\n然后分别修改三个文件夹中的application.properties，\n\nnacos1:\n\n```properties\nserver.port=8845\n```\n\nnacos2:\n\n```properties\nserver.port=8846\n```\n\nnacos3:\n\n```properties\nserver.port=8847\n```\n\n然后分别启动三个nacos节点：\n\n```\nstartup.cmd\n```\n\n### 2.5.nginx反向代理\n\n安装nginx\n\n[nginx安装笔记](http:\u002F\u002Fwww.okgoes.com\u002Fblog\u002Fdetail?blog_id=265)\n\n修改conf\u002Fnginx.conf文件，配置如下：\n\n```nginx\nupstream nacos-cluster {\n    server 127.0.0.1:8845;\n\tserver 127.0.0.1:8846;\n\tserver 127.0.0.1:8847;\n}\n\nserver {\n    listen       80;\n    server_name  localhost;\n\n    location \u002Fnacos {\n        proxy_pass http:\u002F\u002Fnacos-cluster;\n    }\n}\n```\n\n而后在浏览器访问：http:\u002F\u002Flocalhost\u002Fnacos即可。\n\n代码中application.yml文件配置如下：\n\n```yaml\nspring:\n  cloud:\n    nacos:\n      server-addr: localhost:80 # Nacos地址\n```\n\n### 2.6.优化\n\n- 实际部署时，需要给做反向代理的nginx服务器设置一个域名，这样后续如果有服务器迁移nacos的客户端也无需更改配置.\n- Nacos的各个节点应该部署到多个不同服务器，做好容灾和隔离\n",{"id":47,"created_at":48,"updated_at":48,"code":49,"author_id":7,"book_id":4,"slug":50,"article_id":51,"title":52,"sort_order":47,"content":53,"status":12},27,"2024-01-19T14:13:23.13+08:00","MN8aNX2d","feign远程调用-27",176,"Feign远程调用","# 2.Feign远程调用\n\n先来看我们以前利用RestTemplate发起远程调用的代码：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920822295_image.png)\n\n存在下面的问题：\n\n•代码可读性差，编程体验不统一\n\n•参数复杂URL难以维护\n\nFeign是一个声明式的http客户端，官方地址：https:\u002F\u002Fgithub.com\u002FOpenFeign\u002Ffeign\n\n其作用就是帮助我们优雅的实现http请求的发送，解决上面提到的问题。\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920830458_image.png)\n\n## 2.1.Feign替代RestTemplate\n\nFegin的使用步骤如下：\n\n### 1）引入依赖\n\n我们在order-service服务的pom文件中引入feign的依赖：\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>org.springframework.cloud\u003C\u002FgroupId>\n    \u003CartifactId>spring-cloud-starter-openfeign\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n```\n\n### 2）添加注解\n\n在order-service的启动类添加注解开启Feign的功能：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920838340_image.png)\n\n### 3）编写Feign的客户端\n\n在order-service中新建一个接口，内容如下：\n\n```java\npackage cn.itcast.order.client;\n\nimport cn.itcast.order.pojo.User;\nimport org.springframework.cloud.openfeign.FeignClient;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\n\n@FeignClient(\"userservice\")\npublic interface UserClient {\n    @GetMapping(\"\u002Fuser\u002F{id}\")\n    User findById(@PathVariable(\"id\") Long id);\n}\n```\n\n这个客户端主要是基于SpringMVC的注解来声明远程调用的信息，比如：\n\n- 服务名称：userservice\n- 请求方式：GET\n- 请求路径：\u002Fuser\u002F{id}\n- 请求参数：Long id\n- 返回值类型：User\n\n这样，Feign就可以帮助我们发送http请求，无需自己使用RestTemplate来发送了。\n\n### 4）测试\n\n修改order-service中的OrderService类中的queryOrderById方法，使用Feign客户端代替RestTemplate：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920846337_image.png)\n\n是不是看起来优雅多了。\n\n### 5）总结\n\n使用Feign的步骤：\n\n① 引入依赖\n\n② 添加@EnableFeignClients注解\n\n③ 编写FeignClient接口\n\n④ 使用FeignClient中定义的方法代替RestTemplate\n\n## 2.2.自定义配置\n\nFeign可以支持很多的自定义配置，如下表所示：\n\n| 类型                   | 作用             | 说明                                                   |\n| ---------------------- | ---------------- | ------------------------------------------------------ |\n| **feign.Logger.Level** | 修改日志级别     | 包含四种不同的级别：NONE、BASIC、HEADERS、FULL         |\n| feign.codec.Decoder    | 响应结果的解析器 | http远程调用的结果做解析，例如解析json字符串为java对象 |\n| feign.codec.Encoder    | 请求参数编码     | 将请求参数编码，便于通过http请求发送                   |\n| feign. Contract        | 支持的注解格式   | 默认是SpringMVC的注解                                  |\n| feign. Retryer         | 失败重试机制     | 请求失败的重试机制，默认是没有，不过会使用Ribbon的重试 |\n\n一般情况下，默认值就能满足我们使用，如果要自定义时，只需要创建自定义的@Bean覆盖默认Bean即可。\n\n下面以日志为例来演示如何自定义配置。\n\n### 2.2.1.配置文件方式\n\n基于配置文件修改feign的日志级别可以针对单个服务：\n\n```yaml\nfeign:  \n  client:\n    config: \n      userservice: # 针对某个微服务的配置\n        loggerLevel: FULL #  日志级别\n```\n\n也可以针对所有服务：\n\n```yaml\nfeign:  \n  client:\n    config: \n      default: # 这里用default就是全局配置，如果是写服务名称，则是针对某个微服务的配置\n        loggerLevel: FULL #  日志级别\n```\n\n而日志的级别分为四种：\n\n- NONE：不记录任何日志信息，这是默认值。\n- BASIC：仅记录请求的方法，URL以及响应状态码和执行时间\n- HEADERS：在BASIC的基础上，额外记录了请求和响应的头信息\n- FULL：记录所有请求和响应的明细，包括头信息、请求体、元数据。\n\n### 2.2.2.Java代码方式\n\n也可以基于Java代码来修改日志级别，先声明一个类，然后声明一个Logger.Level的对象：\n\n```java\npublic class DefaultFeignConfiguration  {\n    @Bean\n    public Logger.Level feignLogLevel(){\n        return Logger.Level.BASIC; \u002F\u002F 日志级别为BASIC\n    }\n}\n```\n\n如果要**全局生效**，将其放到启动类的@EnableFeignClients这个注解中：\n\n```java\n@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)\n```\n\n如果是**局部生效**，则把它放到对应的@FeignClient这个注解中：\n\n```java\n@FeignClient(value = \"userservice\", configuration = DefaultFeignConfiguration .class)\n```\n\n## 2.3.Feign使用优化\n\nFeign底层发起http请求，依赖于其它的框架。其底层客户端实现包括：\n\n•URLConnection：默认实现，不支持连接池\n\n•Apache HttpClient ：支持连接池\n\n•OKHttp：支持连接池\n\n因此提高Feign的性能主要手段就是使用**连接池**代替默认的URLConnection。\n\n这里我们用Apache的HttpClient来演示。\n\n1）引入依赖\n\n在order-service的pom文件中引入Apache的HttpClient依赖：\n\n```xml\n\u003C!--httpClient的依赖 -->\n\u003Cdependency>\n    \u003CgroupId>io.github.openfeign\u003C\u002FgroupId>\n    \u003CartifactId>feign-httpclient\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n```\n\n2）配置连接池\n\n在order-service的application.yml中添加配置：\n\n```yaml\nfeign:\n  client:\n    config:\n      default: # default全局的配置\n        loggerLevel: BASIC # 日志级别，BASIC就是基本的请求和响应信息\n  httpclient:\n    enabled: true # 开启feign对HttpClient的支持\n    max-connections: 200 # 最大的连接数\n    max-connections-per-route: 50 # 每个路径的最大连接数\n```\n\n接下来，在FeignClientFactoryBean中的loadBalance方法中打断点：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920860466_image.png)\n\nDebug方式启动order-service服务，可以看到这里的client，底层就是Apache HttpClient：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920868838_image.png)\n\n总结，Feign的优化：\n\n1.日志级别尽量用basic\n\n2.使用HttpClient或OKHttp代替URLConnection\n\n①  引入feign-httpClient依赖\n\n②  配置文件开启httpClient功能，设置连接池参数\n\n## 2.4.最佳实践\n\n所谓最近实践，就是使用过程中总结的经验，最好的一种使用方式。\n\n自习观察可以发现，Feign的客户端与服务提供者的controller代码非常相似：\n\nfeign客户端：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920877641_image.png)\n\nUserController：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920883971_image.png)\n\n有没有一种办法简化这种重复的代码编写呢？\n\n### 2.4.1.继承方式\n\n一样的代码可以通过继承来共享：\n\n1）定义一个API接口，利用定义方法，并基于SpringMVC注解做声明。\n\n2）Feign客户端和Controller都集成改接口\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920899793_image.png)\n\n优点：\n\n- 简单\n- 实现了代码共享\n\n缺点：\n\n- 服务提供方、服务消费方紧耦合\n- 参数列表中的注解映射并不会继承，因此Controller中必须再次声明方法、参数列表、注解\n\n### 2.4.2.抽取方式\n\n将Feign的Client抽取为独立模块，并且把接口有关的POJO、默认的Feign配置都放到这个模块中，提供给所有消费者使用。\n\n例如，将UserClient、User、Feign的默认配置都抽取到一个feign-api包中，所有微服务引用该依赖包，即可直接使用。\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920906856_image.png)\n\n### 2.4.3.实现基于抽取的最佳实践\n\n#### 1）抽取\n\n首先创建一个module，命名为feign-api：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920912973_image.png)\n\n项目结构：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920920167_image.png)\n\n在feign-api中然后引入feign的starter依赖\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>org.springframework.cloud\u003C\u002FgroupId>\n    \u003CartifactId>spring-cloud-starter-openfeign\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n```\n\n然后，order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920925918_image.png)\n\n#### 2）在order-service中使用feign-api\n\n首先，删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口。\n\n在order-service的pom文件中中引入feign-api的依赖：\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>cn.itcast.demo\u003C\u002FgroupId>\n    \u003CartifactId>feign-api\u003C\u002FartifactId>\n    \u003Cversion>1.0\u003C\u002Fversion>\n\u003C\u002Fdependency>\n```\n\n修改order-service中的所有与上述三个组件有关的导包部分，改成导入feign-api中的包\n\n#### 3）重启测试\n\n重启后，发现服务报错了：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920947926_image.png)\n\n这是因为UserClient现在在cn.itcast.feign.clients包下，\n\n而order-service的@EnableFeignClients注解是在cn.itcast.order包下，不在同一个包，无法扫描到UserClient。\n\n#### 4）解决扫描包问题\n\n方式一：\n\n指定Feign应该扫描的包：\n\n```java\n@EnableFeignClients(basePackages = \"cn.itcast.feign.clients\")\n```\n\n方式二：\n\n指定需要加载的Client接口：\n\n```java\n@EnableFeignClients(clients = {UserClient.class})\n```\n\n\n",{"id":55,"created_at":56,"updated_at":56,"code":57,"author_id":7,"book_id":4,"slug":58,"article_id":59,"title":60,"sort_order":55,"content":61,"status":12},28,"2024-01-19T14:13:37.381+08:00","E784VKZw","gateway服务网关-28",177,"Gateway服务网关","# 3.Gateway服务网关\n\nSpring Cloud Gateway 是 Spring Cloud 的一个全新项目，该项目是基于 Spring 5.0，Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关，它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。\n\n## 3.1.为什么需要网关\n\nGateway网关是我们服务的守门神，所有微服务的统一入口。\n\n网关的**核心功能特性**：\n\n- 请求路由\n- 权限控制\n- 限流\n\n架构图：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920957392_image.png)\n\n**权限控制**：网关作为微服务入口，需要校验用户是是否有请求资格，如果没有则进行拦截。\n\n**路由和负载均衡**：一切请求都必须先经过gateway，但网关不处理业务，而是根据某种规则，把请求转发到某个微服务，这个过程叫做路由。当然路由的目标服务有多个时，还需要做负载均衡。\n\n**限流**：当请求流量过高时，在网关中按照下流的微服务能够接受的速度来放行请求，避免服务压力过大。\n\n在SpringCloud中网关的实现包括两种：\n\n- gateway\n- zuul\n\nZuul是基于Servlet的实现，属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux，属于响应式编程的实现，具备更好的性能。\n\n## 3.2.gateway快速入门\n\n下面，我们就演示下网关的基本路由功能。基本步骤如下：\n\n1. 创建SpringBoot工程gateway，引入网关依赖\n2. 编写启动类\n3. 编写基础配置和路由规则\n4. 启动网关服务进行测试\n\n### 1）创建gateway服务，引入依赖\n\n创建服务：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920981050_image.png)\n\n引入依赖：\n\n```xml\n\u003C!--网关-->\n\u003Cdependency>\n    \u003CgroupId>org.springframework.cloud\u003C\u002FgroupId>\n    \u003CartifactId>spring-cloud-starter-gateway\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n\u003C!--nacos服务发现依赖-->\n\u003Cdependency>\n    \u003CgroupId>com.alibaba.cloud\u003C\u002FgroupId>\n    \u003CartifactId>spring-cloud-starter-alibaba-nacos-discovery\u003C\u002FartifactId>\n\u003C\u002Fdependency>\n```\n\n### 2）编写启动类\n\n```java\npackage cn.itcast.gateway;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class GatewayApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(GatewayApplication.class, args);\n\t}\n}\n```\n\n### 3）编写基础配置和路由规则\n\n创建application.yml文件，内容如下：\n\n```yaml\nserver:\n  port: 10010 # 网关端口\nspring:\n  application:\n    name: gateway # 服务名称\n  cloud:\n    nacos:\n      server-addr: localhost:8848 # nacos地址\n    gateway:\n      routes: # 网关路由配置\n        - id: user-service # 路由id，自定义，只要唯一即可\n          # uri: http:\u002F\u002F127.0.0.1:8081 # 路由的目标地址 http就是固定地址\n          uri: lb:\u002F\u002Fuserservice # 路由的目标地址 lb就是负载均衡，后面跟服务名称\n          predicates: # 路由断言，也就是判断请求是否符合路由规则的条件\n            - Path=\u002Fuser\u002F** # 这个是按照路径匹配，只要以\u002Fuser\u002F开头就符合要求\n```\n\n我们将符合`Path` 规则的一切请求，都代理到 `uri`参数指定的地址。\n\n本例中，我们将 `\u002Fuser\u002F**`开头的请求，代理到`lb:\u002F\u002Fuserservice`，lb是负载均衡，根据服务名拉取服务列表，实现负载均衡。\n\n### 4）重启测试\n\n重启网关，访问http:\u002F\u002Flocalhost:10010\u002Fuser\u002F1时，符合`\u002Fuser\u002F**`规则，请求转发到uri：http:\u002F\u002Fuserservice\u002Fuser\u002F1，得到了结果：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705920989182_image.png)\n\n### 5）网关路由的流程图\n\n整个访问的流程如下：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705921003618_image.png)\n\n总结：\n\n网关搭建步骤：\n\n1. 创建项目，引入nacos服务发现和gateway依赖\n2. 配置application.yml，包括服务基本信息、nacos地址、路由\n\n路由配置包括：\n\n1. 路由id：路由的唯一标示\n2. 路由目标（uri）：路由的目标地址，http代表固定地址，lb代表根据服务名负载均衡\n3. 路由断言（predicates）：判断路由的规则，\n4. 路由过滤器（filters）：对请求或响应做处理\n\n接下来，就重点来学习路由断言和路由过滤器的详细知识\n\n## 3.3.断言工厂\n\n我们在配置文件中写的断言规则只是字符串，这些字符串会被Predicate Factory读取并处理，转变为路由判断的条件\n\n例如Path=\u002Fuser\u002F**是按照路径匹配，这个规则是由\n\n`org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory`类来\n\n处理的，像这样的断言工厂在SpringCloudGateway还有十几个:\n\n\n| **名称**   | **说明**                       | **示例**                                                                                                 |\n| ---------- | ------------------------------ | -------------------------------------------------------------------------------------------------------- |\n| After      | 是某个时间点后的请求           | -  After=2037-01-20T17:42:47.789-07:00[America\u002FDenver]                                                   |\n| Before     | 是某个时间点之前的请求         | -  Before=2031-04-13T15:14:47.433+08:00[Asia\u002FShanghai]                                                   |\n| Between    | 是某两个时间点之前的请求       | -  Between=2037-01-20T17:42:47.789-07:00[America\u002FDenver],  2037-01-21T17:42:47.789-07:00[America\u002FDenver] |\n| Cookie     | 请求必须包含某些cookie         | - Cookie=chocolate, ch.p                                                                                 |\n| Header     | 请求必须包含某些header         | - Header=X-Request-Id, \\d+                                                                               |\n| Host       | 请求必须是访问某个host（域名） | -  Host=**.somehost.org,**.anotherhost.org                                                               |\n| Method     | 请求方式必须是指定方式         | - Method=GET,POST                                                                                        |\n| Path       | 请求路径必须符合指定规则       | - Path=\u002Fred\u002F{segment},\u002Fblue\u002F**                                                                           |\n| Query      | 请求参数必须包含指定参数       | - Query=name, Jack或者-  Query=name                                                                      |\n| RemoteAddr | 请求者的ip必须是指定范围       | - RemoteAddr=192.168.1.1\u002F24                                                                              |\n| Weight     | 权重处理                       |                                                                                                          |\n\n我们只需要掌握Path这种路由工程就可以了。\n\n## 3.4.过滤器工厂\n\nGatewayFilter是网关中提供的一种过滤器，可以对进入网关的请求和微服务返回的响应做处理：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705921012118_image.png)\n\n### 3.4.1.路由过滤器的种类\n\nSpring提供了31种不同的路由过滤器工厂。例如：\n\n\n| **名称**             | **说明**                     |\n| -------------------- | ---------------------------- |\n| AddRequestHeader     | 给当前请求添加一个请求头     |\n| RemoveRequestHeader  | 移除请求中的一个请求头       |\n| AddResponseHeader    | 给响应结果中添加一个响应头   |\n| RemoveResponseHeader | 从响应结果中移除有一个响应头 |\n| RequestRateLimiter   | 限制请求的流量               |\n\n### 3.4.2.请求头过滤器\n\n下面我们以AddRequestHeader 为例来讲解。\n\n> **需求**：给所有进入userservice的请求添加一个请求头：Truth=itcast is freaking awesome!\n\n只需要修改gateway服务的application.yml文件，添加路由过滤即可：\n\n```yaml\nspring:\n  cloud:\n    gateway:\n      routes:\n      - id: user-service \n        uri: lb:\u002F\u002Fuserservice \n        predicates: \n        - Path=\u002Fuser\u002F** \n        filters: # 过滤器\n        - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头\n```\n\n当前过滤器写在userservice路由下，因此仅仅对访问userservice的请求有效。\n\n### 3.4.3.默认过滤器\n\n如果要对所有的路由都生效，则可以将过滤器工厂写到default下。格式如下：\n\n```yaml\nspring:\n  cloud:\n    gateway:\n      routes:\n      - id: user-service \n        uri: lb:\u002F\u002Fuserservice \n        predicates: \n        - Path=\u002Fuser\u002F**\n      default-filters: # 默认过滤项\n      - AddRequestHeader=Truth, Itcast is freaking awesome!\n```\n\n### 3.4.4.总结\n\n过滤器的作用是什么？\n\n① 对路由的请求或响应做加工处理，比如添加请求头\n\n② 配置在路由下的过滤器只对当前路由的请求生效\n\ndefaultFilters的作用是什么？\n\n① 对所有路由都生效的过滤器\n\n## 3.5.全局过滤器\n\n上一节学习的过滤器，网关提供了31种，但每一种过滤器的作用都是固定的。如果我们希望拦截请求，做自己的业务逻辑则没办法实现。\n\n### 3.5.1.全局过滤器作用\n\n全局过滤器的作用也是处理一切进入网关的请求和微服务响应，与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义，处理逻辑是固定的；而GlobalFilter的逻辑需要自己写代码实现。\n\n定义方式是实现GlobalFilter接口。\n\n```java\npublic interface GlobalFilter {\n    \u002F**\n     *  处理当前请求，有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理\n     *\n     * @param exchange 请求上下文，里面可以获取Request、Response等信息\n     * @param chain 用来把请求委托给下一个过滤器 \n     * @return {@code Mono\u003CVoid>} 返回标示当前过滤器业务结束\n     *\u002F\n    Mono\u003CVoid> filter(ServerWebExchange exchange, GatewayFilterChain chain);\n}\n```\n\n在filter中编写自定义逻辑，可以实现下列功能：\n\n- 登录状态判断\n- 权限校验\n- 请求限流等\n\n### 3.5.2.自定义全局过滤器\n\n需求：定义全局过滤器，拦截请求，判断请求的参数是否满足下面条件：\n\n- 参数中是否有authorization，\n- authorization参数值是否为admin\n\n如果同时满足则放行，否则拦截\n\n实现：\n\n在gateway中定义一个过滤器：\n\n```java\npackage cn.itcast.gateway.filters;\n\nimport org.springframework.cloud.gateway.filter.GatewayFilterChain;\nimport org.springframework.cloud.gateway.filter.GlobalFilter;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.server.ServerWebExchange;\nimport reactor.core.publisher.Mono;\n\n@Order(-1)\n@Component\npublic class AuthorizeFilter implements GlobalFilter {\n    @Override\n    public Mono\u003CVoid> filter(ServerWebExchange exchange, GatewayFilterChain chain) {\n        \u002F\u002F 1.获取请求参数\n        MultiValueMap\u003CString, String> params = exchange.getRequest().getQueryParams();\n        \u002F\u002F 2.获取authorization参数\n        String auth = params.getFirst(\"authorization\");\n        \u002F\u002F 3.校验\n        if (\"admin\".equals(auth)) {\n            \u002F\u002F 放行\n            return chain.filter(exchange);\n        }\n        \u002F\u002F 4.拦截\n        \u002F\u002F 4.1.禁止访问，设置状态码\n        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);\n        \u002F\u002F 4.2.结束处理\n        return exchange.getResponse().setComplete();\n    }\n}\n```\n\n### 3.5.3.过滤器执行顺序\n\n请求进入网关会碰到三类过滤器：当前路由的过滤器、DefaultFilter、GlobalFilter\n\n请求路由后，会将当前路由过滤器和DefaultFilter、GlobalFilter，合并到一个过滤器链（集合）中，排序后依次执行每个过滤器：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705921023889_image.png)\n\n排序的规则是什么呢？\n\n- 每一个过滤器都必须指定一个int类型的order值，**order值越小，优先级越高，执行顺序越靠前**。\n- GlobalFilter通过实现Ordered接口，或者添加@Order注解来指定order值，由我们自己指定\n- 路由过滤器和defaultFilter的order由Spring指定，默认是按照声明顺序从1递增。\n- 当过滤器的order值一样时，会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。\n\n详细内容，可以查看源码：\n\n`org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()`方法是先加载defaultFilters，然后再加载某个route的filters，然后合并。\n\n`org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()`方法会加载全局过滤器，与前面的过滤器合并后根据order排序，组织过滤器链\n\n## 3.6.跨域问题\n\n### 3.6.1.什么是跨域问题\n\n跨域：域名不一致就是跨域，主要包括：\n\n- 域名不同： www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com\n- 域名相同，端口不同：localhost:8080和localhost8081\n\n跨域问题：浏览器禁止请求的发起者与服务端发生跨域ajax请求，请求被浏览器拦截的问题\n\n解决方案：CORS，这个以前应该学习过，这里不再赘述了。不知道的小伙伴可以查看https:\u002F\u002Fwww.ruanyifeng.com\u002Fblog\u002F2016\u002F04\u002Fcors.html\n\n### 3.6.2.模拟跨域问题\n\n找到课前资料的页面文件：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705921030671_image.png)\n\n放入tomcat或者nginx这样的web服务器中，启动并访问。\n\n可以在浏览器控制台看到下面的错误：\n\n![image.png](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Feditor1705921034843_image.png)\n\n从localhost:8090访问localhost:10010，端口不同，显然是跨域的请求。\n\n### 3.6.3.解决跨域问题\n\n在gateway服务的application.yml文件中，添加下面的配置：\n\n```yaml\nspring:\n  cloud:\n    gateway:\n      # 。。。\n      globalcors: # 全局的跨域处理\n        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题\n        corsConfigurations:\n          '[\u002F**]':\n            allowedOrigins: # 允许哪些网站的跨域请求 \n              - \"http:\u002F\u002Flocalhost:8090\"\n            allowedMethods: # 允许的跨域ajax的请求方式\n              - \"GET\"\n              - \"POST\"\n              - \"DELETE\"\n              - \"PUT\"\n              - \"OPTIONS\"\n            allowedHeaders: \"*\" # 允许在请求中携带的头信息\n            allowCredentials: true # 是否允许携带cookie\n            maxAge: 360000 # 这次跨域检测的有效期\n```\n",{"id":55,"created_at":56,"updated_at":56,"code":57,"author_id":7,"book_id":4,"slug":58,"article_id":59,"title":60,"sort_order":55,"content":61,"status":12}]