day08-Elasticsearch

黑馬商城作為一個電商項目,商品的搜索肯定是訪問頻率最高的頁面之一。目前搜索功能是基于數據庫的模糊搜索來實現的,存在很多問題。

首先,查詢效率較低。

由于數據庫模糊查詢不走索引,在數據量較大的時候,查詢性能很差。黑馬商城的商品表中僅僅有不到9萬條數據,基于數據庫查詢時,搜索接口的表現如圖:

改為基于搜索引擎后,查詢表現如下:

需要注意的是,數據庫模糊查詢隨著表數據量的增多,查詢性能的下降會非常明顯,而搜索引擎的性能則不會隨著數據增多而下降太多。目前僅10萬不到的數據量差距就如此明顯,如果數據量達到百萬、千萬、甚至上億級別,這個性能差距會非常夸張。

其次,功能單一

數據庫的模糊搜索功能單一,匹配條件非常苛刻,必須恰好包含用戶搜索的關鍵字。而在搜索引擎中,用戶輸入出現個別錯字,或者用拼音搜索、同義詞搜索都能正確匹配到數據。

綜上,在面臨海量數據的搜索,或者有一些復雜搜索需求的時候,推薦使用專門的搜索引擎來實現搜索功能。

目前全球的搜索引擎技術排名如下:

排名第一的就是我們今天要學習的elasticsearch.

elasticsearch是一款非常強大的開源搜索引擎,支持的功能非常多,例如:

代碼搜索

商品搜索

解決方案搜索

地圖搜索

通過今天的學習大家要達成下列學習目標:

  • 理解倒排索引原理
  • 會使用IK分詞器
  • 理解索引庫Mapping映射的屬性含義
  • 能創建索引庫及映射
  • 能實現文檔的CRUD

1.初識elasticsearch

Elasticsearch的官方網站如下:

https://www.elastic.co/cn/elasticsearch/

本章我們一起來初步了解一下Elasticsearch的基本原理和一些基礎概念。

1.1.認識和安裝

Elasticsearch是由elastic公司開發的一套搜索引擎技術,它是elastic技術棧中的一部分。完整的技術棧包括:

  • Elasticsearch:用于數據存儲、計算和搜索
  • Logstash/Beats:用于數據收集
  • Kibana:用于數據可視化

整套技術棧被稱為ELK,經常用來做日志收集、系統監控和狀態分析等等:

整套技術棧的核心就是用來存儲搜索計算的Elasticsearch,因此我們接下來學習的核心也是Elasticsearch。

我們要安裝的內容包含2部分:

  • elasticsearch:存儲、搜索和運算
  • kibana:圖形化展示

首先Elasticsearch不用多說,是提供核心的數據存儲、搜索、分析功能的。

然后是Kibana,Elasticsearch對外提供的是Restful風格的API,任何操作都可以通過發送http請求來完成。不過http請求的方式、路徑、還有請求參數的格式都有嚴格的規范。這些規范我們肯定記不住,因此我們要借助于Kibana這個服務。

Kibana是elastic公司提供的用于操作Elasticsearch的可視化控制臺。它的功能非常強大,包括:

  • 對Elasticsearch數據的搜索、展示
  • 對Elasticsearch數據的統計、聚合,并形成圖形化報表、圖形
  • 對Elasticsearch的集群狀態監控
  • 它還提供了一個開發控制臺(DevTools),在其中對Elasticsearch的Restful的API接口提供了語法提示

1.1.1.安裝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 \--privileged \--network hm-net \-p 9200:9200 \-p 9300:9300 \elasticsearch:7.12.1

注意,這里我們采用的是elasticsearch的7.12.1版本,由于8以上版本的JavaAPI變化很大,在企業中應用并不廣泛,企業中應用較多的還是8以下的版本。

如果拉取鏡像困難,可以直接導入課前資料提供的鏡像tar包:

安裝完成后,訪問9200端口,即可看到響應的Elasticsearch服務的基本信息:

1.1.2.安裝Kibana

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

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

如果拉取鏡像困難,可以直接導入課前資料提供的鏡像tar包:

安裝完成后,直接訪問5601端口,即可看到控制臺頁面:

選擇Explore on my own之后,進入主頁面:

然后選中Dev tools,進入開發工具頁面:

1.2.倒排索引

elasticsearch之所以有如此高性能的搜索表現,正是得益于底層的倒排索引技術。那么什么是倒排索引呢?

倒排索引的概念是基于MySQL這樣的正向索引而言的。

1.2.1.正向索引

我們先來回顧一下正向索引。

例如有一張名為tb_goods的表:

id

title

price

1

小米手機

3499

2

華為手機

4999

3

華為小米充電器

49

4

小米手環

49

...

...

...

其中的id字段已經創建了索引,由于索引底層采用了B+樹結構,因此我們根據id搜索的速度會非常快。但是其他字段例如title,只在葉子節點上存在。

因此要根據title搜索的時候只能遍歷樹中的每一個葉子節點,判斷title數據是否符合要求。

比如用戶的SQL語句為:

select * from tb_goods where title like '%手機%';

那搜索的大概流程如圖:

說明:

  • 1)檢查到搜索條件為like '%手機%',需要找到title中包含手機的數據
  • 2)逐條遍歷每行數據(每個葉子節點),比如第1次拿到id為1的數據
  • 3)判斷數據中的title字段值是否符合條件
  • 4)如果符合則放入結果集,不符合則丟棄
  • 5)回到步驟1

綜上,根據id精確匹配時,可以走索引,查詢效率較高。而當搜索條件為模糊匹配時,由于索引無法生效,導致從索引查詢退化為全表掃描,效率很差。

因此,正向索引適合于根據索引字段的精確搜索,不適合基于部分詞條的模糊匹配。

而倒排索引恰好解決的就是根據部分詞條模糊匹配的問題。

1.2.2.倒排索引

倒排索引中有兩個非常重要的概念:

  • 文檔(Document):用來搜索的數據,其中的每一條數據就是一個文檔。例如一個網頁、一個商品信息
  • 詞條(Term):對文檔數據或用戶搜索數據,利用某種算法分詞,得到的具備含義的詞語就是詞條。例如:我是中國人,就可以分為:我、是、中國人、中國、國人這樣的幾個詞條

創建倒排索引是對正向索引的一種特殊處理和應用,流程如下:

  • 將每一個文檔的數據利用分詞算法根據語義拆分,得到一個個詞條
  • 創建表,每行數據包括詞條、詞條所在文檔id、位置等信息
  • 因為詞條唯一性,可以給詞條創建正向索引

此時形成的這張以詞條為索引的表,就是倒排索引表,兩者對比如下:

正向索引

id(索引)

title

price

1

小米手機

3499

2

華為手機

4999

3

華為小米充電器

49

4

小米手環

49

...

...

...

倒排索引

詞條(索引)

文檔id

小米

1,3,4

手機

1,2

華為

2,3

充電器

3

手環

4

倒排索引的搜索流程如下(以搜索"華為手機"為例),如圖:

流程描述:

1)用戶輸入條件"華為手機"進行搜索。

2)對用戶輸入條件分詞,得到詞條:華為手機

3)拿著詞條在倒排索引中查找(由于詞條有索引,查詢效率很高),即可得到包含詞條的文檔id:1、2、3

4)拿著文檔id到正向索引中查找具體文檔即可(由于id也有索引,查詢效率也很高)。

雖然要先查詢倒排索引,再查詢倒排索引,但是無論是詞條、還是文檔id都建立了索引,查詢速度非常快!無需全表掃描。

1.2.3.正向和倒排

那么為什么一個叫做正向索引,一個叫做倒排索引呢?

  • 正向索引是最傳統的,根據id索引的方式。但根據詞條查詢時,必須先逐條獲取每個文檔,然后判斷文檔中是否包含所需要的詞條,是根據文檔找詞條的過程
  • 倒排索引則相反,是先找到用戶要搜索的詞條,根據詞條得到保護詞條的文檔的id,然后根據id獲取文檔。是根據詞條找文檔的過程

是不是恰好反過來了?

那么兩者方式的優缺點是什么呢?

正向索引

  • 優點:
    • 可以給多個字段創建索引
    • 根據索引字段搜索、排序速度非常快
  • 缺點:
    • 根據非索引字段,或者索引字段中的部分詞條查找時,只能全表掃描。

倒排索引

  • 優點:
    • 根據詞條搜索、模糊搜索時,速度非常快
  • 缺點:
    • 只能給詞條創建索引,而不是字段
    • 無法根據字段做排序

1.3.基礎概念

elasticsearch中有很多獨有的概念,與mysql中略有差別,但也有相似之處。

1.3.1.文檔和字段

elasticsearch是面向文檔(Document)存儲的,可以是數據庫中的一條商品數據,一個訂單信息。文檔數據會被序列化為json格式后存儲在elasticsearch中:

{"id": 1,"title": "小米手機","price": 3499
}
{"id": 2,"title": "華為手機","price": 4999
}
{"id": 3,"title": "華為小米充電器","price": 49
}
{"id": 4,"title": "小米手環","price": 299
}

因此,原本數據庫中的一行數據就是ES中的一個JSON文檔;而數據庫中每行數據都包含很多列,這些列就轉換為JSON文檔中的字段(Field)

1.3.2.索引和映射

隨著業務發展,需要在es中存儲的文檔也會越來越多,比如有商品的文檔、用戶的文檔、訂單文檔等等:

所有文檔都散亂存放顯然非常混亂,也不方便管理。

因此,我們要將類型相同的文檔集中在一起管理,稱為索引(Index)。例如:

商品索引

{"id": 1,"title": "小米手機","price": 3499
}{"id": 2,"title": "華為手機","price": 4999
}{"id": 3,"title": "三星手機","price": 3999
}

用戶索引

{"id": 101,"name": "張三","age": 21
}{"id": 102,"name": "李四","age": 24
}{"id": 103,"name": "麻子","age": 18
}

訂單索引

{"id": 10,"userId": 101,"goodsId": 1,"totalFee": 294
}{"id": 11,"userId": 102,"goodsId": 2,"totalFee": 328
}
  • 所有用戶文檔,就可以組織在一起,稱為用戶的索引;
  • 所有商品的文檔,可以組織在一起,稱為商品的索引;
  • 所有訂單的文檔,可以組織在一起,稱為訂單的索引;

因此,我們可以把索引當做是數據庫中的表。

數據庫的表會有約束信息,用來定義表的結構、字段的名稱、類型等信息。因此,索引庫中就有映射(mapping),是索引中文檔的字段約束信息,類似表的結構約束。

1.3.3.mysql與elasticsearch

我們統一的把mysql與elasticsearch的概念做一下對比:

MySQL

Elasticsearch

說明

Table

Index

索引(index),就是文檔的集合,類似數據庫的表(table)

Row

Document

文檔(Document),就是一條條的數據,類似數據庫中的行(Row),文檔都是JSON格式

Column

Field

字段(Field),就是JSON文檔中的字段,類似數據庫中的列(Column)

Schema

Mapping

Mapping(映射)是索引中文檔的約束,例如字段類型約束。類似數據庫的表結構(Schema)

SQL

DSL

DSL是elasticsearch提供的JSON風格的請求語句,用來操作elasticsearch,實現CRUD

如圖:

那是不是說,我們學習了elasticsearch就不再需要mysql了呢?

并不是如此,兩者各自有自己的擅長之處:

  • Mysql:擅長事務類型操作,可以確保數據的安全和一致性
  • Elasticsearch:擅長海量數據的搜索、分析、計算

因此在企業中,往往是兩者結合使用:

  • 對安全性要求較高的寫操作,使用mysql實現
  • 對查詢性能要求較高的搜索需求,使用elasticsearch實現
  • 兩者再基于某種方式,實現數據的同步,保證一致性

1.4.IK分詞器

Elasticsearch的關鍵就是倒排索引,而倒排索引依賴于對文檔內容的分詞,而分詞則需要高效、精準的分詞算法,IK分詞器就是這樣一個中文分詞算法。

1.4.1.安裝IK分詞器

方案一:在線安裝

運行一個命令即可:

docker exec -it es ./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip

然后重啟es容器:

docker restart es

方案二:離線安裝

如果網速較差,也可以選擇離線安裝。

首先,查看之前安裝的Elasticsearch容器的plugins數據卷目錄:

docker volume inspect es-plugins

結果如下:

[{"CreatedAt": "2024-11-06T10:06:34+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/es-plugins/_data","Name": "es-plugins","Options": null,"Scope": "local"}
]

可以看到elasticsearch的插件掛載到了/var/lib/docker/volumes/es-plugins/_data這個目錄。我們需要把IK分詞器上傳至這個目錄。

找到課前資料提供的ik分詞器插件,課前資料提供了7.12.1版本的ik分詞器壓縮文件,你需要對其解壓:

然后上傳至虛擬機的/var/lib/docker/volumes/es-plugins/_data這個目錄:

最后,重啟es容器:

docker restart es

1.4.2.使用IK分詞器

IK分詞器包含兩種模式:

  • ik_smart:智能語義切分
  • ik_max_word:最細粒度切分

我們在Kibana的DevTools上來測試分詞器,首先測試Elasticsearch官方提供的標準分詞器:

POST /_analyze
{"analyzer": "standard","text": "黑馬程序員學習java太棒了"
}

結果如下:

{"tokens" : [{"token" : "黑","start_offset" : 0,"end_offset" : 1,"type" : "<IDEOGRAPHIC>","position" : 0},{"token" : "馬","start_offset" : 1,"end_offset" : 2,"type" : "<IDEOGRAPHIC>","position" : 1},{"token" : "程","start_offset" : 2,"end_offset" : 3,"type" : "<IDEOGRAPHIC>","position" : 2},{"token" : "序","start_offset" : 3,"end_offset" : 4,"type" : "<IDEOGRAPHIC>","position" : 3},{"token" : "員","start_offset" : 4,"end_offset" : 5,"type" : "<IDEOGRAPHIC>","position" : 4},{"token" : "學","start_offset" : 5,"end_offset" : 6,"type" : "<IDEOGRAPHIC>","position" : 5},{"token" : "習","start_offset" : 6,"end_offset" : 7,"type" : "<IDEOGRAPHIC>","position" : 6},{"token" : "java","start_offset" : 7,"end_offset" : 11,"type" : "<ALPHANUM>","position" : 7},{"token" : "太","start_offset" : 11,"end_offset" : 12,"type" : "<IDEOGRAPHIC>","position" : 8},{"token" : "棒","start_offset" : 12,"end_offset" : 13,"type" : "<IDEOGRAPHIC>","position" : 9},{"token" : "了","start_offset" : 13,"end_offset" : 14,"type" : "<IDEOGRAPHIC>","position" : 10}]
}

可以看到,標準分詞器智能1字1詞條,無法正確對中文做分詞。

我們再測試IK分詞器:

POST /_analyze
{"analyzer": "ik_smart","text": "黑馬程序員學習java太棒了"
}

執行結果如下:

{"tokens" : [{"token" : "黑馬","start_offset" : 0,"end_offset" : 2,"type" : "CN_WORD","position" : 0},{"token" : "程序員","start_offset" : 2,"end_offset" : 5,"type" : "CN_WORD","position" : 1},{"token" : "學習","start_offset" : 5,"end_offset" : 7,"type" : "CN_WORD","position" : 2},{"token" : "java","start_offset" : 7,"end_offset" : 11,"type" : "ENGLISH","position" : 3},{"token" : "太棒了","start_offset" : 11,"end_offset" : 14,"type" : "CN_WORD","position" : 4}]
}

1.4.3.拓展詞典

隨著互聯網的發展,“造詞運動”也越發的頻繁。出現了很多新的詞語,在原有的詞匯列表中并不存在。比如:“泰褲辣”,“傳智播客” 等。

IK分詞器無法對這些詞匯分詞,測試一下:

POST /_analyze
{"analyzer": "ik_max_word","text": "傳智播客開設大學,真的泰褲辣!"
}

結果:

{"tokens" : [{"token" : "傳","start_offset" : 0,"end_offset" : 1,"type" : "CN_CHAR","position" : 0},{"token" : "智","start_offset" : 1,"end_offset" : 2,"type" : "CN_CHAR","position" : 1},{"token" : "播","start_offset" : 2,"end_offset" : 3,"type" : "CN_CHAR","position" : 2},{"token" : "客","start_offset" : 3,"end_offset" : 4,"type" : "CN_CHAR","position" : 3},{"token" : "開設","start_offset" : 4,"end_offset" : 6,"type" : "CN_WORD","position" : 4},{"token" : "大學","start_offset" : 6,"end_offset" : 8,"type" : "CN_WORD","position" : 5},{"token" : "真的","start_offset" : 9,"end_offset" : 11,"type" : "CN_WORD","position" : 6},{"token" : "泰","start_offset" : 11,"end_offset" : 12,"type" : "CN_CHAR","position" : 7},{"token" : "褲","start_offset" : 12,"end_offset" : 13,"type" : "CN_CHAR","position" : 8},{"token" : "辣","start_offset" : 13,"end_offset" : 14,"type" : "CN_CHAR","position" : 9}]
}

可以看到,傳智播客泰褲辣都無法正確分詞。

所以要想正確分詞,IK分詞器的詞庫也需要不斷的更新,IK分詞器提供了擴展詞匯的功能。

1)打開IK分詞器config目錄:

注意,如果采用在線安裝的通過,默認是沒有config目錄的,需要把課前資料提供的ik下的config上傳至對應目錄。

2)在IKAnalyzer.cfg.xml配置文件內容添加:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties><comment>IK Analyzer 擴展配置</comment><!--用戶可以在這里配置自己的擴展字典 *** 添加擴展詞典--><entry key="ext_dict">ext.dic</entry>
</properties>

3)在IK分詞器的config目錄新建一個 ext.dic,可以參考config目錄下復制一個配置文件進行修改

傳智播客
泰褲辣

4)重啟elasticsearch

docker restart es# 查看 日志
docker logs -f elasticsearch

再次測試,可以發現傳智播客泰褲辣都正確分詞了:

{"tokens" : [{"token" : "傳智播客","start_offset" : 0,"end_offset" : 4,"type" : "CN_WORD","position" : 0},{"token" : "開設","start_offset" : 4,"end_offset" : 6,"type" : "CN_WORD","position" : 1},{"token" : "大學","start_offset" : 6,"end_offset" : 8,"type" : "CN_WORD","position" : 2},{"token" : "真的","start_offset" : 9,"end_offset" : 11,"type" : "CN_WORD","position" : 3},{"token" : "泰褲辣","start_offset" : 11,"end_offset" : 14,"type" : "CN_WORD","position" : 4}]
}

1.4.4.總結

分詞器的作用是什么?

  • 創建倒排索引時,對文檔分詞
  • 用戶搜索時,對輸入的內容分詞

IK分詞器有幾種模式?

  • ik_smart:智能切分,粗粒度
  • ik_max_word:最細切分,細粒度

IK分詞器如何拓展詞條?如何停用詞條?

  • 利用config目錄的IkAnalyzer.cfg.xml文件添加拓展詞典和停用詞典
  • 在詞典中添加拓展詞條或者停用詞條

2.索引庫操作

Index就類似數據庫表,Mapping映射就類似表的結構。我們要向es中存儲數據,必須先創建Index和Mapping

2.1.Mapping映射屬性

Mapping是對索引庫中文檔的約束,常見的Mapping屬性包括:

  • type:字段數據類型,常見的簡單類型有:
    • 字符串:text(可分詞的文本)、keyword(精確值,例如:品牌、國家、ip地址)
    • 數值:longintegershortbytedoublefloat
    • 布爾:boolean
    • 日期:date
    • 對象:object
  • index:是否創建索引,默認為true
  • analyzer:使用哪種分詞器
  • properties:該字段的子字段

例如下面的json文檔:

{"age": 21,"weight": 52.1,"isMarried": false,"info": "黑馬程序員Java講師","email": "zy@itcast.cn","score": [99.1, 99.5, 98.9],"name": {"firstName": "云","lastName": "趙"}
}

對應的每個字段映射(Mapping):

字段名

字段類型

類型說明

是否

參與搜索

是否

參與分詞

分詞器

age

integer

整數

——

weight

float

浮點數

——

isMarried

boolean

布爾

——

info

text

字符串,但需要分詞

IK

email

keyword

字符串,但是不分詞

——

score

float

只看數組中元素類型

——

name

firstName

keyword

字符串,但是不分詞

——

lastName

keyword

字符串,但是不分詞

——

2.2.索引庫的CRUD

由于Elasticsearch采用的是Restful風格的API,因此其請求方式和路徑相對都比較規范,而且請求參數也都采用JSON風格。

我們直接基于Kibana的DevTools來編寫請求做測試,由于有語法提示,會非常方便。

2.2.1.創建索引庫和映射

基本語法

  • 請求方式:PUT
  • 請求路徑:/索引庫名,可以自定義
  • 請求參數:mapping映射

格式

PUT /索引庫名稱
{"mappings": {"properties": {"字段名":{"type": "text","analyzer": "ik_smart"},"字段名2":{"type": "keyword","index": "false"},"字段名3":{"properties": {"子字段": {"type": "keyword"}}},// ...略}}
}

示例

# PUT /heima
{"mappings": {"properties": {"info":{"type": "text","analyzer": "ik_smart"},"email":{"type": "keyword","index": "false"},"name":{"properties": {"firstName": {"type": "keyword"}}}}}
}

2.2.2.查詢索引庫

基本語法

  • 請求方式:GET
  • 請求路徑:/索引庫名
  • 請求參數:無

格式

GET /索引庫名

示例

GET /heima

2.2.3.修改索引庫

倒排索引結構雖然不復雜,但是一旦數據結構改變(比如改變了分詞器),就需要重新創建倒排索引,這簡直是災難。因此索引庫一旦創建,無法修改mapping

雖然無法修改mapping中已有的字段,但是卻允許添加新的字段到mapping中,因為不會對倒排索引產生影響。因此修改索引庫能做的就是向索引庫中添加新字段,或者更新索引庫的基礎屬性。

語法說明

PUT /索引庫名/_mapping
{"properties": {"新字段名":{"type": "integer"}}
}

示例

PUT /heima/_mapping
{"properties": {"age":{"type": "integer"}}
}

2.2.4.刪除索引庫

語法:

  • 請求方式:DELETE
  • 請求路徑:/索引庫名
  • 請求參數:無

格式:

DELETE /索引庫名

示例:

DELETE /heima

2.2.5.總結

索引庫操作有哪些?

  • 創建索引庫:PUT /索引庫名
  • 查詢索引庫:GET /索引庫名
  • 刪除索引庫:DELETE /索引庫名
  • 修改索引庫,添加字段:PUT /索引庫名/_mapping

可以看到,對索引庫的操作基本遵循的Restful的風格,因此API接口非常統一,方便記憶。

3.文檔操作

有了索引庫,接下來就可以向索引庫中添加數據了。

Elasticsearch中的數據其實就是JSON風格的文檔。操作文檔自然保護等幾種常見操作,我們分別來學習。

3.1.新增文檔

語法:

POST /索引庫名/_doc/文檔id
{"字段1": "值1","字段2": "值2","字段3": {"子屬性1": "值3","子屬性2": "值4"},
}

示例:

POST /heima/_doc/1
{"info": "黑馬程序員Java講師","email": "zy@itcast.cn","name": {"firstName": "云","lastName": "趙"}
}

響應:

3.2.查詢文檔

根據rest風格,新增是post,查詢應該是get,不過查詢一般都需要條件,這里我們把文檔id帶上。

語法:

GET /{索引庫名稱}/_doc/{id}

示例:

GET /heima/_doc/1

查看結果:

3.3.刪除文檔

刪除使用DELETE請求,同樣,需要根據id進行刪除:

語法:

DELETE /{索引庫名}/_doc/id值

示例:

DELETE /heima/_doc/1

結果:

3.4.修改文檔

修改有兩種方式:

  • 全量修改:直接覆蓋原來的文檔
  • 局部修改:修改文檔中的部分字段

3.4.1.全量修改

全量修改是覆蓋原來的文檔,其本質是兩步操作:

  • 根據指定的id刪除文檔
  • 新增一個相同id的文檔

注意:如果根據id刪除時,id不存在,第二步的新增也會執行,也就從修改變成了新增操作了。

語法:

PUT /{索引庫名}/_doc/文檔id
{"字段1": "值1","字段2": "值2",// ... 略
}

示例:

PUT /heima/_doc/1
{"info": "黑馬程序員高級Java講師","email": "zy@itcast.cn","name": {"firstName": "云","lastName": "趙"}
}

由于id1的文檔已經被刪除,所以第一次執行時,得到的反饋是created

所以如果執行第2次時,得到的反饋則是updated

3.4.2.局部修改

局部修改是只修改指定id匹配的文檔中的部分字段。

語法:

POST /{索引庫名}/_update/文檔id
{"doc": {"字段名": "新的值",}
}

示例:

POST /heima/_update/1
{"doc": {"email": "ZhaoYun@itcast.cn"}
}

執行結果

3.5.批處理

批處理采用POST請求,基本語法如下:

POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

其中:

  • index代表新增操作
    • _index:指定索引庫名
    • _id指定要操作的文檔id
    • { "field1" : "value1" }:則是要新增的文檔內容
  • delete代表刪除操作
    • _index:指定索引庫名
    • _id指定要操作的文檔id
  • update代表更新操作
    • _index:指定索引庫名
    • _id指定要操作的文檔id
    • { "doc" : {"field2" : "value2"} }:要更新的文檔字段

示例,批量新增:

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"}}

3.6.總結

文檔操作有哪些?

  • 創建文檔:POST /{索引庫名}/_doc/文檔id { json文檔 }
  • 查詢文檔:GET /{索引庫名}/_doc/文檔id
  • 刪除文檔:DELETE /{索引庫名}/_doc/文檔id
  • 修改文檔:
    • 全量修改:PUT /{索引庫名}/_doc/文檔id { json文檔 }
    • 局部修改:POST /{索引庫名}/_update/文檔id { "doc": {字段}}

4.RestAPI

ES官方提供了各種不同語言的客戶端,用來操作ES。這些客戶端的本質就是組裝DSL語句,通過http請求發送給ES。

官方文檔地址:

Elasticsearch clients | Elastic Docs

由于ES目前最新版本是8.8,提供了全新版本的客戶端,老版本的客戶端已經被標記為過時。而我們采用的是7.12版本,因此只能使用老版本客戶端:

然后選擇7.12版本,HighLevelRestClient版本:

4.1.初始化RestClient

在elasticsearch提供的API中,與elasticsearch一切交互都封裝在一個名為RestHighLevelClient的類中,必須先完成這個對象的初始化,建立與elasticsearch的連接。

分為三步:

1)在item-service模塊中引入esRestHighLevelClient依賴:

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

2)因為SpringBoot默認的ES版本是7.17.10,所以我們需要覆蓋默認的ES版本:

<properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><elasticsearch.version>7.12.1</elasticsearch.version></properties>

3)初始化RestHighLevelClient:

初始化的代碼如下:

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.150.101:9200")
));

這里為了單元測試方便,我們創建一個測試類IndexTest,然后將初始化的代碼編寫在@BeforeEach方法中:

package com.hmall.item.es;import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;import java.io.IOException;public class IndexTest {private RestHighLevelClient client;@BeforeEachvoid setUp() {this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.150.101:9200")));}@Testvoid testConnect() {System.out.println(client);}@AfterEachvoid tearDown() throws IOException {this.client.close();}
}

4.1.創建索引庫

由于要實現對商品搜索,所以我們需要將商品添加到Elasticsearch中,不過需要根據搜索業務的需求來設定索引庫結構,而不是一股腦的把MySQL數據寫入Elasticsearch.

4.1.1.Mapping映射

搜索頁面的效果如圖所示:

實現搜索功能需要的字段包括三大部分:

  • 搜索過濾字段
    • 分類
    • 品牌
    • 價格
  • 排序字段
    • 默認:按照更新時間降序排序
    • 銷量
    • 價格
  • 展示字段
    • 商品id:用于點擊后跳轉
    • 圖片地址
    • 是否是廣告推廣商品
    • 名稱
    • 價格
    • 評價數量
    • 銷量

對應的商品表結構如下,索引庫無關字段已經劃掉:

結合數據庫表結構,以上字段對應的mapping映射屬性如下:

字段名

字段類型

類型說明

是否

參與搜索

是否

參與分詞

分詞器

id

long

長整數

——

name

text

字符串,參與分詞搜索

IK

price

integer

以分為單位,所以是整數

——

stock

integer

字符串,但需要分詞

——

image

keyword

字符串,但是不分詞

——

category

keyword

字符串,但是不分詞

——

brand

keyword

字符串,但是不分詞

——

sold

integer

銷量,整數

——

commentCount

integer

評價,整數

——

isAD

boolean

布爾類型

——

updateTime

Date

更新時間

——

因此,最終我們的索引庫文檔結構應該是這樣:

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"}}}
}

4.1.2.創建索引

創建索引庫的API如下:

代碼分為三步:

  • 1)創建Request對象。
    • 因為是創建索引庫的操作,因此Request是CreateIndexRequest
  • 2)添加請求參數
    • 其實就是Json格式的Mapping映射參數。因為json字符串很長,這里是定義了靜態字符串常量MAPPING_TEMPLATE,讓代碼看起來更加優雅。
  • 3)發送請求
    • client.indices()方法的返回值是IndicesClient類型,封裝了所有與索引庫操作有關的方法。例如創建索引、刪除索引、判斷索引是否存在等

item-service中的IndexTest測試類中,具體代碼如下:

@Test
void testCreateIndex() throws IOException {// 1.創建Request對象CreateIndexRequest request = new CreateIndexRequest("items");// 2.準備請求參數request.source(MAPPING_TEMPLATE, XContentType.JSON);// 3.發送請求client.indices().create(request, RequestOptions.DEFAULT);
}static final String MAPPING_TEMPLATE = "{\n" +"  \"mappings\": {\n" +"    \"properties\": {\n" +"      \"id\": {\n" +"        \"type\": \"keyword\"\n" +"      },\n" +"      \"name\":{\n" +"        \"type\": \"text\",\n" +"        \"analyzer\": \"ik_max_word\"\n" +"      },\n" +"      \"price\":{\n" +"        \"type\": \"integer\"\n" +"      },\n" +"      \"stock\":{\n" +"        \"type\": \"integer\"\n" +"      },\n" +"      \"image\":{\n" +"        \"type\": \"keyword\",\n" +"        \"index\": false\n" +"      },\n" +"      \"category\":{\n" +"        \"type\": \"keyword\"\n" +"      },\n" +"      \"brand\":{\n" +"        \"type\": \"keyword\"\n" +"      },\n" +"      \"sold\":{\n" +"        \"type\": \"integer\"\n" +"      },\n" +"      \"commentCount\":{\n" +"        \"type\": \"integer\"\n" +"      },\n" +"      \"isAD\":{\n" +"        \"type\": \"boolean\"\n" +"      },\n" +"      \"updateTime\":{\n" +"        \"type\": \"date\"\n" +"      }\n" +"    }\n" +"  }\n" +"}";

4.2.刪除索引庫

刪除索引庫的請求非常簡單:

DELETE /hotel

與創建索引庫相比:

  • 請求方式從PUT變為DELTE
  • 請求路徑不變
  • 無請求參數

所以代碼的差異,注意體現在Request對象上。流程如下:

  • 1)創建Request對象。這次是DeleteIndexRequest對象
  • 2)準備參數。這里是無參,因此省略
  • 3)發送請求。改用delete方法

item-service中的IndexTest測試類中,編寫單元測試,實現刪除索引:

@Test
void testDeleteIndex() throws IOException {// 1.創建Request對象DeleteIndexRequest request = new DeleteIndexRequest("items");// 2.發送請求client.indices().delete(request, RequestOptions.DEFAULT);
}

4.3.判斷索引庫是否存在

判斷索引庫是否存在,本質就是查詢,對應的請求語句是:

GET /hotel

因此與刪除的Java代碼流程是類似的,流程如下:

  • 1)創建Request對象。這次是GetIndexRequest對象
  • 2)準備參數。這里是無參,直接省略
  • 3)發送請求。改用exists方法
@Test
void testExistsIndex() throws IOException {// 1.創建Request對象GetIndexRequest request = new GetIndexRequest("items");// 2.發送請求boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);// 3.輸出System.err.println(exists ? "索引庫已經存在!" : "索引庫不存在!");
}

4.4.總結

JavaRestClient操作elasticsearch的流程基本類似。核心是client.indices()方法來獲取索引庫的操作對象。

索引庫操作的基本步驟:

  • 初始化RestHighLevelClient
  • 創建XxxIndexRequest。XXX是CreateGetDelete
  • 準備請求參數( Create時需要,其它是無參,可以省略)
  • 發送請求。調用RestHighLevelClient#indices().xxx()方法,xxx是createexistsdelete

5.RestClient操作文檔

索引庫準備好以后,就可以操作文檔了。為了與索引庫操作分離,我們再次創建一個測試類,做兩件事情:

  • 初始化RestHighLevelClient
  • 我們的商品數據在數據庫,需要利用IHotelService去查詢,所以注入這個接口
package com.hmall.item.es;import com.hmall.item.service.IItemService;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.io.IOException;@SpringBootTest(properties = "spring.profiles.active=local")
public class DocumentTest {private RestHighLevelClient client;@Autowiredprivate IItemService itemService;@BeforeEachvoid setUp() {this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.150.101:9200")));}@AfterEachvoid tearDown() throws IOException {this.client.close();}
}

5.1.新增文檔

我們需要將數據庫中的商品信息導入elasticsearch中,而不是造假數據了。

5.1.1.實體類

索引庫結構與數據庫結構還存在一些差異,因此我們要定義一個索引庫結構對應的實體。

hm-service模塊的com.hmall.item.domain.dto包中定義一個新的DTO:

package com.hmall.item.domain.po;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.time.LocalDateTime;@Data
@ApiModel(description = "索引庫實體")
public class ItemDoc{@ApiModelProperty("商品id")private String id;@ApiModelProperty("商品名稱")private String name;@ApiModelProperty("價格(分)")private Integer price;@ApiModelProperty("商品圖片")private String image;@ApiModelProperty("類目名稱")private String category;@ApiModelProperty("品牌名稱")private String brand;@ApiModelProperty("銷量")private Integer sold;@ApiModelProperty("評論數")private Integer commentCount;@ApiModelProperty("是否是推廣廣告,true/false")private Boolean isAD;@ApiModelProperty("更新時間")private LocalDateTime updateTime;
}

5.1.2.API語法

新增文檔的請求語法如下:

POST /{索引庫名}/_doc/1
{"name": "Jack","age": 21
}

對應的JavaAPI如下:

可以看到與索引庫操作的API非常類似,同樣是三步走:

  • 1)創建Request對象,這里是IndexRequest,因為添加文檔就是創建倒排索引的過程
  • 2)準備請求參數,本例中就是Json文檔
  • 3)發送請求

變化的地方在于,這里直接使用client.xxx()的API,不再需要client.indices()了。

5.1.3.完整代碼

我們導入商品數據,除了參考API模板“三步走”以外,還需要做幾點準備工作:

  • 商品數據來自于數據庫,我們需要先查詢出來,得到Item對象
  • Item對象需要轉為ItemDoc對象
  • ItemDTO需要序列化為json格式

因此,代碼整體步驟如下:

  • 1)根據id查詢商品數據Item
  • 2)將Item封裝為ItemDoc
  • 3)將ItemDoc序列化為JSON
  • 4)創建IndexRequest,指定索引庫名和id
  • 5)準備請求參數,也就是JSON文檔
  • 6)發送請求

item-serviceDocumentTest測試類中,編寫單元測試:

@Test
void testAddDocument() throws IOException {// 1.根據id查詢商品數據Item item = itemService.getById(100002644680L);// 2.轉換為文檔類型ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);// 3.將ItemDTO轉jsonString doc = JSONUtil.toJsonStr(itemDoc);// 1.準備Request對象IndexRequest request = new IndexRequest("items").id(itemDoc.getId());// 2.準備Json文檔request.source(doc, XContentType.JSON);// 3.發送請求client.index(request, RequestOptions.DEFAULT);
}

5.2.查詢文檔

我們以根據id查詢文檔為例

5.2.1.語法說明

查詢的請求語句如下:

GET /{索引庫名}/_doc/{id}

與之前的流程類似,代碼大概分2步:

  • 創建Request對象
  • 準備請求參數,這里是無參,直接省略
  • 發送請求

不過查詢的目的是得到結果,解析為ItemDTO,還要再加一步對結果的解析。示例代碼如下:

可以看到,響應結果是一個JSON,其中文檔放在一個_source屬性中,因此解析就是拿到_source,反序列化為Java對象即可。

其它代碼與之前類似,流程如下:

  • 1)準備Request對象。這次是查詢,所以是GetRequest
  • 2)發送請求,得到結果。因為是查詢,這里調用client.get()方法
  • 3)解析結果,就是對JSON做反序列化

5.2.2.完整代碼

item-serviceDocumentTest測試類中,編寫單元測試:

@Test
void testGetDocumentById() throws IOException {// 1.準備Request對象GetRequest request = new GetRequest("items").id("100002644680");// 2.發送請求GetResponse response = client.get(request, RequestOptions.DEFAULT);// 3.獲取響應結果中的sourceString json = response.getSourceAsString();ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);System.out.println("itemDoc= " + ItemDoc);
}

5.3.刪除文檔

刪除的請求語句如下:

DELETE /hotel/_doc/{id}

與查詢相比,僅僅是請求方式從DELETE變成GET,可以想象Java代碼應該依然是2步走:

  • 1)準備Request對象,因為是刪除,這次是DeleteRequest對象。要指定索引庫名和id
  • 2)準備參數,無參,直接省略
  • 3)發送請求。因為是刪除,所以是client.delete()方法

item-serviceDocumentTest測試類中,編寫單元測試:

@Test
void testDeleteDocument() throws IOException {// 1.準備Request,兩個參數,第一個是索引庫名,第二個是文檔idDeleteRequest request = new DeleteRequest("item", "100002644680");// 2.發送請求client.delete(request, RequestOptions.DEFAULT);
}

5.4.修改文檔

修改我們講過兩種方式:

  • 全量修改:本質是先根據id刪除,再新增
  • 局部修改:修改文檔中的指定字段值

在RestClient的API中,全量修改與新增的API完全一致,判斷依據是ID:

  • 如果新增時,ID已經存在,則修改
  • 如果新增時,ID不存在,則新增

這里不再贅述,我們主要關注局部修改的API即可。

5.4.1.語法說明

局部修改的請求語法如下:

POST /{索引庫名}/_update/{id}
{"doc": {"字段名": "字段值","字段名": "字段值"}
}

代碼示例如圖:

與之前類似,也是三步走:

  • 1)準備Request對象。這次是修改,所以是UpdateRequest
  • 2)準備參數。也就是JSON文檔,里面包含要修改的字段
  • 3)更新文檔。這里調用client.update()方法

5.4.2.完整代碼

item-serviceDocumentTest測試類中,編寫單元測試:

@Test
void testUpdateDocument() throws IOException {// 1.準備RequestUpdateRequest request = new UpdateRequest("items", "100002644680");// 2.準備請求參數request.doc("price", 58800,"commentCount", 1);// 3.發送請求client.update(request, RequestOptions.DEFAULT);
}

5.5.批量導入文檔

在之前的案例中,我們都是操作單個文檔。而數據庫中的商品數據實際會達到數十萬條,某些項目中可能達到數百萬條。

我們如果要將這些數據導入索引庫,肯定不能逐條導入,而是采用批處理方案。常見的方案有:

  • 利用Logstash批量導入
    • 需要安裝Logstash
    • 對數據的再加工能力較弱
    • 無需編碼,但要學習編寫Logstash導入配置
  • 利用JavaAPI批量導入
    • 需要編碼,但基于JavaAPI,學習成本低
    • 更加靈活,可以任意對數據做再加工處理后寫入索引庫

接下來,我們就學習下如何利用JavaAPI實現批量文檔導入。

5.5.1.語法說明

批處理與前面講的文檔的CRUD步驟基本一致:

  • 創建Request,但這次用的是BulkRequest
  • 準備請求參數
  • 發送請求,這次要用到client.bulk()方法

BulkRequest本身其實并沒有請求參數,其本質就是將多個普通的CRUD請求組合在一起發送。例如:

  • 批量新增文檔,就是給每個文檔創建一個IndexRequest請求,然后封裝到BulkRequest中,一起發出。
  • 批量刪除,就是創建N個DeleteRequest請求,然后封裝到BulkRequest,一起發出

因此BulkRequest中提供了add方法,用以添加其它CRUD的請求:

可以看到,能添加的請求有:

  • IndexRequest,也就是新增
  • UpdateRequest,也就是修改
  • DeleteRequest,也就是刪除

因此Bulk中添加了多個IndexRequest,就是批量新增功能了。示例:

@Test
void testBulk() throws IOException {// 1.創建RequestBulkRequest request = new BulkRequest();// 2.準備請求參數request.add(new IndexRequest("items").id("1").source("json doc1", XContentType.JSON));request.add(new IndexRequest("items").id("2").source("json doc2", XContentType.JSON));// 3.發送請求client.bulk(request, RequestOptions.DEFAULT);
}

5.5.2.完整代碼

當我們要導入商品數據時,由于商品數量達到數十萬,因此不可能一次性全部導入。建議采用循環遍歷方式,每次導入1000條左右的數據。

item-serviceDocumentTest測試類中,編寫單元測試:

@Test
void testLoadItemDocs() throws IOException {// 分頁查詢商品數據int pageNo = 1;int size = 1000;while (true) {Page<Item> page = itemService.lambdaQuery().eq(Item::getStatus, 1).page(new Page<Item>(pageNo, size));// 非空校驗List<Item> items = page.getRecords();if (CollUtils.isEmpty(items)) {return;}log.info("加載第{}頁數據,共{}條", pageNo, items.size());// 1.創建RequestBulkRequest request = new BulkRequest("items");// 2.準備參數,添加多個新增的Requestfor (Item item : items) {// 2.1.轉換為文檔類型ItemDTOItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);// 2.2.創建新增文檔的Request對象request.add(new IndexRequest().id(itemDoc.getId()).source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON));}// 3.發送請求client.bulk(request, RequestOptions.DEFAULT);// 翻頁pageNo++;}
}

5.6.小結

文檔操作的基本步驟:

  • 初始化RestHighLevelClient
  • 創建XxxRequest。
    • XXX是IndexGetUpdateDeleteBulk
  • 準備參數(IndexUpdateBulk時需要)
  • 發送請求。
    • 調用RestHighLevelClient#.xxx()方法,xxx是indexgetupdatedeletebulk
  • 解析結果(Get時需要)

6.作業

6.1.服務拆分

搜索業務并發壓力可能會比較高,目前與商品服務在一起,不方便后期優化。

需求:創建一個新的微服務,命名為search-service,將搜索相關功能抽取到這個微服務中

6.2.商品查詢接口

item-service服務中提供一個根據id查詢商品的功能,并編寫對應的FeignClient

6.3.數據同步

每當商品服務對商品實現增刪改時,索引庫的數據也需要同步更新。

提示:可以考慮采用MQ異步通知實現。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/88110.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/88110.shtml
英文地址,請注明出處:http://en.pswp.cn/web/88110.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

transformers 筆記:自定義模型(配置+模型+注冊為AutoCLass+本地保存加載)

Transformers 模型設計上是可定制的。每個模型的代碼都包含在 Transformers 倉庫的 model 子文件夾中&#xff08;transformers/src/transformers/models at main huggingface/transformers&#xff09;&#xff0c;每個模型文件夾通常包含&#xff1a; modeling.py&#xff1…

Java工具類,對象List提取某個屬性為List,對象List轉為對象Map其中某個屬性作為Key值

Java工具類package org.common;import lombok.extern.slf4j.Slf4j;import java.util.*; import java.util.stream.Collectors;Slf4j public final class CollectorHelper {/*** param element* param propertyName* param <E>* return*/public static <E> List toL…

ATE FT ChangeKit學習總結-20250630

目錄 一、基本概念 二、主要特點 三、BOM LIST Shuttle Hot Plate Dock Plate Contactor 四、設計要點 五、參考文獻與鏈接 一、基本概念 Change Kit在半導體封裝測試領域中是一個重要的組件,它作為Handler(自動化分類機)的配套治具,在芯片測試過程中發揮著關鍵作…

【網絡協議安全】任務14:路由器DHCP_AAA_TELNET配置

本文檔將詳細介紹在華為 eNSP 仿真環境中&#xff0c;實現路由器 DHCP 服務器功能、AAA 認證以及 TELNET 遠程登錄配置的完整步驟&#xff0c;適用于華為 VRP 系統路由器。 一、配置目標 路由器作為 DHCP 服務器&#xff0c;為局域網內的設備自動分配 IP 地址、子網掩碼、網關…

深度探索:現代翻譯技術的核心算法與實踐(第一篇)

引言:翻譯技術的演進之路 從早期的基于規則的機器翻譯(RBMT)到統計機器翻譯(SMT),再到如今主導行業的神經機器翻譯(NMT),翻譯技術已經走過了漫長的發展道路。現代翻譯系統不僅能夠處理簡單的句子,還能理解上下文、識別領域術語,甚至捕捉微妙的文化差異。 本系列文章將帶…

玩轉Docker | 使用Docker部署NotepadMX筆記應用程序

玩轉Docker | 使用Docker部署NotepadMX筆記應用程序 前言一、NotepadMX介紹工具簡介主要特點二、系統要求環境要求環境檢查Docker版本檢查檢查操作系統版本三、部署NotepadMX服務下載NotepadMX鏡像編輯部署文件創建容器檢查容器狀態檢查服務端口安全設置四、訪問NotepadMX服務訪…

Web前端:not(否定偽類選擇器)

not&#xff08;否定偽類選擇器&#xff09;CSS中的 :not() 選擇器是?個否定偽類選擇器&#xff0c;它?于選擇不符合給定選擇器的元素。這是?種排除特定元素的?法&#xff0c;可以?來簡 化復雜的選擇器&#xff0c;提? CSS 規則的靈活性和精確性。:not() 選擇器的基本語法…

【BTC】比特幣網絡

目錄 一、比特幣網絡架構 1.1 節點加入與離開 二、消息傳播方式 三、交易處理機制 四、網絡傳播問題 五、實際應用問題及解決 本章節講比特幣網絡的工作原理&#xff0c;講解新創建的區塊是如何在比特幣網絡中傳播的。 一、比特幣網絡架構 比特幣工作在應用層&#xff…

Clickhouse 的歷史發展過程

20.5.3 開始支持多線程20.6.3 支持explainmysql 20.8 實時同步mysql&#x1f4cc; ?一、早期版本階段&#xff08;1.1.x系列&#xff09;??版本范圍?&#xff1a;1.1.54245&#xff08;2017-07&#xff09;→ 1.1.54394&#xff08;2018-07&#xff09;?核心特性?&#x…

玩轉n8n工作流教程(一):Windows系統本地部署n8n自動化工作流(n8n中文漢化)

在Windows系統下使用 Docker 本地部署N8N中文版的具體操作&#xff0c;進行了詳盡闡述&#xff0c;玩轉n8n工作流教程系列內容旨在手把手助力從0開始一步一步深入學習n8n工作流。想研究n8n工作流自動化的小伙伴們可以加個關注一起學起來。后續也會持續分享n8n自動化工作流各種玩…

mini-program01の系統認識微信小程序開發

一、官方下載并安裝 1、下載&#xff08;I選了穩定版&#xff09; https://developers.weixin.qq.com/miniprogram/dev/devtools/download.htmlhttps://developers.weixin.qq.com/miniprogram/dev/devtools/download.html 2、安裝&#xff08;A FEW MOMENT LATER&#xff09;…

如何將 Java 項目打包為可執行 JAR 文件

如何將 Java 項目打包為可執行 JAR 文件我將詳細介紹將 Java 項目打包為可執行 JAR 文件的完整流程&#xff0c;包括使用 IDE 和命令行兩種方法。方法一&#xff1a;使用 IntelliJ IDEA 打包步驟 1&#xff1a;配置項目結構打開項目點擊 File > Project Structure在 Project…

【Starrocks 異常解決】-- mysql flink sync to starrocks row error

1、異常信息 flink 1.20 starrocks 3.3.0 mysql 8.0 errorLog: Error: Target column count: 35 doesnt match source value column count: 28. Column separator: \t, Row delimiter: \n. Row: 2025-05-22 6 23400055 214 dssd 1 1 1928 mm2er 360 20000.00000000 1…

Jenkins 使用宿主機的Docker

背景&#xff1a;不想在Jenkins 內部安裝Docker,想直接使用Jenkins服務所在的系統安裝的docker當你在 Jenkins 中執行 docker 命令時&#xff0c;實際上是通過 Docker 客戶端與 Docker 守護進程進行通信。Docker 客戶端和守護進程之間的通信是通過一個名為 /var/run/docker.soc…

工具+服務雙驅動:創客匠人打造中醫IP差異化競爭力

一、技術工具場景化定制&#xff1a;中醫專業的可視化破圈在中醫IP同質化嚴重的行業現狀下&#xff0c;創客匠人以場景化技術工具破解專業傳播難題。系統內置的“體質測試”模塊可生成個性化調理報告&#xff0c;“案例庫”支持前后對比圖上傳&#xff0c;“直播問診”自動添加…

JVM對象分配內存如何保證線程安全?

大家好&#xff0c;我是鋒哥。今天分享關于【JVM對象分配內存如何保證線程安全&#xff1f;】面試題。希望對大家有幫助&#xff1b; JVM對象分配內存如何保證線程安全&#xff1f; 超硬核AI學習資料&#xff0c;現在永久免費了&#xff01; 在Java中&#xff0c;JVM&#xf…

機器學習中的數據對齊

文章目錄前言數據集怎么理解數據數據對齊為什么偏偏是這樣對齊&#xff1f;前言 在神經網絡中&#xff0c;我們往往會根據數據集構建訓練集、測試集&#xff0c;有時會有驗證集。但是&#xff0c;在構建完成后&#xff0c;如果直接將這些數據直接扔進模型訓練&#xff0c;輸入…

機器學習:更多分類回歸算法之決策樹、SVM、KNN

下面介紹的這幾種算法&#xff0c;既能用于回歸問題又能用于分類問題&#xff0c;接下來了解下吧。 決策樹 可參考&#xff1a; 決策樹&#xff08;Decision Tree&#xff09; | 菜鳥教程 決策樹&#xff08;Decision Tree&#xff09;是一種常用的監督學習算法&#xff0c;可用…

Vue 整合 Vue Flow:從零構建交互式流程圖

目錄引言目的適用場景環境準備基礎組件 (index.vue)自定義組件 (矩形、菱形等)RectangleNode.vue (矩形節點)&#xff1a;DiamondNode.vue (菱形節點)&#xff1a;ImageNode(自定義圖片節點):操作實現 (#操作實現) 拖拽節點 (#拖拽節點) 連線 (多連接點) 刪除節點 …

C# WPF - Prism 學習篇:搭建項目(一)

一、前期準備開發工具&#xff1a;Visual Studio 2022二、創建項目1、創建WPF 應用“WpfApp.StudyDemo”&#xff1a;2、項目結構如下&#xff1a; 三、安裝 Prism1、選中項目“WpfApp.PrismDemo”&#xff0c;在右鍵菜單中選擇“管理 NuGet 程序包(N)...”。2、在搜索框中輸入…