文章目录
写在前面
由于spring cloud体系的部分组件已经闭源或停止维护,
spring cloud alibaba是一个更好的解决方案,且性能较好
1、系统架构的演变
单体应用架构 --> 垂直应用架构 --> 分布式架构 --> SOA架构 --> 微服务架构。
后面还有一个正在悄然兴起的service mesh(服务网格化)。
1、单体和垂直的对比:
2、分布式架构和SOA架构对比
3、微服务架构(在soa的基础上将服务分得更彻底)
2、开始搭建环境
模块间调用关系
新建maven父工程mycloud:
父工程不需要src文件夹,删掉
添加依赖(这里主要注意springboot、springcloud、springcloud alibaba版本要对应)
spring cloud alibaba的父工程和子工程的pom.xml 配置可参考:https://blog.csdn.net/a__int__/article/details/111996865
<!-- 父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<version>2.1.13.RELEASE</version>
<artifactId>spring-boot-starter-parent</artifactId>
</parent>
<!-- 版本依赖的锁定 -->
<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>Greenwich.RELEASE</spring-cloud.version>
<spring-cloud-alibaba.version>2.1.3.RELEASE</spring-cloud-alibaba.version>
</properties>
<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>
注意springboot和spring cloud alibaba 的版本要匹配
具体参考官方:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
2.1、shop-common
在父工程里创建模块shop-common(里面只放实体类)
需要的相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
</dependencies>
新建三个实体类
User
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
// 用户
@Entity(name = "shop_user")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer uid;
private String username;
private String password;
private String telephone;
}
Product
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
// 用户
@Entity(name = "shop_product")
@Data
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer pid;
private String pname;
private Double pprice; // 价格
private String stock; // 库存
}
Order
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
// 用户
@Entity(name = "shop_order")
@Data
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long oid;
// 用户
private Integer uid;
private String username;
// 商品
private Integer pid;
private Double pprice; // 价格
private String pname;
// 数量
private Integer number;
}
2.2、shop-user、shop-product、shop-order
在父工程里创建模块shop-user
引入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.haha</groupId>
<artifactId>shop-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
这里面上一个模块的引入需要注意
新建内容与结构如下
配置文件application.yaml (记得在数据库新建库shop)
server:
port: 8071
spring:
application:
name: service-user
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Hongkong
username: root
password: root
jpa:
hibernate:
ddl-auto: update
show-sql: true
UserApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
剩下几个java文件
同理shop-product、shop-order两个板块内容几乎的shop-uer一样
配置文件里需要注意一下
shop-product和shop-order中
引入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.haha</groupId>
<artifactId>shop-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2.3、不使用服务治理,直接调用
这里以客户下单购买东西为例:shop-order调用shop-product
然后为product里面添加一个findByPid()的查询
接口ProductService.java
public interface ProductService {
Product findByPid(Integer pid);
}
ProductServiceImpl.java
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductDao productDao;
@Override
public Product findByPid(Integer pid) {
return productDao.findById(pid).orElse(null);
}
}
ProductController.java
@RestController
@Slf4j
public class ProductController {
@Autowired
private ProductService productService;
//商品信息查询
@RequestMapping("/product/{pid}")
public Product product(@PathVariable("pid") Integer pid){
log.info("接下来要进行{}号商品信息的查询",pid);
Product product = productService.findByPid(pid);
log.info("查询结果{}", JSON.toJSONString(product));
return product;
}
}
然后记得在数据库里面新建一个叫shop的库,之后运行ProductApplication
会看到库里自动生成了这些表
往product表里添加写数据
测试一下127.0.0.1:8081/product/1
接下来往订单微服务shop-order中添加代码
先在OrderApplication.java里面添加RestTemplate,用来调用shop-product的接口
接下来通过pid调用产品,然后生成订单
接口OrderService.java
public interface OrderService {
void createOrder(Order order);
}
OrderServiceImpl.java
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Override
public void createOrder(Order order) {
orderDao.save(order);
}
}
OrderController.java
@RestController
@Slf4j
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderService orderService;
// 下单
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid){
log.info("接收到{{}号商品的下单请求,接下来调用微服务查询",pid);
//调用,查询商品信息
Product product = restTemplate.getForObject(
"http://127.0.0.1:8081/product/"+pid,Product.class
);
log.info("查询到{}号商品,内容{}",pid, JSON.toJSONString(product));
// 下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
orderService.createOrder(order);
log.info("创建订单成功,订单:{}",JSON.toJSONString(order));
return order;
}
}
然后启动OrderApplication.java
注意这时是两个服务都在启动着
访问http://127.0.0.1:8091/order/prod/1
通过RestTemplate成功调用product的服务
这样调用发现的几个缺点:
1、服务ip、端口需要手动填写
2、其中一个服务掉了不容易发现
3、nacos 服务治理(注册中心)
nacos为微服务间提供服务治理,相当于服务的注册中心
nacos下载地址:https://github.com/alibaba/nacos/releases/tag/1.3.2
下载完进入bin目录
cmd进入nacos\bin目录下,输入启动指令:startup.cmd -m standalone
然后进入浏览器访问:http://127.0.0.1:8848/nacos/index.html(账号密码都是nacos)
接下来在两个微服务上安装nacos-cline
下面几个步骤消费者服务者都需要
第一步添加依赖
<!--nocos客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
第二步在主类上添加注解
第三步:在application.yaml配置nacos服务地址(shop-order和shop-product都需要)
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
配置完之后如果运行报错,可能是springboot和springcloud alibaba版本不匹配
第四步:启动shop-order和shop-product微服务,就能看看服务列表了
接下来开始使用调用
首先在OrderController注入DiscoveryClient,将ip和端口用DiscoveryClient获取
@Autowired
private DiscoveryClient discoveryClient;
.
.
.
List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
ServiceInstance instance = instances.get(0);
.
.
.
Product product = restTemplate.getForObject(
"http://"+instance.getHost()+":"+instance.getPort()+"/product/"+pid ,Product.class
);
然后重新启动项目,就可以进行服务调用了
4、Ribbon 负载均衡(接口选择)
现在我们要将shop-product启动两个微服务,注册到注册中心
在idea里找到Edit Configurations…–>springboot -->填写name、Main class、VM options
然后启动ProductionApplication2
相当于现在有三个微服务在运行
相当于现在有两个product服务,接下来我们让order随机选择调用其中一个微服务
4.1、自定义负载均衡
int index = new Random().nextInt(instances.size());
然后重新启动就可以了
4.2、基于Ribbon负载均衡
第一步:在restTemplate()上添加注解@LoadBalanced
第二步:把 “ IP+端口 ” 改为服务名称service-product
然后重启就可以了
4.3、Ribbon负载均衡 策略调整
ribbon总共有七种负载均衡策略
策略类 | 描述 |
---|---|
RandomRule 随机策略 | 随机选择server |
RoundRobinRule 轮询策略 | 按照顺序选择server(ribbon默认策略) |
RetryRule 重试策略 | 在一个配置时间段内,当选择server不成功,则一直尝试选择一个可用的server |
BestAvailableRule 最低并发策略 | 逐个考察server,如果server断路器打开,则忽略,再选择其中并发链接最低的server |
AvailabilityFilteringRule 可用过滤策略 | 过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值) |
ResponseTimeWeightedRule 响应时间加权重策略 | 根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间 |
ZoneAvoidanceRule 区域权重策略 | 综合判断server所在区域的性能,和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server |
默认使用的是RoundRobinRule 轮询策略
在application.yaml里面配置策略
service-product:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
重启OrderApplication试一下
5、Fegin http客户端(接口调用工具)
Nacos很好的兼容了fegin,fegin内部集成了Ribbon。
第一步:往shop-order的pom.xml加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:往OrderApplication.java添加fegin的注解@EnableFeignClients
第三步:添加一个ProductFeignService
import com.haha.domain.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "service-product")
public interface ProductFeignService {
@RequestMapping("/product/{pid}")
public Product findByPid(@PathVariable Integer pid);
}
第四步:修改OrderController
重启运行试试
6、Sentinel 服务容错(解决高并发问题)
下面我们来模拟测试高并发
6.1、jmeter高并发测试工具 安装、启动
需要用到工具jmeter,下载地址:https://jmeter.apache.org/
下载完解压,进入其bin目录,修改jmeter.properties
在大概39行下面添加language=zh_CN
然后双击jmeter.bat启动
6.2、修改OrderController模拟高并发
先在shop-order中修改application.yaml配置tomcat的线程为10
再将OrderController复制一个OrderController2
注释掉OrderController的@RestController
修改OrderController2内容如下
package com.haha.controller;
import com.alibaba.fastjson.JSON;
import com.haha.domain.Order;
import com.haha.domain.Product;
import com.haha.service.OrderService;
import com.haha.service.ProductFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class OrderController2 {
@Autowired
private OrderService orderService;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private ProductFeignService productFeignService;
// 下单
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid){
log.info("接收到{{}号商品的下单请求,接下来调用微服务查询",pid);
//调用,查询商品信息
Product product = productFeignService.findByPid(pid);
//模拟调用商品微服务需要2秒
try{
Thread.sleep(20001);
}catch (InterruptedException e){
e.printStackTrace();
}
log.info("查询到{}号商品,内容{}",pid, JSON.toJSONString(product));
// 下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
// 暂时不写入数据库
// orderService.createOrder(order);
log.info("创建订单成功,订单:{}",JSON.toJSONString(order));
return order;
}
@RequestMapping("/order/msg")
public String msg(){
return "测试高并发";
}
}
重启OrderApplication.java
先试试能访问不
6.3、jmeter模拟高并发
先添加一个线程组
线程数20、循环100次
在该线程组下创建一个取样器
填写请求内容
再添加一个监听器
然后点击启动
然后再发起访问
我们发现访问响应速度慢了很多
6.4、Sentinel 服务容错思路(可跳过)
参考文章:https://www.jianshu.com/p/3c1b8562e95b
这就是高并发带来的问题,会带来服务雪崩,也就是整个服务链上的服务可能都会堵塞在这里。
对于这样的问题无法从源头上解决,但是我们可以做好足够的容错机制,
保证一个服务出现问题时,不会影响其他服务。
常见的容错思路有隔离、超时、限流、熔断、降级 这几种。
1、隔离,常见的隔离方式有:线程池隔离和信号量隔离。
2、超时,获取服务时超出时间就断开线程。
3、限流,限制系统的输入和输出流量。
4、熔断,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。
服务熔断三种状态:
- 熔断关闭状态(Closed):服务没有故障时,熔断器关闭。
- 熔断开启状态(Open):后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法
- 半熔断状态(Half-Open):尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。
5、降级,就是使用备用方案。
常见的容错组件:Sentinel、Resilience4J、Hystrix
6.5、Sentinel 开始使用
现在我们要做下单功能,也就是使用order调用product,所以在上游服务order中部署Sentinel
进入shop-order
1、添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、然后我们把OrderController2的@RestController也注释掉,新建一个OrderController3
OrderController3内容如下
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class OrderController3 {
@RequestMapping("/order/msg1")
public String msg1(){
return "测试高并发1";
}
@RequestMapping("/order/msg2")
public String msg2(){
return "测试高并发2";
}
}
3、下载并启动sentinel 控制台
https://github.com/alibaba/Sentinel/releases
如果从github上下载太慢,可以直接百度jar包的名字,一般有网站可以下载
启动指令
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.jar
浏览器访问登陆:http://localhost:8080/
用户名、密码都是sentinel
sentinel 的版本1.8依赖关系:fastjson 升级到 1.2.71、nacos-client 升级到 1.3.0( sentinel-datasource-nacos 模块)
4、在shop-order配置文件中加入配置
spring:
cloud:
sentinel:
transport:
port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可
dashboard: localhost:8080 # 指定控制台服务的地址
然后重启order微服务(nacos保持启动)
访问:localhost:8091/order/msg1 (必须至少访问一次,sentinel才能发现它)
如果想关闭sentinel的懒加载在配置中输入
##取消Sentinel控制台懒加载,即项目启动即连接
sentinelspring.cloud.sentinel.eager = true
接下来为接口http://localhost:8091/order/msg2添加一个限流
接下来如果我们不断刷新访问这个端口,就会发现被流控了
6.6、Sentinel 的一些概念
资源:Sentinel 要保护的东西
规则:以什么样的方式保护资源
容错的三个核心思想:
- 1、保证自己不被上游压垮
- 2、保证自己不被下游拖垮
- 3、保证外界环境良好
容错的三个核心思想实现措施:
1、流量控制:将随机的请求调整成合适的排队请求。(针对资源)
比如第一分钟有8个请求进来,第二分钟有2个请求进来,流量控制可以把它分成第一分钟请求5个,剩下3个放到第二分钟才让进来
2、熔断降级:让有故障或不稳定的请求快速失败。(针对资源)
- 控制并发线程数让熔断降级
- 控制响应时间让熔断降级
3、系统负载保护:集群环境下,本系统承载过高,转移流量到其他系统(针对系统)