1.创建一个父类工程并引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modules>
<module>common</module>
<module>product</module>
<module>order</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.aaa</groupId>
<artifactId>springcloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 只要是父工程打包方式都是pom. 把src删除 -->
<packaging>pom</packaging>
<name>springcloud</name>
<description>springcloud</description>
<!-- 定义版本号 -->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- springcloud的版本 -->
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<!-- 阿里巴巴的版本 springboot springcloud springcloudalibaba 他们的版本必须对应
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
-->
<spring-cloud-alibaba.version>2.2.8.RELEASE</spring-cloud-alibaba.version>
</properties>
<!--
dependencyManagement: 只负责jar的管理不负责jar的下载。
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring Cloud和Spring Boot的依赖关系:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
2.创建一个公共模块
引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.aaa</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
</project>
创建公共实体类
product商品
@TableName(value ="tbl_product")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Product implements Serializable {
@TableId(type = IdType.AUTO)
private Integer pid;
private String pname;
private BigDecimal price;
private Integer stock;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
order订单
@TableName(value ="tbl_order")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Order implements Serializable {
/**
* 订单id
*/
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 订单号
*/
private String orderNo;
/**
* 购买的商品id
*/
private Integer productId;
/**
* 购买时的商品价格
*/
private BigDecimal price;
/**
* 购买时的商品名
*/
private String pname;
/**
* 购买的数量
*/
private Integer num;
/**
* 购买者的id
*/
private Integer userid;
/**
* 状态(0未支付,1已支付 2已发货 3确认收货 4.取消)
*/
private Integer status;
/**
* 下单时间
*/
//订单创建时间--Date存在线程安全问题--默认按照驼峰命名
private LocalDateTime createTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
3.创建商品微服务
引入依赖
<dependencies>
<!--公共依赖-->
<dependency>
<groupId>com.aaa</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--数据库的依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--springboot-web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
application.properties文件创建
server.port=8001
spring.application.name=aaa-product
spring.datasource.username=
spring.datasource.password=
spring.datasource.url=jdbc:mysql://localhost:3306/tbl_product?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath*:mapper/*.xml
编写启动类
@SpringBootApplication
@MapperScan("com.aaa.mapper")
public class ProductApp {
public static void main(String[] args) {
SpringApplication.run(ProductApp.class,args);
}
}
mapper层
public interface ProductMapper extends BaseMapper<Product> {
}
service层
public interface ProductService {
public Product getById(Integer pid);
}
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
public Product getById(Integer pid) {
Product product = productMapper.selectById(pid);
return product;
}
}
controller层
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/getById/{pid}")
public Product getById(@PathVariable Integer pid){
Product product = productService.getById(pid);
return product;
}
}
4.创建订单微服务
引入依赖
<dependencies>
<!--公共依赖-->
<dependency>
<groupId>com.aaa</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--数据库的依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--springboot-web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.16</version> <!-- 检查最新版本 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
application.properties文件、启动文件、mapper层略
service层
public interface OrderService {
public Order createOrder(Order order);
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Override
public Order createOrder(Order order) {
orderMapper.insert(order);
return order;
}
}
controller层
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
@PostMapping("/saveOrder")
public Order saveOrder(Integer pid, Integer num) {
Order order = new Order();
order.setOrderNo(IdUtil.getSnowflakeNextIdStr());//订单编号: 时间戳+随机数 雪花算法: 生成一个唯一的id
order.setNum(num);
order.setStatus(0);
order.setCreateTime(LocalDateTime.now());
order.setUserid(1);
order.setProductId(pid);
//根据商品id查询商品信息---远程调用商品微服务的接口。
// 基于Http协议调用。[1]可以自己封装HttpClient工具类【适合所有场景】 [2] spring框架提高了一个HttpClient的工具类【适合spring工程】RestTemplate。
//String url, Class<T> responseType, Object... uriVariables
Product product = restTemplate.getForObject("http://localhost:8001/product/getById/" + pid,Product.class);
if(product==null||product.getStock()<num){
throw new RuntimeException("商品不存在或库存不足");
}
order.setPname(product.getPname());
order.setPrice(product.getPrice());
Order order1 = orderService.createOrder(order);
return order1;
}
}
config
@Configuration
public class RestConfig {
@Bean
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}
Nacos Discovery--服务治理
什么是服务治理
服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册与发现。
服务注册:在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服
务的详细信息。并在注册中心形成一张服务的*清单*,服务注册中心需要以*心跳30s 90s*的方式去监测清单中 的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。
服务发现:服务调用方向服务注册中心咨询服务,并获取*所有服务*的实例清单,实现对具体服务实例的访问。
除了微服务,还有一个组件是服务注册中心,它是微服务架构非常重要
的一个组件,在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:
\1. 服务发现:
服务注册:保存服务提供者和服务调用者的信息
服务订阅:服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息
\2. 服务配置:
配置订阅:服务提供者和服务调用者订阅微服务相关的配置
配置下发:主动将配置推送给服务提供者和服务调用者
\3. 服务健康检测
检测服务提供者的健康情况,如果发现异常,执行服务剔除
常见的注册中心
Zookeeper
zookeeper是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式
应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用
配置项的管理等。
Eureka
Eureka是Springcloud Netflix中的重要组件,主要作用就是做服务注册和发现。但是现在已经闭
源 ,停更不停用。
Consul
Consul是基于GO语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册、服务发现
和配置管理的功能。Consul的功能都很实用,其中包括:服务注册/发现、健康检查、Key/Value
存储、多数据中心和分布式一致性保证等特性。Consul本身只是一个二进制的可执行文件,所以
安装和部署都非常简单,只需要从官网下载后,在执行对应的启动脚本即可。
Nacos(服务治理 配置中心)
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是 Spring
Cloud Alibaba 组件之一,负责服务注册发现和服务配置.
nacos实战入门
下载nacos软件
发布历史 | Nacos 官网https://nacos.io/download/release-history/
选择对应下载版本解压
打开bin目录--修改startup.cmd文件---默认按照集群模式启动----修改为单机启动
启动nacos服务并访问nacos页面
默认账号密码均为nacos
微服务接入nacos
引入nacos依赖包
<!--nacos的jar包-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置文件中指定nacos服务地址
修改订单微服务代码
负载均衡
通俗的讲, 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。
手动实现负载均衡
手动实现负载均衡使用的策略:随机策略。 如果我想改变策略--修改源代码。第三方公司提高了一个组件。ribbon.
介绍ribbon
什么是Ribbon
是 Netflix 发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中, nacos一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从nacos中读 取到的服务信息,在调用服务节点提供的服务时,会合理(策略)的进行负载。 在SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务。
ribbon是netflix公司的组件,作用自动从注册中心拉取服务器信息,并根据相应的策略完成负载均衡的调用。
使用ribbon
nacos依赖自带ribbon包
bean方法上增加注解
在RestTemplate所在的bean方法上加入一个注解。
修改controller代码
完成了负载均衡,策略默认为轮询策略
ribbon负载均衡策略
Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为com.netflix.loadbalancer.IRule , 具体的负载策略如下图所示:
1.3 自定义ribbon负载均衡策略 - 盛开的太阳 - 博客园 (cnblogs.com)https://www.cnblogs.com/ITPower/p/13295955.html 第一种:局部使用
shop-product: # 这里使用服务的名称 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #使用的的负载均衡策略 |
# 指定调用商品微服务使用的负载均衡的策略 qy174-product.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule |
第二种:全局使用
Openfeign远程调用组键
之前我们使用RestTemplate+ribbon完成负载均衡。 语法不符合我们调用的习惯。
Controller----Service----->Dao.
@autoWire注入一个Service对象,对象调用类中的方法,习惯该方法需要什么参数就参数参数类型。
openfeign概述
OpenFeign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务一样简单, 只需要创建一个接口并添加一个注解即可。
Nacos很好的兼容了OpenFeign, OpenFeign负载均衡默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。
使用openfeign
引入依赖
<!--引入openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
创建接口
@FeignClient(value = "qy174-product") //openfeign为该接口生成代理实现类。
public interface ProductFeign {
//这里的方法的请求方式以及参数类型必须和提供者一致。
@GetMapping("/product/getById/{pid}")
public Product getById(@PathVariable Integer pid);
}
开启注释驱动
@SpringBootApplication
@MapperScan("com.aaa.mapper")
@EnableFeignClients//开启openfeign的注解驱动
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class,args);
}
}
修改controller代码
nacos集群模式
1.修改conf/application.properties文件
2.创建数据库nacos并导入sql语句
3.修改cluster.conf.example文件
4.启动三台nacos服务
5.微服务连接
gateway网关
大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端(pc androud ios 平板)要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。
这样的架构,会存在着诸多的问题:
l 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性 。
l 认证复杂,每个服务都需要独立认证。
l 存在跨域请求,在一定场景下处理相对复杂。
常用网关组件
l Ngnix+lua
使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用
lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本
l Kong
基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:
只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
l Zuul 1.0(慢 servlet 2.0 ) zuul2.0 没出来。
Netflix开源的网关,功能丰富,使用JAVA开发,易于二次开发 问题:缺乏管控,无法动态配
置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx
l Spring Cloud Gateway
Spring公司为了替换Zuul而开发的网关服务,将在下面具体介绍。
注意:**SpringCloud alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud**
概述gateway网关
Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。
优点:
l 性能强劲:是第一代网关Zuul的1.6倍
l 功能强大:内置了很多实用的功能,例如转发、监控、限流等
l 设计优雅,容易扩展.
缺点:
l 其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高
l 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行 web.Jar
l 需要Spring Boot 2.0及以上的版本,才支持
gateway内置了服务器 netty服务器。千万不要在使用tomcat作为服务器。
使用gateway网关
1.创建网关微服务
2.添加依赖
<dependencies>
<!--如果引入了gateway的依赖不能在引用spring-boot-starter-web,否则会报错。因为web内置了tomcat服务器,而gateway内置netty服务器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
3.配置文件
server:
port: 80
#spring:
# application:
# name: aaa-gateway
#配置路由转发
cloud:
gateway:
routes:
- id: aaa-product #路由id,没有实际意义。如果不定义UUID随机生成
uri: http://localhost:8001 # 表示路由真实转发的微服务的地址
predicates: #断言: 如果断言满足要求则转发到对应uri地址 http://localhost:80/product/getById/1==>http://localhost:8001/product/getByIid/1
- Path=/product/**
- id: aaa-order
uri: http://localhost:9001
predicates:
- Path=/order/**
测试
增强版
现在在配置文件中写死了转发路径的地址, 前面我们已经分析过地址写死带来的问题, 接下来我们从注册中心获取此地址。
思考: gateway网关它也是一个微服务,那么它也可以从注册中心拉取服务器清单列表。
引入依赖
<!--nacos的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
修改配置文件
认证校验
package com.aaa.filter;
import com.aaa.vo.UrlVo;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import static org.apache.logging.log4j.message.MapMessage.MapFormat.JSON;
@Component
public class LoginFilter implements GlobalFilter,Ordered {
@Autowired
private UrlVo urlVo;
@Override
public int getOrder() {
return 0;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//1.获取请求路径
String path = request.getPath().toString();
path = path.substring(1);
path = path.substring(path.lastIndexOf("/"));
//2.判断该路径是否属于放行路径--白名单
if (urlVo.getWhite().contains(path)){
return chain.filter(exchange);
}
//3.判断用户是否登录
String token = request.getHeaders().getFirst("token");
//4.校验token是否为空,以及是否合法
if (StringUtils.hasText(token)&&"admin".equals(token)){
return chain.filter(exchange);
}
//3.2封装返回数据
Map<String,Object> map = new HashMap<>();
map.put("msg", "未登录");
map.put("code", 501);
//3.3做json转换
byte[] bytes = com.alibaba.fastjson.JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8);
//3.4调用buffer Factory方法,生成DataBuffer对象
DataBuffer buffer = response.bufferFactory().wrap(bytes);
//4.调用Mono中的just方法,返回要写给前端的JSON数据
return response.writeWith(Mono.just(buffer));
}
}
跨域解决gateway
第一种:配置类
package com.aaa.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.cors.reactive.CorsWebFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter coreFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);//允许认证
config.addAllowedOrigin("*");//允许任何源
config.addAllowedHeader("*");//允许任何头
config.addAllowedMethod("*");//允许任何方法
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
第二种:通过配置文件
spring:
cloud:
gateway:
globalcors: #全局跨域处理
add-to-simple-url-handler-mapping: true
corsConfigurations:
'[/**]': #拦截一切请求
allowedOrigins: #允许哪些网站的跨域请求
- "http://localhost:8080"
allowedMethods: #允许的跨域ajax的请求方式
- "GET"
- "POST"
- "PUT"
- "DELETE"
- "OPTIONS"
allowedHeaders: "*" #允许在请求中携带的头信息,这里允许所有请求头
allowCredentials: true #是否允许携带cookie
maxAge: 3600000 #这次跨域检测的有效期