1.单体架构和分布式架构的区别
1.1 单体架构
(1)什么是单体架构
将业务的所有功能集中在一个项目中开发,打成一个包部署
(2)单体架构的优缺点
优点:①架构简单②部署成本低
缺点:耦合度高(维护困难、升级困难)
1.2 分布式架构
(1)什么是分布式架构
根据业务功能对系统开发,每个业务功能模块作为独立项目开发,称为一个服务
(2)分布式架构的优缺点
优点:①降低服务耦合②有利于服务升级和扩展
缺点:服务调用关系错综复杂
2.微服务
2.1 微服务是什么
微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:
(1)单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
(2)面向服务:微服务对外暴露业务接口
(3)自治:团队独立、技术独立、数据独立、部署独立
(4)隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
2.2 微服务技术对比
微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。在国内最知名的就是SpringCloud和阿里巴巴的Dubbo
2.3 企业需求
2.4 微服务的远程调用
(1)创建相关数据库和配置如下
①数据库cloud_user中的tb_user表:
②数据库cloud_order中的tb_order表:
③userservice服务的application.xml配置
server: port: 8081 spring: application: name: userservice datasource: url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver
④orderservice服务的application.xml配置
server: port: 8001 spring: application: name: ordeservice datasource: url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver
(2)创建RestTemplate并注入spring容器
(3)服务远程调用RestTemplate
(4)id为103的查询结果如下:
(2)微服务调用方式总结
①基于RestTemplate发起的http请求实现远程调用
②http请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可
2.5 提供者与消费者
(1)服务提供者:一次业务中,被其他微服务调用的服务(提供接口给其他微服务)
(2)服务消费者:一次业务中,调用其他微服务的服务(调用其他微服务提供的接口)
(3)提供者与消费者角色其实是相对的,一个服务可以同时是服务提供者和服务消费者
3.Eureka注册中心
3.1 eureka的作用
(1)消费者该如何获取服务提供者具体信息?
①服务提供者启动时向eureka注册自己的信息
②eureka保存这些信息
③消费者根据服务名称向eureka拉取提供者信息
(2)如果有多个服务提供者,消费者该如何选择?
服务消费者利用负载均衡算法,从服务列表中挑选一个
(3)消费者如何感知服务提供者健康状态?
①服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
②eureka会更新记录服务列表信息,心跳不正常会被剔除
③消费者就可以拉取到最新的信息
3.2 在Eureka架构中,微服务角色有两类
- EurekaServer:服务端,注册中心
(1)记录服务信息
(2)心跳监控 - EurekaClient:客户端
(1)Provider:服务提供者,例如案例中的 user-service
①注册自己的信息到EurekaServer
②每隔30秒向EurekaServer发送心跳
(2)consumer:服务消费者,例如案例中的 order-service
①根据服务名称从EurekaServer拉取服务列表
②基于服务列表做负载均衡,选中一个微服务后发起远程调用
3.3 搭建EurekaServer
(1)创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
(2)编写启动类,添加@EnableEurekaServer注解
(3)添加application.yml文件,编写配置
server: port: 10086 spring: application: name: eurekaserver # eureka的服务器名称 eureka: client: service-url: # eureka的地址信息 defaultZone: http://127.0.0.1:10086/eureka/
3.4 Eureka注册
- 将user-service服务注册到EurekaServer步骤如下:
(1)在user-service项目中引入spring-cloud-starter-netflix-eureka-client的依赖
<!--eureka客户端依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
(2)在application.yml文件中,编写配置
spring: application: name: userservice # user服务的服务名称 eureka: client: service-url: # eureka的地址信息 defaultZone: http://127.0.0.1:10086/eureka
(3)模拟多实例部署(为了避免端口冲突,需要修改端口设置)
- 将order-service服务注册到EurekaServer步骤如下:
3.5 服务拉取
服务拉取是基于服务名称获取服务列表,然后再对服务列表做负载均衡;在order-service完成服务拉取
(1)修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口号:
String url = "http://userservice/user/" + order.getUserId();
(2)在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:
@LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); }
4.Ribbon负载均衡
4.1 负载均衡流程
当请求进入Ribbon类以后,请求会被LoadBalancerInterceptor负载均衡拦截器给拦截,会得到请求中的服务名称(如userservice)。把它交给RibbonLoadBalancerClient,而RibbonLoadBalancerClient会把服务交给DynamicServiceListLoadBalancer,DynamicServiceListLoadBalancer它就会去eureka-server里拉取服务列表,得到多个服务的信息,找IRule做负载均衡,IRule会基于规则(比如轮询)把值返回给RibbonLoadBanlancerClient
4.2 负载均衡策略
4.3 修改负载均衡策略
(1)代码方式(全局配置,访问任何服务都可以):在order-service中的OrderApplication类中,定义一个新的IRule
@Bean public IRule randomRule(){ return new RandomRule(); }
(2)配置文件方式(针对某个微服务而言):在order-service的application.yml文件中,添加新的配置也可以修改规则
userservice: # 服务名称 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则
4.4 加载方式
Ribbon默认采用的是懒加载,即第一次访问时才会创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在
项目启动时创建,降低第一次访问的耗时,通过下面的配置开启饥饿加载:
ribbon: eager-load: enabled: true # 开启饥饿加载 clients: userservice # 指定对userservice这个服务饥饿加载
补充:
clients: # 指定对多个服务饥饿加载 - userservice - xxxservice
5.Nacos注册中心
5.1 认识Nacos
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内更受欢迎。
(1)在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
①GitHub主页:https://github.com/alibaba/nacos
②GitHub的Release下载页:https://github.com/alibaba/nacos/releases
(2)启动
进入bin目录,然后执行windows命令 startup.cmd -m standalone
单机启动
(3)成功页面如下(默认登陆账号密码都为nacos)
5.2 服务注册到Nacos
(1)在cloud-demo父工程中添加spring-cloud-alibaba的管理依赖
<!-- spring-cloud-alibaba --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.6.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency>
(2)在order-service和user-service中添加nacos的客户端依赖
<!-- Nacos的客户端依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
(3)配置user-service&order-service中的application.yml文件
spring: cloud: nacos: server-addr: localhost:8848 # nacos服务端地址
(4)启动并测试
5.3 Nacos服务分级存储模型
(1)一级是服务,例如userservice(一个服务可以包含多个实例)
(2)二级是集群,例如上海或杭州
(3)三级是实例,例如杭州机房的某一台机器部署了userservice的服务器
5.4 集群
(1)在order-service中设置实例的集群属性
spring: cloud: nacos: server-addr: localhost:8848 # nacos服务端地址 discovery: cluster-name: HZ # 配置集群名称,也就是机房的位置,例如杭州
(2)在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务:
userservice: # 要做配置的微服务名称 ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则
补充:NacosRule负载均衡策略
①优先选择同集群服务实例列表
②本地集群找不到提供者,才去其他集群寻找,并且会报警告
③确定了可用实例列表后,再采用随机负载均衡挑选实例
5.5 根据权重负载均衡
(1)在Nacos控制台可以设置实例的权重值(0~1之间)
(2)同集群内的多个实例,权重越高被访问的频率越高
(3)权重设置为0则完全不会被访问
5.6 环境隔离-namespace
Nacos中服务器和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
(1)在Nacos控制台可以创建namespace,用来隔离不同环境
(2)然后填写一个新的命名空间信息
(3)保存之后会在控制台看到这个命名空间的id
(4)修改order-service的application.yml,添加namespace
spring: cloud: nacos: server-addr: localhost:8848 # nacos服务端地址 discovery: cluster-name: HZ # 配置集群名称,也就是机房的位置,例如杭州 namespace: 94cc0d41-4020-40fc-bc84-5ac7045a224e # 命名空间,填ID
(5)重启order-service后,再来看控制台:
(6)此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错
补充:
①每个namespace都有唯一id
②服务设置namespace时要写id而不是全称
③不同namespace下的服务互相不可见
5.7 临时实例和非临时实例
服务注册到Nacos时,可以选择注册为临时或非临时实例,通过下面的配置来设置:
spring: cloud: nacos: server-addr: localhost:8848 # nacos服务端地址 discovery: cluster-name: HZ # 配置集群名称,也就是机房的位置,例如杭州 ephemeral: false # 设置为非临时实例
临时实例宕机时,会从nacos的服务列表中剔除,而非临时实例则不会
5.8 Nacos与eureka的异同点
(1)Nacos与eureka的共同点
①都支持服务注册和服务拉取
②都支持服务提供者心跳方式做健康检测
(2)Nacos与Eureka的区别
①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
②临时实例心跳不正常会被剔除,非临时实例则不会被剔除
③Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
④Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
6.Nacos配置管理
6.1 统一配置管理
(1)在Nacos中添加配置信息
(2)在弹出表单中填写配置信息:
①Data ID:服务名称+当前项目的运行环境.后缀名(如:userservice-dev.yaml)
②Group:一般不用改,默认就行
③描述:介绍配置文件是干什么的
④配置内容:有热更新需求的配置在这里
(3)引入Nacos的配置管理客户端依赖:
<!--nacos配置管理依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
(4)在userservice的resource目录下添加一个bootstrap.yml文件
注意:bootstrap.yml比application.yml优先级高很多,把nacos地址、文件相关信息等放到bootstrap.yml里面,就可以完成nacos配置文件的读取
6.2 配置自动刷新
Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要通过下面两种配置实现:
(1)方式一:在使用@Value注入的变量所在的类上添加注解@RefreshScope
(2)方式二:使用@ConfigurationProperties注解
6.3 多环境配置共享
微服务启动时会从nacos中读取多个配置文件:
(1)服务名-环境.后缀,例如:userservice-dev.yaml
(2)服务名.后缀,例如:userservice.yaml
无论环境如何变化,服务名.后缀这个文件一定会被加载。因此,多环境共享配置可以写入这个文件
(3)多种配置的优先级:
6.4 Nacos生产环境下一定要部署为集群状态
7.http客户端Feign
7.1 RestTemplate方式调用存在的问题
(1)我们先来看看以前利用RestTemplate发起远程调用的代码:
String url = "http://userservice/user/" + order.getUserId(); User user = restTemplate.getForObject(url, User.class);
(2)存在下面的问题:
①代码可读性差,编程体验不统一
②参数复杂的URL难以维护
7.2 Feign的使用
Feign是一个声明式的http客户端,其作用就是帮助我们优雅的实现http请求的发送,解决RestTemplate
方式调用所存在的问题
(1)引入依赖:
<!-- Feign的依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
(2)在调用者(order-service)的启动类上添加注解并开启Feign的功能
(3)创建clients包,编写Feign的客户端
@FeignClient("userservice") public interface UserClient { @GetMapping("/user/{id}") User findById(@PathVariable("id") Long id); }
Feign的客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
①服务名称:userservice
②请求方式:GET
③请求路径:/user/{id}
④请求参数:Long id
⑤返回值类型:User
(4)用Feign客户端代替RestTemplate
@Autowired private UserClient userClient; public Order queryOrderById(Long orderId) { // 1.查询订单 Order order = orderMapper.findById(orderId); //2.利用Feign发起http请求,查询用户 User user = userClient.findById(order.getUserId()); //3.封装User到Order order.setUser(user); //4.返回 return order;
7.3 自定义Feign的配置
(1)Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:
注意:一般我们需要配置的就是日志级别
(2)配置Feign日志有两种方式
方式一:配置文件方式
①全局生效
②局部生效
方式二:java代码方式,需要先声明一个Bean:
(1)自定义一个配置类
public class FeignClientConfiguration { @Bean public Logger.Level feignLogLevel(){ return Logger.Level.BASIC; } }
①如果是全局配置,把它放到@EnableFeignClients这个注解中
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); }
②如果是局部配置,把它放到@FeignClient这个注解中
@FeignClient(value = "userservice",configuration = FeignClientConfiguration.class) public interface UserClients { @GetMapping("/user/{id}") User findById(@PathVariable("id") Long id); }
8.统一网关GateWay
8.1 网关功能
(1)身份认证和权限较验
(2)服务路由(选择需要的微服务)、负载均衡
(3)请求限流
8.2 网关的技术实现
在SpringCloud中网关的实现包括两种:
(1)gateway(2)zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
8.3 搭建网关服务的步骤
(1)创建新的module,引入SpringCloudGateway的依赖和Nacos的服务发现依赖:
<!-- 网关依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- nacos服务发现依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
(2)编写路由配置及nacos地址
server: port: 10010 # 网关端口 spring: application: name: gateway # 服务名称 cloud: nacos: server-addr: localhost:8848 # nacos地址 gateway: routes: # 网关路由配置 - id: user-service # 路由id,自定义,只要唯一即可 # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址 uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称 predicates: # 路由断言,也就是判断请求是否符合路由规则的条件 - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求 - id: order-service # 路由id,自定义,只要唯一即可 uri: lb://orderservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称 predicates: # 路由断言,也就是判断请求是否符合路由规则的条件 - Path=/order/** # 这个是按照路径匹配,只要以/order/开头就符合要求
(3)网关路由可以配置的内容包括:
①路由id:路由唯一标识
②uri:路由目的地,支持lb和http两种
③predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
④filters:路由过滤器,处理请求或响应
8.4 路由断言工厂Route Predicate Factory
(1)我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件。例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的
像这样的断言工厂在SpringCloudGateway还有十几个
(2)Spring提供了11种基本的Predicate工厂:
8.5 路由过滤器 GatewayFilter
(1)GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
(2)Spring提供了31种不同的路由过滤器工厂。例如:
(3)案例:给所有进入userservice的请求添加一个请求头:Truth=itcast is freaking awesome!
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器:
(4)默认过滤器:如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:
8.6 全局过滤器
(1)全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter是通过配置去定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。定义方式是去实现GlobalFilter接口。
(2)案例:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
参数中是否有authorization,authorization参数值是否为admin;如果同时满足则放行,否则拦截
①自定义类,实现GlobalFilter接口,添加@Order注解:
8.7 过滤器执行顺序
(1)过滤器执行顺序
①每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
②GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
③路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
④当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
8.8 跨域问题处理
(1)跨域:域名不一致就是跨域,主要包括:
①域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
②域名相同,端口不同:localhost:8080和localhost8081
(2)跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
(3)解决方案:CORS
(4)网关处理跨域采用的同样是CORS方案,并且只需要简单配置即可实现: