文章目录
- 搭建springcloud微服务项目
- 1. 微服务父工程
- 2. 创建子模块-shop-common
- 3. 创建子模块--shop-product
- 4. 创建子模块--shop-order
- 服务治理组件
- 1. 如何使用nacos
- 2. 微服务客户端连接到nacos注册中心
- 3. 消费端如何通过nacos调用提供者
- 负载均衡
- 1. 自己控制负载均衡
- 2. ribbon完成负载均衡
- 2.1 如何使用ribbon
- 2.2 自定义负载均衡策略
- 远程调用组件-Openfeign
- 1. 概述
- 2. 如何使用openfeign组件
搭建springcloud微服务项目
技术栈:
- springcloud-alibaba
- mybatis-plus 持久性框架
- mysql数据库5.7以上
- springboot来搭建每个微服务。
1. 微服务父工程
把其他的全删掉,只保留一个pom文件
<?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>
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;
}
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: qy156-shop-parent
* @description:
* @author: 闫克起
* @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);
}
}
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;
/**
* @program: qy156-shop-parent
* @description:
* @author: 闫克起
* @create: 2022-11-18 14:34
**/
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);
}
}
配置类中注入restTemplate
@SpringBootApplication
public class AppOrder {
public static void main(String[] args) {
SpringApplication.run(AppOrder.class, args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
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: qy156-shop-parent
* @description:
* @author: 闫克起2
* @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: 服务治理组件。---->阿里巴巴的产品。
这里使用nacos
服务治理组件
1. 如何使用nacos
https://github.com/alibaba/nacos/releases
nacos1.3以后支持了集群模式。1.3以前不支持。
安装nacos服务端。
必须安装jdk并配置环境变量。而且不能把nacos放入中文目录
bin目录下startup.cmd启动nacos
访问nacos服务器
2. 微服务客户端连接到nacos注册中心
(1)引入依赖
<!--引入nacos的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)修改配置文件
测试:
3. 消费端如何通过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: qy156-shop-parent
* @description:
* @author: 闫克起2
* @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拉取服务清单的模式是否存在问题?
- 没有实现负载均衡的问题。如果后期商品微服务在部署时 是一个集群。调用者应该把请求均摊到每个服务器上。
负载均衡
通俗的讲, 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。
模拟搭建多个商品微服务。
1. 自己控制负载均衡
人为的完成订单微服务调用商品微服务负载均衡的特性。
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: qy156-shop-parent
* @description:
* @author: 闫克起2
* @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 uri = serviceInstance.getUri().toString();
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 "下单成功";
}
}
这两行是控制使用哪个微服务
//随机产生一个下标--0~size
int index = new Random().nextInt(instances.size());
ServiceInstance serviceInstance = instances.get(index);
如果想改变负载均衡的策略,例如想变成轮询策略,就需要来这里修改源代码,硬编码问题【开闭原则】
提供了ribbon组件—该组件可以完成负载均衡。
2. ribbon完成负载均衡
ribbon是 Netflix 发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中,nacos一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从nacos中读取到的服务信息,在调用服务节点提供的服务时,会合理(策略)的进行负载。
在SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自动的从注册中心中获取服务提供者的列表信息,并基于内置的负载均衡算法,请求服务。
Ribbon自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务
2.1 如何使用ribbon
不需要再引入任何依赖
只需要再RestTemplate获取的bean上添加一个LoadBalance注解
@LoadBalanced是告诉RestTemplate使用ribbon完成负载均衡。自动从注册中心拉取服务。使用内置的负载均衡策略完成服务的调用。
修改controller代码
测试发现默认使用的是轮询策略。ribbon是否可以改变为其他策略。而ribbon中提供了很多策略。
策路名 | 策略描述 | 实现说明 |
---|---|---|
BestAvailableRule | 选择─个最小的并发请求的server | 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server |
RandomRule | 随机选择一个server | 在index上随机。选择index对应位置的server |
RoundRobinRule | 轮询方式轮询选择 | 轮询index,选择index对应位置的server |
AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为circuittripped的后端server ,并过滤掉那些高并发的的后端server (active connections超过配置的阈值) | 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态 |
WeightedResponseTimeRule | 根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。 | 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择server。 |
RetryRule | 对选定的负载均衡策略机上重试机制。 | 在一个配置时间段内当选择server不成功,则—直尝试使用subRule的方式选择一个可用的server |
ZoneAvoidanceRule | 复合判断server所在区域的性能和server的可用性选择server | 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前—个判断判定一个zone的运行性能是否可用,别除不可用的zone (的所有server) , AvailabilityPredicate用于过滤掉连接数过多的Server。 |
如何使用相应的策略:
shop-product: # 这里使用服务的名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #使用的的负载均衡策略
如果上面的策略不够用,你也可以自定义策略类。实现IRule接口完成自定义策略。
如果上面的策略不够用,你也可以自定义策略类。实现IRule接口完成自定义策略。
2.2 自定义负载均衡策略
不管任何一个负载均衡,都是IRule接口的子类。
我们自定义的规则类 也必须继承AbastractLoadBalancerRule类。
需求:
要求自定义的算法:依旧是轮询策略,但是每个服务器被调用5次后轮到下一个服务,即以前是每个服务被调用1次,现在是每个被调用5次 .
自定义规则类—模拟原来有的类。
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: qy156-shop-parent
* @description:
* @author: 闫克起2
* @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使用上面自定义的规则
远程调用组件-Openfeign
我们上面服务与服务之间的调用,使用的为RestTemplate工具类,来完成相应的调用。但是RestTemplate这种模式不符合我们编程的习惯。
dao----service----controller:在service类中注入dao对象,然后调用dao中的方法,并传入相关的参数,以及接受相关的返回类型。
1. 概述
OpenFeign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务一样简单, 只需要创建一个接口并添加一个注解即可。
Nacos很好的兼容了OpenFeign, Feign负载均衡默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。
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: qy156-shop-parent
* @description:
* @author: 闫克起2
* @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 "成功";
}
}
如果nacos单机出现故障,导致所有微服务服务注册和拉取相应的服务信息。从而导致整个项目无法使用。
所以就需要给nacos搭建集群模式
链接:xxx