Spring-Cloud微服务学习笔记(1)
叠甲:配合黑马的微服务视频享用
目录
入门
单体架构:将业务的所有功能集中在一个项目开发,打成一个包部署
分布式架构:根据业务系统对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务
而微服务是一种经过良好架构设计的分布式架构方案,微服务结构特征:
单一职责:微服务拆分力度更小,每一个服务对应唯一的业务能力,做到单一职责,避免重复业务开发。
面向服务:微服务对外暴露接口
自治:队独立,技术独立,数据独立,部署独立
隔离性强:服务调用做好隔离,容错,降级,避免出现级联问题(
隔离性:一个服务挂了 / 卡了,别把整个系统拖死,其他服务还能正常用。
级联问题(雪崩效应):一个服务故障 → 调用它的所有服务都卡死 → 整个系统全部崩溃。
)
微服务结构

在微服务架构中,配置中心和注册中心是两个重要的组件。
配置中心用来统一管理项目中所有配置,各种参数、各种开关,全部都放到一个集中的地方进行统一管理,并提供一套标准的接口。当各个服务需要获取配置的时候,就来「配置中心」的接口拉取。
注册中心则是用来管理服务实例的注册和发现的。各个服务实例在启动时会向注册中心注册自己的信息(如IP地址和端口号),其他服务实例可以通过注册中心来发现并调用这些服务12。
微服务技术对比



微服务远程调用
1.基于RestTemplate发起的http请求实现远程调用
2.http请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可。
RestTemplate 是 Spring 提供的、专门用来发送 HTTP 请求的工具类,作用等同于:Java 代码里的浏览器 / Postman。
你在微服务里服务 A 调用服务 B,本质就是发 HTTP 请求,RestTemplate 就是帮你简化这件事的工具,不用你手写原生的 HTTP 连接、流处理、JSON 转换,一行代码就能调用别的服务接口。要想使用restTemplate,先把它注册到IOC容器中
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
public User getUser(Long userId) {
// 调用远程用户服务的接口,一行代码完成
String url = "http://localhost:8081/user/" + userId;
// getForObject = 发送GET请求,把返回JSON自动转成User对象
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
等价于代码里打开浏览器访问 http://localhost:8081/user/1001
常用方法
和 HTTP 方法一一对应:
getForObject:发送 GET 请求,获取返回对象postForObject:发送 POST 请求,传 JSON / 表单exchange:通用方法(支持 GET/POST/PUT/DELETE,更灵活)delete:发送 DELETE 请求
它自动帮你做:
- 建立 HTTP 连接
- 发送请求参数
- 接收 JSON 结果
- 自动转成 Java 对象(不需要你手动解析)
提供者与消费者
服务提供者:一次业务中,被其他微服务调用的服务(提供接口给其他微服务)
服务消费者:一次业务中,调用其他微服务的服务。(调用其他微服务提供的接口)
Eureka

消费者该如何获取服务提供者具体信息?
①:服务提供者启动时向eureka注册自己的信息
②:eureka保存这些信息
③:消费者根据服务名称向eureka拉取提供者信息
如果有多个服务提供者,消费者该如何选择?
①:服务消费者利用负载均衡算法,从服务列表中挑选一个
消费者如何感知服务提供者健康状态?
①:服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
②:eureka会更新记录服务列表信息,心跳不正常会被剔除
③:消费者就可以拉取到最新的信息
要想成为Eureka注册中心,现在启动类使用@EnableEurekaClient的注解
然后将一些配置写在yml中
spring:
application:
name: eurekaserver #Eureka的服务名称
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/ #Eureka的地址信息
如果需要注册服务,需要经过差不多的步骤,引依赖+配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
如果想启动多个springboot实例我们可以采用复制配置后添加虚拟机配置,添加-Dserver.port=????的指令。
服务拉取是基于服务名称获取服务的列表,然后队服务列表做负载均衡。负载均衡注解为
@LoadBanlad,默认为轮询。
Ribbon负载均衡
发起调用请求后会被拦截器拦截,然后根据请求的服务名称去寻找对应的服务列表,默认采用轮询的方式选择服务后再发起真正的请求。

如果想改变负载均衡的策略

ribbon默认采用懒加载,即第一次访问时才创建LoadBalanceClient,请求时间会很长。而饥饿加载会在项目启动时创建,降低第一次访问的耗时
配置如下
ribbon:
eager-load:
enabled: true
clients: userservice
Nacos
Nacos是阿里巴巴的产品,是springcloud的一种组件,相比eureka功能更加丰富,在国内受欢迎程度较高
页面如下
登陆界面默认都是nacos

需要注册服务的配置如下
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 nacos所在端口
cluster-name: 深圳 集群名称
nacos服务分级存储模型
一级是服务名 例如userservice
二级是集群名 例如广州、伤害
三级是实例 例如广州部署了一台userservice服务器
服务调用尽可能选择本地集群的服务,跨集群调用延迟较高,本地集群不可访问时,再去访问其他集群。(优先选择本地集群,然后随机发出请求)
Nacos还提供了权重配置,权重越大访问频率越高(0~1之间)
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
环境隔离:
1.namespace用来做环境隔离
2.每个namespace都有唯一id
3.不同namespace下的服务不可见

1.Nacos与eureka的共同点
都支持服务注册和服务拉取
都支持服务提供者心跳方式做健康检测
2.Nacos与Eureka的区别
Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
临时实例心跳不正常会被剔除,非临时实例则不会被剔除
Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式 ; Eureka采用AP方式
AP 模式(可用性优先)
能访问就行,数据不一定最新。👉 就算部分节点挂了,剩下的还能继续提供服务。👉 适合:服务发现(绝大多数场景)
Eureka 从头到尾都是 AP
Nacos 默认也是 AP(临时实例)
- CP 模式(一致性优先)
数据必须一致才给你访问。
👉 有节点挂了、数据不一致 → 直接拒绝访问。
👉 适合:需要强一致的配置、数据库
Nacos 的 非临时实例(永久实例)= CP 模式
nacos热更新配置

由于我们需要先获取nacos配置再读取本地配置,因此不能把nacos地址放在本地配置中,springcloud提供了bootstrap.yml,我们可以将nacos地址与配置放在里面,项目启动时就能直接读取


多环境配置共享 [服务器名] . 后缀名 可以在不同的环境下共享同一个服务的配置
如果有个配置名重复,则就近原则进行配置
优先级 服务名-环境-后缀 > 服务名-后缀 > 本地配置
nacos集群搭建步骤
1.搭建数据库集群并初始化数据库表(mysql集群实际上是表中的数据一摸一样的多个数据库)
2.修改集群配置(节点信息),数据库配置
3.分别启动多个nacos节点
4.nginx反向代理(Nginx 做的事情:收到请求。看一眼自己配置的 Nacos 集群列表(你写的 upstream)。自动选一台正常运行的 Nacos。把请求转发给它你看到的页面,就是那台 Nacos 的页面)
http客户端Feign
Feign是一个声明式的http客户端
如果不用Feign
// 你要手动构建请求、拼接URL、处理参数、发请求、转JSON
String url = "http://order-service/getOrder?id=" + id;
ResponseEntity<Order> resp = restTemplate.getForEntity(url, Order.class);
Order order = resp.getBody();
使用Feign
// 你只声明:我要调用这个接口
@FeignClient("order-service")
public interface OrderClient {
@GetMapping("/getOrder")
Order getOrder(@RequestParam("id") Long id);
}
在服务启动类上加上@EnableFrignClients的注解
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id);
}
不需要自己写实现类,Feign会根据你提供的url地址和服务接口自动写成动态实现类
自定义配置Feign

Feign的性能优化
Feign底层的客户端实现
URLConnection: 默认实现,不支持连接池
Apache HttpClient: 支持连接池
OKHttp: 支持连接池
因此优化Feign的性能主要包括:
使用连接池代替默认的URLConnection
日志级别,最好用basic或none
|
对比项 |
默认 URLConnection |
连接池(Apache HttpClient / OKHttp) |
|
连接方式 |
每次请求都新建 TCP 连接,请求结束后立即断开 |
预先创建一批连接放在池里,请求时直接复用,用完归还 |
|
性能开销 |
每次都要经历 TCP 三次握手、四次挥手,高并发下开销巨大 |
避免频繁创建 / 销毁连接,大幅减少网络 IO 和 CPU 开销 |
|
响应时间 |
高并发下明显变长(大量时间浪费在连接建立上) |
平均响应时间显著缩短,尤其是高并发场景 |
|
资源占用 |
频繁创建连接会导致端口、线程资源耗尽,容易出现连接超时 |
连接数可控,资源利用率更高,稳定性更好 |
Feign的最佳实践
1.让controller和FeignClient继承统一接口
2.将FrignClient,pojo,feign的默认配置都定义到一个项目中,供所有消费者使用。
网关GateWay

server:
port: 10010 #网管端口
spring:
application:
name: gateway #网关名称
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos地址
gateway:
routes: #网关路由配置
- id: user-service #路由id,自定义,只需一个即可
uri: lb://userservice # 路由的目标地址,lb是负载均衡,后面跟服务名称
predicates: #路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** #这个按照路径匹配,只要以/user/开头就符合要求
配置内容
路由id:路由唯一标识
url:路由目的地,支持lb和http两种
predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
filters: 路由过滤器,处理请求或响应

路由断言工厂
我们在配置文件中写的断言规则知识字符串,这些字符串会被predicate factory读取并处理,转变为路由判断的条件

路由过滤器 GateWayFilter
GateWayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理

此作用是给请求添加了名为Truth 值为Itcast is freaking awesome的请求头
全局过滤器GlobalFilter
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
@order注解表示过滤器优先级

请求进入网关后会碰见三种过滤器:当前路由的过滤器,DefaultFilter,GlobalFilter
请求路由后会将这三个过滤器合并到一个过滤器链(集合)中,排序后依次经过过滤器
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增
当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器>GlobalFilter的顺序执行
跨域:域名不一样就是跨域,主要为跨域不同或端口不通( 只有浏览器发请求才会跨域。 )
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。
跨域配置
spring:
cloud:
gateway:
globalcors: #全局的跨域处理
add-to-simple-url-handler-mapping: true #解决options请求被拦截问题
corsConfigurations:
'[/**]': # 匹配所有请求路径(/** 代表拦截所有请求)
allowedOrigins: #允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: #允许的跨域ajax的请求方法
- GET
- POST
- DELETE
- PUT
- OPTIONS
allowedHeaders: "*" #允许跨域请求中携带的请求头
allowCredentials: true #是否携带token
maxAge: 360000 #这次跨域检测的有效期
RabbitMQ
同步通讯与异步通讯
同步存在的优点:
时效性较强,能够立即得到结果
同步存在的问题:
耦合度高:每次加入新的需求,都要修改原来的代码
性能下降:调用者需要等待服务提供者响应,如果调用链过长则响应时间等于各个调用的时间之和。
资源浪费:调用链中的每个服务在等待响应过程中,不能释放占用的资源,高并发场景下会极度浪费系统资源。
级联失败:如果服务提供者出现问题,所有调用方都跟着出问题如同多米诺骨牌,迅速导致整个服务器崩溃。
异步通信优点:
耦合度低,吞吐量提升,故障隔离,流量削峰。
异步通信缺点:
依赖于Broker的可靠性,安全性,吞吐能力。
架构复杂,业务没有明显的流程线,不好追踪管理。
MQ(MessageQuene): 中文是消息队列,字面来看就是存放消息的队列。也是事件驱动架构中的Broker。

创建rabbitmq容器

15672是rabbitmq访问端端口
5672是客户端端口
这是Rabbitmq管理端的界面

Channel:操作MQ的工具
exchange:路由消息到队列中
quene:缓存消息
virtual host:虚拟主机,是对quene,exchange等资源的逻辑分组。
publisher:消息发布者,将消息发送到队列quene
quene:消息队列,负责缓存并接收消息
consumer:订阅队列,处理队列中的消息
@Test
public void testSendMessage() throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.150.101");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("itcast");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.发送消息
String message = "hello, rabbitmq!";
channel.basicPublish("", queueName, null, message.getBytes());
System.out.println("发送消息成功:【" + message + "】");
// 5.关闭通道和连接
channel.close();
connection.close();
}
public static void main(String[] args) throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.136.130");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("itcast");
factory.setPassword("123456");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.订阅消息
channel.basicConsume(queueName, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
// 5.处理消息
String message = new String(body);
System.out.println("接收到消息:【" + message + "】");
}
});
System.out.println("等待接收消息。。。。");
}
基本队列的消息发送流程
1.建立connection
2.创建channel
3.利用channel声明队列
4.利用channel向队列发送消息
基本队列的消息接收流程
1.建立connection
2.创建channel
3.利用channel声明队列
4.定义consumer的消费行为handleDelivery
5.利用channel将消费者与队列绑定
都需要利用channel声明同一个消息队列是因为避免任意一方的channel找不到对应的消息队列。(启动顺序可能存在差异)
SpringAMQP
AMPQ是应用于应用程序或之间传递业务消息的开放标准,该协议与平台无关,更符合微服务中独立性的要求。SpringAMPQ是基于AMPQ协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,spring-amqp是基础抽象,spring-rabbit是底层的默认实现。
特征
1.侦听器容器,用于异步处理入站消息
2.用于发送和接收消息的rabbittemplate
3,RabbitAdmin用于自动声明队列,交换和绑定
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
public void testSimpleQuene(){
String queneName = "Simple.quene";
String message = "hello my baby!";
rabbitTemplate.convertAndSend(queneName,message);
}
}
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueneMessage(String msg) throws InterruptedException{
System.out.println("spring 消费者接收到消息 :[ " + msg + "] ");
}
SpringAMPQ默认不会自己创建队列,需要提前创建好。
而且消息一旦被消费就会从队列中删除,RabbitMq没有消息回溯功能。
Work Quene 工作队列
Rabbitmq为了预防一个监听者拿走所有消息,存在着预取机制,为控制消费者一次拿多少条消息 。

发布,订阅模式
发布订阅模式允许将同一消息发送给多个消费者。实现方式是加入了exchange(交换机)
交换机不能缓存消息,如果路由失败则消息丢失
常见的exchange:广播(Fanout),路由(Direct),话题(topic)

Fanout exchange 会将收到的消息路由到每一个跟其绑定的queue

Direct exchange会将接收到的消息根据规则路由到指定的队列,因此成为路由模式
每一个队列和交换机设置一个Binding key
发布者发送消息时,会指定消息的routingKey
交换机将消息路由到BindingKey与消息RoutingKey一致的队列

更多推荐




所有评论(0)