0%

搭建集群的基本步骤

  • 搭建数据库,初始化数据库表结构
  • 下载nacos安装包
  • 配置nacos
  • 启动nacos集群
  • nginx反向代理

配置Nacos

  • 进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf
  • 加入集群ip地址
  • 修改application.properties配置文件,修改mysql配置

启动nacos集群

  • 将nacos文件夹复制三份,分别命名为:nacos1,nacos2,nacos3
  • 分别修改application.properties端口号
  • 修改nginx配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream nacos-cluster {
server 127.0.0.1:8845;
server 127.0.0.1:8846;
server 127.0.0.1:8847;
}

server {
listen 80;
server_name localhost;

location /nacos {
proxy_pass http://nacos-cluster;
}
}

统一配置管理

  • 在工作台创建配置,将需要热更新的配置(将来会变化的配置)
  • 配置获取步骤:
    1. 项目启动
    2. 读取nacos中配置文件(bootstrap.yml引导文件)
    3. 读取本地配置文件
    4. 创建spring容器
    5. 加载bean

配置bootstrap.yml

  • 在客户端pom文件引入nacos配置管理依赖
1
2
3
4
5
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  • 创建bootstrap.yml配置文件
1
2
3
4
5
6
7
8
9
10
11
spring:
application:
name: userservice
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
namespace: 7550979b-3a00-4833-b82f-19f2d9e22b2e
  • 删除application.yml文件中重复配置

配置热更新

  • 方式一:使用@Value注入,在所在类上添加@RefreshScope注解
  • 方式二:使用@ConfigurationProperties注解,注入后自动刷新

多环境配置共享

微服务启动时会从nacos读取多个配置文件

  • [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml
  • [spring.application.name].yaml,例如:userservice.yaml

无论profile如何变化,[spring.application.name].yaml一定会加载,因此共享配置写入这个文件

配置文件优先级

  1. [spring.application.name]-[spring.profiles.active].yaml
  2. [spring.application.name].yaml
  3. 本地配置

windows命令

startup.cmd -m standalone

服务注册到Nacos

  • 在cloud-demo父工程中添加spring-cloud-alibaba的管理依赖
1
2
3
4
5
6
7
<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>
  • 添加nacos的客户端依赖
1
2
3
4
5
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  • 修改客户端配置
1
2
3
4
spring:
cloud:
nacos:
server-addr: localhost:8848 #nacos服务地址

Nacos服务分级存储模型

  • 一个服务分为多个集群
  • 一个集群有多个实例
  • 服务跨集群调用问题
    • 服务调用尽可能选择本地集群的服务,跨集群调用较迟较高
    • 本地集群不可访问时,再去访问其它集群

修改指定实例集群

逐个修改,逐个启动

1
2
3
4
5
spring:
cloud:
nacos:
discovery:
cluster-name: HZ #集群名称

NacosRule负载均衡

  • 默认使用轮询规则
  • 在服务端中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务
1
2
3
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则
  • 注意将客户端权重都设置为1
  • NacosRule优先选择本地集群,在本地多个集群进行随机负载均衡
  • 跨集群访问服务端会发出警告
权重负载均衡
  • 服务器设备性能有差异,部分实例所在机器性能差别,我们希望好的机器承担更多用户请求
  • Nacos提供了权重配置来控制访问频率,权重越大访问频率越高
  • 在控制台编辑页面修改,范围0-1,0不会访问

环境隔离

  • Nacos中服务存储和数据存储的最外层都是namespace,用来做最外层空间隔离
  • 从外到内:Namespace → Group → Service/Data
  • 在控制台新建命名空间
  • 在配置文件添加namespace
1
2
3
4
5
spring:
cloud:
nacos:
discovery:
namespace: 7550979b-3a00-4833-b82f-19f2d9e22b2e #命名空间ID
  • 不同命名空间的服务互相不可见

Nacos注册中心细节

  • 消费者定时拉取服务,存储在服务列表缓存,直接使用
  • 健康检测与Eureka区别
    • 实例分为临时实例与非临时实例
    • 默认是临时实例,采用心跳检测,实例给注册中心提供信息
    • 非临时实例不会做心跳检测,由nacos主动询问
    • nacos不会剔除非临时实例,会等待恢复
    • 有服务变更,会主动推送消息给消费者,更新缓存

临时实例和非临时实例

修改配置文件

1
2
3
4
5
spring:
cloud:
nacos:
discovery:
ephemeral: false #是否是临时实例

负载均衡流程

  • order-service发起请求到Ribbon
    • 发送请求到RibbonLoadBanlancerClient
    • 获取id后,发送给DynamicServerListLoadBalancer
  • Ribbon要求eureka-server拉取userservice,返回服务器列表
  • Ribbon通过IRule接口进行负载均衡

负载均衡策略

内置负载均衡规则类 规则描述
RoundRobinRule 简单轮询
AvailabilityFilteringRule 对以下两种服务器进行忽略:(1)默认情况下3次连接失败,会设置为短路状态,持续30秒。如果再次失败,短路时间几何级增加。(2)并发数过高的服务器。并发数上限,可由客户端设置。
WeightedResponseTimeRule 为每一个服务器赋予一个权重。服务器响应时间越长,权重越小。此规则会按权重值随机选择服务器
ZoneAvoidanceRule 以区域可用的服务器为基础进行服务器选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、机架等。而后再对Zone内多个服务器轮询
BestAvailableRule 忽略短路服务器,选择并发数低的
RandomRule 随机选择
RetryRule 重试机制的选择逻辑
通过定义IRule实现可以修改负载均衡规则,有两种方式
  • 代码方式:在order-service中定义一个新的IRule(阵对全局)
1
2
3
4
@Bean
public IRule randomRole() {
return new RandomRule();
}
  • 配置文件方式:在配置文件中,添加新的配置(阵对某个微服务)
1
2
3
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则

饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问耗时,通过配置开启饥饿加载

1
2
3
4
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: userservice #指定饥饿加载的服务名称

服务调用出现的问题

  • url硬编码问题,不用硬编码如何获取服务提供者地址信息
  • 如果有多个服务提供者(集群),消费者如何选择
  • 消费者如何得知服务提供者的健康状态

Eureka的作用

  • eureka-server:注册中心,管理微服务
  • eureka-client:客户端,分为提供者和消费者
  • 每一个客户端启动时,会把自己信息注册,每30s一次心跳续约,确认状态
  • 消费者需要时,注册中心拉取服务,负载均衡选取一个微服务,提供给远程调用

创建eureka-server服务

  • 引入eureka依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
  • 编写启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.itcast.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
  • 编写配置文件
1
2
3
4
5
6
7
8
9
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka

服务注册

  • 引入依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 配置文件
1
2
3
4
5
6
7
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
  • 启动多个user-service实例
1
2
//配置VM options,防止端口冲突
-Dserver.port={端口号}

服务拉取

服务拉取是基于服务名称获取服务列表,然后再对服务列表做负载均衡

  • 修改OrderService的代码,修改访问的url路径,用服务名代替ip,端口
1
String url = "http://userservice/user/" + order.getUserId();
  • 在order-service项目的启动类中的RestTemplate添加负载均衡注解
1
2
3
4
5
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}

  • 注册RestTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
@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();
}
}
  • 修改order-service中的OrderService的queryOrderById方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
public class OrderService {

@Autowired
private OrderMapper orderMapper;

@Autowired
private RestTemplate restTemplate;

public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2.利用RestTemplate发起http请求
//2.1 url路径
String url = "http://localhost:8081/user/" + order.getUserId();
//2.2 发送http请求,实现远程调用
User user = restTemplate.getForObject(url, User.class);
//2.3 封装User对象到Order
order.setUser(user);
// 4.返回
return order;
}
}

提供者与消费者

  • 服务提供者:一次业务中,被其它微服务调用的服务
  • 服务消费者:一次业务中,调用其它微服务的服务
  • 同一个服务既可以是提供者也可以是消费者

微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
  • 面向服务:微服务对外暴露业务接口
  • 自治:团队独立、技术独立、数据独立、部署独立
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

微服务技术对比

Dubbo SpringCloud SpingCloudAlibaba
注册中心 zookeeper,Redis Eureka,Consul Nacos,Eureka
服务远程调用 Dubbo协议 Feign(http协议) Dubbo,Feign
配置中心 SpringCloudConfig SpringCloudConfig,Nacos
服务网关 SpringCloudGateway,Zuul SpringCloudGateway,Zuul
服务监控和保护 dubbo-admin,功能弱 Hystrix Sentinel

  • 桥接模式:直接分配物理网络,可以和其它系统通讯,但容易造成IP地址冲突
  • NAT模式:网络地址转换方式,Linux可以访问外网,不会造成IP冲突
  • 仅主极模式:IP地址独立,Linux是独立的主机,不能访问外网

    一般建议使用NAT模式

  • 搜索服务
    • 将帖子保存至Elasticsearch服务器
    • 从Elasticsearch服务器删除帖子
    • 从Elasticsearch服务器搜索帖子
  • 发布事件
    • 发布帖子时,将帖子异步的提交到Elasiticsearch服务器
    • 增加评论时,将帖子异步的提交到Elasticsearch服务器
    • 在消费组件中增加一个方法,消费贴子发布事件
  • 显示结果
    • 在控制器中处理搜索请求,在HTML上显示搜索结果

  • 引入依赖
    • spring-boot-starter-data-elasticsearch
  • 配置Elasticsearch
    • cluster-name,cluster-nodes
  • Spring Data Elasticsearch
    • ElasticsearchTemplate
    • ElasticsearchRepository

定义Repository接口

  • DAO层实现实体类对应的Repository接口,继承于ElasiticsearchRepository,泛型为实体和id类型
  • 父接口中已经定义好了对es服务器的增删改查方法,不需要自己实现

测试增删改查方法

  • save方法,保存一条
  • saveAll,保存多条
  • save覆盖即可
  • deleteById
  • 首先构造搜索请求对象SearchRequest,传入索引名
  • 构造搜索条件SearchSourceBuilder
1
2
3
4
5
6
7
8
9
10
11
12
//构建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
//在discusspost索引的title和content字段中都查询“互联网寒冬”
.query(QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content"))
// matchQuery是模糊查询,会对key进行分词:searchSourceBuilder.query(QueryBuilders.matchQuery(key,value));
// termQuery是精准查询:searchSourceBuilder.query(QueryBuilders.termQuery(key,value));
.sort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
.sort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
.sort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
//一个可选项,用于控制允许搜索的时间:searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
.from(0)// 指定从哪条开始查询
.size(10);// 需要查出的总记录条数
  • 调用searchRequest的source方法,将搜索条件传入请求体
  • 创建应答对象
1
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  • 转换为json格式输出

  • 添加高亮

    • 声明高亮对象
    1
    2
    3
    4
    5
    6
    7
    //高亮
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.field("title");
    highlightBuilder.field("content");
    highlightBuilder.requireFieldMatch(false);
    highlightBuilder.preTags("<span style='color:red'>");
    highlightBuilder.postTags("</span>");
    • 遍历时处理高亮
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 处理高亮显示的结果,获取高亮数据进行进行替换
    // 可能有多段匹配,取第一段即可
    HighlightField titleField = hit.getHighlightFields().get("title");
    if (titleField != null) {
    discussPost.setTitle(titleField.getFragments()[0].toString());
    }
    HighlightField contentField = hit.getHighlightFields().get("content");
    if (contentField != null) {
    discussPost.setContent(contentField.getFragments()[0].toString());
    }