SpringCloud
单体应用
项目所有的资源都在一个应用中,打包成一个war包,使用一个tomcat去运行,运行在一个进程中
单体应用的优点
- 部署相对分布式比较简单
- 项目前期搭建比较快
- 项目规模小时,性能比较高
单体应用的缺点
- 一个模块挂了,整个项目都受影响
- 单个tomcat更能处理的并发有限,可以做集群,但是不方便局部(某一个模块)扩展
- 维护/开发/升级比较麻烦
- 代码臃肿,编译,打包都比较慢
- 技术选型单一
- 数据库选型单一
微服务(分布式)
1.什么是微服务
将一个大的应用拆分成多个小的应用(服务),这些小的应用相对独立,每个小的应用都有自己的容器(Tomcat),有自己的运行进程,这些小的应用通过网络协议(HTTP Rest)进行相互通信,所有的应用一起工作完成整个项目的业务。
2.微服务优缺点
2.1.优点
1.方便局部扩展
2.技术选型多样化
3.单个微服务复杂性低
4.单个微服务容易开发和维护
5.服务与服务之间相对松耦合
6.数据库选型多样化(分库)
7.当项目规模大,微服务整体来说性能好
2.2.缺点
1.微服务之间数据交互速度受网络影响
2.技术成本高
3.开发成本高
4.整个项目总体来看,比较复杂
5.微服务部署比较麻烦
五大组件
- 注册中心Eureka : 管理微服务的通信地址
- 配置中心Config : 管理微服务的配置文件
- 网关:zuul :微服务的访问入口
- 负载均衡:Ribbon/Feign :微服务之间的请求以及负载均衡
- 断路器:hystirx : 解决微服务故障问题
1.注册中心原理
注册中心用来管理服务的通信地址,当一个微服务启动时,会向注册中心提交自己的通信地址,注册中心会生成一个通信地址服务清单,各个微服务也会获取到这个清单,当一个微服务调用另一个微服务时,该服务会在通信地址服务清单中找到另一个服务的通信地址,发送http请求调用
搭建注册中心
1.创建父工程
1.1springcloud-parent管理依赖
pom.xml
<!--1.SpringBoot的父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<!-- 3. 抽取公共的内容-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!--所有子模块一定要用到的公共的jar包-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<!--2.管理SpringCloud的jar包-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.创建注册中心模块
springcloud-eureka-server-3000
2.1.springcloud-eureka-server-3000
pom.xml导入依赖
spring-cloud-starter-netflix-eureka-server包括了eureka的客户端包所以在配置文件中需要配置不让注册中心注册自己
<dependencies>
<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>
</dependencies>
2.2.application.yml配置文件
server:
port: 3000
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false #禁用注册中心向自己注册
fetchRegistry: false #不让注册中心获取服务的注册列表
serviceUrl:
defaultZone: http://localhost:3000/eureka/
#注册中心的注册地址 ,其他微服务需要向这个地址注册
2.3.主配置类
@SpringBootApplication
@EnableEurekaServer//开启注册中心
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class);
}
}
通过http://localhost:3000/访问
3.创建用户模块springcloud-producer-users-server-1000
3.1.导入依赖
<dependencies>
<!--导入eureka客户端的jar包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 集成Web的jar包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
注意:如果出现jar下载不了,一直爆红
1.子模块没有继承父模块
2.父模块中没有依赖springboot的父模块
3.父模块中spring-cloud-dependencies依赖包有问题
4.maven仓库有问题
5.网络问题也会导致下载不了jar包
3.2.application.yml配置文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/ #注册中心地址
server:
port: 1000 #当前端口
spring:
#应用名称
application:
name: user-server
3.3.配置类
@SpringBootApplication
@EnableDiscoveryClient//开启注册中心客户端,可以不用打
public class UsersApplication {
public static void main(String[] args) {
SpringApplication.run(UsersApplication.class);
}
}
3.创建支付模块springcloud-consumer-pay-server-2000
3.1.导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3.2.application.yml配置文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/ #注册中心地址
instance:
prefer-ip-address: true #使用ip地址注册
instance-id: pay-server:2000 #服务注册到注册中心的id
server:
port: 2000
spring:
application:
name: pay-server #应用名称
3.3.配置类
@SpringBootApplication
@EnableDiscoveryClient
public class PayServerApplication {
public static void main(String[] args) {
SpringApplication.run(PayServerApplication.class);
}
}
4.启动注册
最后将注册中心,用户模块,支付模块分别启动,通过http://localhost:3000/
访问
5.注册中心的集群
单点故障:如果只有一个EurekaSever,如果EurekaSever挂了那么整个微服务都不可用;
所以我们要做注册中心的集群;
5.1.本机模拟两个注册中心地址
1.设置本机域名
C:\Windows\System32\drivers\etc\hosts
在hosts中配置
5.2.修改springcloud-eureka-server-3000配置文件
使用springboot多环境配置方式,配置两个注册中心,让它们相互调用,可以防止单点故障;
#使用SpringBoot多环境配置的方式来配置 2个 注册中心
#主配置
spring:
profiles:
active: peer1 #你激活谁,启动的时候就是用的谁的配置
---
#第一个EurekaServer的配置
spring:
profiles: peer1
application:
name: eureka-server #服务名称
eureka:
instance:
hostname: peer1
prefer-ip-address: true #使用id地址注册
instance-id: eureka-server:3000 #服务注册到注册中心的id
client:
serviceUrl:
defaultZone: http://peer2:3001/eureka/ #要跟上端口号
server:
port: 3000
---
#第二个EurekaServer的配置
spring:
profiles: peer2
application:
name: eureka-server
eureka:
instance:
hostname: peer2
prefer-ip-address: true
instance-id: eureka-server:3001
client:
serviceUrl:
defaultZone: http://peer1:3000/eureka/
server:
port: 3001
5.3.修改用户模块和支付模块的配置文件
由于有两个注册中心,所以要在微服务中配置两个域名访问;
启动一个注册中心后,配置中更改激活另一个注册中心,再启动,最后再启动用户模块和支付模块;
负载均衡–Ribbon
在某个服务做了集群
之后,再去请求要将请求分发到集群的服务,但是一个请求只能访问一个路径,所以需要做负载均衡器,将请求分发,通过负载均衡算法去调用集群中某一个路径;
一般都在消费者(pay)中集成Ribbon
1.导包、新建user用户集群
<!--集成ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2.配置类
修改 RestTemplate的@Bean定义的方法,加上 @LoadBalanced开启负载均衡
3.Controller层
因为配置了负载均衡,所以访问的路径不在是目标服务的端口,而是目标服务的服务名
//String url = "http://localhost:1000/users/"+id;
String url = "http://user-server/users/"+id;
2.自定义负载均衡算法
Ribbon默认的负载均衡算法是轮询,可以自定义算法为随机等等。。。
在配置类中
@Bean//更改负载均衡算法为随机 默认是轮询
public IRule randomRule() {
return new RandomRule();
}
Feign
Feign的底层就是Ribbon,是对Ribbon的再次封装
- 集成Feign
1.新建模块springcloud-comsumer-order-server-2020
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.itsource</groupId>
<artifactId>springcloud-user-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--集成Feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.准备一个Feign的接口
@FeignClient(value = "目标服务的服务名称")
表示UserFeign 这个接口是Feign的客户端
@FeignClient(value = "user-server")//表示UserFeign 这个接口是Feign的客户端
public interface UserFeign {//定义一个接口作为Feign的客户端接口,用来调用服务
@GetMapping("/users/{id}")//路径是目标服务的controller层的完整路径
User getUsers(@PathVariable("id") Long id);
}
3.配置类中开启Feign
@EnableFeignClients
开启Feign的客户端
@SpringBootApplication
@EnableFeignClients//开启Feign客户端
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class);
}
}
断路器–Hystrix
Hystrix当一个服务发生故障是用来做隔离的
-
资源隔离(限流):包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
⇒ 线程池隔离就是一次只允许规定的线程数量进行访问
⇒ 信号量隔离就是每次请求一次就会计数一次,当到达指定数量时,就是将请求隔离 -
熔断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。
-
降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
-
缓存:提供了请求缓存、请求合并实现。
1.雪崩效应
当一个微服务发生故障,所以与这个微服务相关的服务都会发生故障,所以就需要断路器来防止这类事情发生;
2.Ribbon集成Hystrix
- 在消费者中导入jar包
<!--集成Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 配置类
加上@EnableCircuitBreaker注解,开启Hystrix - controller中
在需要断路保护的方法上加上@HystrixCommand(fallbackMethod = "getUsersFallBack")
,@HystrixCommand表示发生故障需要降级的方法,fallbackMethod表示发生故障执行的方法 - 写一个返回托底数据的方法
方法名要和fallbackMethod 中属性指向的方法名一致,形参列表要和需要保护的方法一致
public User getUsersFallBack(@PathVariable("id") Long id) {
return new User(-1L, "无效","无法访问");
}
3.Feign集成Hystrix
只要导入了Feign的jar包就不需要导Hystrix的jar包了,因为Feign的jar包中集成了Hystrix的包
- application.yml配置
feign:
hystrix:
enabled: true #开启熔断支持
- 在集成Feign时的自定义接口中
fallback = UserFeignFallBack.class,表示发生故障回调的托底类的字节码文件
@FeignClient(value = "user-server",fallback = UserFeignFallBack.class)//表示这个接口是Feign的客户端
public interface UserFeign {//定义一个接口作为Feign的客户端接口,用来调用服务
@GetMapping("/users/{id}")//路径是目标服务的controller层的完整路径
User getUsers(@PathVariable("id") Long id);
}
- 自定义托底类
@Component//交给spring管理
//自定义托底的类,哪一个需要托底就实现哪一个的Feign接口
//UserFeign 集成Feign时的自定义接口
public class UserFeignFallBack implements UserFeign{
//实现其中的方法就是托底方法
@Override
public User getUsers(Long id) {
return new User(-1L,"无效","暂不可用");
}
}
zuul网关
zuul作为微服务群的请求入口,保护着微服务的安全,可以通过zuul实现,统一的权限校验,限流,日志,监控,负载均衡(请求分发)等功能
新建springcloud-zuul-server-4000项目
1.导包
<!--导入zuul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.配置类
在配置类中打@EnableZuulProxy标签,表示开启zuul
@SpringBootApplication
@EnableZuulProxy//开启zuul
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class);
}
}
3.application.yml中配置
zuul作为独立的应用,也需要注册到注册中心,获取其他微服务的请求路径
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心地址
server:
port: 4000 #当前端口
spring:
#应用名称
application:
name: zuul-server
4.访问
通过localhost:4000/order-server/order/users/1访问
但是发现访问路径中暴露了目标服务的服务名称,所以需要设置服务名称的别名
- 在zuul的application.yml中配置
zuul:
ignored-services: "*" #禁止浏览器 使用服务名的方式去访问目标服务
routes:
pay-server: "/pay/**" # pay-server这个服务使用 /pay路径去访问
order-server: "/order/**"
访问方式: zuul的ip:zuul端口/目标服务的访问路径/资源路径
最后通过http://localhost:4000/order/order/users/1
访问
5.zuul的工作流程
- 浏览器发送请求到zuul
- zuul从请求路径中找到目标服务的服务名称
- 通过Ribbon发起远程调用,根据资源路径获取数据
6.zuul的权限校验
自定义类继承ZuulFilter实现其中方法
//自定义过滤器
@Component
public class LoginFilter extends ZuulFilter {
//返回的类型
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
//执行顺序 数字越小,越先执行
@Override
public int filterOrder() {
return 1;
}
//判断是否执行run方法,false表示不执行
@Override
public boolean shouldFilter() {
//获取请求头
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
//获取URI
String requestURI = request.getRequestURI();
//判断 如果登录请求就不做检查
if (StringUtils.hasLength(requestURI) && requestURI.endsWith("login")) {
return false;
}
return true;
}
//过滤器的核心方法
@Override
public Object run() throws ZuulException {
//获取请求头
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
HttpServletResponse response = currentContext.getResponse();
//设置编码
response.setContentType("application/json;charset=utf-8");
//获取请求头中的token
String token = request.getHeader("token");
if (!StringUtils.hasLength(token)) {
//如果请求头中没有token,就返回登录信息
Map<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("msg", "请登录...");
try {
//响应信息给前台
response.getWriter().print(JSON.toJSONString(result));
//阻止请求继续执行
currentContext.setSendZuulResponse(false);
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
config分布式配置中心
微服务架构中,每个项目都有一个yml配置,管理起来麻烦。所以使用spring cloud config来统一管理。
1.新建模块springcloud-config-server-5000、导包
<!--eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--webjar包支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--配置中心服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
2.配置类
@EnableConfigServer
开启配置中心
@SpringBootApplication
@EnableConfigServer//开启config配置中心
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class);
}
}
3.更改配置文件存放位置
springcloud-comsumer-order-server-2020的配置文件为例
- 在码云上新建仓库,存放我们的配置文件,将以前的application.yml中的配置添加到仓库新建文件中,会生成一个链接;
- 将springcloud-comsumer-order-server-2020的application.yml文件删除,新建bootstrap.yml文件,因为bootstrap.yml优先级比application.yml的高,在服务启动时就从配置中心读取文件;
- bootstrap.yml配置
spring:
cloud:
config:
uri: http://localhost:5000 #本地配置中心的地址
name: springcolud-order #码云上的配置文件名字
profile: dev #测试环境
4.springcloud-config-server-5000的配置
配置中心和注册中心的配置文件是不能提交到码云上的;
- application.yml配置
eureka:
client:
service-url:
defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心服务端的注册地址
instance:
prefer-ip-address: true #使用ip进行注册
instance-id: config-server:5000 #服务注册到注册中心的id
#端口号
server:
port: 5000
#服务名称
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: *** #码云上复制的链接
username: *** #码云注册账号
password: *** #密码
通过http://localhost:5000/springcolud-order-dev.yml查看
- 流程:服务启动时,配置中心通过链接去获取配置文件,然后其他服务根据服务名去本地配置中心拉取自己的配置文件;
微服务之间数据通信–负载均衡
springcloud-user-common
因为一个服务调用另一个服务,需要两边都要有对应的获取数据的类,
所以需要提取一个公共的domain,各个服务添加依赖即可
public class User {
private Integer id;
private String username;
private String password;
....
}
springcloud-producer-users-server-1000
1.集成DataSource和Mybatis
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<!-- mysql 数据库驱动. -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--集成mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!--依赖公共的user-->
<dependency>
<groupId>cn.itsource</groupId>
<artifactId>springcloud-user-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2.application.yml配置文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心地址
server:
port: 1000 #当前端口
spring:
#应用名称
application:
name: user-server
datasource:
username: root
password: 123456
url: jdbc:mysql:///test
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
mapper-locations: classpath:cn/itsource/mapper/*Mapper.xml
3.准备mapper、service
public interface UserMapper {
//查询所有
List<User> findAll();
//查询一条数据
User findById(Long id);
}
4.userMapper.xml
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.itsource.mapper.UserMapper">
<select id="findAll" resultType="cn.itsource.domain.User">
SELECT * FROM user
</select>
<select id="findById" resultType="cn.itsource.domain.User">
SELECT * FROM user WHERE id=#{id}
</select>
</mapper>
6.配置类
@SpringBootApplication
@EnableDiscoveryClient//开启注册中心客户端,可以不用打
@MapperScan("cn.itsource.mapper")//扫描映射器对应接口的包
public class UsersApplication {
public static void main(String[] args) {
SpringApplication.run(UsersApplication.class);
}
}
7.Controller层
@GetMapping 组合标签,是@RequestMapping(method = RequestMethod.GET)的缩写。
@RestController
public class UserController {
@Autowired
private IUserService userService;
//查询全部数据
@GetMapping("/user")
public List<User> getUser() {
List<User> list = userService.findAll();
return list;
}
//查询一条数据
@GetMapping("/user/{id}")
public User getUserById(@PathVariable("id") Long id) {
User user = userService.findById(id);
return user;
}
}
springcloud-comsumer-pay-server-2000
1.导入 springcloud-user-common的依赖
<!--依赖公共的user-->
<dependency>
<groupId>cn.itsource</groupId>
<artifactId>springcloud-user-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<!--集成ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2.application.yml
支付模块也要注入DataSource
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心地址
instance:
prefer-ip-address: true #使用ip地址注册
instance-id: pay-server:2000 #服务注册到注册中心的id
server:
port: 2000
spring:
application:
name: pay-server #应用名称
datasource:
username: root
password: 123456
url: jdbc:mysql:///test
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
3.配置类注入RestTemplate
@SpringBootApplication
@EnableDiscoveryClient//开启注册中心
public class PayServerApplication {
public static void main(String[] args) {
SpringApplication.run(PayServerApplication.class);
}
//RestTemplate是SpringMvc提供的一个基于Rest风格的http调用工具
@Bean
@LoadBalanced//开启客户端负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
4.Controller层
@RestController
public class PayController {
@Autowired
private RestTemplate restTemplate;
//获取所有数据
@GetMapping("/pay/user")
public List<User> getUser() {
//地址user-server是另一个服务的应用名称,不能用localhost:1000
String url = "http://user-server/user/";
List list = restTemplate.getForObject(url, List.class);
return list;
}
//获取单个数据
@GetMapping("/pay/user/{id}")
public User getUserById(@PathVariable("id") Long id) {
String url = "http://user-server/user/"+id;
//url 请求访问的地址 responseType 返回的数据类型
User user = restTemplate.getForObject(url, User.class);
return user;
}
}