1. 传统单体应用架构
程序在编写完成之后会被打包并部署为一个具体的应用。Java Web项目打包成WAR包,Java项目打包成JAR包。
但是随着用户人数的增加,一台机器可能满足不了系统的负载,此时我们就会考虑水平扩展。
面对上述情况,我们通常会增加服务器的数量,并将打包好的应用拷贝到不同服务器,然后通过负载均衡器实现应用的水平扩展。
缺点
// 1. 应用维护困难 随着时间推移项目逐渐变大,一旦项目变得庞大而复杂,就很难进行二次开发与维护。
// 2. 可靠性低 因为所有模块都运行在一个进程中,任何一个模块出现问题,都会导致整个进程崩溃,进而影响到整个应用。
// 3. 不利于技术更新 如果需要更改某个技术,可能需要将整个应用重新开发。
2. SOA架构
针对传统单体架构的问题,企业开始通过SOA【面向服务的架构】来解决。
SOA:把应用中相近的功能聚合到一起,以服务的形式提供出去。即:他将原来的单体架构细分为不同的子系统,再由各个子系统互相调用所需的服务。
优点
// 1. 将项目拆分成若干个子项目,不同的团队可以负责不同的子项目,从而提高开发效率。
// 2. 把模块拆分,使用接口通信,降低了模块之间的耦合度。
// 3. 能够升级单个服务而无需重写整个应用。
缺点
// SOA中各个相互独立的服务还是部署在同一个Tomcat实例,随着业务功能的增多,会导致SOA服务越来越复杂。
3. 微服务架构
微服务架构:是一种架构风格和架构思想。
它倡导我们在传统单体架构的基础上,将系统按照功能拆分为更加细粒度的服务。
所拆分的每一个服务都是一个独立的应用,单独部署。这些应用对外提供公共的API,可以独立承担对外服务的职责。
围绕着这一思想的一系列体系结构【开发,测试,部署等等】,我们就可以称它为“微服务架构”。
优点
// 1. 每个微服务可以独立部署 当某个微服务发生变更时,不需要重新部署整个项目。
// 2. 技术选型灵活 各个微服务的技术选型可以不一样,当需要对某个微服务技术升级时,不需要面临对整个项目的重新开发。
// 3. 复杂度可控 每个微服务专注单个功能,体积小,复杂度低,便于维护。
// 4. 易于容错 当某个服务发生故障时,在设计良好的情况下,其他服务一般不会被影响。
// 5. 易于扩展 单个服务可以水平扩展,比如:订单服务访问高,我可以对其多部署几个服务。
SOA与微服务区别
SOA项目分为多个子系统,粗粒度。微服务项目分为多个微服务,细粒度。
SOA项目服务部署在一起,互相依赖。微服务项目每个微服务可以独立部署。
4. 微服务的拆分建议
- 通过业务功能分解并定义与业务功能相对应的服务。
- 按照动词或用例分解,并定义负责特定操作的服务。例如:一个负责完成订单的航运服务。
- 通过定义一个对给定实体或资源的所有操作负责的服务。例如:一个负责管理用户账户的账户服务。
补充. 分布式微服务与集群的关系
分布式:多个独立的计算机集合,每个计算机做不同的事,共同支撑一个系统。
集群:多个独立的计算机集合,每个计算机做相同的事,共同支撑某一个服务。
分布式中的每一个节点都可以做集群。
5. 微服务架构的组件
前言:开发微服务架构的项目,我们一般采用如下组件。
// 1. 微服务注册中心 注册系统中所有服务的地方。
// 2. 服务注册 服务提供方将自己的调用地址注册到服务注册,用于让服务调用方能够方便快速地调用自己
// 3. 服务发现 服务调用方从注册中心找到自己要调用的服务的地址。
// 4. 负载均衡 服务提供方以多实例的形式提供服务,使用负载均衡能让服务调用方连接到合适的服务节点。
// 5. 服务容错 通过一系列保护机制,保证服务调用者在调用到异常服务时能快速返回结果,免得长时间等待。
// 6. 服务网关 服务调用的唯一入口,一般用来实现用户鉴权,动态路由,负载均衡,限流等功能。
// 7. 分布式配置中心 将本地的配置信息注册到配置中心统一管理。 为什么要统一管理呢? 各个微服务的配置文件可能不一样,而且配置在本地,如果要修改配置,修改完需要重启。有了分布式配置中心,就可以解决这些问题。
// 微服务实例的开发 springBoot
// 服务注册 eureka zookeeper nacos consul
// 服务发现 feign dubbo
// 负载均衡 ribbon
// 服务容错 hystrix sentinal
// Api网关 zuul gateway
// 分布式配置中心 springcloud config nacos
5. SpringBoot与SpringCloud的区别
SpringCloud
它是在SpringBoot的基础上构建的,它不是一个具体的技术,而是一个简化分布式系统构建的工具集。
Spring Cloud中包含多个子项目,如:Spring Cloud Netflix,Spring Cloud Config,Spring Cloud Starters等。
SpringBoot
它是一个用来开微服务实例的。
6. SpringCloud与Dubbo区别
Spring Cloud:本身也是基于SpringBoot开发而来,SpringCloud是一系列框架的有序集合,就是把非常流行的微服务的技术整合到一起。
dubbo:本身只是众多分布式开发中解决问题的一种方式,它主要是做服务调用,而spring cloud 是一系列的有序集合。
7. Spring Cloud for Alibaba
spring cloud中的几乎所有的组件都使用Netflix公司的产品【eureka,hystrix,rabbin,zuul,config...】,然后在其基础上做了一层封装【比如推出openFeign(对feign进行了一个升级),推出了gateWay来替换zuul】,还增加了几个没什么大作用的组件,然后就推出来了。
然而Netflix公司的18年12月12日宣布的服务旗下产品进行维护,发现组件Eureka,Hystrix等 已经停止更新,而其他的众多组件预计会在明年(即2020年)停止维护。如此一来,SpringCloud就要凉凉了。
所以开发者们急需其他的一些替代产品,此时spring cloud alibaba就来了,它是由阿里巴巴提供的。Spring Cloud 这时就急了鸭,他就与spring cloud alibaba达成合作,将Spring cloud alibaba整合到sping cloud,将其作为Spring Cloud 下的子项目。
整合之后,依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
说说Spring Cloud Alibaba的版本号
SpringCloud的版本号是英文方式。因为springcloud是微服务的解决方案,他会有很多子项目,每个子项目都维护这自己的版本号,为了避免冲突,就使用了伦敦地铁站的名字作为版本号。以首字母作为顺序,a,b,c,d....排列。
再说咱们国人开发的,我们没有用英文名,还是数字那种方式。
SpringCloud for alibaba:https://spring.io/projects/spring-cloud-alibaba
8. 说说Hystrix
一个讲的特别好的视频:https://www.bilibili.com/video/BV1nX4y1K748?p=13
Hystrix:是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库、防止出现级联失败也就是雪崩效应。
在目前更加主流的cloud alibba中用 Sentinel 来代替它。
雪崩效应举例:
// A为服务提供者
// B为服务调用者
// C和D是B的服务调用者
// 随着时间的推移,当A的不可用引起B的不可用,并将不可用逐渐放大到C和D时,整个服务就崩了。
// 总结:造成雪崩的原因
// 1. 服务提供者不可用(程序BUG,缓存击穿,大量用户请求等)
// 2. 服务消费者不可用(同步等待造成的资源耗尽)
// 3. 重试加大流量(用户重试,代码逻辑重试)
// 说人话:一个服务不可用,导致一系列服务不可用。我们就是要解决这个问题~~~~~~~~~~~~~~~~~~~~
// 解决雪崩的方案:
// 1. 采用缓存。
// 2. 请求合并,将相同的请求进行合并然后调用批处理接口。
// 3. 服务熔断,当一定时间内,异常请求比例到达阈值时,启动熔断,停止具体服务调用,通过fallback快速返回托底数据 。
// 4. 服务降级,服务熔断以后,客户端调用自己本地方法返回兜底数据。
// 5. 隔离,分为线程池隔离与信号量隔离。通过判断线程池或信号量是否已满,超出容量的请求直接降级,从而达到限流的作用。
// 熔断和降级区别:
// 降级是出错了返回托底数据。
// 熔断是出错了会在一段时间不再访问服务,直接返回托底数据。一段时间后,又关闭熔断尝试放一个请求去访问服务,根据执行情况来确定是否关闭或开启熔断。
8.1 Hystrix实现熔断的原理
8.2 基于Feign使用Hystrix的降级
openfeign依赖默认集成了hystrix依赖,所以无需添加hystrix依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class SpringcloudConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudConsumerApplication.class, args);
}
}
// @FeignClient注解中指定 fallback 拖地数据类
@FeignClient(value = "cloud-provider",configuration = FeignConfig.class,fallback = UserClientFallback.class)
public interface UserClient {
@RequestMapping("/provider/user/listUser")
public List<User> listUser();
@RequestMapping("/provider/user/getUserById")
public User getUserById(@RequestParam("userId") Integer userId);
}
// 创建实现类【需要加入IOC容器】,实现远程接口。该实现类用于返回托底数据
@Component
public class UserClientFallback implements UserClient {
@Override
public List<User> listUser() {
System.out.println("托底数据");
return null;
}
@Override
public User getUserById(Integer userId) {
System.out.println("托底数据");
return null;
}
}
server:
port: 8001
spring:
application:
name: cloud_consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
feign:
hystrix:
enabled: true # 开启hystrix支持
logging:
level:
com.lihao: debug
此时,我们的基于Feign的Hystrix服务降级就实现了。但是这种方法,调用方不能捕获服务异常的原因。
// @FeignClient注解中指定 *****fallbackFactory****** 的托底数据类
@FeignClient(value = "cloud-provider", configuration = FeignConfig.class, fallbackFactory = UserClientFallback.class)
public interface UserClient {
@RequestMapping("/provider/user/listUser")
public List<User> listUser();
@RequestMapping("/provider/user/getUserById")
public User getUserById(@RequestParam("userId") Integer userId);
}
// 这种方式就能获取服务异常的原因了
// 通过实现FallbackFactory接口来达到获取服务异常的原因。
@Slf4j
@Component
public class UserClientFallback implements FallbackFactory<UserClient> {
// 实现FallbackFactory会要求实现create(Throwable throwable)方法。 通过在该方法中通过匿名内部类的方式实现拖地数据
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public List<User> listUser() {
log.info("异常原因:"+throwable);
System.out.println("拖地数据");
return null;
}
@Override
public User getUserById(Integer userId) {
log.info("异常原因:"+throwable);
System.out.println("拖地数据");
return null;
}
};
}
}
8.4 Hystrix的熔断实现
我们可以对Hystrix的默认熔断配置进行修改。比如说:默认是5秒重试,我们可以改。【这种方式可能是用于配置全局的,我不确定。我看有些视频里面时将其配置注解里面的】
# 1. 熔断后休眠时间:sleepWindowInMilliseconds
# 2. 熔断触发最小请求次数:requestVolumeThreshold
# 3. 熔断触发错误比例阈值:errorThresholdPercentage
# 4. 熔断超时时间:timeoutInMilliseconds
hystrix:
command:
default:
circuitBreaker:
# 打开熔断器 默认true
enabled: true
#这两个属性是互斥操作,只能开启其中一个
# 强制让服务拒绝请求
forceOpen: true
# 强制让服务接收请求
forceClosed: true
#这两个属性是互斥操作,只能开启其中一个
# 意思是,当发生熔断后,几秒后重试
# 熔断后休眠时长,默认值5秒
sleepWindowInMilliseconds: 5000
# 意思是,当发生熔断后,几秒后重试
# 意思是,如果你的请求数量在10秒内达到了20个,并且有超过百分之50都是错误的。就会触发。【必须10秒内达到20个请求】
# 熔断触发最小请求次数,默认值是20
requestVolumeThreshold: 10
# 触发熔断错误比例阈值,默认值50%
errorThresholdPercentage: 50
# 意思是,如果你的请求数量在10秒内达到了20个,并且有超过百分之50都是错误的。就会触发。【必须10秒内达到20个请求】
execution:
isolation:
thread:
# 熔断超时设置,默认为1秒
timeoutInMilliseconds: 2000
// 我所了解到的 Hystrix实现熔断
// 注意该种方式,需要额外导入hystrix的依赖,才能使用hystrix的注解。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
// 自己没有演示成功,待后期确认
@RequestMapping("/getUserById22222222222")
@HystrixCommand(fallbackMethod = "getUserById22222222222Fallback", commandProperties = {
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,value = "10"),
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "50"),
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "5000")
})
public User getUserById22222222222(Integer userId) {
if (userId == 1) {
return userClient.getUserById22222222222(userId);
} else {
int a = 1 / 0;
}
return userClient.getUserById22222222222(userId);
}
public User getUserById22222222222Fallback(Integer userId) {
System.out.println("熔断触发,返回托底数据");
return null;
}
9. Feign
Feign:是一个声明式WebService客户端,使用Feign能让编写WebService客户端更加简单,它的使用方法是定义一个接口,然后在上面添加注解。不再需要拼接URL,参数等操作。
- 集成Ribbon的负载均衡功能
- 集成了Hystrix的熔断器功能
- 支持请求压缩
- 大大简化了远程调用的代码,同时功能还增强啦
- Feign以更加优雅的方式编写远程调用代码,并简化重复代码
Ribbon负载均衡策略:
- 随机
- 轮询
- 权重
- 响应时间
- 重试
Feign自身已经集成了Ribbon,因此使用Feign的时候,不需要额外引入依赖。
Feign内置的ribbon默认设置了请求超时时长,默认是1000【超过1秒就超时】,可以修改ribbon内部有重试机制,一旦超时,会自动重新发起请求。如果不希望重试可以关闭配置:
# 修改服务地址轮询策略,默认是轮询,配置之后变随机
springcloud-provider: # 提供方的 application 的 name
ribbon:
#轮询
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule # 设置负载均衡策略为轮询
ConnectTimeout: 10000 # 连接超时时间 是指去尝试连接服务的时间,连接10秒还连接不起就超时。
ReadTimeout: 2000 # 数据读取超时时间 是指建立连接后,调用服务,超过2秒还没相应就超时。
MaxAutoRetries: 1 # 最大重试次数
MaxAutoRetriesNextServer: 0 # 最大重试下一个服务次数(集群的情况才会用到)
OkToRetryOnAllOperations: false # 无论是请求超时 或者 连接超时都进行重试
也阔以用这种方式来改,将feignName指定为你要调的服务。如果想所有都用这个配置,feignName改成default
9.1 使用Feign
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
// 消费方主启动类上加 @EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class SpringcloudConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudConsumerApplication.class, args);
}
}
// 在消费方创建一个接口【推荐将其写在 common模块】
@FeignClient(value = "springcloud-provider") // value的值:提供方的 application 的 name。
public interface UserClient {
// 要求路径必须写完整
@RequestMapping("/provider/user/listUser")
public List<User> listUser(); // 和提供方的方法一样
}
@RestController
@RequestMapping("/consumer/user")
public class ConsumerUserController {
@Autowired
private UserClient userClient;
@RequestMapping("/listUser")
@HystrixCommand(fallbackMethod = "backListUser")
public List<User> listUser(){
int a = 1/0;
return userClient.listUser();
}
public List<User> backListUser(){
List<User> list = new ArrayList<>();
User user = new User();
user.setName("张三");
list.add(user);
return list;
}
}
9.2 Feign集成Hystrix
# 消费方开启Feign熔断
feign:
hystrix:
enabled: true # 开启Feign的熔断功能
// 实现刚才编写的UserClient,作为FallBack的处理类
@Component
public class UserClientFallback implements UserClient{
/***
* 服务降级处理方法
* @param id
* @return
*/
@Override
public List<User> listUser() {
List<User> list = new ArrayList<>();
User user = new User();
user.setName("张三");
list.add(user);
return list;
}
}
@FeignClient(value = "springcloud-provider",fallback = UserClientFallback.class) // 在@FeignClient注解中,指定FallBack处理类。
public interface UserClient {
@RequestMapping("/provider/user/listUser")
public List<User> listUser();
}
9.3 Feign的日志级别配置
通过loggin.level.xx=debug来设置日志级别。然而这个对Feign客户端不会产生效果。
因为@FeignClient注解修饰的客户端在被代理时,都会创建一个新的Feign.Logger实例。我们需要额外通过配置类的方式指定这个日志的级别才可以。
实现步骤:
- 在application.yml配置文件中开启日志级别配置
- 编写配置类,定义日志级别bean。
- 在接口的@FeignClient中指定配置类
- 重启项目,测试访问
# 在消费方的配置文件中
# com.lihao 包下的日志级别都为Debug
logging:
level:
com.lihao: debug
// 在消费方创建Feign配置类,定义日志级别
@Configuration
public class FeignConfig {
/***
* Feign支持4中级别:
* NONE:不记录任何日志,默认值
* BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
* HEADERS:在BASIC基础上,额外记录了请求和响应的头信息
* FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
*
* @return
*/
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
@FeignClient(value = "springcloud-provider",fallback = UserClientFallback.class,configuration = FeignConfig.class) // 指定配置类
public interface UserClient {
@RequestMapping("/provider/user/listUser")
public List<User> listUser();
}