本篇文章是 ElasticSearch 这个热门技术的基础知识手册,一共 20 张图,1.5w字,图文结合,代码讲解。欢迎大家,点赞、收藏,更多相关优质文章可以点击下面的链接,都是此类的干货。
-
Spring Boot 基础知识复习手册
-
IntelliJ IDEA 入门到进阶学习手册
-
Docker 入门到进阶学习手册
-
新增数据
-
查询数据
-
更新数据
-
删除数据
-
批量操作
-
聚合操作
-
映射操作
-
分词操作
-
...
本篇文章的内容均以 Docker 环境为基础。
首先拉取镜像:
docker pull elasticsearch:7.4.2
然后下载kibana,这是一个可视化检索数据的工具:
docker pull kibana:7.4.2
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
创建一个配置文件并写入配置,使得外部机器能够访问 elasticsearch:
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
这样就可以启动 elasticsearch 了:
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx128m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2
其中开放的 9200
端口为向elasticsearch发送请求的端口,而 9300
为集群环境下elasticsearch之间互相通信的端口;"discovery.type=single-node"
表示以单节点运行elasticsearch;ES_JAVA_OPTS="-Xms64m -Xmx128m"
用于指定elasticsearch的内存占用,而且必须指定,否则elasticsearch将占用系统的全部内存;最后设置elasticsearch的挂载点。
若是启动报错:
"Caused by: java.nio.file.AccessDeniedException: /usr/share/elasticsearch/data/nodes",
chmod 777 /mydata/elasticsearch/data/
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://www.ithui.top:9200 -p 5601:5601 -d kibana:7.4.2
启动完成后访问 http://www.ithui.top:5601/:
ElasticSearch 入门
ElasticSearch 通过接收请求的方式来对数据进行处理,接下来对elasticsearch进行一个简单的入门。首先是 _cat
请求,通过该请求能够查询elasticsearch的一些基本信息,具体如下:
-
GET /_cat/nodes:查看所有节点
-
GET /_cat/health:查看elasticsearch的健康状况
-
GET /_cat/master:查看主节点
-
GET /_cat/indices:查看所有索引
比如查看 elasticsearch 的所有节点,则需要发送 http://www.ithui.top/_cat/nodes 请求,结果如下:
若是想查看 elasticsearch 的健康状态,则发送 http://www.ithui.top:9200/_cat/health 请求,结果如下:
新增数据
elasticsearch通过接收PUT和POST请求来新增数据,然而在新增数据之前,我们需要来了解elasticsearch中的几个概念:
-
索引
-
类型
-
文档
-
属性
我们可以类比一下mysql中的概念来更形象地理解它们。在mysql中,若是想保存一条数据,我们首先需要创建数据库,然后在数据库中创建数据表,最后将数据作为一条记录插入数据表;而elasticsearch中的索引就相当于mysql中的数据库,类型就相当于数据表,文档就相当于一条记录。
所以,若是想在elasticsearch中新增一条数据,我们就需要指定这条数据放在哪个索引的哪个类型下,该条数据也被称为一个文档,而且这些数据是json格式的,json中的键被称为属性。
在elasticsearch中新增一条数据我们有更加专业的说法,称其为 索引一个文档
,接下来就可以发送一个请求 http://www.ithui.top:9200/customer/external/1 ,该请求表示向customer索引下的external文档存放一个标识为1的数据,数据可以存放在请求体中携带过去:
我们来分析一下该请求的返回结果:
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
其中以 _
开头的属性称为元数据,它表示的是elasticsearch中的基本信息,比如 _index
表示当前索引;_type
表示当前类型;_id
表示当前数据的标识;_version
表示版本;result
表示当前操作的状态,这里是新建状态,若是索引的文档已经存在,则状态为更新状态。
PUT请求方式同样也能够新增数据,然而它与POST有些许不同,POST能够不携带id进行数据的保存,比如:
此时elasticsearch会自动为我们分配一个唯一的id,所以若是不携带id,则每次请求都将是一次新增数据的操作。然而PUT请求方式是无法实现这样的效果的,也就是说,若是使用PUT方式发送请求,则必须携带id,否则就会报错:
查询数据
elasticsearch接收GET请求用于查询数据,比如发送 http://www.ithui.top:9200/customer/external/1 :
其中数据存放在 _source
中, _seq_no
属性和 _primary_term
属性是用来做并发控制的,数据在每次更新之后都会加1,是乐观锁机制。
它的原理是这样的,假设此时有两个请求同时来到并且均想要修改id为1的数据,那么可以让这两个请求去判断一下当前的数据是否是最新的,怎么知道数据是最新的呢?
就是判断 seq_no
和 _primary_term
属性,比如这个请求:http://www.ithui.top:9200/customer/external/1?if_seq_no=0&if_primary_term=1 。
它在更新数据前会去判断 seq_no
是否等于0, _primary_term
是否等于1,若成立,则证明数据是最新的,所以它能够修改成功:
此时需要注意了,当数据更新成功后, _seq_no
属性便会自动向上递增,此时第二个请求就无法更新了:
doc
属性进行封装,它与不携带
_update
进行数据更新的方式有什么区别呢?
若是不携带 _update
更新数据,则每次发送请求elasticsearch都认为是一次更新,而携带 _update
更新数据,则elasticsearch会检查当前数据是否与原数据一致,若一致,则不会进行任何的操作,包括版本号、并发控制属性等都不会发生变化。
这里的PUT方式与POST方式效果一样,没有任何区别。
删除数据
elasticsearch通过接收DELETE请求来完成数据的删除操作。比如 http://www.ithui.top:9200/customer/external/1 ,通过该请求能够删除id为1的数据;elasticsearch还支持直接删除索引,比如 http://www.ithui.top:9200/customer ,该请求将删除customer索引;但是我们无法直接删除类型,elasticsearch是不支持直接删除类型的。
批量操作
elasticsearch还支持批量操作,不过批量操作我们就需要在kibana中进行测试了,我们在前面已经启动了kibana的镜像,只需访问 http://www.ithui.top:5601/ 即可来到kibana的界面:
点击左侧导航栏的 Dev Tools
进入到开发工具:
编写好请求后点击运行图标即可发送请求,批量操作的数据格式非常有讲究:
{"index":{"_id":"1"}}
{"name":"zhangsan"}
{"index":{"_id":"2"}}
{"name":"lisi"}
"index"
表示新增操作,并且指定了数据的id为1,而具体需要新增的数据值是在第二行存放着:
{"name":"zhangsan"}
;第三行也是如此,仍然是新增操作,指定数据id为2,数据值为:
{"name":"lisi"}
,这样运行之后elasticsearch会执行两个新增操作,将这两个数据保存起来。
POST /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"Test Title"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"Test Title2"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"Update Title"}}
首先是第一行, _bulk
指定了这次操作为批量操作,因为没有设置其它的任何信息,所以接下来的所有操作都需要指定索引、类型等信息;
第二行 delete
表示一个删除操作,其后指定了索引、类型以及要删除的数据id,因为删除操作不需要携带请求体数据,所以我们可以直接在下一行编写第二个操作;
第三行 create
表示一个新增操作,并指定了索引、类型、数据id,第四行就是需要新增的数据值了;第五行与第六行也是一个新增操作;第七行和第八行是一个更新操作,而且因为是update更新,所以请求体数据需要用 doc
属性包装。
运行该操作,得到结果:
第一个删除操作,因为不存在这样的一个数据,所以删除失败了,状态为404,但是后面的操作却成功了,从这里可以说明,elasticsearch批量操作中的每个操作都是相互独立的,互相不会造成任何影响。
ElasticSearch高级使用
elasticsearch有两种检索方式,一种是前面说过的,发送REST请求,将数据以请求体的方式携带,还有一种方式就是直接将数据拼接在url路径上,比如:
GET bank/_search?q=*&sort=account_number:asc
它表示这是一个GET请求,要操作的索引是bank, _search
表示这是一次检索, q=*
表示查询所有, sort=account_number:asc
表示以 account_number
的值进行升序排序。
我们还可以这样进行检索:
GET bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"account_number": "asc"
}
]
}
两种检索方式得到的结果是一样的,来介绍一下这种检索方式的语法。首先是 query
,它用于指定查询条件, match_all
表示匹配所有,大括号后面可以编写匹配的规则;其次是 sort
,它用于指定排序条件, account_number:asc
则表示以该属性的值进行升序排序,若是想指定多个规则,可以继续在 sort
属性中进行编写:
"sort": [
{
"account_number": "asc"
},
{
"balance": "desc"
}
]
此时则表示先按 account_number
进行升序,再按 balance
进行降序排序。
通过请求体数据进行检索的方式被称为 Query DSL
,即:查询领域对象语言,在elasticsearch中我们将会大量地编写这种语言。查询领域对象语言的基本语法是:
{
QUERY_NAME:{
ARGUMENT:VALUE,
ARGUMENT:VALUE,
....
}
}
首先整个语句需要被一对大括号包含,在大括号内需要编写对应的操作,比如查询,就编写:
{
"query":{
}
}
在 query
中又需要指定查询的条件,比如匹配部分、匹配所有,以及匹配的规则等等,若是想要排序,则编写:
{
"sort":[
]
}
sort
是一个数组,表示可以指定多个排序的规则。在其中还可以指定分页,只需要设置 from
和size
属性值即可:
GET bank/_search
{
"from": 0,
"size": 5
}
这里表示从第一条数据开始,每页显示5条数据。我们还能通过指定 _source
属性来决定 elasticsearch 检索出的数据中包含哪些属性值,以剔除不必要的属性:
GET bank/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"account_number": "asc"
}
],
"from": 0,
"size": 5,
"_source": ["balance","firstname"]
}
此时表示只返回 balance
和 firstname
属性值。匹配规则中除了可以指定匹配全部外,还可以匹配指定的属性值,比如:
GET bank/_search
{
"query": {
"match": {
"account_number": "20"
}
}
}
此时表示检索 account_number
为20的数据,这是一个精确检索的操作。它当然还支持模糊检索,比如:
GET bank/_search
{
"query": {
"match": {
"address": "mill lane"
}
}
}
此时表示检索 address
中包含mill和lane的数据,我们来看看elasticsearch返回的结果:
来观察一下这两条数据,第一条数据有一个 _score
的属性,它表示的是当前数据的得分情况,因为该数据中包含了Mill lane字符串,所以能够最大程度地匹配上我们的匹配规则,故它的得分最高;再看第二条数据,因为该数据中只包含了Mill而没有Lane,所以它的匹配度更低一些,故而得分低一些。
虽然elasticsearch可以进行分词模糊匹配,但我们若是就想查询哪些数据中含有mill road呢?这个时候,我们可以采用 match_phrase
来实现短语匹配,比如:
GET bank/_search
{
"query": {
"match_phrase": {
"address": "mill lane"
}
}
}
我们还可以进行多字段匹配,比如:
GET bank/_search
{
"query": {
"multi_match": {
"query": "mill",
"fields": ["address","city"]
}
}
}
此时表示在 address
和 city
属性中匹配mill字符串,倘若有一个满足,都符合我们的匹配规则。
当我们需要同时指定多个检索规则的时候,我们可以使用 bool
属性完成复合检索:
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"gender": "F"
}
},
{
"match": {
"address": "mill"
}
}
],
"must_not": [
{
"match": {
"age": "28"
}
}
],
"should": [
{
"match": {
"lastname": "Wallace"
}
}
]
}
}
}
在这段语句中,首先是 bool
属性复合了四个检索条件,然后是 must
属性,它表示必须满足检索条件,条件为 gender
等于F, address
包含mill;而 must_not
表示必须排除检索条件,条件为 age
等于28;最后是 should
,它表示应该满足检索条件,检索条件为 lastname
等于Wallace。
它的关键在于即使不满足该条件,这条数据也可以被查询出来,但如果某条数据满足了 should
指定的条件,它就会得到相应的得分,可以认为这是一个加分项。
我们还可以通过 filter
过滤器实现检索,比如:
GET bank/_search
{
"query": {
"bool": {
"filter": {
"range": {
"age": {
"gte": 18,
"lte": 30
}
}
}
}
}
}
这段语句表示检索年龄在18~30之间的数据,但需要注意的是,使用 filter
属性并不会影响数据的得分。
使用 match
能够实现精确检索,但elasticsearch推荐我们使用 match
属性进行模糊检索,而使用 term
进行精确检索,比如:
GET /bank/_search
{
"query": {
"term": {
"balance": "32838"
}
}
}
聚合
elasticsearch 除了能够检索数据,它还能够对数据进行分析,elasticsearch 中的聚合就提供了从数据中分组和提取数据的能力。
比如这样的一个需求,检索地址中包含mill的所有人的年龄分布以及平均年龄,该如何编写语句呢?
GET /bank/_search
{
"query": {
"match": {
"address": "mill"
}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 10
}
},
"ageAvg":{
"avg": {
"field": "age"
}
}
}
}
首先通过 query
属性检索地址中包含mill的人,然后通过 aggs
指定聚合操作,第一个聚合为 ageAgg
,这是聚合操作的名字,可以取任意值,其中 terms
表示分布情况;第二个聚合为 ageAvg
,其中 avg
表示平均值,来看检索得到的结果:
其中 buckers
中的数据是每种年龄的分布情况,比如38岁的有2人,28岁的有1人;ageAvg
中的就是年龄的平均值了,为34.0。
又比如这个需求,按照年龄聚合,并求这些年龄段的人的平均薪资,语句就该这么写:
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"ageAvg": {
"avg": {
"field": "balance"
}
}
}
}
}
}
这是一个嵌套的聚合操作,首先通过 terms
按照年龄聚合, size
表示分布有多少种情况,这里假设有100种,然后在该聚合的基础上进行平均聚合,这里需要注意平均聚合语句的位置是在年龄聚合的里面的。
映射
映射是用来定义一个文档,以及它所包含的属性是如何存储和索引的,但我们发现,在使用elasticsearch的过程中,我们并没有对数据进行类型的指定,这是因为elasticsearch会自动猜测映射类型,我们可以通过请求查看映射信息:
GET bank/_mapping
得到结果:
{
"bank" : {
"mappings" : {
"properties" : {
"account_number" : {
"type" : "long"
},
"address" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"age" : {
"type" : "long"
}
...
}
}
}
}
每个属性的 type
中都显示了它的类型。然而有些属性的类型并不是我们想要的,这个时候我们可以修改指定属性的类型:
PUT /my_index
{
"mappings": {
"properties": {
"age":{"type": "integer"},
"email":{"type": "keyword"},
"name":{"type": "text"}
}
}
}
按照我们获取到的映射规则对其进行设置即可。此时我们获取my_index索引的映射信息进行查看:
{
"my_index" : {
"aliases" : { },
"mappings" : {
"properties" : {
"age" : {
"type" : "integer"
},
"email" : {
"type" : "keyword"
},
"name" : {
"type" : "text"
}
}
},
...
}
}
说明设置是成功的。但这种方式仅限于设置不存在的索引,若是需要设置的索引已经存在,则我们无法通过这种方式来改变属性的类型。我们需要采用另外一种方式:
PUT /my_index/_mapping
{
"properties":{
"id":{"type":"long"}
}
}
然而这种方式只能用于添加新的映射规则, 而不能修改之前属性的类型。所以若是想要修改已存在的属性类型,只能再创建一个新的索引,并指定好映射规则,再将之前的数据迁移到新的索引中来:
# 创建新的映射规则索引
PUT /newbank
{
"mappings": {
"properties": {
"address": {
"type": "text"
},
"age": {
"type": "integer"
},
"balance": {
"type": "long"
},
"city": {
"type": "text"
}
}
}
}
# 数据迁移
POST _reindex
{
"source": {
"index": "bank"
},
"dest": {
"index": "newbank"
}
}
分词
在前面有说到elasticsearch是通过分词来进行模糊检索的,而分词在elasticsearch中是通过tokenizer分词器实现的,分词器通过接收一个字符流,将其分割为独立的tokens词元。
elasticsearch中默认有一些分词器,但它们都只支持英文的分词,对于中文,我们需要额外安装一个分词器,这里以 ik
分词器为例,首先下载ik分词器,下载地址:https://github.com/medcl/elasticsearch-analysis-ik
同样下载 7.4.2 版本的 ik 分词器:
在该位置复制下载地址,然后在服务器上进行安装,因为在启动 elasticsearch 镜像的时候我们做了数据卷的映射,所以只需将其下载好存放在 /mydata/elasticsearch/plugins
目录下即可:
cd /mydata/elasticsearch/plugins
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
下载完成后解压一下:
unzip elasticsearch-analysis-ik-7.4.2.zip
解压完成后重启elasticsearch,这样我们就安装好了ik分词器,可以来测试一下:
POST _analyze
{
"analyzer": "ik_smart",
"text": "分词器检索。"
}
分词结果:
{
"tokens" : [
{
"token" : "分词器",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "检索",
"start_offset" : 3,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 1
}
]
}
我们来看看elasticsearch默认使用的分词器的分词效果:
POST _analyze
{
"analyzer": "standard",
"text": "分词器检索。"
}
分词结果:
{
"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
}
]
}
很显然,默认的分词器只是把每个字都当成了一个词而已,在中文的处理上与ik分词器相比就要逊色很多了。
但有时候ik分词器仍然达不到我们的需求,比如最近才流行起来的网络热词,ik分词器肯定是没有办法对其进行分词的,所以我们需要对其定制扩展词库,找到nginx映射目录下的html文件夹,在该文件夹下创建es文件夹用于存放ik分词器需要使用到的一些资源( 关于nginx的运行与配置请看文章最后一节
):
cd cd /mydata/nginx/html/
mkdir es
然后在es目录下新建一个文本信息:
[root@izrcf5u3j3q8xaz es]# vim fenci.txt
[root@izrcf5u3j3q8xaz es]# cat fenci.txt
乔碧萝
cd /mydata/elasticsearch/plugins/ik/config/
修改它的配置文件:
vim IKAnalyzer.cfg.xml
修改内容如下,将定制的文本地址配置进去即可:
配置完成后一定要重启elasticsearch,此时我们来测试一下:
POST _analyze
{
"analyzer": "ik_smart",
"text": "乔碧萝殿下。"
}
分词结果:
{
"tokens" : [
{
"token" : "乔碧萝",
"start_offset" : 0,
"end_offset" : 3,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "殿下",
"start_offset" : 3,
"end_offset" : 5,
"type" : "CN_WORD",
"position" : 1
}
]
}
前面介绍了elasticsearch的一些基础知识,接下来我们来看看如何在SpringBoot应用中使用elasticsearch。
想要通过Java程序操作elasticsearch,我们也需要发送请求给elasticsearch进行操作,这里我们使用ElasticSearch-Rest-Client对elasticsearch进行操作。
首先引入依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
因为SpringBoot管理了elasticsearch的依赖版本,所以我们需要指定一下elasticsearch的版本与其一致:
<properties>
<elasticsearch.version>7.4.2</elasticsearch.version>
</properties>
然后编写一个配置类,向容器中注册一个操作elasticsearch的组件:
@Configuration
public class GuliMailElasticSearchConfig {
@Bean
public RestHighLevelClient esRestClient() {
return new RestHighLevelClient(
RestClient.builder(
new HttpHost("www.ithui.top", 9200, "http")));
}
}
接下来我们就可以通过它操作 elasticsearch 了:
@Autowired
private RestHighLevelClient client;
@Data
class User {
private String name;
private Integer age;
private String gender;
}
@Test
public void index() throws IOException {
IndexRequest indexRequest = new IndexRequest("users");
indexRequest.id("1");
// indexRequest.source("name","zhangsan","age",20,"gender","男");
User user = new User();
user.setName("zhangsan");
user.setAge(20);
user.setGender("男");
String json = JSON.toJSONString(user);
indexRequest.source(json, XContentType.JSON);
// 执行保存操作
IndexResponse index = client.index(indexRequest, RequestOptions.DEFAULT);
// 响应数据
System.out.println(index);
}
RestHighLevelClient提供了非常多的方式用于保存数据,但比较常用的是通过json数据直接保存,首先需要指定索引, IndexRequest indexRequest = new IndexRequest("users");
指定了users索引,然后指定数据id,接着指定数据值,最后使用client执行保存操作,然后可以拿到响应数据。
elasticsearch的其它简单操作,诸如:更新、删除等,都只需要转换一下调用方法即可,如更新操作,就需要使用client调用update方法,接下来我们看看Java程序该如何实现较为复杂的检索操作。
比如现在想聚合出年龄的分布情况,并求出每个年龄分布人群的平均薪资,就应该这样进行编写:
@Test
public void search() throws IOException {
// 创建检索请求
SearchRequest searchRequest = new SearchRequest("bank");
// 指定需要检索的索引
// searchRequest.indices("bank");
// 指定检索条件
SearchSourceBuilder builder = new SearchSourceBuilder();
// 查询条件
builder.query(QueryBuilders.matchQuery("address","mill"));
// 聚合条件
builder.aggregation(AggregationBuilders.terms("ageAgg").field("age"));
builder.aggregation(AggregationBuilders.avg("balanceAvg").field("balance"));
searchRequest.source(builder);
// 执行检索请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 分析结果
System.out.println(searchResponse.toString());
}
这些语法我们在最开始接触elasticsearch的时候都已经了解过了,只不过这里是一个链式的方法调用而已。
Nginx
首先拉取 nginx 的镜像:
docker pull nginx:1.10
然后随意地启动一个 nginx 实例:
docker run -p 80:80 --name nginx -d nginx:1.10
启动该 nginx 实例的目的是将 nginx 中的配置文件复制出来:
docker container cp nginx:/etc/nginx .
这样当前目录下就会产生一个nginx文件夹,将其先重命名为conf,然后再创建一个nginx文件夹,并将conf文件夹移动进去:
mv nginx conf
mkdir nginx
mv conf/ nginx/
然后正式启动一个新的 nginx 实例:
docker run -p 80:80 --name nginx \
-v /mydata/nginx/html:/usr/share/nginx/html \
-v /mydata/nginx/logs:/var/log/nginx \
-v /mydata/nginx/conf:/etc/nginx \
-d nginx:1.10
公众号运营至今,离不开小伙伴们的支持。为了给小伙伴们提供一个互相交流的平台,特地开通了官方交流群。关注公众号「Java后端」回复「进群」即可。
推荐阅读
1. 推荐几个好玩的 GitHub 项目
2. 推荐几个程序员常用的软件
3.
图解 Spring 解决循环依赖
本文分享自微信公众号 - Java后端(web_resource)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。