将项目所有模块(功能)打成jar或者war,然后部署一个进程
优点:
1:部署简单:由于是完整的结构体,可以直接部署在一个服务器上即可。
2:技术单一:项目不需要复杂的技术栈,往往一套熟悉的技术栈就可以完成开发。缺点:
1:系统启动慢,一个进程包含了所有的业务逻辑,涉及到的启动模块过多,导致系统的启动、重启时间周期过长;
2:系统错误隔离性差、可用性差,任何一个模块的错误均可能造成整个系统的宕机;
3:可伸缩性差:系统的扩容只能只对这个应用进行扩容,无法结合业务模块的特点进行伸缩。
4: 线上问题修复周期长:任何一个线上问题修复需要对整个应用系统进行全面升级。
5: 跨语言程度差
6: 不利于安全管理,所有开发人员都拥有全量代码。小型项目适合单体应用架构. 比如:OA办公系统。管理类的项目 仓库管理系统。
微服务架构论文:
https://martinfowler.com/articles/microservices.html
译文:
https://blog.csdn.net/qq_42261668/article/details/102839758In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.
简单来说,微服务架构风格[1]是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式的管理,服务可用不同的语言开发,使用不同的数据存储技术。
解读微服务特点:
1:微服务是一种项目架构思想(风格)
2:微服务架构是一系列小服务的组合(组件化与多服务)
3:任何一个微服务,都是一个独立的进程(独立开发、独立维护、独立部署)
4:轻量级通信http协议(跨语言,跨平台)
5:服务粒度(围绕业务功能拆分—模块拆分【系统管理服务】【日志服务】【焦虑测试】【抑郁测试系统】)
6:去中心化管理(去中心化"地治理技术、去中心化地管理数据)
1.易于开发和维护
一个微服务只关注一个特定的业务功能,所以它的业务清晰、代码量较少。开发和维护单个微服务相对比较简单,整个应用是由若干个微服务构建而成,所以整个应用也会维持在可控状态;⒉.单个微服务启动较快
单个微服务代码量较少,所以启动会比较快;3.局部修改容易部署
单体应用只要有修改,就要重新部署整个应用,微服务解决了这样的问题。一般来说,对某个微服务进行修改,只需要重新部署这个服务即可;4.技术栈不受限 在微服务中,我们可以结合项目业务及团队的特点,合理地选择技术栈
5.按需伸缩
焦虑系统访问量大,只需要对焦虑系统进行扩展
1、服务太多,导致服务间的依赖错综复杂,运维难度大
2、微服务放大了分布式架构的系列问题- 分布式事务(seata)
- 分布式锁怎么处理(redisson) ,
- 服务注册与发现(nacos) .
- 依赖服务不稳定(sentinel)导致服务雪崩怎么办?
3、运维复杂度陡增,部署数量多、监控进程多导致整体运维复杂度提升。
springcloud来解决上面微服务面临的挑战
- Springcloud为微服务思想提供了完美的解决方案
- Springcloud是一些列框架的集合体(服务的注册与发现【注册中心】、服务间远程调用、服务降级、服务熔断、服务限流、分布式事务等)
一般我们说springc1oud 其实指的是Springc1oud-netflix,Springcloud并不是造轮子,只是把Netflix公司的组件做二次开发。
我们本次是使用的电商项目中的商品微服务、订单微服务为案例进行讲解。
父工程不写东西,src:可以删除
<?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">
<modelVersion>4.0.0</modelVersion>
<!--引入父依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
<groupId>com.aaa</groupId>
<artifactId>qy163-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--父工程那么它的打包方式必须为pom打包-->
<packaging>pom</packaging>
<!--定义版本号-->
<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>
<!--管理了cloud版本-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--alibaba的版本-->
<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>
</project>
<?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>qy163-parent</artifactId>
<groupId>com.aaa</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>qy163-common</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
</dependencies>
</project>
package com.aaa.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
@Data
@TableName(value = "shop_product")
public class Product {
@TableId(value="pid",type = IdType.AUTO )
private Integer pid;
private String pname;
//小数类型必须使用BigDecimal,不要用double(会精度丢失)
private BigDecimal pprice;
private Integer stock;
}
package com.aaa.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
@Data
@TableName(value = "shop_order")
public class Order {
@TableId(value="oid",type = IdType.AUTO )
private Integer oid;
private Integer uid;
private String username;
private Integer pid;
private String pname;
private BigDecimal pprice;
private Integer number;
}
<?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>qy163-parent</artifactId>
<groupId>com.aaa</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>qy163-product</artifactId>
<dependencies>
<dependency>
<groupId>com.aaa</groupId>
<artifactId>qy163-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
#端口号:8080~8089 []
server.port=8080
#数据源
spring.datasource.url=jdbc:mysql://localhost:3306/springcloud-product?serverTimezone=Asia/Shanghai
spring.datasource.password=123456
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#打印sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
package com.aaa.product;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.aaa.product.dao")
public class ProductApp {
public static void main(String[] args) {
SpringApplication.run(ProductApp.class,args);
}
}
package com.aaa.product.dao;
import com.aaa.pojo.Product;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface ProductDao extends BaseMapper<Product> {
}
package com.aaa.product.service;
import com.aaa.pojo.Product;
public interface ProductService {
//根据id查询商品信息
public Product findById(Integer pid);
}
package com.aaa.product.service.impl;
import com.aaa.pojo.Product;
import com.aaa.product.dao.ProductDao;
import com.aaa.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductDao productDao;
@Override
public Product findById(Integer pid) {
Product product = productDao.selectById(pid);
return product;
}
}
package com.aaa.product.controller;
import com.aaa.pojo.Product;
import com.aaa.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("getById/{pid}")
public Product getById(@PathVariable Integer pid){
Product product = productService.findById(pid);
return product;
}
}
<?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>qy163-parent</artifactId>
<groupId>com.aaa</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>qy163-order</artifactId>
<dependencies>
<dependency>
<groupId>com.aaa</groupId>
<artifactId>qy163-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
#端口号:8090~8099 []
server.port=8090
#数据源
spring.datasource.url=jdbc:mysql://localhost:3306/springcloud-order?serverTimezone=Asia/Shanghai
spring.datasource.password=123456
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#打印sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
package com.aaa.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.aaa.order.dao")
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
package com.aaa.order.dao;
import com.aaa.pojo.Order;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface OrderDao extends BaseMapper<Order> {
}
package com.aaa.order.service;
import com.aaa.pojo.Order;
public interface OrderService {
public Integer save(Order order);
}
package com.aaa.order.service.impl;
import com.aaa.order.dao.OrderDao;
import com.aaa.order.service.OrderService;
import com.aaa.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Override
public Integer save(Order order) {
return orderDao.insert(order);
}
}
package com.aaa.order.controller;
import com.aaa.order.service.OrderService;
import com.aaa.pojo.Order;
import com.aaa.pojo.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
//该类默认没有交给spring容器管理
@Autowired
private RestTemplate restTemplate;
@GetMapping("save")
public String save(Integer pid,Integer num){
Order order = new Order();
//用户信息--登录中获取
order.setUid(1);
order.setUsername("懒羊羊");
order.setNumber(num);
//商品信息的设置: 远程调用商品微服务的接口。 基于Http协议进行远程调用。
//两种基于http调用的方式: 第一种: 可以使用httclient工具jar 自己封装工具类
// 第二种方式: spring框架中提供了一个工具类。RestTemplate.
Product product = restTemplate.getForObject("http://localhost:8080/product/getById/" + pid, Product.class);
if(product==null){
return "下单失败";
}
order.setPid(product.getPid());
order.setPname(product.getPname());
order.setPprice(product.getPprice());
orderService.save(order);
return "下单成功";
}
}
服务治理介绍
先来思考一个问题
通过上一章的操作,我们已经可以实现微服务之间的调用。但是我们把服务提供者的网络地址(ip,端口)等硬编码到了代码中,这种做法存在许多问题:
l 一旦服务提供者地址变化,就需要手工修改代码
l 一旦是多个服务提供者,无法实现负载均衡功能
l 一旦服务变得越来越多,人工维护调用关系困难
那么应该怎么解决呢, 这时候就需要通过注册中心动态的实现服务治理。什么是服务治理
服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册与发现。
服务注册:在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服务的详细信息。并在注册中心形成一张服务的清单,服务注册中心需要以心跳30s 90s的方式去监测清单中 的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。
服务发现:服务调用方向服务注册中心咨询服务,并获取所有服务的实例清单,实现对具体服务实例的访问。
架构师 项目经理
eureka: 它是netflix公司提供的一款组件,这款组件已经停止更新。
nacos: 它是阿里巴巴提供的一款服务治理组件。
zookeeper: 它是apache公司提供的服务治理组件。
consul: 服务治理的组件
3.2 安装nacos治理组件
https://github.com/alibaba/nacos/releases
(qy163-product–>pom.xml)
<!--nacos依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(qy163-product–>application.properties)
#注册中心的地址
spring.cloud.nacos.discovery.server-addr=localhost:8848
(qy163-product–>application.properties)
#起名字 单词之间使用-划线
spring.application.name=qy163-product
(qy163-order–>pom.xml)
<!--nacos依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(qy163-order–>application.properties)
#注册中心的地址
spring.cloud.nacos.discovery.server-addr=localhost:8848
(qy163-order–>application.properties)
#起名字 单词之间使用-划线
spring.application.name=qy163-order
(qy163-order–>OrderController)
通俗的讲, 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。
思考: 上面的负载均衡,是不是自己写的代码。缺点: 代码比较复杂。 如果想改变复杂均衡的策略,必须都是修改order的代码。
基于Ribbon实现服务调用, 是通过拉取到的所有服务列表组成(服务名-请求路径的)映射关系。借助 RestTemplate 最终进行调用
演示: ribbon默认采用的是轮询策略。 是否可以修改负载均衡策略。
Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为 com.netflix.loadbalancer.IRule , 具体的负载策略如下图所示:
(qy163-order–>application.properties)
修改order的配置文件#修改ribbon负载均衡策略 qy163-product.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule #qy163-product:表示对访问哪个微服务设负载均衡策略 #com.netflix.loadbalancer.RandomRule 负载均衡策略
总结:ribbon完成了从nacos自动拉取服务,并完成负载均衡功能。
上面服务之间的调用 使用的是RestTemplate. 使用Resttemplate远程调用,它不符合我们的编程习惯。Controller—Service—Dao 再Controller注入一个service对象,然后调用service中的方法,根据方法传递相应的参数以及接受方法的返回的结果。
我们可以使用Openfeign组件来完成。
OpenFeign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务一样简单, 只需要创建一个接口并添加一个注解即可。
Nacos很好的兼容了OpenFeign, OpenFeign负载均衡默认集成了 Ribbon, 所以在Nacos下使用OpenFegin默认就实现了负载均衡的效果。
总结: openfeign就是为了完成服务的远程调用,它就像调用本地方法一样。
(qy163-order–>pom.xml)
org.springframework.cloud
spring-cloud-starter-openfeign
(qy163-order–>ProductFeign )
package com.aaa.order.feign;
import com.aaa.pojo.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
//@FeignClient(value = "服务提供者的名字")
@FeignClient(value = "qy163-product")
public interface ProductFeign {
//抽象方法一定要和服务提供者(qy163-product-->controller-->ProductController)中的接口方法一模一样
@GetMapping("/product/getById/{pid}") //url+@getMapping中的内容: http://qy163-product/product/getById/
public Product getById(@PathVariable Integer pid);
}
(qy163-order–>OrderController)
package com.aaa.order.controller;
import com.aaa.order.feign.ProductFeign;
import com.aaa.order.service.OrderService;
import com.aaa.pojo.Order;
import com.aaa.pojo.Product;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
//该类默认没有交给spring容器管理
@Autowired
private RestTemplate restTemplate;
@Autowired
private ProductFeign productFeign;
//在spring中提供了一个类DiscoveryClient 该类可以从注册中心nacos 拉取指定的服务清单
/* @Autowired
private DiscoveryClient discoveryClient;*/
@GetMapping("save")
public String save(Integer pid,Integer num){
//springcloud 扫描到@FeignClient注解后,就会为该注解接口生成一个代理实现类,再把代理实现类注册该相应的属性
System.out.println("`````````````````"+productFeign+"`````````````````");
Order order = new Order();
//用户信息--登录中获取
order.setUid(1);
order.setUsername("懒羊羊");
order.setNumber(num);
/*List instances = discoveryClient.getInstances("qy163-product");
//随机负载均衡
int i = new Random().nextInt(instances.size());
ServiceInstance serviceInstance = instances.get(i);
//ServiceInstance serviceInstance = instances.get(0);
//System.out.println(serviceInstance.getUri());
//System.out.println(serviceInstance.getHost());
String path = serviceInstance.getUri().toString();*/
//商品信息的设置: 远程调用商品微服务的接口。 基于Http协议进行远程调用。
//两种基于http调用的方式: 第一种: 可以使用httclient工具jar 自己封装工具类
// 第二种方式: spring框架中提供了一个工具类。RestTemplate.
//Product product = restTemplate.getForObject(path + "/product/getById/" + pid, Product.class);
//Product product = restTemplate.getForObject("http://qy163-product/product/getById/" + pid, Product.class);
Product product = productFeign.getById(pid);
if(product==null){
return "下单失败";
}
order.setPid(product.getPid());
order.setPname(product.getPname());
order.setPprice(product.getPprice());
orderService.save(order);
return "下单成功";
}
}
它的官网没有提供下载包。只能自己创建eureka的服务端。
(qy163-eureka-server–>pom.xml)
<?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>qy163-parent</artifactId>
<groupId>com.aaa</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>qy163-eureka-server</artifactId>
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>
(qy163-eureka-server–>application.properties)
#端口号
server.port=7001
eureka.instance.hostname=localhost
#是否把该服务注册到eureka服务上
eureka.client.register-with-eureka=false
#是否该服务从注册中心eureka上拉取服务
eureka.client.fetch-registry=false
#微服务访问eureka访问的地址
eureka.client.service-url.defaultZone=http://localhost:7001/eureka/
security.basic.enabled=false
(qy163-eureka-server–>EurekaApp)
其他微服务可以注册到eureka
eureka.client.service-url.defaultZone=http://localhost:7001/eureka/
修改配置文件
package com.aaa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class,args);
}
}
(qy163-product–>qy163-eureka-server)
(qy163-product–>pom.xml)
(qy163-product–>application.properties)