系统架构
- 单体架构
- 一个应用打包成一个服务,部署到一个服务器上
- 适合小项目,维护成本低
- 问题:代码耦合、迭代困难、扩展受限、阻碍创新、技术债务
- 分布式架构
- 按功能拆分系统,拆分为多个应用,分别部署到不同的服务器
- 一个子项目负责一个功能,功能解耦合
- 可以为某一模块加集群
- 问题:有业务的重叠(订单服务、用户管理)
- SOA架构(面向服务的架构)
- 系统整体拆分为服务层和表现层
- 服务层封装了具体的业务逻辑供表现层调用
- 表现层则负责处理与页面的交互操作
- 将重复的代码抽象出来,形成统一的服务供其他系统或者业务模块来进行调用
- 问题:出现服务集群地址硬编码的问题,需要增加一个注册中心来解决各个服务之间的注册与发现,抽取服务的粒度较大
- 微服务
- 在SOA架构的基础上进一步扩展,将其彻底拆分为一个个小的可以独立部署的微服务
- 隔离性强:服务调用的隔离、容错、避免出现级联问题
- 面向服务:微服务对外暴露Restful等轻量协议的接口
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
- 自治:独立打包、部署和升级,小团队的交付周期将缩短,运维成本也将大幅度下降
DubboX框架(服务远程调用)
- Dubbo是阿里巴巴公司开源的一个基于Java的高性能RPC(远程调用)框架,后期阿里巴巴停止了该项目的维护,于是当当网在这之上推出了自己的Dubbox。
- 节点角色说明:
Provider: 暴露服务的服务提供方,服务消费者(controller、jsp、html)
Registry: 服务注册与发现的注册中心,注册中心(zookeeper)
Consumer: 调用远程服务的服务消费方,服务提供者(service、mapper)
Container: 服务运行容器。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Zookeeper(注册中心)
- zookeeper负责地址的注册与查找,是服务提供者和服务消费者的注册中心
- 可以为分布式应用程序协调服务,适合作为Dubbo服务的注册中心
-
服务提供方(Provider)
暴露服务可被服务消费方(Consumer)
使用dobbo协议远程调用服务
Ribbon(负载均衡)
- Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
- 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。
- 在nacos里面已经集成了ribbon的依赖,我们不需要去引入ribbon的依赖
- Ribbon默认提供 很多种负载均衡算法,例如轮询、随机 等等。
- 在consumer中使用Ribbon
- application.yml
server:
port: 80
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.145.131:8848 #nacos注册中心的地址
application:
name: ribbon-consumer #注册到nacos的服务名
- ConfigBean
@Configuration
public class BeanConfig {
/**
* 拦截器
* 1.通过ribbon-provider-->List<Service>--->使用负载均衡策略返回Service
* 2.把ribbon-provider换成ip和端口
*/
@LoadBalanced//开启负载均衡,默认是轮训策略
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
//随机策略
@Bean
public IRule iRule(){
return new RandomRule();
}
}
- ConsumerController
@RestController
@RequestMapping(value = "/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
//springcloud提供的发现服务的工具栏
@Autowired
private DiscoveryClient discoveryClient;
private int index;
@RequestMapping("/getUserById/{id}")
public User getUserById(@PathVariable Integer id){
//List<ServiceInstance> instanceList = discoveryClient.getInstances("ribbon-provider");
//随机策略
//int currentIndex = new Random().nextInt(instanceList.size());
//轮训策略
//index = index+1;
//int currentIndex = index % instanceList.size();
//ServiceInstance instance = instanceList.get(currentIndex);
//String url = "http://"+instance.getHost()+":"+instance.getPort()+"/provider/getUserById/"+id;
String url = "http://ribbon-provider/provider/getUserById/"+id;
return restTemplate.getForObject(url, User.class);
}
}
- RibbonConsumerApp
@SpringBootApplication
@EnableDiscoveryClient //开启nacos,允许注册当前服务并发现其他服务
public class RibbonConsumerApp {
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApp.class,args);
}
}
- ribbon问题:拼接url和参数显得好傻
使用Feign可以解决url拼接的问题
Nacos(注册中心配置中心)
- Nacos(Naming Configuration Sevice)是阿里开源的一个注册中心和配置中心
- nacos的启动器
spring-cloud-starter-alibaba-nacos-config
spring-cloud-starter-alibaba-nacos-discovery
- 具体使用
# 服务提供者:nacos_provider && 服务消费者:nacos_consumer
1.pom.xml
web、nacos-discovery、springcloud_common
2.application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.145.131:8848
spring:
application:
name: nacos-provider
3、app
@EnableDiscoveryClient
- 配置中心——配置隔离
namespace ------------------- 环境:dev、test
group ------------------- 项目名:xx医疗系统、yy物流系统
Dat Id ------------------- 工程名:配置文件名 - nacos持久化
原因:nacos自带derby数据库,集群环境下每台nacos的数据不一样
1、修改conf/application.properties
2、建库建表
1.创建nacos库
2.找到conf/nacos-mysql.sql并执行
3、测试
重启nacos,上传配置文件,测试是否把配置文件持久化到mysql - nacos集群搭建
数量:3台
3台原因:投票
投票原因:选leader
选leader原因:同步数据
Nginx(代理服务)
- Nginx (engine x) 是一款轻量级的Web 服务器 、反向代理服务器及电子邮件(IMAP/POP3)代理服务器。
- 反向代理:是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端。总结:反向代理就是代替服务器接受用户的请求,正向代理就是代理服务器代替用户去访问服务器
- Nginx特点:反向代理 负载均衡 动静分离,高并发、高性能、高可靠性、可扩展性好、热部署、BSD许可证
- 负载均衡:数据流量分摊到多个服务器上执行,多台服务器共同完成工作任务,提高了数据的吞吐量。
6种负载均衡策略
负载均衡策略 | 说明 |
轮询 | 默认 |
weight | 权重方式 |
ip_hash | 依据ip分配方式 |
least_conn | 按连接数 |
fair | 按响应时间 |
url_hash | 依据URL分配 |
- 动静分离:将静态的资源放到反向代理服务器,节省用户的访问时间
- 项目中的作用:在集群时,我们需要指定IP和端口号才能使用Nacos,使用Nginx后可以只使用IP就会自动调用集群中的其中一个
配置nginx代理nacos集群
vim /usr/local/nginx/conf/nginx.conf:
upstream nacosList{
server 192.168.116.131:8848;
server 192.168.116.131:8849;
server 192.168.116.131:8850;
}
server{
listen 90;
server_name localhost;
location /{
proxy_pass http://nacosList;
}
}
Feign(封装请求 负载均衡)
- 能解决的问题
Ribbon使用RestTemplate调用其它服务的API时,所需要的参数须在请求的URL中进行拼接
- RestTemplate:远程调用一个 HTTP 接口,这个类是 Spring 框架提供的一个工具类。
- 简介
feign是springcloud提供的声明式的http客户端,工作在consumer端
feign支持springmvc注解
feign集成了ribbon也支持负载均衡 - feign原理
1、将feign接口扫描到spring容器:
@EnableFeignClients开启feign注解扫描:扫描被@FeignClient标识的接口生成代理类,并把代理类交给spring容器管理;
2、为接口的方法创建RequestTemplate
当consumer调用feign代理类时,代理类会调用SynchronousMethodHandler.invoke()创建RequestTemplate(url,参数,httpMethod);
3、发出请求
发请求时会通过RequestTemplate创建Request对象,然后client(HttpClient、OkHttp、UrlConnect)使用Request对象发送请求 - feign接口传参
1、?传参
@RequestParam(“id”)
2、restful传参
@PathVariable(“id”)
3、pojo传参
@RequestBody - 具体使用
- 服务消费者----》feign接口-----》服务提供者
- feign启动器:spring-cloud-starter-openfeign
- 创建feign_provider
- 创建feign_interface
- pom.xml
openfeign、springcloud_common
- feign接口
@FeignClient("服务名")
public interface UserFeign{
@RequestMapping("/getUserById/{id}") //请求的url
public User getUserById(@PathVariable("id") Integer id);
}
- 创建feign_consumer
- pom.xml
feign_interface
- controller
public class ConsumerController {
@Autowired
private UserFeign userFeign;//代理类
}
- app
@EnableFeignClients
- 优化
- 开启feign日志
feign:
client:
config:
default:
loggerLevel: full #开启feign日志
logging:
level:
com.bjpowernode.feign: debug
- http连接池
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
- gzip压缩
server:
compression:
enabled: true #浏览器<------>consumer的gzip压缩
feign:
compression:
request:
enabled: true #consumer<------>provider的gzip压缩
response:
enabled: true
- feign超时
#方式一
ribbon:
ConnectionTimeout: 5000
ReadTimeout: 5000
#方式二
feign:
client:
config:
feign-provider:
ConnectionTimeout: 5000
ReadTimeout: 5000
Sentinel(哨兵 服务保护)
- 使用的版本:1.8.1
- 作用:微服务架构中,如果单个服务出现问题,可能拖死上游服务,造成整个链路中的所有微服务都不可用(服务雪崩)。使用服务保护组件来避免这个问题。
- sentinel是阿里的一套开源的服务保护框架,主要作用:流量控制、熔断降级
- 流量控制:
qps:不被上游服务压死
thread:不被下游服务拖死 - 熔断降级:
当慢调用或异常比例超过阈值,暂时切断对下游服务的调用,使用兜底方案,避免级联故障
流量控制
- 流控规则
1.阈值类型
QPS:每秒访问次数
线程数:线程数
2.流控模式
直接:对当前资源的流量控制
关联:当关联的资源达到阈值时,限流自己
链路:指定资源从入口资源进来的流量(api级别的针对来源) - 3.流控效果
快速失败:直接流控
Warm Up(预热):开始时阈值只有9/3,等5s后,阈值才提升到9
排队等待:每秒接受1次请求,超过阈值则排队等待,等待时间为10s - 热点规则
1.规则说明:将规则具体到参数是,eg:统计一段时间内最常购买(或频繁访问)的商品 ID 并进行限制
2.参数例外项:给参数开个后门,eg:参数=2,则5秒钟之内可以携带20次 - 系统规则
流控规则是针对方法设定的,系统规则是针对一个应用设定的。eg:sentinel-consumer服务的所有接口qps=1 - 授权规则
根据调用来源来判断该次请求是否允许放行
若配置白名单,则只有请求来源位于白名单内时才可通过;
若配置黑名单,则请求来源位于黑名单时不通过,其余的请求通过。
熔断降级
- 当满足条件的时候,切断对下游服务的调用,执行降级逻辑(兜底方案)
1.慢调用比例
属性 | 说明 |
最大RT(请求数量) | 需要设置的阈值,超过该值则为慢调用 |
比例阈值 | 慢调用占所有的调用的比率,范围:[0~1] |
熔断时长 | 在这段时间内发生熔断、拒绝所有请求 |
最小请求数 | 即允许通过的最小请求数,在该数量内不发生熔断 |
- 异常比例
属性 | 说明 |
异常比例阈值 | 异常比例=发生异常的请求数÷请求总数取值范围:[0~1] |
熔断时长 | 在这段时间内发生熔断、拒绝所有请求 |
最小请求数 | 即允许通过的最小请求数,在该数量内不发生熔断 |
- 异常数
属性 | 说明 |
异常数 | 请求发生异常的数量 |
熔断时长 | 在这段时间内发生熔断、拒绝所有请求 |
最小请求数 | 即允许通过的最小请求数,在该数量内不发生熔断 |
整合Feign
- 微服务远程调用都是基于Feign来完成的,可以将Feign与Sentinel整合,在Feign里面实现服务熔断
- 配置feign,打开支持
feign:
sentinel:
enabled: true #开启Feign对Sentinel的支持
- 为feign接口指定降级逻辑
@FeignClient(value="sentinel-provider",fallbackFactory = UserFeignFallback.class)
@RequestMapping(value = "/provider")
public interface UserFeign {
@RequestMapping(value = "/getUserById/{id}")
public User getUserById(@PathVariable("id") Integer id);
}
- 一旦Feign远程调用服务失败,就会执行容错逻辑
@Component
public class UserFeignFallback implements FallbackFactory<UserFeign> {
private Logger log = LoggerFactory.getLogger(UserFeignFallback.class);
@Override
public UserFeign create(Throwable t) {
return new UserFeign() {
@Override
public User getUserById(Integer id) {
return new User(id,"feign调用失败:"+t,0);
}
};
}
}
持久化
- 一旦服务重启,当前配置的针对某个接口的规则就丢掉了,使用持久化保存配置的规则
- 将配置放到Nacos中
- 使用sentinel-dashboard-nacos-1.8.1.jar,修改nacos的ip地址
- 在nacos中配置jar中指定的命名空间名:sentinel
Gateway
- 前端调用微服务的问题
- 客户端请求不同的微服务,就要维护不同的ip,硬编码问题
- 客户端无法实现负载均衡
解决方案有两种:1.Ngnix+lua 2. Spring Cloud Gateway
- 执行流程
- Gateway Client向Gateway Server发送请求
- HandlerMapping负责路由查找,并根据路由断言判断路由是否可用
- WebHandler创建过滤器链并调用
- 请求会一次经过PreFilter–微服务–PostFilter的方法,最终返回响应给Gateway Client
路由
- 通过断言条件路由,浏览器访问:http://127.0.0.1:9527/consumer/getUserById/1
spring:
cloud:
gateway:
routes:
- id: sentinel-consumer #自定义的路由ID,保持唯一
uri: http://localhost:80 # 请求要转发到的地址
predicates: #断言
- Path=/consumer/** #只有断言条件返回true(请求路径包含“/consumer”)时,才进行路由转发
- 通过服务名路由,浏览器访问:http://127.0.0.1:9527/sentinel-consumer/consumer/getUserById/1
spring:
cloud:
gateway:
routes:
- id: sentinel-consumer #自定义的路由ID,保持唯一
uri: lb://sentinel-consumer #lb代表从注册中心获取服务
predicates: #断言
- Path=/consumer/** #只有断言条件返回true(请求路径包含“/consumer”)时,才进行路由转发