黑马商城-微服务笔记

发布于:2025-05-01 ⋅ 阅读:(16) ⋅ 点赞:(0)

image-20250412162934905

认识微服务

单体架构

image-20250412163937564

微服务架构

image-20250412171012550

image-20250412171659023

image-20250412171755023

微服务拆分

image-20250412172723018

image-20250412172923440

服务拆分原则

什么时候拆分?
●创业型项目:先采用单体架构,快速开发,快速试错。随着规模扩大,逐
渐拆分。
●确定的大型项目:资金充足,目标明确,可以直接选择微服务架构,避免
后续拆分的麻烦。

怎么拆分?
从拆分目标来说,要做到:
高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。

从拆分方式来说,一般包含两种方式:
纵向拆分:按照业务模块来拆分
横向拆分:抽取公共服务,提高复用性

拆分服务

image-20250412180511427

image-20250412180716953

mkdir controller domain service mapper

远程调用

image-20250412211026185

image-20250412211413873

服务治理

注册中心原理

image-20250412215956194

Nacos:注册中心

image-20250412220451736

image-20250412221120953
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim

服务发现与负载均衡

image-20250413124852103

OpenFeign

image-20250413131528632

image-20250413131904477

image-20250413132608043

连接池

image-20250413135231346

image-20250413135624659

最佳实践

image-20250413140935610 image-20250413141000997

image-20250413143053015

日志输出

image-20250413152947130

image-20250413153852302

网关路由

快速入门

image-20250413204214456

image-20250413204736700

image-20250413204805119

image-20250413204825789

image-20250413211237708

路由属性

网关路由对应的ava类型是RouteDefinition,其中常见的属性有:
id:路由唯一标示
uri:路由目标地址
predicates:路由断言,判断请求是否符合当前路由。
filters:路由过滤器,对请求或响应做特殊处理。

路由断言

image-20250414163626069

路由过滤器

image-20250414164004541

网关请求处理流程

image-20250414172725716

网关登录校验

image-20250414173813119

自定义过滤器

image-20250414174819694

image-20250414175347369

image-20250414181002766

image-20250414185555092

image-20250414195640151

实现登录校验

需求:在网关中基于过滤器实现登录校验功能
提示:黑马商城是基于WT实现的登录校验,目前相关功能在hm-service模块。我们可以将其中的WT
工具拷贝到gateway模块,然后基于GlobalFilter来实现登录校验。

网关传递用户

image-20250414215531210

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
    }
}

image-20250414222505034

OpenFeign传递用户

image-20250415124457258

image-20250415125525558

image-20250415134401450

配置管理

image-20250415135428582

image-20250415185644510 image-20250415185752473

image-20250415185925619

配置热更新

image-20250415210201596

image-20250415211247748

动态路由

要实现动态路由首先要将路由配置保存到Nacos,当Nacos中的路由配置变更时,推送最新配置到网关,实时更新网关
中的路由信息。
我们需要完成两件事情:
①监听Nacos配置变更的消息
②当配置变更时,将最新的路由信息更新到网关路由表

image-20250415213438182

image-20250415215843819

服务保护

image-20250416182703020

雪崩问题

image-20250416183810692

雪崩问题-解决方案

image-20250416192846887

image-20250416193145709

image-20250416193816026

服务保护技术

image-20250416194239521

sentinel

Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址:https:/sentinelquard.io/zh-cn/index.html

image-20250416194504238

java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

我们在cart-service模块中整合sentinel,连接sentinel-dashboard控制台,步骤如下: 1)引入sentinel依赖

<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2)配置控制台

修改application.yaml文件,添加下面内容:

spring:
  cloud: 
    sentinel:
      transport:
        dashboard: localhost:8090

簇点链路

簇点链路,就是单机调用链路。是一次请求进入服务后经过的每一个被Sentinel.监控的资源链。默认Sentinel:会监控
SpringMVC的每一个Endpoint(htp接口)。限流、熔断等都是针对簇点链路中的资源设置的。而资源名默认就是接
口的请求路径:

image-20250416201708215

线程隔离

image-20250416204310424

流控是控制接收请求的速度,线程隔离是最多能接收请求的次数,就算流控设置的再慢,如果线程卡住了的话,不设置线程隔离也会导致资源占用

Fallback

image-20250416211623282

image-20250416211915707

image-20250416212024053

服务熔断

image-20250416220746683

分布式事务

image-20250417115029628

image-20250417121053834

Seata

image-20250417121330967

image-20250417121642616

image-20250417122432675

Docker部署

需要注意,要确保nacos、mysql都在hm-net网络中。如果某个容器不再hm-net网络,可以参考下面的命令将某容器加入指定网络:

docker network connect [网络名] [容器名]

在虚拟机的/root目录执行下面的命令:

docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.100.129 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2

image-20250417144304389

image-20250417145207773

XA模式

image-20250417181648451

seata:
  data-source-proxy-mode: XA

image-20250417194413872

AT模式

image-20250417201509554

异步通信

RabbitMQ

image-20250417204116873

image-20250417204221821

image-20250417204429397

image-20250417205634865

异步调用

image-20250417205958436

image-20250417210925730

异调用的优势是什么?
耦合度低,拓展性强
异步调用,无需等待,性能好
故障隔离,下游服务故障不影响上游业务
缓存消息,流量削峰填谷

异步调用的问题是什么?
不能立即得到调用结果,时效性差
不确定下游业务执行是否成功
业务安全依赖于Broker的可靠性

image-20250417211547904

追求可用性:Kafka、 RocketMQ 、RabbitMQ

追求可靠性:RabbitMQ、RocketMQ

追求吞吐能力:RocketMQ、Kafka

追求消息低延迟:RabbitMQ、Kafka

安装

docker run \
 -e RABBITMQ_DEFAULT_USER=itheima \
 -e RABBITMQ_DEFAULT_PASS=123321 \
 -v mq-plugins:/plugins \
 --name mq \
 --hostname mq \
 -p 15672:15672 \
 -p 5672:5672 \
 --network hm-net\
 -d \
 rabbitmq:3.8-management

架构

image-20250417215956333

image-20250417220149499

数据隔离

需求:在RabbitMQ的控制台完成下列操作:
新建一个用户hmall
为hmal用户创建一个virtual host
测试不同virtual host之间的数据隔离现象

java客户端

image-20250418131149174

image-20250418131756056

image-20250418132114120

image-20250418132210745

image-20250418160946501

Work Queues

image-20250418162641962

模拟WorkQueue,实现一个队列绑定多个消费者

image-20250418163535415
消费者消息推送限制

image-20250418165940449

Work模型的使用:

多个消费者绑定到一个队列,可以加快消息处理速度
同一条消息只会被一个消费者处理
通过设置prefetch来控制消费者预取的消息数量,处理完一条再处理下一条,实现能者多劳

Fanout交换机

image-20250418173713507

image-20250418174134837

Direct交换机

image-20250418185608250

image-20250418201850254
Topic交换机

image-20250418204619384

声明队列交换机

image-20250418210956089

image-20250418211309966

image-20250418215446099

消息转换器

image-20250419125812348

image-20250419130027395

image-20250419133851471

发送者消息可靠性

发送者重连

image-20250419151158368

image-20250419152052057

发送者确认

image-20250419153409947

image-20250419153704172

image-20250419153818353

image-20250419153949303

MQ的可靠性

image-20250419163303917

image-20250419180731096

image-20250419180829349

image-20250419180932824

Layz Queue

从RabbitMQ的3.6.0版本开始,就增加了Lazy Queue的概念,也就是惰性队列。
惰性队列的特征如下:
·接收到消息后直接存入磁盘,不再存储到内存
·消费者要消费消息时才会从磁盘中读取并加载到内存(可以提前缓存部分消息到内存,最多2048条)
在3.12版本后,所有队列都是Lazy Queue模式,无法更改。

image-20250419193256976

image-20250419193311662

消费者确认机制

image-20250419200946341

image-20250419201431027

image-20250419212133306

失败消息处理策略

image-20250419213621692

image-20250419213823716

业务幂等性

image-20250419215858666

image-20250419220227298

image-20250419223546161

如何保证支付服务与交易服务之间的订单状态一致性?
首先,支付服务会正在用户支付成功以后利用MQ消息通知交易服务
完成订单状态同步。
其次,为了保证MQ消息的可靠性,我们采用了生产者确认机制、消
费者确认、消费者失败重试等策略,确保消息投递和处理的可靠性。同
时也开启了MQ的持久化,避免因服务宕机导致消息丢失。
最后,我们还在交易服务更新订单状态时做了业务幂等判断,避免
因消息重复消费导致订单状态异常。
如果交易服务消息处理失败,有没有什么兜底方案?

延迟消息

延迟消息:发送者发送消息时指定一个时间,消费者不会立刻收到消息,而是在指定时间之后才收到消息。

image-20250420124204477

死信交换机

当一个队列中的消息满足下列情况之一时,就会成为死信(dead letter):
·消费者使用basic.rejecti或basic.nack声明消费失败,并且消息的requeue参数设置为false
消息是一个过期消息(达到了队列或消息本身设置的过期时间),超时无人消费
要投递的队列消息堆积满了,最早的消息可能成为死信

如果队列通过dead-letter-exchange属性指定了一个交换机,那么该队列中的死信就会投递到这个交换机中。这个交
换机称为死信交换机(Dead Letter Exchange,简称DLX)。

image-20250420124957099

延迟消息插件

image-20250420133100272

安装

https://github.com/rabbitmq/rabbitmq-delayed-message-exchange

因为我们是基于Docker安装,所以需要先查看RabbitMQ的插件目录对应的数据卷。

docker volume inspect mq-plugins

结果如下:

[
    {
        "CreatedAt": "2024-06-19T09:22:59+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/mq-plugins/_data",
        "Name": "mq-plugins",
        "Options": null,
        "Scope": "local"
    }
]

插件目录被挂载到了/var/lib/docker/volumes/mq-plugins/_data这个目录,我们上传插件到该目录下。

img

接下来执行命令,安装插件:

docker exec -it mq rabbitmq-plugins enable rabbitmq_delayed_message_exchange

运行结果如下:

img

image-20250420134217855

image-20250420134301643

取消超时订单

image-20250420151606101

Elasticsearch分布式搜索

介绍

image-20250420165732329

image-20250420170048403

image-20250420170216145

image-20250420170555243

image-20250420170614203

image-20250420171201447

安装elasticsearch

通过下面的Docker命令即可安装单机版本的elasticsearch:

docker run -d \
  --name es \
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  -e "discovery.type=single-node" \
  -v es-data:/usr/share/elasticsearch/data \
  -v es-plugins:/usr/share/elasticsearch/plugins \
  -v es-config:/usr/share/elasticsearch/config \
  --privileged \
  --network hm-net \
  -p 9200:9200 \
  -p 9300:9300 \
  elasticsearch:7.12.1

安装Kibana

通过下面的Docker命令,即可部署Kibana:

docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=hm-net \
-p 5601:5601  \
kibana:7.12.1

倒排索引

image-20250420190151488

IK分词器

image-20250420192631955

image-20250420192756354

在线安装

docker exec -it es ./bin/elasticsearch-plugin  install https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip
image-20250420200953502

POST /_analyze
{
“analyzer”: “ik_smart”,
“text”: “传智播客开放全日制大学,刘德华这简直泰裤辣啊”
}

分词器的作用是什么?
创建倒排索引时,对文档分词
用户搜索时,对输入的内容分词
K分词器有几种模式?
ik smart:智能切分,粗粒度
ik max word:最细切分,细粒度IK分词器
如何拓展分词器词库中的词条?
利用config目录的IkAnalyzer…cfg.xml文件添加拓展词典
在词典中添加拓展词条

基础概念

image-20250420205805830

image-20250420210616077

Mapping映射属性

mapping是对索引库中文档的约束,常见的mapping.属性包括:
type:字段数据类型,常见的简单类型有:
字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
数值:long、integer、short、byte、double、float,
布尔:boolean
日期:date
对象:object

​ index:是否创建索引,默认为true
​ analyzer:使用哪种分词器
​ properties:该字段的子字段

索引库操作

image-20250420213400823

image-20250420213802922

# 新增索引库
PUT /heima
{
  "mappings": {
    "properties": {
      "info": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "age": {
        "type": "byte"
      },
      "email": {
        "type": "keyword",
        "index": false
      },
      "name": {
        "type": "object", 
        "properties": {
          "firstNmae": {
            "type": "keyword"
          },
          "lastNmae": {
            "type": "keyword"
          }
        }
        
      }
    }
  }
}

# 查询索引库
GET /heima

# 删除索引库
DELETE /heima

# 修改索引库
PUT /heima/_mapping
{
  "properties": {
    "info": {
      "type": "byte"
    }
  }
}

文档操作CRUD

# 新增文档
POST /heima/_doc/1
{
  "info": "黑马程序员Java讲师",
  "email": "zy@itcast.cn",
  "name": {
    "firstName": "云",
    "lastName": "赵"
  }
}

# 查询文档
GET /heima/_doc/1


# 删除文档
DELETE /heima/_doc/1

# 全量修改
PUT /heima/_doc/2
{
  "info": "黑马程序员Python讲师",
  "email": "zs@itcast.cn",
  "name": {
    "firstName": "四",
    "lastName": "赵"
  }
}

# 增量修改
POST /heima/_update/1
{
  "doc": {
    "email": "ZhaoYun@itcast.cn"
  }
}

批量处理

image-20250421182002257

# 批量新增
POST /_bulk
{"index": {"_index":"heima", "_id": "3"}}
{"info": "黑马程序员C++讲师", "email": "ww@itcast.cn", "name":{"firstName": "五", "lastName":"王"}}
{"index": {"_index":"heima", "_id": "4"}}
{"info": "黑马程序员前端讲师", "email": "zhangsan@itcast.cn", "name":{"firstName": "三", "lastName":"张"}}

# 批量删除
POST /_bulk
{"delete":{"_index":"heima", "_id": "3"}}
{"delete":{"_index":"heima", "_id": "4"}}

JavaRestClient

image-20250421193216409

客户端初始化

image-20250421193435528

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

商品Mapping映射

image-20250421202440795

索引库操作

PUT /items
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "price":{
        "type": "integer"
      },
      "stock":{
        "type": "integer"
      },
      "image":{
        "type": "keyword",
        "index": false
      },
      "category":{
        "type": "keyword"
      },
      "brand":{
        "type": "keyword"
      },
      "sold":{
        "type": "integer"
      },
      "commentCount":{
        "type": "integer",
        "index": false
      },
      "isAD":{
        "type": "boolean"
      },
      "updateTime":{
        "type": "date"
      }
    }
  }
}

image-20250421202846414

image-20250421203159933

文档的crud

image-20250421214542515

修改文档数据有两种方式:
·方式一:全量更新。再次写入id一样的文档,就会删除旧文档,添加新文档。与新增的javaAPI一致。
·方式二:局部更新。只更新指定部分字段。

image-20250422124449893

文档操作的基本步骤:
,初始化RestHighLevelClient
创s建XxxRequest。XXX是Index、Get、Update、Delete
·准备参数(Index和Update时需要)
发送请求。调用RestHighLevelClient#.XXx()方法,XXx是
index、get、update、delete
·解析结果(Get时需要)

批处理

image-20250422125133769

DSL查询

image-20250422182620904

image-20250422185324928

全文检索

image-20250422190301517

精确查询

image-20250422191011111

复合查询
image-20250422191938952

image-20250422192617395

GET /items/_search
{
  "query": {
    "match": {
      "name": "华为"
    }
  }
}

# 要搜索手机,但品牌必须是华为,价格必须是900~1599,那么可以这样写
GET /items/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"name": "手机"}}
      ],
      "filter": [
        {"term": {"brand.keyword": { "value": "华为" }}},
        {"range": {"price": {"gte": 90000, "lt": 159900}}}
      ]
    }
  }
}
排序和分页

image-20250422194521905

image-20250422202253436

深度分页问题

image-20250422203626471

image-20250422204448415

高亮显示

image-20250422205247510

image-20250422210035988

image-20250422210321596

JavaRestClient查询

image-20250422210956999

image-20250422211854464

构建查询条件

image-20250422213426309

image-20250422214108105

需求:利用javaRestClient:实现搜索功能,条件如下:
搜索关键字为脱脂牛奶
品牌必须为德亚
价格必须低于300

@Test
void testSearch() throws IOException {
    // 创建request对象
    SearchRequest request = new SearchRequest("items");
    // 配置参数
    request.source()
            .query(QueryBuilders.boolQuery()
                            .must(QueryBuilders.matchQuery("name", "脱脂牛奶"))
                            .filter(QueryBuilders.termQuery("brand.keyword", "德亚"))
                            .filter(QueryBuilders.rangeQuery("price").lt(30000)));
    // 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //System.out.println("response = " + response);
    // 解析结果
    parseResponseResult(response);
}

排序和分页

@Test
void testSortAndPage() throws IOException {
    // 模拟前端传递的分页参数
    int pageNo = 1, pageSize = 5;
    // 创建request对象
    SearchRequest request = new SearchRequest("items");
    // 配置参数
    request.source().query(QueryBuilders.matchAllQuery());
    request.source().from((pageNo - 1) * pageSize).size(pageSize);
    request.source().sort("sold", SortOrder.DESC)
            .sort("price", SortOrder.ASC);
    // 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    //System.out.println("response = " + response);
    // 解析结果
    parseResponseResult(response);
}

高亮显示

image-20250422224220403

数据聚合

image-20250423125844553

DSL聚合

image-20250423130423058

image-20250423132249596

image-20250423133152292

RestClient聚合

image-20250423133634055

@Test
void testAgg() throws IOException {
    SearchRequest request = new SearchRequest("items");
    // 配置参数
    request.source().size(0);
    String brandAggName = "brandAgg";
    request.source().aggregation(
        AggregationBuilders.terms(brandAggName)
        .field("brand.keyword")
        .size(10)
    );
    // 发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 解析结果
    Aggregations aggregations = response.getAggregations();
    Terms brandTerms = aggregations.get(brandAggName);
    List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
    for (Terms.Bucket bucket : buckets) {
        System.out.println("brand: " + bucket.getKeyAsString());
        System.out.println("count: " + bucket.getDocCount());
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到