1. 回顾
1. Mycat完成分表操作。
1.mycat分表支持得规则有哪些?
2.mycat分表后join。
3.mycat分表后定义全局表。【全局表得特点】
2. springcloud微服务
1. 架构演变史
2. 微服务会带来哪些问题。
3. 微服务有哪些组件可以解决上面带来得问题。
4. 常用微服务架构?
5. 搭建微服务工程。
3. 架构演变史(了解)
单体应用架构---->垂直应用架构---->分布式架构----->SOA架构----->微服务架构
3.1 单体应用架构
把所有得功能都写在一个工程。这种架构就是单体架构。
优点:
项目架构简单,小型项目的话,开发成本低。
项目部署在一个节点上,维护方便. 项目部署比较简单
缺点:
全部功能集成在一个工程中,对于大型项目来讲不易开发和维护[修改代码]。
项目模块之间紧密耦合,单点容错率低。
无法针对不同模块进行针对性优化和水平扩展
3.2 垂直应用架构
随着访问量的逐渐增大,单一应用只能依靠增加节点来应对,但是这时候会发现并不是所有的模块 都会有比较大的访问量.
还是以上面的电商为例子, 用户访问量的增加可能影响的只是用户和订单模块, 但是对消息模块的影响就比较小. 那么此时我们希望只多增加几个订单模块, 而不增加消息模块. 此时单体应用就做不到了, 垂直应用就应运而生了.
所谓的垂直应用架构,就是将原来的一个应用拆成互不相干的几个应用,以提升效率。比如我们可 以将上面电商的单体应用拆分成:
电商系统(用户管理 商品管理 订单管理)
后台系统(用户管理 订单管理 客户管理)
CMS系统(广告管理 营销管理)
这样拆分完毕之后,一旦用户访问量变大,只需要增加电商系统的节点就可以了,而无需增加后台 和CMS的节点。建立三个工程。
优点:
系统拆分实现了流量分担,解决了并发问题,可以针对不同模块进行优化和水平扩展
一个系统的问题不会影响到其他系统,提高容错率
缺点:
系统之间相互独立, 无法进行相互调用
系统之间相互独立, 会有重复的开发任务.
3.3 分布式架构
当垂直应用越来越多,重复的业务代码就会越来越多。这时候,我们就思考可不可以将重复的代码 抽取出来,做成统一的业务层作为独立的服务(service),然后由前端控制层(controller)调用不同的业务层服务呢?
这就产生了新的分布式系统架构。它将把工程拆分成表现层controller和服务层service两个部分,服务层中包含业务 逻辑。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。
优点:
抽取公共的功能为服务层,提高代码复用性
缺点:
调用关系错综复杂,难以维护
3.4 SOA架构–dubbo[阿里-15年停更]
在分布式架构下,当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心对集群进行实时管理。此时,用于资源调度和治理中心(SOA Service Oriented Architecture,面向服务的架构)是关键。
优点:
使用注册中心解决了服务间调用关系的自动调节
缺点:
服务间会有依赖关系,一旦某个环节出错会影响较大( 服务雪崩 )
服务关系复杂,运维、测试部署困难。
3.5 微服务架构
微服务架构在某种程度上是面向服务的架构SOA继续发展的下一步,它更加强调服务的"彻底拆分"-而且每个服务都可以独立得运行---->必须要springboot(独立的系统) 必须依赖于springboot技术。Springcloud如果没有springboot 那么springcloud也无法使用。 springboot可以独立使用。因为springboot里面内置了tomcat 独立运行。Java -jar xxx.jar
优点:
l 服务原子化拆分,独立打包、部署和升级,保证每个微服务清晰的任务划分,利于扩展
l 微服务之间采用Restful等轻量级http协议相互调用
缺点: 小型项目----微服务架构不合适。仓库系统—微服务。
l 微服务系统开发的技术成本高《高》(容错、分布式事务等)
4. 微服务面临得问题
l 这么多小服务,如何管理他们?
l 这么多小服务,他们之间如何通讯?
l 这么多小服务,客户端怎么访问他们?
l 这么多小服务,一旦出现问题了,应该如何自处理?
l 这么多小服务,一旦出现问题了,应该如何排错?
4.1 提供相应得组件来解决上面得问题
nacos注册中心 来管理这些微服务
openfeign来完成服务之间得调用。
gateway网关–解决客户访问微服务。、
熔断器----解决服务自处理能力
链路追踪—追踪到相应错误得微服务。
5. 常用得微服务框架
ServiceComb
Apache ServiceComb,前身是华为云的微服务引擎 CSE (Cloud Service Engine)
云服务,是全球首个Apache微服务顶级项目。它提供了一站式的微服务开源解决方案,致力于帮助企业、用户和开发者将企业应用轻松微服务化上云,并实现对微服务应用的高效运维管理.
SpringCloud
它提供得很多组件都已经停止维护了。
Spring Cloud是一系列框架的集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring
Boot的开发风格做到一键启动和部署。
Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包
springcloud-alibaba微服务架构
它使用得组件大多是都是用得阿里巴巴。如果阿里没有提供,则spring公司就自己创建相应得组件并使用自己得。
6. 如何搭建springcloud微服务项目【重点】
模拟电商: ----商品微服务和订单微服务。
技术栈:
- springcloud-alibaba
- mybatis-plus 持久性框架
- mysql数据库5.7以上
- springboot来搭建每个微服务。
6.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>
<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>qy156-shop-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>qy156-shop-parent</name>
<description>Demo project for Spring Boot</description>
<!--定义版本号-->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF- 8</project.reporting.outputEncoding>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
</properties>
<!--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>
6.2 创建子模块-shop-common
把其他模块公共得代码放入到该模块。—实体 工具类
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
定义相关得实体类
@Data
@TableName(value = "shop_order")
public class Order {
//订单id
@TableId
private Long oid;
//用户id
private Integer uid;
//用户名
private String username;
//商品id---购买时99---->活动结束后199
private Long pid;
//商品得名称
private String pname;
//商品得价格
private Double pprice;
//购买得数量
private Integer number;
}
@Data
@TableName("shop_product")
public class Product {
@TableId
private Long pid;
private String pname;
private Double pprice;
private Integer stock;
}
6.3 创建子模块–shop-product
负责对商品表所有操作----提供了所有对于商品操作得接口
<dependencies>
<!--引入公共模块-->
<dependency>
<groupId>com.aaa</groupId>
<artifactId>shop-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--引入springboot-starter-web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
配置文件
# 定义端口号 [8001~8009 未来方便搭建集群]
server:
port: 8001
#数据源得信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?serverTimezone=Asia/Shanghai
username: root
password: root
# mybatis打印日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
dao接口
/**
* @program: shop-parent
* @description:
* @create: 2022-11-17 16:22
**/
public interface ProductDao extends BaseMapper<Product> {
}
service代码
@Service
public class ProductService implements IProductService {
@Autowired
private ProductDao productDao;
@Override
public Product findById(Long pid) {
return productDao.selectById(pid);
}
}
controller:
@RestController
@RequestMapping("product")
public class ProductController {
@Autowired
private IProductService productService;
@GetMapping("/getById/{pid}")
public Product getById(@PathVariable Long pid){
return productService.findById(pid);
}
}
主启动类
@SpringBootApplication
@MapperScan(basePackages = "com.aaa.product.dao")
public class ProductApp {
public static void main(String[] args) {
SpringApplication.run(ProductApp.class,args);
}
}
测试:
6.4 创建子模块–shop-order
关于订单表得所有操作接口
<dependencies>
<!--引入公共模块-->
<dependency>
<groupId>com.aaa</groupId>
<artifactId>shop-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--引入springboot-starter-web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
配置
#端口号---[9001~9009]集群模式
server:
port: 9001
#数据源得信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?serverTimezone=Asia/Shanghai
username: root
password: root
# sql显示在控制台
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
dao接口
package com.aaa.order.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper
public interface OrderDao extends BaseMapper<OrderDao> {
}
service代码
@Service
public class OrderService implements IOrderService {
@Autowired
private OrderDao orderDao;
@Override
public int save(Order order) {
return orderDao.insert(order);
}
}
controller代码
package com.aaa.order.controller;
import com.aaa.entity.Order;
import com.aaa.entity.Product;
import com.aaa.order.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @program: shop-parent
* @description:
* @create: 2022-11-17 16:33
**/
@RestController
@RequestMapping("/order")
public class OrderController {
//必须创建并交于spring容器管理。这样才能被注入到相应的类属性上
@Autowired
private RestTemplate restTemplate;
@Autowired
private IOrderService orderService;
@GetMapping("/saveOrder")
public String saveOrder(Long pid,Integer num){
Order order=new Order();
//用户得信息可以从登录后获取--Session redis jwt
order.setUid(1);
order.setUsername("张成");
//为订单对象设置商品得信息
order.setPid(pid);
//需要远程调用商品微服务中的指定接口[注意:凡是关于商品的操作都有商品微服务来执行。]
//远程调用的方式:第一种基于TCP协议的RPC远程调用 第二种基于http协议Restful风格的调用。
//分布式架构:TCP协议的
//微服务架构:http协议的。---在spring框架中封装了一个工具RestTemplate。 如果不是使用的spring框架。你需要自己封装HttpClient工具
Product product = restTemplate.getForObject("http://localhost:8001/product/getById/"+pid, Product.class);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(num);
orderService.save(order);
return "下单成功";
}
}
思考: 上面我们写远程调用代码 是否存在缺陷?
[1] 我们把商品微服务地址写死在自己代码中 硬编码—如果商品微服务地址发生改变。需要修改我们自己当前微服务的代码
[2] 如果商品微服务 搭建了集群模式。 订单微服务这边如何调用相应的商品微服务从而达到负载均衡的特性。
服务器治理组件。
eureka:当作服务治理组件。—>netflix公司的产品—停止更新维护
zookeeper: 服务治理组件。---->dubbo分布式框架配合
nacos: 服务治理组件。------>阿里巴巴的产品。
7. 服务治理组件
7.1 原理
7.2 如何使用nacos
https://github.com/alibaba/nacos/releases
nacos1.3以后支持了集群模式。1.3以前不支持。
安装nacos服务端。
必须按照jdk并配置环境变量。而且不能把nacos放入中文目录
启动nacos
访问nacos服务器
7.3 微服务客户端连接到nacos注册中心
(1)引入依赖
<!--引入nacos的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)修改配置文件
测试:
7.4 消费端如何通过nacos调用提供者。
引入nacos依赖和配置nacos地址
修改控制层代码
package com.aaa.order.controller;
import com.aaa.entity.Order;
import com.aaa.entity.Product;
import com.aaa.order.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @program: shop-parent
* @description:
* @create: 2022-11-17 16:33
**/
@RestController
@RequestMapping("/order")
public class OrderController {
//必须创建并交于spring容器管理。这样才能被注入到相应的类属性上
@Autowired
private RestTemplate restTemplate;
@Autowired
private IOrderService orderService;
//在nacos中封装了一个类DiscoveryClient,该类可以获取注册中心中指定的清单列表。
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/saveOrder")
public String saveOrder(Long pid,Integer num){
Order order=new Order();
//用户得信息可以从登录后获取--Session redis jwt
order.setUid(1);
order.setUsername("张成");
//为订单对象设置商品得信息
order.setPid(pid);
//获取指定的实例
List<ServiceInstance> instances = discoveryClient.getInstances("shop-product");
ServiceInstance serviceInstance = instances.get(0);
// String path = serviceInstance.getHost().toString();
// System.out.println(path+"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
// Integer port = serviceInstance.getPort();
// System.out.println(port+"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
String uri = serviceInstance.getUri().toString();
// System.out.println(uri+"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
Product product = restTemplate.getForObject(uri+"/product/getById/"+pid, Product.class);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(num);
orderService.save(order);
return "下单成功";
}
}
如果后期 提供者的地址发生改变,也不影响消费者的代码。
思考: 上面使用从nacos拉取服务清单的模式是否存在问题?
没有实现负载均衡的问题。如果后期商品微服务在部署时 是一个集群。调用者应该把请求均摊到每个服务器上。
8. 负载均衡
通俗的讲, 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。
模拟搭建多个商品微服务。
人为的完成订单微服务调用商品微服务负载均衡的特性。
package com.aaa.order.controller;
import com.aaa.entity.Order;
import com.aaa.entity.Product;
import com.aaa.order.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
/**
* @program: shop-parent
* @description:
* @create: 2022-11-17 16:33
**/
@RestController
@RequestMapping("/order")
public class OrderController {
//必须创建并交于spring容器管理。这样才能被注入到相应的类属性上
@Autowired
private RestTemplate restTemplate;
@Autowired
private IOrderService orderService;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/saveOrder")
public String saveOrder(Long pid,Integer num){
Order order=new Order();
//用户得信息可以从登录后获取--Session redis jwt
order.setUid(1);
order.setUsername("张成");
//为订单对象设置商品得信息
order.setPid(pid);
//在nacos中封装了一个类DiscoveryClient,该类可以获取注册中心中指定的清单列表。
//获取指定的实例
List<ServiceInstance> instances = discoveryClient.getInstances("shop-product");
//随机产生一个下标--0~size
int index = new Random().nextInt(instances.size());
ServiceInstance serviceInstance = instances.get(index);
// String path = serviceInstance.getHost().toString();
// System.out.println(path+"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
// Integer port = serviceInstance.getPort();
// System.out.println(port+"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
String uri = serviceInstance.getUri().toString();
// System.out.println(uri+"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
Product product = restTemplate.getForObject(uri+"/product/getById/"+pid, Product.class);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(num);
orderService.save(order);
return "下单成功";
}
}
如何想改变负载均衡的策略 需要修改源代码。变成轮询策略。—硬编码问题。【开闭原则】
提供了ribbon组件—该组件可以完成负载均衡。
8.2 ribbon
是 Netflix 发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中, nacos一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从nacos中读 取到的服务信息,在调用服务节点提供的服务时,会合理(策略)的进行负载。 在SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务。
Ribbon自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务
如何使用ribbon—不需要再引入任何依赖
只需要再RestTemplate获取的bean上添加一个LoadBalance注解
修改controller代码
测试发现默认使用的是轮询策略。ribbon是否可以改变为其他策略。而ribbon中提供了很多策略。
如何使用相应的策略:
shop-product: # 这里使用服务的名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #使用的的负载均衡策略
如果上面的策略不够用,你也可以自定义策略类。实现IRule接口完成自定义策略。
总结
ribbon组件中提供了很多负载均衡的策略。我们也可以自定义负载均衡的策略。
8.3 自定义负载均衡策略
不管任何一个负载均衡,都是IRule接口的子类。
我们自定义的规则类 也必须继承AbastractLoadBalancerRule类。
需求:
要求自定义的算法:依旧是轮询策略,但是每个服务器被调用5次后轮到下一个服务,即以前是每个服务被调用1次,现在是每个被调用5次 .
(1)自定义规则类—模拟原来有的类。
package com.aaa.order.rule;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancer;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
/**
* @program: shop-parent
* @description:
* @create: 2022-11-19 14:42
**/
public class MyRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
//初始化方法 读取配置文件内容
}
//统计访问的次数
private int total;
//作为集群服务器下标
private int index;
@Override
public Server choose(Object key) {
//获取负载均衡选择器
ILoadBalancer lb = getLoadBalancer();
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
//获取所有可用的服务器
List<Server> upList = lb.getReachableServers();
//获取所有的服务器。
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//判断该服务访问的次数是否>5次
if(total<5){
server=upList.get(index);
total++;
}else{
total=0;
index++;
index=index%upList.size();
}
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
}
(2)创建一个配置类,该类用于创建上面的bean对象
@Configuration
public class RuleConfig {
@Bean
public MyRule myRule(){
return new MyRule();
}
}
(3)ribbon使用上面自定义的规则
9. 远程调用组件-Openfeign
我们上面服务与服务之间的调用,使用的为RestTemplate工具类,来完成相应的调用。但是RestTemplate这种模式不符合我们编程的习惯。
dao—service—controller:再service类中注入dao对象,然后调用dao中的方法,并传入相关的参数,以及接受相关的返回类型。
9.1 概述
OpenFeign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务一样简单, 只需要创建一个接口并添加一个注解即可。
Nacos很好的兼容了OpenFeign, Feign负载均衡默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。
9.2 如何使用openfeign组件
(1)引入相关的依赖
<!--引入openfeign的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(2)创建feign接口
value:调用远程微服务的名称
@FeignClient(value = "shop-product")
public interface ProductFeign {
@GetMapping("/product/getById/{pid}")
public Product getById(@PathVariable Long pid);
}
(3)开启feign注解的驱动
(4)使用自定义的feign接口
package com.aaa.order.controller;
import com.aaa.entity.Order;
import com.aaa.entity.Product;
import com.aaa.order.feign.ProductFeign;
import com.aaa.order.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
/**
* @program: shop-parent
* @description:
* @create: 2022-11-17 16:33
**/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private IOrderService orderService;
//spring容器会为该接口生成带来实现类。
@Autowired
private ProductFeign productFeign;
@GetMapping("/saveOrder")
public String saveOrder(Long pid,Integer num){
Order order=new Order();
//用户得信息可以从登录后获取--Session redis jwt
order.setUid(1);
order.setUsername("张恒");
//为订单对象设置商品得信息
order.setPid(pid);
//就像调用本地方法一样
Product product = productFeign.getById(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(num);
orderService.save(order);
return "成功";
}
}
10. nacos集群模式
如果nacos单机出现故障,导致所有微服务服务注册和拉取相应的服务信息。从而导致整个项目无法使用。
注意: nacos1.3以后才允许集群的搭建。必须停止你虚拟机的网卡
条件: 8849 8850 8851 这三台nacos集群。
(1)指定mysql存储源
由于nacos三台服务器的数据应该保持同步,所以我们需要把微服务的信息统一存储到mysql数据库。默认存储在内存中。
(2)修改端口号conf/application.properties
(3) 修改
(4)复制三份nacos服务器
(5)启动三台nacos集群
10.2 微服务注册到nacos集群。
思考: 客户端微服务 到底写哪个nacos服务器地址。如果给定一个 万一该nacos服务器宕机。你需要重写修改微服务
连接其他nacos服务器。我们可以使用nginx—代理后面的nacos集群服务器。
总结:
自定义负载均衡策略
openfeign组件完成微服务之间的调用
搭建了nacos注册中心集群
eureka实验一下
11. eureka作为注册中心 【扩展】
它是netflix公司生成的注册中心组件----但是该组件已经停止更新了。 有些微服务老项目还在使用eureka注册中心。
(1) eureka需要自己创建服务端程序。
(2)依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
(3)修改配置类
# 端口号
server:
port: 7001
# eureka的配置
eureka:
instance:
hostname: localhost
# 该服务是否从注册中心拉取服务器
client:
fetch-registry: false
register-with-eureka: false
# 对外暴漏的地址
service-url:
defaultZone: http://localhost:7001/eureka/
(3)主启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class,args);
}
}
(4)访问eureka界面
(5)其他微服务注册到eureka服务器上
[1]引入eureka客户端的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
[2]加入相关配置
#指定eureka服务端的地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
[3]查看
总结
12. 网关
这样的架构,会存在着诸多的问题:
l 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性
l 认证复杂,每个服务都需要独立认证。
l 存在跨域请求,在一定场景下处理相对复杂。
- 路由转发。
- 身份认证。
- 统一跨域解决。
- 黑白名单ip
- 敏感词
- 限流
12.2. 常用的网关
- nginx—它可以当网关 【】
- zuul----早期的微服务就是使用的该组件作为网关,但是它的底层使用的servlet. 它的效率非常慢。而且它是netflix的产品。 netflix预计产品zuul2, 但是zuul2夭折。
- springcloud gateway—它是spring公司出品的网关。它的效率是zuul的1.6倍。
12.3. springcloud gateway
Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。
12.4. 如何使用
其实网关 它也是一个微服务,那么我们也可以创建网关微服务。
<dependencies>
<!--这里引入了gateway的依赖后,不能引用spring-boot-starter-web依赖。
因为:gateway它使用的是netty服务器。
spring-boot-starter-web里面内置了tomcat服务器.
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
(2)创建主启动类
@SpringBootApplication
public class GatewayApp {
public static void main(String[] args) {
SpringApplication.run(GatewayApp.class,args);
}
}
(3)修改配置文件
# 配置路由
spring:
cloud:
gateway:
routes:
- id: shop-product #路由的唯一标识。如果没有给定默认按照UUID生成
uri: http://localhost:8001 #真实转发的地址
predicates: # 断言 如果断言满足要求,则转发到uri指定的真实地址.
- Path=/product/** # 如果客户的请求路径以product开头,则满足该断言要求,则转发的uri真实地址。
- id: shop-order
uri: http://localhost:9001
predicates:
- Path=/order/**
(4)启动gateway
(5)演示
12.5. gateway负载均衡转发
spring:
cloud:
gateway:
routes:
- id: shop-product #路由的唯一标识。如果没有给定默认按照UUID生成
uri: http://localhost:8001 #真实转发的地址
predicates: # 断言 如果断言满足要求,则转发到uri指定的真实地址.
- Path=/product/** # 如果客户的请求路径以product开头,则满足该断言要求,则转发的uri真实地址。
- id: shop-order
uri: http://localhost:9001
predicates:
- Path=/order/**
有没有需要改进的?
我们真实转发的地址,万一搭建是一个集群。 我们观察到gateway本身也是一个微服务,是否可以从注册中心拉取相关的微服务,然后访问该服务呢。
(1)引入nacos注册中心的依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)修改配置文件
java.lang.IllegalArgumentException: Param ‘serviceName’ is illegal,
serviceName is blank
别忘记为服务起名称
12.6. 简洁版
# 配置路由
spring:
cloud:
gateway:
routes:
- id: shop-product #路由的唯一标识。如果没有给定默认按照UUID生成
uri: lb://shop-product #真实转发的地址 lb: ---loadbalanced
predicates: # 断言 如果断言满足要求,则转发到uri指定的真实地址.
- Path=/product/** # 如果客户的请求路径以product开头,则满足该断言要求,则转发的uri真实地址。
- id: shop-order
uri: lb://shop-order
predicates:
- Path=/order/**
思考: 如果这时增加新的微服务, 需要修改网关的路由配置。
改为自动路由发现。
(1)修改gateway的配置文件
(2)访问网关
12.7. gateway流程
12.7.1 断言的种类
l 基于Datetime类型的断言工厂
此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
-After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
l 基于远程地址的断言工厂 RemoteAddrRoutePredicateFactory*:*
接收一个IP地址段,判断请求主机地址是否在地址段中
-RemoteAddr=192.168.1.1/24
l 基于Cookie的断言工厂
CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求
cookie是否具有给定名称且值与正则表达式匹配。
-Cookie=chocolate, ch.
l 基于Header的断言工厂
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否
具有给定名称且值与正则表达式匹配。 key value
-Header=X-Request-Id, \d+
l 基于Host的断言工厂
HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。
-Host=**.testhost.org
l 基于Method请求方法的断言工厂
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
-Method=GET
l 基于Path请求路径的断言工厂
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
-Path=/foo/{segment}基于Query请求参数的断言工厂
QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具
有给定名称且值与正则表达式匹配。
-Query=baz, ba.
l 基于路由权重的断言工厂
WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发
routes:
-id: weight_route1 uri: host1 predicates:
-Path=/product/**
-Weight=group3, 1
-id: weight_route2 uri: host2 predicates:
-Path=/product/**
-Weight= group3, 9
如果上面的内置断言无法满足需求 可以自定义断言。【了解】
案例: 年龄必须在18~65之间才能访问我指定的微服务。
自定义断言类
package com.aaa.predicate;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotNull;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* @program: shop-parent
* @description:
* @create: 2022-11-21 16:27
**/
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
public AgeRoutePredicateFactory() {
super(AgeRoutePredicateFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("minAge", "maxAge");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return (serverWebExchange)->{
ServerHttpRequest request = serverWebExchange.getRequest();
//获取传递的年龄
String age = request.getHeaders().getFirst("age");
if(StringUtils.hasText(age)){
int a = Integer.parseInt(age);
if(a>=config.getMinAge()&&a<=config.getMaxAge()){
return true;
}
}
return false;
};
}
@Validated
public static class Config {
@NotNull
private int minAge;
@NotNull
private int maxAge;
public int getMinAge() {
return minAge;
}
public void setMinAge(int minAge) {
this.minAge = minAge;
}
public int getMaxAge() {
return maxAge;
}
public void setMaxAge(int maxAge) {
this.maxAge = maxAge;
}
}
}
总结
自定义断言
gateway网关—路由转发
ribbon: 实现负载均衡
openfeign: 完成服务之间的调用。
nacos: 注册中心
12.8. gateway中的过滤器
为请求到达微服务前可以添加相应的请求设置, 响应后为响应结果添加一些设置。
gateway内部含有很多种过滤。
https://www.cnblogs.com/zhaoxiangjun/p/13042189.html
举例: StripPrefix 用于截断原始请求的路径。
测试:
12.9. 我们也可以自定义全局过滤器。
例子: 认证过滤。
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的认证校验。
开发中的鉴权逻辑:
l 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
l 认证通过,将用户信息进行加密形成token[jwt],返回给客户端,作为登录凭证
l 以后每次请求,客户端都携带认证的token [携带请求头]
l 服务端对token进行解密,判断是否有效。
package com.aaa.filter;
import com.alibaba.fastjson.JSON;
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.HttpStatus;
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;
/**
* @program: shop-parent
* @description:
* @create: 2022-11-22 15:07
**/
@Component
public class LoginFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//判断请求路径是否为放行。
String path = request.getPath().toString();
if("/login".equals(path)){
return chain.filter(exchange);//放行
}
//获取请求头的token值。
String token = request.getHeaders().getFirst("token");
if(StringUtils.hasText(token)){
//校验token是否有效
if("admin".equals(token)){
return chain.filter(exchange);//放行
}
}
//3.1设置状态码
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//3.2封装返回数据
Map<String, Object> map = new HashMap<>();
map.put("msg", "未登录");
map.put("code", "NOTLOGING");
//3.3作JSON转换
byte[] bytes = JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8);
//3.4调用bufferFactory方法,生成DataBuffer对象
DataBuffer buffer = response.bufferFactory().wrap(bytes);
//4.调用Mono中的just方法,返回要写给前端的JSON数据
return response.writeWith(Mono.just(buffer));
}
//优先级 值越小优先级越高
@Override
public int getOrder() {
return 0;
}
}
12.10. 完成统一的跨域解决
第一种通过配置文件
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedHeaders: "*"
allowedMethods: "*"
default-filters:
- DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST
第二种写一个配置类
@Configuration
public class MyCorsConfiguration {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
return new CorsFilter(configSource);
}
}
总结:
1. gateway完成全局过滤器--认证过滤器
2. 跨域---并通过前端进行验证。
13. 配置中心。
nacos—配置中心
apollo—配置中心
nacos配置中心
使用nacos作为配置中心,其实就是将nacos当做一个服务端,将各个微服务看成是客户端,我们将各个微服务的配置文件统一存放在nacos上,然后各个微服务从nacos上拉取配置即可。
注意: 我们需要在微服务bootstrap配置文件中指定配置中心的地址。
bootstrap和application配置文件的区别。
bootstrap和application都是SpringBoot项目中的配置文件,他们的区别主要有以下的几个方面
(1)加载顺序区别
bootstrap配置文件是比application配置文件优先加载的,因为bootstrap是由spring父上下文加载,而application是由子上下文加载
(2)优先级区别
bootstrap加载的配置信息是不能被application的相同配置覆盖的,如果两个配置文件同时存在,也是以bootstrap为主
(3)应用场景区别
bootstrap常见应用场景
1.配置一些固定的,不能被覆盖的属性.用于一些系统级别的参数配置
本地的配置文件是默认不能覆盖远程的配置的
2.一些需要加密/解密的场景
3.当你使用了nacos配置中心时,这时需要在boostrap配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息、
如何在微服务中使用nacos配置中心。
<!--引入nacos配置中心的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
创建bootstrap配置文件
#指定nacos配置中心的地址---必须在bootstrap配置文件中指定。
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
我们需要在配置中心中创建配置文件
编写ProductController类
@Value("${student.name}")
private String name;
@GetMapping("getInfo")
public String getInfo(){
return "姓名:"+name;
}
13.2 多个微服务共享公共配置
(1)可以把一些公共配置内容单独抽取
(2)微服务中使用公共的配置
13.3 nacos配置中心中常用的概念
命名空间(Namespace) (开发环境 测试环境 生产环境)
每种环境 他们的配置内容也是不同的。
命名空间可用于进行不同环境的配置隔离。一般一个环境划分到一个命名空间
配置分组(Group) (区分的项目)
仓库系统 电商系统 金融系统 物流系统
配置分组用于将不同的服务可以归类到同一分组。一般将一个项目的配置分到一组
配置集(Data ID)
在系统中,一个配置文件通常就是一个配置集。一般微服务的配置就是一个配置集
14. 链路追踪技术
链路追踪的介绍
在大型系统的微服务化构建中,一个系统被拆分成了许多微服务。这些模块负责不同的功能,组合成系统,最终可以提供丰富的功能。在这种架构中,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心【区域】,也就意味着这种架构形式也会存在一些问题:
l 如何快速发现问题?
l 如何判断故障影响范围?
l 如何梳理服务依赖以及依赖的合理性?
l 如何分析链路性能问题以及实时容量规划?
定位该请求的故障—发生在哪个微服务上。
理念: 在每个微服务节点上–安装一个GPS. —程序中----日志。我们在通过查看日志 就可以定位响应的错误。
收集-----展示到一个统一的界面。
第一个就是在每个微服务上 帮你产生日志记录。
第二个技术就是收集这些日志,并能展示到一个图形化界面上。
14.1 Sleuth
它的作用就是为微服务产生日志记录。
Trace (一次请求的完整链路–包含很多span(微服务接口))
由一组Trace Id(贯穿整个链路)相同的Span串联形成一个树状结构。为了实现请求跟踪,当请求到达分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的标识(即TraceId),同时在分布式系统内部流转的时候,框架始终保持传递该唯一值,直到整个请求的返回。那么我们就可以使用该唯一标识将所有的请求串联起来,形成一条完整的请求链路。
Span
代表了一组基本的工作单元。为了统计各处理单元的延迟,当请求到达各个服务组件的时候,也通过一个唯一标识(SpanId)来标记它的开始、具体过程和结束。通过SpanId的开始和结束时间戳,就能统计该span的调用时间,除此之外,我们还可以获取如事件的名称。请求信息等元数据。
Annotation
用它记录一段时间内的事件,内部使用的重要注释:
l cs(Client Send)客户端发出请求,开始一个请求的命令
l sr(Server Received)服务端接受到请求开始进行处理, sr-cs = 网络延迟(服务调用的时间)
l ss(Server Send)服务端处理完毕准备发送到客户端,ss - sr = 服务器上的请求处理时间
l cr(Client Reveived)客户端接受到服务端的响应,请求结束。 cr - cs = 请求的总时间
如何使用sleuth
在微服务中引入sleuth依赖—整个父工程中
<!--引入sleuth依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
</dependencies>
浏览器
整个请求链路–响应的时间是500多毫秒。 是哪个台服务器出现故障。 可以通过sleuth产生的日志进行查看,自己计算时间戳来查看。 这种方案十分麻烦。
我们通过zipkin收集sleuth产生的日志 并以图形化展示。
14.2 zipkin
作用: 收集微服务上sleuth产生的日志 并以图形化的模式来展示给客户。
zipkin有服务端【服务器】和客户端【微服务】
运行zipkin服务端
java -jar zipkin-server-2.12.9-exec.jar
微服务接入zipkin
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
作用:
使用配置–做微服务的配置管理
sleuth+zipkin 完成链路追踪—定位错误