文章目录
- 微服务
- 介绍
- 微服务技术栈
- 分布式架构
- 微服务特点总结
- 微服务技术对比
- SpringCloud
- 服务拆分与远程调用
- Eureka注册中心
- 负载均衡:
- Nacos注册中心
- Nacos配置管理
- 基于Feign的远程调用:
- 统一网关Gateway:
- Docker
- 遇到的问题:
- Docker的安装:
- Docker基本操作:
- 数据卷:
- 自定义镜像:
- Dockerfile:
- DockerCompose:
- 微服务部署:
- Docker镜像仓库:
- 服务异步通讯:RabbitMQ:
- SpringAMQP:
- ElasticSearch:
- RestClient基本用法:
- DSL查询语法:
- 搜索结果处理-排序:
- 搜索结果处理-分页:
- 搜索结果处理-高亮:
- 搜索结果处理-总结:
- RestClient查询文档:
- RestClient查询-match、term、range、bool查询:
- RestClient查询-排序和分页:
- RestClient查询-高亮:
- 旅游项目案例:
- 数据聚合:
- 自动补全:
微服务
介绍
微服务技术栈
分布式架构
介绍
分布式架构需考虑的问题
微服务特点总结
微服务技术对比
技术对比
企业需求
SpringCloud
介绍
版本兼容
服务拆分与远程调用
微服务远程调用
okHttpApi也可以进行远程调用
RestTemplate:是Spring提供的用于访问Rest服务的客户端
提供者与消费者
服务者与消费者的角色是相对的。一个服务可以同时是服务提供者和服务消费者。
Eureka注册中心
服务调用出现的问题
Eureka的作用
Eureka注册中心配置搭建
注册user-service
启动一个项目的多个实例
服务拉取
总结
负载均衡:
负载均衡流程:
负载均衡策略:
自定义负载均衡策略:
第一种:是全局配置,orderservice这个消费者访问任何服务的时候均会按照配置的这个随机选择策略。
第二种:是局部配置,先指定服务名再配置用户希望的负载均衡策略。
饥饿加载:
总结:
Nacos注册中心
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件 。相比Eureka功能更为丰富,在国内受欢迎程度较高。
Nacos下载安装与启动
下载地址:https://github.com/alibaba/nacos/releases/tag/1.4.1
启动方式:
D:\nacos\bin>startup.cmd -m standalone
配置总结:
SpringCloud-common定义了统一的接口规范,所以只需要修改依赖即可。
首先:还得在父工程pom文件中,引入:
<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服务注册发现客户端依赖:
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Nacos服务分级存储模型:
分级模型总结:
注意:一种服务有多个集群时,先通过启动一个项目的多个实例,再分别通过修改yml文件配置并指定要启动的一个服务启动!
配置负载均衡规则:消费者优先选择本地集群的服务者。
Nacos负载均衡总结
根据权重负载均衡
作用:也可以用于服务器升级,将需要升级的服务器访问权重调小。
环境隔离
Nacos注册中心原理
Nacos对比Eureka总结:
Nacos配置管理
配置步骤:
读取配置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ziru2uvn-1688347828537)(C:\Users\25817\AppData
配置热更新:
方式一:
方式二:
总结:
多环境配置共享:
开发、测试、生产环境相同的配置共享。
修改环境:
等价于上面配置
配置优先级:
总结:
nacos集群搭建:
Nacos的单例模式不推荐用在生产环境,所以需要对nacos服务做高可用,所以需要通过nacos集群来解决这个问题。
基于Feign的远程调用:
RestTemplate存在的问题:
Feign:
Feign使用步骤:
自定义Feign的配置:
@EnableFeignClients 注解在启动类上;
@FeignClient 注解在client的配置接口;
Feign的性能优化:
性能优化介绍:
性能优化操作步骤:
总结:
Feign的最佳实践
方式一(继承):
方式二(抽取):
方式二的实现:
总结:
统一网关Gateway:
网关功能:
功能总结:
网关搭建:
路由断言工厂:
总结:
路由的过滤器配置:
默认过滤器配置,会对所有进入网关的请求路由进行配置
全局过滤器GlobalFilter:
全局过滤器配置步骤:
过滤器执行顺序:
网关跨域cors的配置:
在gateway服务的yml进行配置
总结:
Docker
项目部署的问题
工作原理:
类似Ubuntu和CentOS都是基于Linux内核,只是系统应用不同,提供的函数库有差异。
工作原理总结:
Docker对比虚拟机:
Hypervisor可以模拟出计算机的硬件,cpu,内存等。
镜像和容器:
镜像只能读,不能写。避免污染镜像。
Docker整体架构:
Docker架构总结:
遇到的问题:
1.Failed to download metadata for repo ‘docker-ce-stable‘: Cannot download repomd.xml
解决方案:原因是华为服务器内核的问题,云服务器切换操作系统即可:
2.启动docker容器报错No chain/target/match by that name.
解决方案:
systemctl restart docker
3.关于SpringBootTest与RunWith注解
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class) //是一个JUnit注解,它是用来指定运行测试用例的运行器
@SpringBootTest //加载SpringBoot应用程序的上下文
public class SpringAmqpTest {
@Resource
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage2SimpleQueue(){
String queueName="simple.queue";
String message="Hello,SpringAmqp3!";
rabbitTemplate.convertAndSend(queueName,message);
}
}
Docker的安装:
见docker安装文件
Docker基本操作:
Docker基本操作-镜像:
打包镜像到文件
这里去掉image
加载镜像
Docker基本操作-镜像命令总结:
Docker基本操作-容器:
创建运行一个Nginx容器
docker logs -f mn #查看docker中的mn容器的日志,不间断的方式
修改容器内容:
shell命令是bash命令的一部分
# 修改nginx容器中的静态文件
sed -i 's#Welcome to nginx#Nginx欢迎您!--by TSW#g' index.html
sed -i 's#<head>#<head><meta charset="utf-8">#g' index.html
exit #退出容器
docker ps #显示运行中的容器的状态
docker ps -a #显示所有容器的状态
docker rm mn #删除非运行态的容器
docker rm -f mn #强制删除运行的容器
docker stop 容器id # 停止指定容器的运行
总结:
redis容器操作:
docker exec -it mr bash #进入redis(name-mr)容器内部
redis -cli #连接redis客户端
docker ps -a #该命令将显示所有容器的列表,包括正在运行和已停止的容器,并显示相应的状态信息
数据卷:
docker volume rm 数据卷名
数据卷总结1:
挂载数据卷:
数据卷挂载案例:
远程mysql连接不上的问题,容器在创建的时候,密码输入格式不正确:
docker run \
--name mysql \
-e MYSQL_ROOT_PASSWORD=123 \
-p 3306:3306 \
-v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \
-v /etc/mysql/data:/var/lib/mysql \
-d mysql:5.7.25
自定义镜像:
Dockerfile:
Dockerfile案例:
总结:
DockerCompose:
微服务部署:
Docker镜像仓库:
具体用法:见文档
服务异步通讯:RabbitMQ:
安装:
RabbitMQ的部署与安装:详见MyCoding笔记文件夹中的RabbitMQ部署指南!
同步通讯的优缺点:
异步通讯的优缺点:
RabbitMQ介绍和安装:
见安装文档
消息模型介绍:
SpringAMQP:
简介:
发送消息:
接收消息:
WorkQueue工作队列:
其他的和简单消息队列一致,差别不大
发布订阅模型:
Fanout Exchange:
简介:
操作步骤:
总结:
Direct Exchange:
介绍:
操作步骤:
总结:
Topic Exchange:
介绍:
操作步骤:
总结:
消息转换器:
发送消息:
MessageConverter在publisher中的启动类上声明!!!
发送消息,把对象序列化成字节!
接收消息:
总结:
ElasticSearch:
简介:
倒排索引:
文档:
索引:
ES对比Mysql:
ES的安装:
详见MyCoding笔记文件夹中的ES安装指南!
Kibana的安装:
详见MyCoding笔记文件夹中的ES安装指南!
分词器:
IK分词器的拓展和停用词典:
其他的扩展词典,停用词等操作见参考资料
索引库操作:
创建索引库:
操作索引库:
文档操作:
RestClient基本用法:
RestClient操作索引库:
注意:mapping的id只能为字符串类型,不能为integer,long。
注意:步骤三里面的第二步需要强制指定ES的版本
RestClient创建索引库:
RestClient删除索引库:
总结:
RestClient操作文档:
客户端初始化:
新增文档:
批量新增文档:
查询文档:
修改文档:
删除文档:
总结:
)]
DSL查询语法:
查询分类和基本语法:
全文检索查询:
上面两种查询功能和结果一样。
精确查询:
地理查询:
第一种:
第二种:
复合查询
介绍:
相关性得分:
复合查询—Function score Query:
# function score 查询
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"match": {
"all": "外滩"
}
},
"functions": [
{
"filter": {
"term": {
"brand": "如家"
}
},
"weight": 10
}
],
"boost_mode": "sum"
}
}
}
term查询是精确查询。不是模糊查询(match)
复合查询—Boolean Query:
搜索结果处理-排序:
介绍:
案例:
DSL语句:
DSL语句:
注意:如果对结果进行排序处理,则不会再进行相关性得分的计算
搜索结果处理-分页:
介绍与应用:
深度分页问题:
搜索结果处理-高亮:
简介
DSL语句:
搜索结果处理-总结:
RestClient查询文档:
入门介绍:
注意点一:
注意点二:
总结:
RestClient查询-match、term、range、bool查询:
Ctrl+Alt+M抽取代码,可提取出来当作一个方法
private void handleResponse(SearchResponse response) {
// 4.解析响应
SearchHits searchHits = response.getHits();
// 4.1查询的总条数
long value = searchHits.getTotalHits().value;
System.out.println("总条数: "+value);
// 4.2查询的结果数组
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 4.3得到source
String json = hit.getSourceAsString();
// 反序列化为对象
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
}
java代码
/**
* 复合查询:bool子查询
* @throws IOException
*/
@Test
void testBoolMatch() throws IOException {
// 1.准备request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1准备BooleanQuery
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 2.2添加term
boolQueryBuilder.must(QueryBuilders.termQuery("city","上海"));
// 2.3添加range
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(250));
request.source().query(boolQueryBuilder);
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// System.out.println(response);
handleResponse(response);
}
RestClient查询-排序和分页:
注意链式编程!
RestClient查询-高亮:
注意:高亮,必须针对带关键字的查询,不能用matchAll查询
高亮请求:
高亮结果解析:
java代码:
/**
* 高亮
* @throws IOException
*/
@Test
void testHighlight() throws IOException {
// 1.准备request
SearchRequest request = new SearchRequest("hotel");
// 全部查询
request.source().query(QueryBuilders.matchQuery("all","如家"));
// 高亮
request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
// 3.发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// System.out.println(response);
// 4.解析响应
SearchHits searchHits = response.getHits();
// 4.1查询的总条数
long value = searchHits.getTotalHits().value;
System.out.println("总条数: "+value);
// 4.2查询的结果数组
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
// 4.3得到source
String json = hit.getSourceAsString();
// 反序列化为对象
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
// 获取高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
// 根据字段名获取高亮结果
HighlightField highlightField = highlightFields.get("name");
if(highlightField!=null){
String name = highlightField.getFragments()[0].string();
hotelDoc.setName(name);
}
System.out.println(hotelDoc);
}
}
总结:
旅游项目案例:
注意:SpringBoot项目启动会自动访问resource文件夹下的static下的index.html,
搜索和分页:
快捷键:Ctrl+Alt+T执行surround with
主要代码java:
@MapperScan("cn.itcast.hotel.mapper")
@SpringBootApplication
public class HotelDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HotelDemoApplication.class, args);
}
/**
* 将RestHighLevelClient注册到bean
* @return
*/
@Bean
public RestHighLevelClient client(){
return new RestHighLevelClient(RestClient.builder(HttpHost.create(
"http://123.60.61.97:9200")
));
}
}
@RestController
@RequestMapping("/hotel")
public class HotelController {
@Resource
private IHotelService hotelService;
@PostMapping("/list")
public PageResult search(@RequestBody RequestParams requestParams){
return hotelService.search(requestParams);
}
}
package cn.itcast.hotel.service.impl;
import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Resource
private RestHighLevelClient client;
@Override
public PageResult search(RequestParams requestParams) {
SearchResponse response = null;
try {
// 1.准备request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1关键字全文搜索
// 若搜索为空,则执行全文查询
String key = requestParams.getKey();
if (key == null || key.equals("")) {
request.source().query(QueryBuilders.matchAllQuery());
} else {
request.source().query(QueryBuilders.matchQuery("all", key));
}
// 2.2分页
request.source().from((requestParams.getPage() - 1) * requestParams.getSize()).size(requestParams.getSize());
// 3.发送请求
response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应,返回结果
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
// System.out.println(response);
}
private PageResult handleResponse(SearchResponse response) {
// 4.解析响应
SearchHits searchHits = response.getHits();
// 4.1查询的总条数
long value = searchHits.getTotalHits().value;
System.out.println("总条数: " + value);
// 4.2查询的结果数组
SearchHit[] hits = searchHits.getHits();
List<HotelDoc> hotelDocs = new ArrayList<>();
for (SearchHit hit : hits) {
// 4.3得到source
String json = hit.getSourceAsString();
// 反序列化为对象
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
hotelDocs.add(hotelDoc);
// System.out.println(hotelDoc);
}
// 4.4封装返回
return new PageResult(value,hotelDocs);
}
}
条件过滤:
java代码:
此处:Ctrl+Alt+M抽取了代码块作buildBasicQuery方法
public PageResult search(RequestParams requestParams) {
SearchResponse response = null;
try {
// 1.准备request
SearchRequest request = new SearchRequest("hotel");
// 2.准备DSL
// 2.1关键字全文搜索
// 构建BooleanQuery
buildBasicQuery(requestParams, request);
// 2.2分页
request.source().from((requestParams.getPage() - 1) * requestParams.getSize()).size(requestParams.getSize());
// 3.发送请求
response = client.search(request, RequestOptions.DEFAULT);
// 4.解析响应,返回结果
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
// System.out.println(response);
}
private void buildBasicQuery(RequestParams requestParams, SearchRequest request) {
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
String key = requestParams.getKey();
// 关键字搜索
// 若搜索为空,则执行全文查询
if (key == null || key.equals("")) {
boolQueryBuilder.must(QueryBuilders.matchAllQuery());
} else {
boolQueryBuilder.must(QueryBuilders.matchQuery("all", key));
}
// 条件过滤:城市条件
if(requestParams.getCity()!=null&&!requestParams.getCity().equals("")){
// filter不参与算分
boolQueryBuilder.filter(QueryBuilders.termQuery("city", requestParams.getCity()));
}
// 条件过滤:品牌条件
if(requestParams.getBrand()!=null&&!requestParams.getBrand().equals("")){
// filter不参与算分
boolQueryBuilder.filter(QueryBuilders.termQuery("brand", requestParams.getBrand()));
}
// 条件过滤:星级条件
if(requestParams.getStarName()!=null&&!requestParams.getStarName().equals("")){
// filter不参与算分
boolQueryBuilder.filter(QueryBuilders.termQuery("starName", requestParams.getStarName()));
}
// 条件过滤:价格条件
if(requestParams.getMinPrice()!=null&& requestParams.getMaxPrice()!=null){
// filter不参与算分
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price")
.gte(requestParams.getMinPrice())
.lte(requestParams.getMaxPrice()));
}
request.source().query(boolQueryBuilder);
}
我附近的酒店:
java代码:
2.3 添加排序
String location = requestParams.getLocation();
if(location!=null&&!location.equals("")){
request.source().sort(SortBuilders
.geoDistanceSort("location",new GeoPoint(location))
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS));
}
// 解析附近酒店距离值 这块代码在封装的解析响应那块
Object[] sortValues = hit.getSortValues();
if(sortValues.length>0){
Object sortValue = sortValues[0];
hotelDoc.setDistance(sortValue);
}
广告置顶:
filter:必须满足条件的文档才参与算分
java代码:
// 2.算分控制
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
// 原始查询
boolQueryBuilder
// function score的数组
, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
// 其中的一个 function score元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
// 过滤条件
QueryBuilders.termQuery("isAD", true),
// 算分函数
//
ScoreFunctionBuilders.weightFactorFunction(10)
)
});
request.source().query(functionScoreQuery);
数据聚合:
DSL实现bucket聚合:
DSL语句:
返回结果:
DSL实现Metrics聚合:
DSL语句:
返回结果:
RestClient实现聚合:
java代码:
请求响应+结果解析,请求三要素:类型,名称,字段
@Test
void Aggregation() throws IOException {
// 1.准备request
SearchRequest request = new SearchRequest("hotel");
// 2.准备dsl
// 2.1设置size
request.source().size(0);
// 2.2聚合
request.source().aggregation(AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(10)
);
// 3.发出请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析结果
Aggregations aggregations = response.getAggregations();
//4.根据聚合名称获取聚合结果
Terms brandTerms = aggregations.get("brandAgg");
//4.2获取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
//4.3遍历
for (Terms.Bucket bucket : buckets) {
//4.4获取key
System.out.println(bucket.getKey());
}
//System.out.println(response);
}
多条件聚合:
java代码:
surround with快捷键:Ctrl+Alt+T
/**
*多条件聚合:品牌,城市,星级
* @return
*/
@Override
public Map<String, List<String>> filters() {
try {
// 1.准备request
SearchRequest request = new SearchRequest("hotel");
// 2.准备dsl
// 2.1设置size
request.source().size(0);
// 2.2聚合
buildAggregation(request);
// 3.发出请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 4.解析结果
// 4.1根据聚合名称获取结果
Map<String, List<String>> result = new HashMap<>();
Aggregations aggregations = response.getAggregations();
List<String> brandList = getAggByName(aggregations,"brandAgg");
List<String> cityList = getAggByName(aggregations,"cityAgg");
List<String> starList = getAggByName(aggregations,"starAgg");
//4.4放入map
result.put("品牌",brandList);
result.put("城市",cityList);
result.put("星级",starList);
return result;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private List<String> getAggByName(Aggregations aggregations,String aggName) {
//4.1根据聚合名称获取聚合结果
Terms brandTerms = aggregations.get(aggName);
//4.2获取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
//4.3遍历
List<String> brandList = new ArrayList<>();
for (Terms.Bucket bucket : buckets) {
//4.4获取key
//System.out.println(bucket.getKey());
brandList.add(bucket.getKeyAsString());
}
return brandList;
}
private void buildAggregation(SearchRequest request) {
request.source().aggregation(AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(100)
);
request.source().aggregation(AggregationBuilders
.terms("cityAgg")
.field("city")
.size(100)
);
request.source().aggregation(AggregationBuilders
.terms("starAgg")
.field("starName")
.size(100)
);
}
带过滤条件的聚合:
java代码:
@PostMapping("/filters")
public Map<String, List<String>> getFilters(@RequestBody RequestParams requestParams){
return hotelService.filters(requestParams);
}
案例截图:
自动补全:
简介及用法:
es的plugin目录地址:/var/lib/docker/volumes/es-plugins/_data
注意:pinyin分词器得在创建索引库的时候,在setting下面配置好
若用户搜索关键词是中文文字,则不应该使用拼音分词器
总结:
DSL实现自动补全:
DSL代码:
# 自动补全的索引库
PUT test2
{
"mappings": {
"properties": {
"title":{
"type": "completion"
}
}
}
}
# 示例数据
POST test2/_doc
{
"title": ["Sony", "WH-1000XM3"]
}
POST test2/_doc
{
"title": ["SK-II", "PITERA"]
}
POST test2/_doc
{
"title": ["Nintendo", "switch"]
}
# 自动补全查询,字段是completion类型
GET /test2/_search
{
"suggest": {
"titleSuggest": {
"text": "so",
"completion": {
"field": "title",
"skip_duplicates": true,
"size": 10
}
}
}
}