按照个人的习性,分布式我学习完以后一定会忘为此写次笔记自己快速复习
目录
- Springcloud介绍
- 注册中心–Eureka
- 注册中心–Nacos
Springcloud介绍及微服务介绍
- 为什么学?
也不是以前的单体架构被淘汰而是,当业务足够大型,进行优化
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。
优点:
- 架构简单
- 部署成本低
缺点:
耦合度高(维护困难、升级困难)
所以就要对传统单体项目进行优化,而分布式就是很好的方式
分布式架构
分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务。
分布式架构的优缺点:
优点:
- 降低服务耦合
- 有利于服务升级和拓展
缺点:
- 服务调用关系错综复杂
分布式架构虽然降低了服务耦合,但是服务拆分时也有很多问题需要思考:
- 服务拆分的粒度如何界定?
- 服务之间如何调用?
- 服务的调用关系如何管理?
人们需要制定一套行之有效的标准来约束分布式架构。
微服务
微服务的架构特征:
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
- 自治:团队独立、技术独立、数据独立,独立部署和交付
- 面向服务:服务提供统一标准的接口,与语言和技术无关
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。
因此,可以认为微服务是一种经过良好架构设计的分布式架构方案 。
任何分布式架构都离不开服务的拆分,微服务也是一样。
服务拆分原则
这里我总结了微服务拆分时的几个原则:
- 不同微服务,不要重复开发相同业务
- 微服务数据独立,不要访问其它微服务的数据库
- 微服务可以将自己的业务暴露为接口,供其它微服务调用
但方案该怎么落地?选用什么样的技术栈?全球的互联网公司都在积极尝试自己的微服务落地方案。
其中在Java领域最引人注目的就是SpringCloud提供的方案了。
Springcloud
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
其中常见的组件包括:
完整的微服务框架,所需中间件
通过docker容器部署
SpringCloud底层是依赖于SpringBoot的,并且有版本的兼容关系需要到官网查看
所以接下来快速复习各个中间件的使用
注册中心 Eureka,nacos
什么是注册中心呢,百度定义
“注册中心的作用一句话概括就是存放和调度服务,实现服务和注册中心,服务和服务之间的相互通信。注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系.
现在建立俩个服务端(user,order)作为微服务部分演示,俩者具有不同的数据库,但是俩个微服务数据具有关联,为此暴露接口完成交互,勇主持中心演示
user部分
实体类
@Data
public class User {
private Long id;
private String username;
private String address;
}
业务层
@Service
public class UserService {
// @Autowired
@Resource
private UserMapper userMapper;
public User queryById(Long id) {
return userMapper.findById(id);
}
}
mapper数据层
@Mapper
public interface UserMapper {
@Select("select * from tb_user where id = #{id}")
User findById(@Param("id") Long id);
}
controller暴露接口,给其他服务使用
@RestController
@RequestMapping("/user")
// @RefreshScope
public class UserController {
@Autowired
private UserService userService;
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,
@RequestHeader(value = "Truth", required = false) String truth) {
System.out.println("truth: " + truth);
return userService.queryById(id);
}
}
order部分
Controller
简单的通过路由查询订单信息
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
return orderService.queryOrderById(orderId);
}
}
实体类排pojo
@Data
public class Order {
private Long id;
private Long price;
private String name;
private Integer num;
private Long userId;
private User user;
}
注意这里的user实体类俩个服务不同数据库,故此需要多个服务完成交互,先申明user尸体
@Data
public class User {
private Long id;
private String username;
private String address;
}
由于order服务需要向另一个微服务交互,为此引入交互工具,restemplate,springmvc自带注入bean即可
任一配置类
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
service服务层
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
RestTemplate restTemplate;
// @Autowired
// private UserClient userClient;
//根据另一个服务端暴露的接口,发送请求完成
public Order queryOrderById(Long orderId) {
url="http://localhost:8082/user/"+order.getUserId().toString();
User user=restTemplate.getForObject(url, User.class);
order.setUser(user);
// 4.返回
return order;
}
}
上述在服务调用关系中,会有两个不同的角色:
服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
但是,服务提供者与服务消费者的角色并不是绝对的,而是相对于业务而言。
如果服务A调用了服务B,而服务B又调用了服务C,服务B的角色是什么?
- 对于A调用B的业务而言:A是服务消费者,B是服务提供者
- 对于B调用C的业务而言:B是服务消费者,C是服务提供者
因此,服务B既可以是服务提供者,也可以是服务消费者
并且order服务中,如要获取另一服务的数据,所以需要俩个微服务端进行交互,以上代码是通过rest风格解决完成交互
但是有以下缺点
- url地址是硬编码,ip,和端口信息需要写死
- 无法检查各个服务间的安全状态,如果发生宕机且多个服务,那么后果不堪设想
- 如果仅使用源生的HTTP完成交互,那么每个服务都需要维护其他服务的地址信息,并且这些地址信息还可能会发生变化。这会使得服务之间的通信变得复杂,可能导致某些服务无法通过传统方式进行通信
为此使用注册中心管理微服务中的各个服务,接下来进行使用注册中心优化服务交互
Eureka
百度百科:Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。
主要作用:
服务向注册中心注册,消费端拉取提供端的服务,提供端会进行定时心跳续约机制如果时间内没有收到回应,注册中心就就会选择另一个服务提供者进行服务
搭建Eureka服务端
- 新建一个maven工程,springboot项目,引入Eureka服务端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- @EnableEurekaServer开启开关 开启服务端
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args
);
}
}
3.配置服务端
server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息 应该是配置集群,用逗号间隔开来 erueka(注册中心)本身也是微服务
defaultZone: http://127.0.0.1:10086/eureka
eureka本身也是服务之一,所以也需要配置客户端( client:)需要配置服务名字和端口,以及defaultZone地址信息
打开游览器游览,instances是注册的服务,目前只有服务器本身
客户端
对原来的服务进行注册使其变为客户端
1.导入注册端客户依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.配置客户端
application:
name: userserive//也需要写清楚,用户注册信息的服务名
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka 这样客户端项目启动起来的时候会向该地址发送注册信息
3.启动成功后再次访问服务端web页面
此时看到被注册成功
演示服务交互
上述列子中改写俩者的,服务消费者和提供者通样注册以后,服务交互改为以下,可以直接使用服务名进行拉取交互,无需写ip
String url="http://userservice/user/"+order.getUserId().toString();
User user=restTemplate.getForObject(url, User.class);
order.setUser(user);
其中可以配置负载均衡
- 消费端服务在注入rest工具时可以使用@LoadBalanced注解选择赋值负载均衡
PS:这种影响是全局的,服务消费端和任意服务端之间的负载均衡准寻这种规则
@Bean
@LoadBalanced
//负载均衡当出现多个同名服务根据算法选择
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
*
* @return 改变规则IRule的子类改变赋值均衡的子类
*/
// @Bean
// public IRule randomRule() {
// return new RandomRule();
// }
- 配置类中进行指定服务提供者的负载均衡规则
userservice: #对应服务的名称
ribbon:
# NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
负载均衡规则userservice: #对应服务的名称
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
原理:
不同规则的含义如下:
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑 |
懒加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定饥饿加载的客户端服务名称
userservice
nacos
国内公司一般都推崇阿里巴巴的技术,比如注册中心,SpringCloudAlibaba也推出了一个名为Nacos的注册中心。相较于源生Nacos 相比 Eureka,提供了更多的功能特点:
和Eureka不同的是,nacos的启动方式是通过下载
- GitHub的Release下载页:https://github.com/alibaba/nacos/releases
- Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:
- 启动非常简单,进入bin目录,结构如下:
进入cmd界面: 输入以下指令启动单机模式
startup.cmd -m standalone
启动成功后输入
localhost:8848/nacos
初始账号密码就是nacos,服务端配置好以后就开始配置服务客户端
1)引入依赖
在cloud-demo父工程的pom文件中的<dependencyManagement>
中引入SpringCloudAlibaba的依赖:
<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>
然后在user-service和order-service中的pom文件中引入nacos-discovery依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
注意:不要忘了注释掉eureka的依赖。
2)配置nacos地址
在user-service和order-service的application.yml中添加nacos地址:
spring:
cloud:
nacos:
server-addr: localhost:8848
注意:不要忘了注释掉eureka的地址
3)重启
重启微服务后,登录nacos管理页面,可以看到微服务信息:
黄色健康不正常
此时服务消费者和服务提供者已经向注册中心发起注册
服务分级
通常会在分布式听到类似集群等概念,这就设计到服务分级
一个服务功能可以由工作室几个工作室实现,而工作室中又有很多工作成员
(1级 :业务)服务->->(二级 :计算机组合 )集群->(3级:某台计算机服务器本身)实列
将服务分部到不同分组,保障不宕机,服务安全,先访问本地集群的服务,如果其中的实列宕机,在访问其他集群的服务
微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。例如:
杭州的集群中当访问统集群的服务宕机时候,就会访问同服务的其他集群
配置集群
userservice
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
配置负载均衡:
修改order-service的application.yml文件,修改负载均衡规则:
```yaml
因为底层是ribbon 所以不配置就是ribbon默认规则(轮训)
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
创建多个实列,不同集群
>不会idea创建多个实列的看这里:点击右上角配置,编辑配置,选择需要的服务点击复制,或者ctrl +d,vm选项输入 -Dserver.port=和复制源不同的端口 (-D项配置开头)启动实列
![在这里插入图片描述](https://img-blog.csdnimg.cn/ca32d3f4e9eb4c98aa7beb7ed82c7eb5.png)
此时服务间发起交互,只有同一集群下的被选中,当选择另一集群时说明,该集群下服务宕机,控制台报错,此时消费者orderservice向userservice发起交互,统集群下的实列无法访问,则访问其他集群,控制台警告跨集群
```java
A cross-cluster call occurs,name = userservice, clusterName = HZ, instance = [Instance{instanceId='10.36.128.27#8086#SH#DEFAULT_GROUP@@userservice'
访问服务名
切换nacos权重在nacos管理界面切换
- 当多个实列重复完成功能,一般希望将大部分请求给配置好的服务器,响应速度快,只有小请求会到处理能力较弱的服务器
- 另一个常用功能就是服务升级,进行服务停机升级,权重设置小一点,限制用户访问端,平滑升级获取bug
- 权重为0则完全不能访问,同集群权重越高访问概率越高
环境隔离
Nacos提供了namespace来实现环境隔离功能。
- nacos中可以有多个namespace
- namespace下可以有group、service等
- 不同namespace之间相互隔离,例如不同namespace的服务互相不可见
- nacos服务端创建namespace 不创建默认public,id默认uuid
3. nacos客户端服务yaml文件设置命名空间,并且重启
nacos:
server-addr: localhost:8848 #配置注册中心地址
discovery:
cluster-name: HZ #代指集群1杭州
namespace: 83efd19a-f0a1-4775-8106-2ad88fa47d97 #填写命名空间id
启动
此时在访问该nacos客户端服务
就没办法和不同命名空间的服务交流
nacos和eureka区别
macos原理细节:消费者定时从注册中心拉取一次服务列表,减少服务端压力,然后再通过负载均衡选择不听的服务端,服务提供者也会每隔一段时间发起心跳检查供给注册中心检查健康状态(nacos间隔短一些),但是也有明显的不同.
- nacos有个临时实列,和Eureka一样的检查,如果注册中心未检查到心跳,则会进行下线.而非临时实列是由注册中心主动向服务发起健康检查,就算挂掉也不会剔除下线,而是等着恢复正常
- 另一个区别在于消费者,消费者定时拉取服务,Eureka也采取定时拉取,但是拉取期间如果服务提供端宕机后,会等到服务恢复才进行消费业务,而nacos采用push推送和pull主动变更消息的形式,每隔时间推送消息,如果服务提供端挂了,nacos会有消息推送的功能,会提前让消费者更换服务
配置上诉提到的临时队列
停掉,orderservice,当服务宕机时候,注册中心会直接让服务下线
默认是临时配置文件,进行申明为非临时配置文件
spring:
cloud:
nacos:
server-addr: localhost:8848 #配置注册中心地址
discovery:
cluster-name: HZ #代指集群1杭州
namespace: 83efd19a-f0a1-4775-8106-2ad88fa47d97 #填写id
ephemeral: false #是否临时队列 默认
#nacos的配置文件ephemeral属性表示配置实例是否为临时实例。当配置实例为临时实例时,如果该实例所在的nacos节点宕机或者被摘除,该实例会自动被删除。这种临时实例的特性可以用于实现一些短时任务或者临时节点的注册。
默认是零时队列
此时停掉该服务,nacos迅速发现服务状态变化,主动询问(速度快),但是并没有下线服务
nacos配置管理
nacos配置管理
nacos除了能够作为服务的注册中心管理服务,还可以管理配置
- 如果微服务的配置文件和数个服务有关
- 或者一个配置文件导致数个服务关联,那么带来的耦合度是极其高
通过nacos配置服务管理,可以实现多配置文件管理,以及热更新,(配置文件发生改变服务不需要重启)
实际操作
- 在nacos官网,点击新建配置,dataID就是文件名,一般命名名称服务名称-profile(配置环境),介绍和分组默认
- 命名规则(规范很重要,根据名字匹配):
服务名-环境.yaml - nacos的配置文件是做热更新的,一般是配置文件,写什么功能的开关,application项目本身的配置不能修改,点击发布
- 微服务拉取配置,需要先写一个bootstrap.yml的配置文件,来申明nacos地址,因为在nacos读取服务设计到一个启动流程问题
项目启动->读取本地配置文件->创建spring容器->加载bean,现在由于在application,前就会拉取配置文件nacos,然后才会读取本地文件,所以需要先写一个bootstrap.yml的配置文件申明nacos地址(优先级更高) - 引入nacos配置管理依赖,
<!--nacos的配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
6.在bootstrap配置配置拉取的文件,和nacos地址
bootstrap.yml
spring:
application:
name: userservice #决定数据源id
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848 # nacos注册地址
config:
file-extension: yaml # 文件后缀名
controller
@Slf4j
@RestController
//@RefreshScope
@RequestMapping("/user")
// @RefreshScope
public class UserController {
@Autowired
private UserService userService;
//验证是否拉取到nacos配置
@Value("${pattern.dateformat}")
private String dateformat;
//
@Autowired
private PatternProperties properties;
@GetMapping("prop")
public PatternProperties properties(){
return properties;
}
@GetMapping("now")
public String now(){
log.info("配置文件修改:源{}",properties.getName());
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(properties.getDateformat()));
// return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,
@RequestHeader(value = "Truth", required = false) String truth) {
System.out.println("truth: " + truth);
return userService.queryById(id);
}
}
此时controller需要返回配置类,而我们定义配置类没有配置文件注入,所以用于验证是否能读取nacos配置文件
@Data
@Component //注册为组件
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
private String envSharedValue;
private String name;
}
配置文件
访问游览器路由
读取成功
实现热更新
nacos配置文件变更后,微服务无需重启就可以感知,不过需要下面俩种配置:
- 在@value(使用该配置)富所在类上添加注解@RefreshScope,
- 使用@configurationProperties注解
此时配置文件发送修改,自动刷新配置
配置共享
多环境共享,开发生产的环境是一样用到该配置项,如果每个配置文件都要用到该配置,环境发生改变,其环境配置文件也要相应改变很麻烦,于是配置文件共享就显得极其重要
nacos命名的环境配置
- 项目服务名-环境.yaml 如 userservice-dev.yaml
- 服务.yaml userservice.yaml 明显第二种和服务有关,是环境通用的
此时使用dev环境,和test环境都能够读取userservice环境的内容,实现了内容共享
日志信息
Located property source: [BootstrapPropertySource {name=‘bootstrapProperties-userservice-test.yaml,DEFAULT_GROUP’}, BootstrapPropertySource {name=‘bootstrapProperties-userservice.yaml,DEFAULT_GROUP’}, BootstrapPropertySource {name=‘bootstrapProperties-userservice,DEFAULT_GROUP’}]
配置优先级
当以上的配置文件出现重复,哪个配置文件的优先级高呢
本地 远端 远端通用的优先级
远端环境>>远端通用>>本地,服务一启动,向nacos注册中心注册服务,所以远程>本地,详细的权限更高