微服务
前言
本笔记参考SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式,史上最全面的springcloud微服务技术栈课程|黑马程序员Java微服务_哔哩哔哩_bilibili,感谢黑马!
微服务的概念
定义
? 维基百科对它的解释:
? 一种软件开发技术- 面向服务的体系结构(SOA)架构样式的一种变体,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据上下文,选择合适的语言、工具对其进行构建。
? 整个微服务的框架可以概括如下:

特征
微服务有如下架构特征:
1.单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责。
2.自治:团队独立、技术独立、数据独立,独立部署和交付。
3.面向服务:服务提供统一标准的接口,与语言和技术无关。
4.隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题。
? 总结下来就是,一个整体的系统中的各个模块可以做拆分,分成一个一个小的微服务模块每个微服务都是独立存在的,数据库也是按照微服务的划分独立存在的,最重要的是可以独立的部署到生产环境上。请求服务的方式可以通过http的形式进行请求。比如下图所示:

SpringCloud
? Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
? SpringCloud是目前国内使用最广泛的微服务框架。常用的微服务组件如下所示:

? 我认为,最便捷的部分就是SpringCloud无缝衔接了当前主流方便的web框架Springboot,非常简单地就可以在Springboot的基础上开发SpringCloud微服务。Springboot和SpringCloud的兼容性如下所示:
| Cloud版本 | SpringBoot版本 |
|---|---|
| 2020.0.x aka llford | 2.4.x |
| Hoxton | 2.2.x,2.3.x(Starting with SR5) |
| Greenwich | 2.1.x |
| Finchley | 2.0.x |
| Edgware | 1.5.x |
| Dalston | 1.5.x |
? 本笔记使用的版本遵循黑马程序员娇嗔,使用的是Hoxton.SR10,SpringBoot版本是2.3.x版本。
服务拆分的例子
原则
? 既然微服务是将单体架构拆分成一个一个微服务,那么在服务拆分的时候需要遵循一些原则,原则如下:
1.不同微服务,不要重复开发相同业务。(不要重复造轮子)
2.微服务数据独立,不要访问其它微服务的数据库。
3.微服务可以将自己的业务暴露为接口,供其它微服务调用。
? 比如一个简单的商城案例,可以简单的分为如下的几个模块:

订单、用户例子
? 教程中的例子有两个微服务,分别为订单的微服务和用户的微服务:order-service和user-service。他们对应的数据库是分别两个不同的数据库:cloud-user和cloud-order,结构和数据如下:

? 在idea中的结构如下所示:

? 其中两个服务如下所示:

? 开启之后可以通过localhost:8080/order/101来请求订单号id为101的订单的详细信息,localhost:8081/user/1来请求查询用户id为1的用户的详细信息。不过Order的pojo类里还包含了User类,但是Order表里只存了User的id,我们就需要通过跨服务来请求User信息。
? 两个pojo类如下所示:

? 用户查询的有关代码和查询结果如下所示:

跨服务请求
? 要求在查询订单的同时,根据订单中包含的userId查询出用户信息,一起返回。如下图所示

? 因此,我们需要在order-service中 向user-service发起一个http的请求,调用http://localhost:8081/user/{userId}这个接口。
? 步骤如下所示:
? 1.注册一个RestTemplate的实例到Spring容器(不要忘记了!)
? 2.修改order-service服务中的OrderService类中的queryOrderById方法,根据Order对象中的userId查询User
? 3.将查询的User填充到Order对象,一起返回

Eureka注册中心
? 在上面的例子中我们在OrderService当中将跨请求的操作写死成localhost:8081端口,若我们的User的微服务有多个实例,且地址会发生改变的话,这么操作就不利于后续的维护拓展。
order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?
有多个user-service实例地址,order-service调用时该如何选择?
order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?
这个时候注册中心的作用就体现出来了!
基本介绍
? Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。
? SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。Eureka包含两个组件:Eureka Server和Eureka Client。
作用

? 回答之前的各个问题。
一、order-service如何得知user-service实例地址?
? 获取地址信息的流程如下:
? 1.服务注册:user-service服务实例启动后,将自己的信息注册到eureka-server(Eureka服务端)。
? 2.eureka-server保存服务名称到服务实例地址列表的映射关系。
? 3.服务发现:order-service根据服务名称,拉取实例地址列表。这个叫服务发现或服务拉取。
二、order-service如何从多个user-service实例中选择具体的实例?
? order-service从实例列表中利用负载均衡算法选中一个实例地址,向该实例地址发起远程调用。
三、order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?
? 1.心跳:user-service会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态。
? 2.当超过一定时间没有发送心跳时,eureka-server会认为微服务实例故障,将该实例从服务列表中剔除
? 3.order-service拉取服务时,就能将故障实例排除了
注意:一个微服务,既可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端
实践
? 把之前的例子应用到Eureka注册中心上,步骤如下所示:

搭建Eureka服务端
? Eureka服务端必须是一个独立的微服务,所以我们现在cloud-demo父工程下创建一个子模块,选择maven工程。
1.在依赖中导入eureka相关依赖:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
2.随后编写启动类,一定要添加一个@EnableEurekaServer注解,开启eureka的注册中心功能,并编写配置文件。

3.启动服务之后访问该端口得到以下画面:

可以看到eureka已经显示在其中了
注册服务
? 我们将user-service注册到eureka-server中。步骤如下
1.引入依赖:在user-service的pom文件中,引入下面的eureka-client依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
2.配置文件:在user-service中,修改application.yml文件,添加服务名称、eureka地址
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
注册完毕之后,再访问eureka界面就可以看到已注册的服务了:

若想实现一个服务有多个机子呈现的话,可以使用idea里的copy configuration选项,再启动一个userservice。

设置参数,将server.port改成8082

之后就会发现Services工具栏有一栏多的Not Started,其下正是UserApplication在8082端口的实例,启动它即可。

启动之后再查看eureka界面就可以发现8082端口实例:

发现服务
在eureka注册服务之后,这时请求服务就不用把主机地址写死了,只需要服务名称,就可以从eureka里拉取对应的服务。
将OrderService里跨服务请求改成服务拉取
步骤:
1.修改OrderService的代码,将url和端口改成付服务名
//原来的版本是
//String url="http://127.0.0.1:8081/user/"+order.getUserId();
String url="http://userservice/user/"+order.getUserId();
2.在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:

这样就可以使用各种负载均衡策略,例如轮询、随机等等。
Ribbon负载均衡
eureka的负载均衡是通过Ribbon来实现的
负载均衡原理
如图所示:

修改负载均衡的策略
原理
IRule决定了负载均衡的策略,如果我们想修改负载均衡的策略,就可以通过IRule入手。
默认的规则实现的是ZoneAvoidanceRule根据zone选择服务列表然后轮询。

其中各个负载均衡类对应的规则描述如下:

步骤
方式一:
比如我们想修改order-service请求user-service时的负载均衡的策略,那么我就可以在order-service的OrderAppliction中重新创建一个IRule类,返回一个内置的负载均衡规则类。

这样设置的话,order-service不管调用什么微服务,都是采用随机负载均衡规则。
方式二:
在配置文件方式修改,可以指定请求某个微服务时采用什么规则,如下图所示:

饥饿加载
顾名思义:饥不择食,上来就先全部加载了。
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

Nacos注册中心
与Eureka类似,但是比Eureka功能更加丰富,在国内受欢迎度较高,Nacos是阿里巴巴的产品,现在是SpringCloud里的组件了。
nacos安装
去github上下载压缩包之后解压缩,解压路径不要有中文,之后通过在bin目录下cmd,命令输入:startup.cmd -m standalone
Nacos服务分级存储模型
? 一个服务可以有多个实例,每个实例部署在不同的机房or地域,就形成了集群。

? 所以在服务调用的时候,尽可能地选择本地集群的服务,因为跨集群调用延迟比较高。
环境隔离-namespace

使用方法:


总结如下:
1.每个namespace都有唯一id
2.服务设置namespace时要写id而不是名称
3.不同namespace下的服务互相不可见
使用
服务注册到Nacos


配置集群
在服务的实例的application.yml里添加discovery下的cluster-name信息

再在nacos里查看

如果想三个实例在不同的集群,那么8081和8082再HZ集群上启动起来之后,修改cluster-name为SH,再启动8083即可,也可以通过修改参数来进行。

可以看到有两个集群,HZ和SH:

NacosRule负载均衡
将OrderService配置到HZ集群中,测试优先调用本地集群的负载均衡规则。默认的规则没有选择同集群的即HZ的userservice。
步骤:

第2步修改规则是在order-service下的application.yml里进行修改。注意还需要把之前在OrderAppliction.java里写的IRule给注释掉!

如果HZ集群的所有实例都停止工作了,nacos就会跨集群调用,这时会弹出警告信息,如下所示:

根据权重负载均衡
实际部署中会出现这样的场景:
服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。
方法:
直接在Nacos控制台设置实例的权重值:

若权重调整为0,则该实例就不会被访问到。
创建非临时实例

总结
原理细节

与eureka比较
共同点:
1.都支持服务注册和服务拉取
2.都支持服务提供者心跳方式做健康检测
不同点:
1.Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式。
2.临时实例心跳不正常会被剔除,非临时实例则不会被剔除
3.Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
4.Nacos集群默认采用AP方式(强调数据服务的可用性),当集群中存在非临时实例时,采用CP模式(强调数据的可靠性和一致性);Eureka采用AP方式。