SpringCloud教程

张开发
2026/4/13 5:13:54 15 分钟阅读

分享文章

SpringCloud教程
idea创建springCloud项目pom依赖?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.sankuai/groupId artifactIdSpringCloud1/artifactId version1.0-SNAPSHOT/version parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.6.13/version relativePath/ /parent properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding project.reporting.outputEncodingUTF-8/project.reporting.outputEncoding java.version1.8/java.version spring-cloud.versionGreenwich.RELEASE/spring-cloud.version spring-cloud-alibaba.version2.1.0.RELEASE/spring-cloud-alibaba.version /properties !-- 依赖管理控制Cloud版本 -- dependencyManagement dependencies !-- Spring Cloud -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version${spring-cloud.version}/version typepom/type scopeimport/scope /dependency !-- Spring Cloud Alibaba -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version${spring-cloud-alibaba.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement !-- 常用依赖你项目必须的 -- dependencies !-- Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- 测试 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies /project创建基础模块----商品公共模块在基础模块上创建实体类需要添加mybaitis-Plus的依赖Data // 等价于Getter Setter ToString EqualsAndHashCode NoArgsConstructor // 生成无参构造 AllArgsConstructor // 生成全参构造 public class User { /** * 主键ID */ TableId(type IdType.AUTO) // 主键自增 private Integer id; /** * 用户名 */ private String username; /** * 密码 */ private String password; /** * 手机号 */ private String telephone; }/** * Author: xxxx * CreateTime: 2026/04/05 19:37 * Description: 商品实体类 * Version: 1.0 */ Data NoArgsConstructor AllArgsConstructor public class Product { /** * 主键ID */ TableId(type IdType.AUTO) private Integer id;//主键 /** * 商品名称 */ private String pname;//商品名称 /** * 商品价格 */ private Double pprice;//商品价格 /** * 库存 */ private Integer stock;//库存 }/** * Author: xxxx * CreateTime: 2026/04/05 19:40 * Description: 订单实体类 * Version: 1.0 */ Data NoArgsConstructor AllArgsConstructor public class Orders { /** * 订单id */ TableId(type IdType.AUTO) private Long id;//订单id /** * 用户id */ private Integer uid;//用户id /** * 用户名 */ private String username;//用户名 /** * 商品id */ private Integer pid;//商品id /** * 商品名称 */ private String pname;//商品名称 /** * 商品单价 */ private Double pprice;//商品单价 /** * 购买数量 */ private Integer number;//购买数量 }依赖?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdcom.sankuai/groupId artifactIdSpringCloud1/artifactId version1.0-SNAPSHOT/version /parent version1.0-SNAPSHOT/version !-- 必须加版本号 -- packagingjar/packaging !-- 必须指定打包方式为jar -- artifactIdshop-common/artifactId properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties dependencies !-- 微服务的公共模块-- !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- MyBatis-PlusSpring Boot版 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version !-- 稳定版兼容Spring Boot 2.x -- /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.24/version /dependency !-- mysql的驱动-- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /dependency /dependencies /project创建商品模块注意要添加版本号否则会编译不通过dependencies dependency groupIdcom.sankuai/groupId artifactIdshop-common/artifactId version1.0-SNAPSHOT/version /dependency /dependencies创建启动器类SpringBootApplication public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); } }创建配置文件连接数据库server.port8071 spring.application.nameshop-product spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver spring.datasource.urljdbc:mysql://alidemo?serverTimezoneUTCuseUnicodetruecharacterEncodingutf-8useSSLtrue spring.datasource.usernameroot spring.datasource.passwordroot #配置日志 mybatis-plus.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImplmysql驱动放入公共模块!-- mysql的驱动-- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /dependency /dependencies创建数据库写Dao层Repository Mapper public interface ProductDao extends BaseMapperProduct { }创建Service接口和实现类public class ProductService { Autowired private ProductDao productDao; public Product FindProduct(Integer id) { return productDao.selectById(id); } }创建控制层/** * 商品控制器 */ RestController public class ProductController { Autowired private ProductService productService; RequestMapping(/findProduct/{pid}) public Product findProduct(PathVariable(pid) Integer pid){ Product product productService.findProduct(pid); return product; } }启动启动器类运行RestTemplate服务调用服务创建Order类daoRepository Mapper public interface OrderDao extends BaseMapperOrders { }serviceService public class OrderService { Autowired private OrderDao orderDao; public Orders findOrder(Integer id) { return orderDao.selectById(id); } public int insertOne(Orders orders) { int i orderDao.insert(orders); return i; } }ControllerRestController public class OrderController { Autowired private OrderService orderService; Autowired private RestTemplate restTemplate; // 根据id查询订单 RequestMapping(/findOrder/{id}) public Orders findOrder(PathVariable(id) Integer id){ return orderService.findOrder(id); } /*** * 下订单功能包含用户信息、商品信息和订单信息 * 那么订单微服务需要调用其他微服务的接口来获取用户信息、商品信息 */ RequestMapping(/addOrder/{pid}) public R addOrder(PathVariable(pid) Integer pid) { /*** * 从前端获取商品id,从商品微服务来查询商品信息 * 使用restTemplate来实现微服务之间的相互调用 * 需要在消费者的启动类OrderApplication创建Template对象 */ String url http://localhost:8081/findProduct/ pid; //在订单微服务调用商品微服务来获取商品信息 Product product restTemplate.getForObject(url, Product.class); //查出来后就生成一个订单 Orders orders new Orders(); orders.setPid(pid); orders.setPname(product.getPname()); orders.setPprice(product.getPprice()); orders.setNumber(product.getStock()); orders.setPprice(product.getPprice()); orders.setUid(1); orders.setUsername(张三); //把数据添加到数据库中 int i orderService.insertOne(orders); return R.success().data(orders,orders).data(product,product); } }启动器类SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }配置文件server.port8072 spring.application.nameshop-order spring.datasource.driver-class-namecom.mysql.jdbc.Driver spring.datasource.urljdbc:mysql://alidemo?serverTimezoneUTCuseUnicodetruecharacterEncodingutf-8useSSLtrue spring.datasource.usernameroot spring.datasource.passwordroot #配置日志 mybatis-plus.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl启动点单模块执行下订单方法在订单模块中先调用产品模块查询出产品信息在订单表中加入订单表的数据将产品id放入订单表中等。Nacos负载均衡在上面的代码中去调用其他的微服务时需要使用写死的url网址如果网址或端口号改变就不好维护可以通过注册中心动态的去实现服务治理。什么是服务治理服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册与发现。服务注册在服务治理框架中都会构建一个注册中心每个服务单元向注册中心登记自己提供服务的详细信息。并在注册中心形成一张服务的清单服务注册中心需要以心跳的方式去监测清单中的服务是否可用如果不可用需要在服务清单中剔除不可用的服务。服务发现服务调用方向服务注册中心咨询服务并获取所有服务的实例清单实现对具体服务实例的访问。Nacos 是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是 Spring Cloud Alibaba 组件之一负责服务注册发现和服务配置可以这样认为 nacoseurekaconfig。nacos 的作用就是一个注册中心用来管理注册上来的各个微服务。搭建 nacos 环境第 1 步安装 nacos下载地址https://github.com/alibaba/nacos/releases下载 zip 格式的安装包然后进行解压缩操作注意尽量不要出现中文路径第 2 步启动 nacos#切换目录 cd nacos/bin #命令启动 startup.cmd -m standalone或者进入到 bin 的文件中 找到 startup.cmd 右键修改 把其中的set MODEcluster 改为 set MODEstandalone 双击启动即可注意在windows的终端需要加./前缀#命令启动./startup.cmd -m standalone进入nacos网址账号和密码都是nacos将商品微服务注册到 nacos接下来开始修改 shop-product 模块的代码将其注册到 nacos 服务上注意商品微服务的父项目一定是当前自定义的父项目 (默认已配置好了)!-- 要配置该子项目的父项目 -- parent groupIdcom.huidian/groupId version1.0-SNAPSHOT/version artifactIdspringcloud-alibaba/artifactId /parent1 在商品微服务 pom.xml 中添加 nacos 的依赖!--nacos客户端-- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency2 在主类上添加 EnableDiscoveryClient 注解一启动项目就会把该服务加入到nacos的服务列表里面import org.springframework.cloud.client.discovery.EnableDiscoveryClient; SpringBootApplication EnableDiscoveryClient public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); } }在 application.properties 中添加 nacos 服务的地址 spring.cloud.nacos.discovery.server-addr127.0.0.1:8848启动服务注册进去了一个服务。将订单微服务注册到 nacos接下来开始修改 shop-order 模块的代码将其注册到 nacos 服务上1 在 pom.xml 中添加 nacos 的依赖!--nacos客户端-- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency2 在主类上添加 EnableDiscoveryClient 注解SpringBootApplication EnableDiscoveryClient public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }3 在配置类中加入端口号的配置spring.cloud.nacos.discovery.server-addr127.0.0.1:8848启动项目此时就把该服务加入了服务列表把他们都注册到nacos里面就可以实现相互调用了就不用再手写ip地址了。实现微服务之间的调用把之前手写的url路径都删了修改订单服务实现微服务之间的调用可以根据服务名字来获取服务的信息。RestController public class OrderController { Autowired private OrderService orderService; Autowired private RestTemplate restTemplate; Autowired private DiscoveryClient discoveryClient; // 根据id查询订单 RequestMapping(/findOrder/{id}) public Orders findOrder(PathVariable(id) Integer id){ return orderService.findOrder(id); } /*** * 下订单功能包含用户信息、商品信息和订单信息 * 那么订单微服务需要调用其他微服务的接口来获取用户信息、商品信息 */ RequestMapping(/addOrder/{pid}) public R addOrder(PathVariable(pid) Integer pid) { /*** * 从前端获取商品id,从商品微服务来查询商品信息 * 使用restTemplate来实现微服务之间的相互调用 * 需要在消费者的启动类OrderApplication创建Template对象 */ //从nacos中根据服务名获取商品微服务的url //因为它获得的是集群集群有好多服务器是一个集合所以get(0)就是获得第一个服务 ServiceInstance serviceInstance discoveryClient.getInstances(shop-product).get(0); String host serviceInstance.getHost();//获取nacos的主机的ip int port serviceInstance.getPort();//获取nacos主机的端口8848 String url http://host:port/findProduct/ pid; //在订单微服务调用商品微服务来获取商品信息 Product product restTemplate.getForObject(url, Product.class); //查出来后就生成一个订单 Orders orders new Orders(); orders.setPid(pid); orders.setPname(product.getPname()); orders.setPprice(product.getPprice()); orders.setNumber(product.getStock()); orders.setPprice(product.getPprice()); orders.setUid(1); orders.setUsername(张三); //把数据添加到数据库中 int i orderService.insertOne(orders); return R.success().data(orders,orders).data(product,product); } }启动服务实现订单服务调取商品服务。我们发现它有一个弊端在获取的服务get(0)是获取第一个如果商品微服务不止一个使用NGINX负载均衡了好多个服务那么它就只能获取第一个服务其他的服务都用不到导致资源浪费等情况。实现服务调用的负载均衡什么是负载均衡通俗的讲负载均衡就是将负载工作任务访问请求进行分摊到多个操作单元服务器组件上进行执行。根据负载均衡发生位置的不同一般分为服务端负载均衡和客户端负载均衡。服务端负载均衡指的是发生在服务提供者一方比如常见的 nginx 负载均衡而客户端负载均衡指的是发生在服务请求的一方也就是在发送请求之前已经选好了由哪个实例处理请求启动ProductApplication和product2手动实现负载均衡在nacos中可以存放多个服务就是集群取出的时候也是按照集群的方式取的所以返回的是一个数组如上图所示get(0),永远就只能取出第一个服务器所以我们根据从nacos获取所返回的数组长度设置一个随机数根据随机数来取服务即可达到负载均衡。RequestMapping(/addOrder/{pid}) public R addOrder(PathVariable(pid) Integer pid) { //从nacos中获取所有的商品微服务产品微服务名shop-product所以返回的是一个数组 ListServiceInstance instances discoveryClient.getInstances(shop-product); //根据从nacos中获取微服务的数量即返回数组的长度随机数获取一个商品微服务 int index new Random().nextInt(instances.size()); ServiceInstance serviceInstance instances.get(index); String host serviceInstance.getHost();//获取nacos的主机的ip int port serviceInstance.getPort();//获取nacos主机的端口8848 String url http://host:port/findProduct/ pid; //在订单微服务调用商品微服务来获取商品信息 Product product restTemplate.getForObject(url, Product.class); //查出来后就生成一个订单 Orders orders new Orders(); orders.setPid(pid); orders.setPname(product.getPname()); orders.setPprice(product.getPprice()); orders.setNumber(product.getStock()); orders.setPprice(product.getPprice()); orders.setUid(1); orders.setUsername(张三); //把数据添加到数据库中 int i orderService.insertOne(orders); return R.success().data(orders,orders).data(product,product); }但是随机数有很大可能就只得到一个数据存在风险。基于 Ribbon 实现负载均衡Ribbon 是 Spring Cloud 的一个组件它可以让我们使用一个注解就能轻松的搞定负载均衡注意负载均衡 Ribbon 是不支持下划线的只支持横线总结在使用 springcloud 的微服务的时候要记住不能使用下划线用横线去代替。第 1 步在 RestTemplate 的生成方法上添加 LoadBalanced 注解Bean LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }补充说明LoadBalanced注解是 Ribbon 实现客户端负载均衡的核心注解它会给RestTemplate实例添加负载均衡拦截器实现服务名到实例地址的解析与轮询调用下划线不兼容的原因Ribbon 在服务名解析时会将下划线_识别为特殊分隔符导致服务发现失败因此 Spring Cloud 微服务命名规范要求使用横线-分隔SpringBootApplication EnableDiscoveryClient public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } Bean LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }第二步修改代码在配置文件配置策略RequestMapping(/addOrder/{pid}) public R addOrder(PathVariable(pid) Integer pid) { //把Ribbon整合进去 通过服务名称来获取服务的地址 String nameshop-product; String url http://name/findProduct/ pid; //在订单微服务调用商品微服务来获取商品信息 Product product restTemplate.getForObject(url, Product.class); //查出来后就生成一个订单 Orders orders new Orders(); orders.setPid(pid); orders.setPname(product.getPname()); orders.setPprice(product.getPprice()); orders.setNumber(product.getStock()); orders.setPprice(product.getPprice()); orders.setUid(1); orders.setUsername(张三); //把数据添加到数据库中 int i orderService.insertOne(orders); return R.success().data(orders,orders).data(product,product); }Ribbon 支持的负载均衡策略Ribbon 内置了多种负载均衡策略内部负载均衡的顶级接口为com.netflix.loadbalancer.IRule具体的负载策略如下表所示策略名策略描述实现说明BestAvailableRule选择一个最小的并发请求的 server逐个考察 Server如果 Server 被 tripped 了则忽略在选择其中 ActiveRequestsCount 最小的 serverAvailabilityFilteringRule过滤掉那些因为一直连接失败的被标记为 circuit tripped 的后端 server并过滤掉那些高并发的的后端 serveractive connections 超过配置的阈值使用一个 AvailabilityPredicate 来包含过滤 server 的逻辑其实就是检查 status 里记录的各个 server 的运行状态WeightedResponseTimeRule根据相应时间分配一个 weight相应时间越长weight 越小被选中的可能性越低一个后台线程定期的从 status 里面读取评价响应时间为每个 server 计算一个 weight。Weight 的计算也比较简单 responsetime 减去每个 server 自己平均的 responsetime 是 server 的权重。当刚开始运行没有形成 status 时使用轮询策略选择 serverRoundRobinRule轮询方式轮询选择 server轮询 index选择 index 对应位置的 serverRandomRule随机选择一个 server在 index 上随机选择 index 对应位置的 serverZoneAvoidanceRule复合判断 server 所在区域的性能和 server 的可用性选择 server使用 ZoneAvoidancePredicate 和 AvailabilityPredicate 来判断是否选择某个 server前一个判断判定一个 zone 的运行性能是否可用剔除不可用的 zone的所有 serverAvailabilityPredicate 用于过滤掉连接数过多的 ServerRetryRule重试策略先按轮询获取服务获取失败则在指定时间内进行重试我们可以通过修改配置来调整 Ribbon 的负载均衡策略具体配置代码及相关说明如下配置内容properties# 调用的提供者的名称指定目标微服务名称 service-product.ribbon.NFLoadBalancerRuleClassNamecom.netflix.loadbalancer.RandomRule基于Feign实现服务调用什么是FeignFeign是Spring Cloud提供的一个声明式的伪Http客户端 它使得调用远程服务就像调用本地服务一样简单 只需要创建一个接口并添加一个注解即可。Nacos很好的兼容了Feign Feign默认集成了 Ribbon 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。Feign的使用1 加入Fegin的依赖!--fegin组件-- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency2 在主类上添加Fegin的注解SpringBootApplication EnableDiscoveryClient EnableFeignClients //开启Fegin public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } Bean LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }3 创建一个service 并使用Fegin实现微服务调用FeignClient(shop-product) //声明调用的提供者的name public interface ProductService { //FeignClientGetMapping 就是一个完整的请求路径 http://service- product/product/{pid} GetMapping(/findProduct/{id}) Product findProduct(PathVariable(id) Integer id); }4 修改controller代码并启动验证RequestMapping(/addOrder2/{pid}) public R addOrder2(PathVariable(pid) Integer pid) { Product product productService.findProduct(pid); //查出来后就生成一个订单 Orders orders new Orders(); orders.setPid(pid); orders.setPname(product.getPname()); orders.setPprice(product.getPprice()); orders.setNumber(product.getStock()); orders.setPprice(product.getPprice()); orders.setUid(1); orders.setUsername(远程调用); //把数据添加到数据库中 int i orderService.insertOne(orders); return R.success().data(orders,orders).data(product,product); }超时时间也可以配置一下超时时间,openfeign默认等待1秒钟超时则出现报错页面#feign的配置连接超时及读取超时配置 【这里】 feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic配置项含义具体说明feign:根节点所有 Feign 客户端的配置起始节点。client:客户端配置针对 Feign 客户端底层行为的具体设置。config:配置集定义具体的配置规则集合。default:默认配置表示这是全局默认配置作用于所有通过 Feign 发起的远程调用。若只想针对特定服务配置需写具体服务名而非defaultconnectTimeout: 5000连接超时建立网络连接的最长等待时间单位为毫秒。此处设置为5 秒。即如果 Feign 尝试连接目标服务5 秒内未连接成功则判定为超时。readTimeout: 5000读取超时连接建立后等待目标服务处理请求并返回数据的最长时间单位为毫秒。此处设置为5 秒。即服务端处理逻辑及返回数据若超过 5 秒Feign 会抛出超时异常。loggerLevel: basic日志级别定义 Feign 的日志打印详细程度。basic表示仅记录请求的基本信息如请求 URL、方法、状态码、响应时间属于最常用的基础日志级别。openfeign默认集成了Ribbon针对shop-product服务配置feign: client: config: # 只对 shop-product 服务生效 shop-product: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic针对全局和shop-product服务配置feign: client: config: # 全局默认配置 default: connectTimeout: 2000 readTimeout: 2000 loggerLevel: none # 仅 shop-product 服务使用单独配置 shop-product: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic5 重启order微服务,查看效果。OpenFeign的日志打印Feign 提供了日志打印功能我们可以通过配置来调整日志级别从而了解 Feign 中说白了就是对Feign 接口的调用情况进行监控和输出。日志级别级别打印内容适用场景NONE无日志生产环境默认BASIC请求方法、URL、响应码、耗时生产环境监控HEADERSBASIC 请求 / 响应头排查请求头问题FULLHEADERS 请求 / 响应正文、元数据开发 / 测试环境调试第一步创建一个配置类Configuration public class FeignConfig { Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }第二步logging: level: # feign日志以什么级别监控哪个接口 com.atguigu.springcloud.service.PaymentFeignService: debug

更多文章