1 微服务
1.1 spring cloud 是什么
- 他是一套分布式框架集合,基于 spring boot 开发的。
- 他是一种规范。
- 将不同公司生产的不同组件以 spring boot 的风格来集成。
- 这样的话,开发者就不用关心组件之间的整合,开箱即用,需要哪个组件直接用 spring boot 整合进来。
1.2 什么是 spring cloud alibaba
- 在 spring cloud 规范下出现的具体一套解决方案。
2 eureka 注册中心
2.1如何在多个微服务中发送远程 http 请求
2.1.1 首先我们在配置类中向 spring 容器注入 RestTemplate 对象的 Bean:
- 这里我们直接在启动类中注册,因为启动类也是一个配置类
package cn.itcast.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2.1.2 我们接着修改 service 层中的服务信息
package cn.itcast.order.service;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
Order order = orderMapper.findById(orderId);
String url = "http://localhost:8081/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
return order;
}
}
2.1.3 最后启动两个不同的服务即可
2.2 搭建 eureka 服务
2.2.1 引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>cn.itcast.demo</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
2.2.2 在启动类上添加 eureka 自动装配注解 @EnableEurekaServer
package cn.itcast.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
2.2.3 新建 application.yml 配置文件配置 eureka 如下信息
server:
port: 10006
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10006/eureka
2.3 eureka 服务注册
2.3.1 引入如下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.3.2 在该 module 的 application.yml 下注册 eureka 服务
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
username: root
password: mysql123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: userservice
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10006/eureka
2.3.3 注册多个相同的服务
- 在 idea 中我们在左下角 service 中可以看到服务列表,我们在服务列表中可以copy一份想要增加的服务。。
- 千万不要忘记更改端口号为未使用过的端口号。
右键 --> copy configuration --> 改名 --> environment --> VMoptions 中输入 -Dserver.port=端口号。
2.3.4 服务发现并且负载均衡
- 引入 eureka-client 依赖。
- 在当前 moudule 的 application.yml 中配置 eureka 地址。
- 给我们的 RestTemplate 添加 @LoadBalanced 注解让 eureka 自动进行负载均衡。
package cn.itcast.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- 最后将服务提供者的服务名称远程调用
- 如:UserService 这个 Moudule 的远程调用服务名称在这个 Moudule 中的 application.yml 文件中配置了,叫做 userservice。
package cn.itcast.order.service;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
Order order = orderMapper.findById(orderId);
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
return order;
}
}
3 Ribbon 负载均衡
3.1 源码理解
3.1.1 @LoadBalanced 注解:标记的方法发起的请求要被 robbin 拦截去做负载均衡。
3.1.2 LoadBalancerInterceptor 核心类实现 ClientHttpRequestInterceptor 接口。
3.1.3 ClientHttpRequestInterceptor 接口是客户端http请求拦截器。
package org.springframework.cloud.client.loadbalancer;
import java.io.IOException;
import java.net.URI;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
3.1.4 在 RibbonLoadBalancerClient 中的 execute 方法中进行如下操作
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
我们此时可以在调试的 loadBalancer 对象中查看到我们注册在 eureka 上的服务:
this = {RibbonLoadBalancerClient@9301}
serviceId = "userservice"
request = {LoadBalancerRequestFactory$lambda@9355}
balancers = {ConcurrentHashMap@9796} size = 0
triggeringLoad = null
triggeringBlackoutPercentage = null
isSecure = false
useTunnel = false
serverListUpdateInProgress = {AtomicBoolean@9797} "false"
serverListImpl = {DomainExtractingServerList@9798}
filter = {ZonePreferenceServerListFilter@9799} "ZonePreferenceServerListFilter{zone='defaultZone'}"
updateAction = {DynamicServerListLoadBalancer$1@9800}
serverListUpdater = {PollingServerListUpdater@9801}
rule = {ZoneAvoidanceRule@9802}
pingStrategy = {BaseLoadBalancer$SerialPingStrategy@9803}
ping = {NIWSDiscoveryPing@9804}
allServerList = {ArrayList@9805} size = 2
upServerList = {ArrayList@9806} size = 2
0 = {DomainExtractingServer@9826} "LAPTOP-EM069R1M:8082"
1 = {DomainExtractingServer@9827} "LAPTOP-EM069R1M:8081"
3.1.5 负载均衡实现
RobbinLoadBalancerClient类 ——> protected Server getServer(ILoadBalancer loadBalancer, Object hint) 方法 ——>
ZoneAwareLoadBalancer类 ——> public Server chooseServer(Object key) 方法 ——> BaseLoadBalancer类 ——>
public Server chooseServer(Object key) 方法 ——> IRule类 ——> RoundRibbonRule轮询负载均衡方法
3.1.6 自定义负载均衡策略
package cn.itcast.order.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CustomIRuleConfig {
@Bean
public IRule randomRule(){
return new RandomRule();
}
}
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: mysql123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: orderservice
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10006/eureka
userservice:
robbin:
NFLoadBalancerRuleClassName: com.netflix.loadBalancer.RandomRule
3.1.7 饥饿加载
- Ribbon 默认是懒加载,既第一次访问的时候才去创建 LoadBalancerClient,请求时间会很长。
- 饥饿加载则会在项目启动时创建,降低第一次访问所需要的时间。
- 开启饥饿加载的配置:
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: mysql123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: orderservice
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10006/eureka
userservice:
robbin:
NFLoadBalancerRuleClassName: com.netflix.loadBalancer.RandomRule
ribbon:
eager-load:
enabled: true
clients:
- userservice
3.1.8 总结
- Ribbon 负载均衡规则:
- 规则接口叫做 IRule。
- 默认实现是 ZoneAvoidanceRule ,根据 zone 选择服务列表然后轮询负载均衡。
- 负载均衡自定义方式:
- 代码方式:
- 配置文件方式:
- 饥饿加载:
- 通过配置文件开启饥饿加载。
- 指定要饥饿加载的服务名称,是个数组。
3 Nacos 注册中心
3.1 Nacos windos 启动命令
startup.cmd -m standalone
3.2 Nacos 依赖
- 一般将其引入到父工程中。
- 引入 nacos 后得把 eureka 的依赖注释或删除,防止冲突。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3.3 nacos 简易使用
3.3.1 客户端引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3.2.2 修改客户端 application.yml 文件中的 nacos 配置
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: mysql123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: orderservice
cloud:
nacos:
server-addr: localhost:8848
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
userservice:
robbin:
NFLoadBalancerRuleClassName: com.netflix.loadBalancer.RandomRule
ribbon:
eager-load:
enabled: true
clients:
- userservice
3.4 nacos 服务分级存储模型
3.4.1 概念
- 一个服务包含多个实例。
- 例如提供一个叫做 userservice 的服务,他的实例为 userservice:8080、userservice:8081 等等。
- 随着业务规模越来越大,以前服务小,所有服务可能放在一个机房,机房炸了,服务也就没了。
- 为了解决这个问题,我们会将一个服务的多个实例,部署到多个机房。
3.4.2 集群粗解释
- 拿上面的例子来说,一个服务可能有八个实例,八个实例被两两分配在四个机房,那么此时装有两个服务实例的一个机房就叫做集群。
3.4.3 nacos 服务分级存储模型定义
3.4.5 服务跨集群调用问题
- 由于跨集群调用,可能涉及两个不同集群之间距离很远,调用的话延迟很高。
- 所以我们一般情况都是先本地调用,如果本地调用无法调用才去进行远程集群调用。
3.5 如何配置一个服务的集群属性
3.5.1 修改 application.yml 文件中的配置
- 如果我们有多个实例,我们可以先将 cluster-name 设置为其中一个集群名称,然后启动其中几个实例。
- 然后我们再修改 cluster-name 为别的集群名称。
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
username: root
password: mysql123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: userservice
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SH
cluster-name: HZ
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
3.5.2 nacos控制台结果
结果 |
---|
| |
3.5.3 nacos 根据服务分级存储模型的负载均衡策略
- 我们可以在两个集群:
- SH集群:orderservice、userservice1、userservice2、
- HZ集群:userservice3
- 我们在用 orderservice 向 userservice 发起几次不同的请求。
- 我们会发现,在 orderservice 同一集群的服务没有挂掉的时候,它也去请求了远程 HZ集群的 userservice3。
- 所以我们要解决这个问题,我们使用 nacos 根据服务分级存储模型的负载均衡策略。
- 在 orderservice 的 application.yml 文件中完成如下配置:
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: mysql123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: orderservice
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
userservice:
robbin:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
ribbon:
eager-load:
enabled: true
clients:
- userservice
3.5.4 nacos 权重负载均衡
- 实际部署中每台机器的性能都不一样,我们希望性能好的机器多承担一些请求,性能差的少承担一点。
- 为了解决这个问题,nacos 帮我提供了权重设置,权重越高,访问率就越高。
- 我们可以直接在 nacos 的网页控制端直接修改不同的权重。
- 这个带给我们的好处,我们可以用在服务升级的过程中。
3.5.5 nacos 环境隔离
- nacos 中服务存储和数据存储这俩者的最外层都是一个叫做 命名空间(namespace) 的东西用来做最外层隔离。
- 包含关系:
- namespace > group > service/data
- 我们可以在 nacos 控制台新建新的命名空间,然后到代码层面去实现分配。
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: mysql123456
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: orderservice
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
namespace: 55f76e06-f468-4723-a8f7-29a7f2004cbf
3.5.6 nacos 临时实例和非临时实例
- 我们可以在 orderservice 的 application.yml 文件中完成如下配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: mysql123456
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: orderservice
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
namespace: 55f76e06-f468-4723-a8f7-29a7f2004cbf
ephemeral: false
- 如果是非实例状态,服务即使停了也不会被剔除,你可以再次启动。
3.5.7 nacos 和 eureka 的区别
- nacos 支持服务端主动检测服务提供者状态。
- 临时实例
- 临时实例采用心跳模式。
- 心跳不正常会被剔除服务列表。
- 非临时实例
- 非临时实例采用主动检测模式。
- 心跳不正常不会被剔除服务列表。
- 会对服务器造成比较大的压力。
- nacos 支持服务列表更新就主动推送给消费者的消息推送模式,服务列表可以及时更新。
- nacos 集群默认采用 AP 方式,当集群中存在非临时实例时,采用 CP 模式,而 eureka 采用 AP 模式。
|