知识
learn!!!
1.Hive
用作离线数据分析
mr: Mapreduce
HDFS: hadoop file system 分布式文件系统
url: jdbc:dm://localhost:5236/dataims?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
username: root
password: Iflytek@123
username: SYSDBA
password: SYSDBA
driver-class-name: dm.jdbc.driver.DmDriver # 达梦数据库驱动
1.语法
创建表:
CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name
[(col_name data_type [COMMENT col_comment], ...)]
[COMMENT table_comment]
[PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]
[CLUSTERED BY (col_name, col_name, ...)
[SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS]
[ROW FORMAT delimited] --指定分隔符
[STORED AS file_format]
[LOCATION hdfs_path]
delimited :hive自带分隔符
row format delimited fields terminated by '&'
例子:
create table t_stu(id int,name string) row format delimited fields terminated by ','
hive> CREATE EXTERNAL TABLE IF NOT EXISTS student2
> (no INT,name STRING,age INT,sex STRING)
> ROW FORMAT DELIMITED
> FIELDS TERMINATED BY '\t'
> STORED AS TEXTFILE
> LOCATION '/user/external';
一般来说,在SQL创建表后,我们就可以使用INSERT语句插入数据。但在Hive中,可以使用LOAD DATA语句插入数据。
同时将数据插入到Hive,最好是使用LOAD DATA来存储大量记录。有两种方法用来加载数据:一种是从本地文件系统,第二种是从Hadoop文件系统。//原文出自【易百教程】,商业转载请联系作者获得授权,非商业请保留原文链接:https://www.yiibai.com/hive/hive_create_table.html
加载数据的语法如下:
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename
[PARTITION (partcol1=val1, partcol2=val2 ...)]
LOCAL是标识符指定本地路径。它是可选的。
OVERWRITE 是可选的,覆盖表中的数据。
PARTITION 这是可选的
hive> LOAD DATA LOCAL INPATH '/home/user/sample.txt' OVERWRITE INTO TABLE employee;
2.复杂类型数据:
list数据如下:
zhangsan beijing,shanghai,hefei,hangzhou
wangwu shanghai,chengdu,nanjing,guangzhou
语法:
create table complex_array(name string,work_locations array
) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' COLLECTION ITEMS TERMINATED BY ','
map数据如下:
1,zhangsan,唱歌:喜欢-跳舞:非常喜欢-游泳:不喜欢
2,lisi, 打游戏:喜欢-跳舞:喜欢-篮球:不喜欢
语法:
create table t_map(id int,name string,hobby map<string,string>) ROW FORMAT DELIMITED FIELDS
TERMINATED BY ',' COLLECTION ITEMS TERMINATED BY '-' map keys TERMINATED by ':'
HIVE建表的时候默认的分隔符是“\001”
3.分区表
单分区表
create TABLE IF NOT EXISTS t_user(id int,name string) partitioned by (country string) row format
delimited fields TERMINATED BY ','
根据字段country进行分区,不能与前面的属性相同,建表后默认会有3个属性。
插入数据需要特殊的语句:
load data local inpath '/root/hivedata/1.txt' into table t_user partition(country='CHINA');
country=china下面存放的是1.txt文件,里面只有id,name属性
注:
分区表字段不能再表中已经存在
分区字段是一个虚拟字段 不存放任何数据
分区字段的数据来自于装载分区表数据的时候指定的
分区表的字段 在hdfs的效果上就是在 建立表的文件夹下面又创建了子文件(数据划分更细致)
只需要查询对应的分区表下的数据即可
双分区表
create TABLE day_hour_table(id int,name string) partitioned by (date string,hour string) row format delimited fields TERMINATED BY ','
插入数据需要特殊的语句:
load data local inpath '/root/hivedata/1.txt' into table day_hour_table partition(date='20220613',hour='08');
4.分桶表
分桶表创建之前 需要开启分桶功能
set hive.enforce.bucketing = true;
set mapreduce.job.reduces=4; //分为4桶
分桶表(分簇表)创建的时候 分桶字段必须是表中已经存在的字段,也就是说需要按照表中的某个字段进行分开。
针对分桶表的数据导入:load data方法不能够导成分桶表的数据 没有分桶效果
create TABLE stu_table(id int,name string,sex string)
clustered by (id) into 4 buckets
row format delimited fields TERMINATED BY ','
插入数据需要特殊的语句:
insert overwrite table stu_table
select * from student cluster by(id);
分桶表的数据 采用insert+select,插入的数据来自于查询结果(查询时执行了mr程序)
对应mr当中的partitioner
默认分桶规则: 按照你指定的分桶字段clustered by 哈希值 & 分桶的个数 set mapreduce.job.reduces=?
分桶表也是把表所映射的结构化数据文件分成更细致的部分,但是更多的是用在join查询提高效率上,
只需要把join的字段在各自表当中进行分桶操作即可。
5.内部表与外部表
正常创建的是内部表。
创建外部表:
create external table stu_table(id int,name string,sex string)
row format delimited fields TERMINATED BY ',' location '/stu' ;
删除表的时候,内部表的元数据和数据会一起删除,而外部表只删除元数据,不删除数据。
6.like
like允许用户复制现有的表结构,但是不复制数据。
create table t_user_copy like t_user; //t_user,已存在的表
7.显示命令
8.多重插入
create table source_table(id int,name string) row format delimited fields TERMINATED BY ',';
create table tets_insert1(id int) row format delimited fields TERMINATED BY ',';
create table tets_insert2(name string) row format delimited fields TERMINATED BY ',';
from source_table
insert overwrite table tets_insert1
select id
insert overwrite table tets_insert2
select name;
9.动态分区插入:
set hive.exec.dynamic.partition=true; #是否开启动态分区功能,默认false
set hive.exec.dynamic.partition.mode=monstrict; #动态分区模式,默认strict
原始表:dynamic_partition_table(day string,ip string)
2022-06-13,ip1
2022-06-13,ip2
目标表:create table d_p_t(ip string) partitioned by (month string,day string);
动态插入:
insert overwrite table d_p_t partition(month,day)
select ip,substr(day,1,7) as month,day
from dynamic_partition_table;
10.select语句
基本的select操作
语法结构:
select [ALL | DISTINCT] select_expr,select_expr,...
from table_test
join table_test2 on expr
[WHERE where_condition]
[GROUP BY col_list]
[CLUSTER BY col_list | [distribute by col_list] [SORT BY | rder by col_list] ]
[limit number]
order by 会对输入做全局排序,sort by不会。
distribute by 根据指定字段将数据分到不同的reducer。
CLUSTER BY 除了具有distribute by的功能外,还会对该字段进行排序。
11.自定义函数
UDF格式:用户自定义函数,user defined function。一对一的输入输出。(最常用的)。
1.先在工程下新建一个pom.xml,加入以下maven的依赖包
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.4</version>
</dependency>
定义UDF函数要注意下面几点:
1.继承org.apache.hadoop.hive.ql.exec.UDF
2.重写evaluate(),这个方法不是由接口定义的,因为它可接受的参数的个数,数据类型都是不确定的。Hive会检查UDF,看能否找到和函数调用相匹配的evaluate()方法
2、将编写的udf的jar包上传到服务器上,并且将jar包添加到hive的class path中.
这种加载只对本session有效
1、进入到hive客户端,执行下面命令
> add jar /hivedata/udf.jar
2、创建一个临时函数名,要跟上面hive在同一个session里面:
create temporary function toUP as 'com.qf.hive.FirstUDF';
3、检查函数是否创建成功
show functions;
4. 测试功能
select toUp('bingbing');
5. 删除函数
drop temporary function if exists tolow;
12.复杂分隔符:
字段分隔符为:$%#
create table hive_tb(name string,age int)
ROW FORMAT SERDE 'org.apache.hadoop.hive.contrib.serde2.MultiDelimitSerde'
with SERDEPROPERTIES ("field.delim"="$%#");
字段分隔符为: ||
使用正则匹配实现多字符分隔符
create table hive_test(name string,age int)
ROW FORMAT serde 'org.apache.hadoop.hive.serde2.RegexSerDe'
with serdeproperties (
'input.regex'='(.*)\\|\\|(.*)',
'output.format.string'='%1$s %2$s'
)
stored as textfile;
2.RestTemplate
简化了发起 HTTP 请求以及处理响应的过程,并且支持 REST
Get 方法
在 RestTemplate 中,发送一个 GET 请求,我们可以通过如下两种方式:
getForEntity
getForEntity 方法的返回值是一个ResponseEntity
getForEntity 的第一个参数为我要调用的服务的地址,这里我调用了服务提供者提供的 /hello 接口,
注意这里是通过服务名调用而不是服务地址,如果写成服务地址就没法实现客户端负载均衡了。
(备注:我项目中需要通过 ConsulClient 去获取服务名,然后在去获取服务的 IP 和 Port,并把它拼接起来组合成我的服务地址,
所以就没法实现客户端的负载均衡了,如果要是实现负载均衡,可以在 SpringBoot 启动类的中加入注解 @LoadBalanced
getForEntity 第二个参数 String.class 表示我希望返回的 body 类型是 String
有时候我在调用服务提供者提供的接口时,可能需要传递参数,有两种不同的方式:
@RequestMapping("/sayhello")
public String sayHello() {
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={1}", String.class, "张三");
return responseEntity.getBody();
}
@RequestMapping("/sayhello2")
public String sayHello2() {
Map<String, String> map = new HashMap<>();
map.put("name", "李四");
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={name}", String.class, map);
return responseEntity.getBody();
}
可以用一个数字做占位符,最后是一个可变长度的参数,来一 一替换前面的占位符
也可以前面使用 name={name} 这种形式,最后一个参数是一个 map,map 的 key 即为前边占位符的名字,map的 value 为参数值
也可以返回一个对象
restTemplate.getForEntity("http://HELLO-SERVICE/getbook1", Book.class);
getForObject
getForObject 函数实际上是对 getForEntity 函数的进一步封装,如果你只关注返回的消息体的内容,
对其他信息都不关注,此时可以使用 getForObject.
POST 方法
在 RestTemplate 中,POST 请求可以通过如下三个方法来发起:
ResponseEntity postForEntity(String url, @Nullable Object request, Class responseType)
方法的第一参数表示要调用的服务的地址
方法的第二个参数表示上传的参数
方法的第三个参数表示返回的消息体的数据类型
postForObject
如果你只关注,返回的消息体,可以直接使用postForObject。用法和getForObject一致。
postForLocation
也是提交新资源,提交成功之后,返回新资源的 URI,postForLocation 的参数和前面两种的参数基本一致,
只不过该方法的返回值为 URI ,这个只需要服务提供者返回一个 URI 即可,该 URI 表示新资源的位置。
EXCHANGE
与其它接口的不同:
允许调用者指定HTTP请求的方法(GET,POST,PUT等)
可以在请求中增加body以及头信息,其内容通过参数 HttpEntity<?>requestEntity 描述
exchange支持‘含参数的类型’(即泛型类)作为返回类型,该特性通过 ParameterizedTypeReferenceresponseType 描述
BeanUtils.copyProperties(dsInfo,dsInfoDetailApiResponseVo); //复制属性
3.linux命令
Linux修改配置文件的命令是什么
Linux添加修改配置文件一般都是使用默认的VI编辑器,命令是vi 文件名。长期编辑代码的程序员会使用功能更强大的编辑器。
简单介绍下vi编辑器的使用方法:
一、vi可以分为三种状态,分别是命令模式(command mode)、插入模式(Insert
mode)和底行模式(last line mode),各模式的功能区分如下:
1、命令行模式command mode)
控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insert mode下,或者到 last line mode。
2、插入模式(Insert mode)
只有在Insert mode下,才可以做文字输入,按「ESC」键可回到命令行模式。
3、底行模式(last line mode)
将文件保存或退出vi,也可以设置编辑环境,如寻找字符串、列出行号等。
不过一般使用时把vi简化成两个模式,就是将底行模式(last line mode)也算入
命令行模式command mode)。
二、命令行模式(command mode)功能键
1、插入模式
按「i」切换进入插入模式「insert mode」,按“i”进入插入模式后是从光标当前位置开始输入文件;
按「a」进入插入模式后,是从目前光标所在位置的下一个位置开始输入文字;
按「o」进入插入模式后,是插入新的一行,从行首开始输入文字。
2、从插入模式切换为命令行模式
按「ESC」键。
3、移动光标
vi可以直接用键盘上的光标来上下左右移动,但正规的vi是用小写英文字母「h」、「j」、「k」、「l」,分别控制光标左、下、上、右移一格。
三、vi保存命令。
按ESC键 跳到命令模式,然后:
:w 保存文件但不退出vi
:w file 将修改另外保存到file中,不退出vi:w! 强制保存,不推出vi
:wq 保存文件并退出vi
:wq! 强制保存文件,并退出vi
q: 不保存文件,退出vi
:q! 不保存文件,强制退出vi
:e! 放弃所有修改,从上次保存文件开始再编辑。
- tar –xvf file.tar 解压 tar包
- find -name application-gsdev.yml 查找该目录下的某个文件
清理缓存的命令:
sync; echo 3 > /proc/sys/vm/drop_caches
查看占用磁盘
du -h --max-depth=1 /iflytek/server/ | sort -hr
/iflytek/server/kafka_2.13-3.9.1:
bin/kafka-console-consumer.sh --bootstrap-server 172.29.236.27:9092 --topic kafka_log
/iflytek/filebeat:
nohup /iflytek/filebeat/filebeat-7.8.0-linux-x86_64/filebeat -e -c /iflytek/filebeat/172.29.230.131/filebeat.yml &
解压tar.gz文件命令:
tar -xzf xxxx.tar.gz
消费 Kafka 中的消息
bin/kafka-console-consumer.sh --bootstrap-server 172.30.32.18:9092 --topic kafka_log --from-beginning
./kafka-server-start.sh ../config/server.properties &
查看内核版本
uname -a
- ls:列出目录中的文件和子目录。
- cd:切换当前工作目录。
- pwd:显示当前工作目录的路径。
- mkdir:创建一个新目录。
- rmdir:删除一个空目录。
- cp:复制文件或目录。
- mv:移动或重命名文件或目录。
- touch:创建一个新文件或更新现有文件的时间戳。
- cat:查看文件内容。
- grep:在文件中查找指定的字符串。
- chmod:更改文件或目录的权限。
- chown:更改文件或目录的所有者。
- df:显示磁盘空间使用情况。
- du:显示目录空间使用情况。
- ps:显示当前正在运行的进程信息。
- kill:终止指定的进程。
- top:实时显示系统资源的使用情况,包括 CPU、内存等。
- ifconfig:显示网络接口的信息,包括 IP 地址、MAC 地址等。
- netstat:显示网络连接状态和统计信息。
- tar:打包或解压文件或目录。
4.达梦
str_to_date这个函数达梦不支持吗?
处理方法
这是Mysql的一个字符串转时间类型的函数。DM没有这个函数,在DM中可以使用 to_data函数代替。对于Mysql转DM的小伙伴,如果碰到难以解决的问题,可以直接百度同类用法,如何迁移到Oracle上执行,把Oracle的用法,直接复用在DM上,一般都可行。使用方式如下:
直接用 to_data 即可。其他都不需要改动。
date_format(date,'%Y-%m-%d') ————–>oracle中的to_char();
str_to_date(date,'%Y-%m-%d') ————–>oracle中的to_date();
%Y:代表4位的年份
%y:代表2位的年份
%m:代表月, 格式为(01……12)
%c:代表月, 格式为(1……12)
%d:代表月份中的天数,格式为(00……31)
%e:代表月份中的天数, 格式为(0……31)
%H:代表小时,格式为(00……23)
%k:代表 小时,格式为(0……23)
%h:代表小时,格式为(01……12)
%I:代表小时,格式为(01……12)
%i:代表分钟, 格式为(00……59) 【只有这一个代表分钟,大写的I 不代表分钟代表小时】
%r:代表 时间,格式为12 小时(hh:mm:ss [AP]M)
%T:代表 时间,格式为24 小时(hh:mm:ss)
%S:代表 秒,格式为(00……59)
%s:代表 秒,格式为(00……59)
select str_to_date('09/01/2009','%m/%d/%Y')
select str_to_date('20140422154706','%Y%m%d%H%i%s')
select str_to_date('2014-04-22 15:47:06','%Y-%m-%d %H:%i:%s')
5.Elasticsearch(ES)
1.简介
ES是一个使用java语言编写的并且基于Lucene编写的搜索引擎, 他提供了分布式的全文搜索服务, 还提供了一个RESTful风格的web接口, 官方还对多种语言提供了相应的API
2.ES特点
分布式: ES主要为了横向扩展能力
全文检索: 将一段词语进行分词, 并且将分出的单个词语统一的放入一个分词库中,在搜索时,根据关键字去分词库中搜索去找到想找到的内容,(倒排索引)
RESTful风格web接口: 操作ES非常简单, 只需要发送一个Http请求并且根据请求方式不同和携带参数不同,执行相应的功能。
3.索引
倒排索引
创建索引,对比关系型数据库,创建索引就等同于创建数据库。
http://localhost:9200/index 使用put请求(具有幂等性)
查看所有的索引:
GET http://localhost:9200/ _cat/indices?v
创建文档:
POST http://localhost:9200/shopping/_doc 生成的id随机
POST http://localhost:9200/shopping/_doc/1001 生成的id为1001,可以用put方法创建
获取索引数据:
GET http://localhost:9200/shopping/_doc/1001 获取主键id为1001的数据
获取这个索引下所有的数据:
修改数据:
全量数据更新:
在请求body里放入修改的数据,使用put请求
局部数量更新:
请求body里写入需要修改的属性及属性值
{
"doc" :{
"title" : "华为手机"
}
}
删除:
多个条件的查询:
{
"query":{
"bool":{
"must":[ //must:同时满足
//should:满足一个相当于or
{
"match":{
"price":1999
}
},
{
"match":{
"category":"小米"
}
}
]
}
}
}
es案例:
package com.test;
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.FuzzyQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import java.util.List;
public class EsDocQuery_batch {
public static void main(String[] args) throws Exception {
//创建es客户端
RestHighLevelClient esClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost",9200,"http"))
);
//1.查询索引中全部数据 QueryBuilders.matchAllQuery())
SearchRequest request = new SearchRequest();
request.indices("user");
request.source(new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()));
SearchResponse res = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
//2.条件查询 termQuery
SearchRequest request = new SearchRequest();
request.indices("user");
request.source(new SearchSourceBuilder().query(QueryBuilders.termQuery("age",43)));
SearchResponse res = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
//3.分页查询 SearchSourceBuilder
// builder.from(0);
// builder.size(2);
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
builder.from(0);
builder.size(100);
request.source(builder);
SearchResponse res = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
//4.查询排序 builder.sort("age", SortOrder.DESC);
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
builder.sort("age", SortOrder.DESC);
request.source(builder);
SearchResponse res = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
//5.过滤字段 builder.fetchSource(includes,excludes);
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
String[] includes = {"name"};
String[] excludes = {};
builder.fetchSource(includes,excludes);
request.source(builder);
SearchResponse res = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
//6.组合查询 QueryBuilders.boolQuery();
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("age",66));
// boolQueryBuilder.mustNot(QueryBuilders.matchQuery("age",66));
// boolQueryBuilder.should(QueryBuilders.matchQuery("age",66));
builder.query(boolQueryBuilder);
request.source(builder);
SearchResponse res = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
//7.范围查询 QueryBuilders.rangeQuery
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
RangeQueryBuilder queryBuilder = QueryBuilders.rangeQuery("age");
queryBuilder.gte(10);
queryBuilder.lte(40);
builder.query(queryBuilder);
request.source(builder);
SearchResponse res = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
//8.模糊查询 QueryBuilders.fuzzyQuery
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "lisi").fuzziness(Fuzziness.ONE);
builder.query(fuzzyQueryBuilder);
request.source(builder);
SearchResponse res = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
System.out.println(hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
//9.聚合查询(最大值) AggregationBuilders.max
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
AggregationBuilder aggregationBuilder = AggregationBuilders.max("maxAge").field("age");
builder.aggregation(aggregationBuilder);
request.source(builder);
SearchResponse res = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
System.out.println(hits.getTotalHits());
System.out.println(res.getTook());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
//10。分组查询 AggregationBuilders.terms
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
AggregationBuilder aggregationBuilder = AggregationBuilders.terms("ageGroup").field("age");
builder.aggregation(aggregationBuilder);
request.source(builder);
SearchResponse res = esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
System.out.println(res);
System.out.println(hits.getTotalHits());
System.out.println(res.getTook());
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
//11.统计数量查询
CountRequest countRequest = new CountRequest();
countRequest.indices("user");
CountResponse res = esClient.count(countRequest, RequestOptions.DEFAULT);
System.out.println(res.getCount());
//关闭客户端
esClient.close();
}
}
索引内容的解析:
{
"took" : 0,
"timed_out" : false,
"_shards" : { // 分片信息
"total" : 1, // 总计分片数
"successful" : 1, // 查询成功的分片数
"skipped" : 0, // 跳过查询的分片数
"failed" : 0 // 查询失败的分片数
},
"hits" : { // 命中结果
"total" : {
"value" : 1, // 数量
"relation" : "eq" // 关系:等于
},
"max_score" : 2.8526313, // 最高分数
"hits" : [
{
"_index" : "person", // 索引
"_type" : "_doc", // 类型
"_id" : "1",
"_score" : 2.8526313,
"_source" : {
"address" : "光明顶",
"modifyTime" : "2021-06-29 16:48:56",
"createTime" : "2021-05-14 16:50:33",
"sect" : "明教",
"sex" : "男",
"skill" : "九阳神功",
"name" : "张无忌",
"id" : 1,
"power" : 99,
"age" : 18
}
}
]
}
}
sdl:
{"query": {
"bool": {
"must": [
{"match":
{"level": "ERROR"}
}
],
}
}
// ,"size": 0
}
{'took': 4,
'timed_out': False,
'_shards': {'total': 5, 'successful': 5, 'skipped': 0, 'failed': 0},
'hits': {'total': 514777, 'max_score': 0.0, 'hits': []}}
if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime) && StringUtils.isNotBlank(level)) {
dsl = "{\"query\":{\"bool\":{\"must\":[{\"terms\":{\"level.keyword\":" + JSON.toJSONString(list) + "}}," +
"{\"match\":{\"case_id\":\"" + id + "\"}},{\"range\":{\"operate_time.keyword\":{\"gte\":\"" + startTime + "\"," +
"\"lte\":\"" + endTime + "\",\"format\":\"yyyy-MM-dd hh:mm:ss\",\"boost\":1.0}}}]}}}";
} else if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime) && StringUtils.isBlank(level)) {
dsl = "{\"query\":{\"bool\":{\"must\":[{\"match\":{\"case_id\":\"" + id + "\"}},{\"range\":{\"operate_time.keyword\":{\"gte\":\"" + startTime + "\"," +
"\"lte\":\"" + endTime + "\",\"format\":\"yyyy-MM-dd hh:mm:ss\",\"boost\":1.0}}}]}}}";
} else if (StringUtils.isNotBlank(startTime) && StringUtils.isBlank(endTime) && StringUtils.isNotBlank(level)) {
dsl = "{\"query\":{\"bool\":{\"must\":[{\"terms\":{\"level.keyword\":" + JSON.toJSONString(list) + "}},{\"match\":{\"case_id\":\"" + id + "\"}}," +
"{\"range\":{\"operate_time.keyword\":{\"gte\":\"" + startTime + "\"," +
"\",\"format\":\"yyyy-MM-dd hh:mm:ss\",\"boost\":1.0}}}]}}}";
} else if (StringUtils.isNotBlank(startTime) && StringUtils.isBlank(endTime) && StringUtils.isBlank(level)) {
dsl = "{\"query\":{\"bool\":{\"must\":[{\"match\":{\"case_id\":\"" + id + "\"}},{\"range\":{\"operate_time.keyword\":{\"gte\":\"" + startTime + "\"," +
"\",\"format\":\"yyyy-MM-dd hh:mm:ss\",\"boost\":1.0}}}]}}}";
} else if (StringUtils.isBlank(startTime) && StringUtils.isNotBlank(endTime) && StringUtils.isNotBlank(level)) {
dsl = "{\"query\":{\"bool\":{\"must\":[{\"terms\":{\"level.keyword\":" + JSON.toJSONString(list) + "}},{\"match\":{\"case_id\":\"" + id + "\"}}," +
"{\"range\":{\"operate_time.keyword\":{\"," +
"\"lte\":\"" + endTime + "\",\"format\":\"yyyy-MM-dd hh:mm:ss\",\"boost\":1.0}}}]}}}";
} else if (StringUtils.isBlank(startTime) && StringUtils.isNotBlank(endTime) && StringUtils.isBlank(level)) {
dsl = "{\"query\":{\"bool\":{\"must\":[{\"match\":{\"case_id\":\"" + id + "\"}},{\"range\":{\"operate_time.keyword\":{\"," +
"\"lte\":\"" + endTime + "\",\"format\":\"yyyy-MM-dd hh:mm:ss\",\"boost\":1.0}}}]}}}";
} else if (StringUtils.isBlank(startTime) && StringUtils.isBlank(endTime) && StringUtils.isNotBlank(level)) {
dsl = "{\"query\":{\"bool\":{\"must\":[{\"terms\":{\"level.keyword\":" + JSON.toJSONString(list) + "}}," +
"{\"match\":{\"case_id\":\"" + id + "\"}}]}}}";
} else {
dsl = "{\"query\":{\"bool\":{\"must\":[{\"match\":{\"case_id\":\"" + id + "\"}}]}}}";
}
4.查询语句
查询指定索引的数据
GET storage/_search
{
"query": {
"match_all": {}
}
}删除指定索引下的数据
GET storage/_delete_by_query?pretty
{
"query": {
"match_all": {}
}
}
# 根据某个属性删除
POST case_unit_info/_delete_by_query
{
"query": {
"term": {
"case_no": "A9401010200002024020156"
}
}
}
5.elasticsearch部署(linux-arm--8.19.11)
1.下载文件
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.19.11-linux-aarch64.tar.gz \
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.19.11-linux-aarch64.tar.gz.sha512
2.校验文件完整性
shasum -a 512 -c elasticsearch-8.19.11-linux-aarch64.tar.gz.sha512
3.解压文件
tar -zxvf elasticsearch-8.19.11-linux-aarch64.tar.gz
cd elasticsearch-8.19.11/
4.修改配置
vim /config/jvm.options(2g以上)
-Xms2g
-Xmx2g
先创建data目录,赋予权限
cd /iflytek/server/elasticsearch/elasticsearch-8.19.11/
mkdir data
chown -R elasticsearch:elasticsearch /iflytek/server/elasticsearch/elasticsearch-8.19.11
修改es的配置文件(elasticsearch.yml,备份源文件)
新增如下配置:
cluster.name: my-single-node-cluster
path.data: /iflytek/server/elasticsearch/elasticsearch-8.19.11/data
path.logs: /iflytek/server/elasticsearch/elasticsearch-8.19.11/logs
node.name: single-node
discovery.type: single-node
network.host: 0.0.0.0
http.port: 9201
transport.port: 9301
http.cors.enabled: true
http.cors.allow-origin: "*"
action.auto_create_index: .security,.monitoring*,.watches,.triggered_watches,.watcher-history*
xpack.ml.enabled: false
discovery.seed_hosts: ["172.29.236.27"]
xpack.security.enabled: true --安全认证
xpack.security.transport.ssl.enabled: false
注:运行 Elasticsearch 需要 JDK 环境,下载的文件中内置了 JDK,也可使用自己的 JDK,在环境变量中配置 ES_JAVA_HOME即可,如未配置,则使用内置的 JDK。推荐使用内置 JDK,因为自己的 JDK 可能会有版本不匹配的问题。
5.密码配置
设置内置用户的密码,执行:
./bin/elasticsearch-setup-passwords interactive
会提示设置以下用户的密码:
- elastic(超级管理员)
- kibana_system
- logstash_system
- beats_system
- apm_system
设置完成后即可启动。
手动为单个用户设置密码
./bin/elasticsearch-users useradd elasticsearch -p your_password
用 elasticsearch-users 创建的用户没有任何角色/权限,需要手动分配角色
给 elasticsearch 用户分配 superuser 角色(拥有所有权限)
./bin/elasticsearch-users roles -a superuser elasticsearch
6.服务启动
# 1. 创建 elasticsearch 用户组
groupadd elasticsearch
# 2. 创建 elasticsearch 用户
useradd elasticsearch -g elasticsearch -p Xjz@123
# 3. 修改 ES 目录所有者
chown -R elasticsearch:elasticsearch /iflytek/server/elasticsearch/elasticsearch-8.19.11
# 4. 切换用户启动
su elasticsearch
cd /iflytek/server/elasticsearch/elasticsearch-8.19.11 && env -i ES_JAVA_HOME=/iflytek/server/elasticsearch/elasticsearch-8.19.11/jdk HOME=$HOME USER=$USER PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ./bin/elasticsearch -d
7.常见问题
failed to obtain node locks:已存在es进程,kill杀掉即可
Suppressed: java.nio.file.AccessDeniedException: /iflytek/server/elasticsearch/node.lock
无权限,赋予路径文件操作权限
修改权限(假设当前用户是 elasticsearch)
chown -R elasticsearch:elasticsearch /iflytek/server/elasticsearch
chmod -R 755 /iflytek/server/elasticsearch
用户冲突,删除用户
1.查看 elasticsearch 用户是否存在
id elasticsearch
2.停止所有 elasticsearch 进程
sudo pkill -u elasticsearch(sudo pkill -9 -u elasticsearch)
3.确认进程已停止
ps -u elasticsearch
4.验证删除成功
id elasticsearch
应该显示: id: 'elasticsearch': no such user
[.kibana_analytics] Action failed with '[index_not_green_timeout] Timeout waiting for the status of the [.kibana_analytics_8.19.11_001] index to become 'green' Refer to https://www.elastic.co/guide/en/kibana/8.13/resolve-migrations-failures.html#_repeated_time_out_requests_that_eventually_fail for information on how to resolve the issue.'. Retrying attempt 5 in 32 seconds.
一般是内存问题
\# 检查集群健康状态
curl -X GET "localhost:9200/_cluster/health?pretty"
status需要是green或者yellow才对,red是异常状态
如果还存在权限问题
chown -R elasticsearch:elasticsearch /iflytek/server/elasticsearch/elasticsearch-8.19.11
sudo chmod -R 755 /iflytek/server/elasticsearch/elasticsearch-8.19.11/
sudo chmod -R 755 /iflytek/server/elasticsearch/elasticsearch-8.19.11/modules/
sudo chown -R elasticsearch:elasticsearch /iflytek/server/elasticsearch/elasticsearch-8.19.11/data/
sudo chmod -R 755 /iflytek/server/elasticsearch/elasticsearch-8.19.11/data/
6.kibana部署
#下载文件
wget https://artifacts.elastic.co/downloads/kibana/kibana-8.19.11-linux-aarch64.tar.gz
#解压文件
tar -zxf kibana-8.19.11-linux-aarch64.tar.gz
#进入目录
cd kibana-8.19.11/
#赋予权限
chown -R elasticsearch:elasticsearch ../kibana-8.19.11
#修改配置
vim config/kibana.yml(配置如下)
# 启动服务
su elasticsearch
nohup ./bin/kibana &
server.port: 5601
server.host: "0.0.0.0" # 0.0.0.0,允许所有网络接口访问
server.name: "your-hostname"
server.ssl.enabled: false
elasticsearch.hosts: ["http://172.29.230.131:9201"]
# 如果是单节点 Elasticsearch,添加以下配置
monitoring.ui.container.elasticsearch.enabled: true
# 可选:禁用 AI 助手插件(消除之前的错误日志)
xpack.observabilityAIAssistant.enabled: false
elasticsearch.username: "kibana_system" --es中默认的kibana用户
elasticsearch.password: "Xjz@123"
elasticsearch.ssl.verificationMode: none
i18n.locale: "zh-CN"
登录的时候使用 elastic或者新增的elasticsearch进行登录即可。
7.日志管理
日志平台
目录介绍
unified-log-java:日志平台后端java代码
unified-log-web:日志平台应用端服务
unified-log-harvester:日志采集器
unified-log-repeater:日志转发器
unified-log-analysis :日志分析中心
unified-log-monitor:监控数据接收器
unified-log-index:索引数据接收器
unified-log-statistics:统计数据接收器
unified-log-front:日志平台前端代码
apache-zookeeper-3.5.5-集群-微服务用 端口2181
172.31.187.185
172.30.34.69
172.31.184.187
kafka_2.13-3.2.0-集群-微服务用 端口9092
172.30.34.206
172.30.34.69
172.30.34.97
8.redis hash
hash: key value(key value) -->key field value
9.端口占用
netstat -ano
netstat -aon|findstr "8080"
协议 本地地址 外部地址 状态 PID
TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 9524
TCP [::]:8080 [::]:0 LISTENING 9524
tasklist|findstr "9524"
taskkill /f /t /im java.exe
tasklist|findstr "9524"
taskkill /F /PID 9797
sudo netstat -tlnp | grep 8080
netstat -ano | findstr 80 //列出进程极其占用的端口,且包含 80
taskkill -PID <进程号> -F //强制关闭某个进程
10.kafka深究
一、前戏
1、在项目中连接kafka,因为是外网,首先要开放kafka配置文件中的如下配置(其中IP为公网IP),
advertised.listeners=PLAINTEXT://112.126.74.249:9092
2、在开始前我们先创建两个topic:topic1、topic2,其分区和副本数都设置为2,用来测试,
[root@iZ2zegzlkedbo3e64vkbefZ ~]# cd /usr/local/kafka-cluster/kafka1/bin/
[root@iZ2zegzlkedbo3e64vkbefZ bin]# ./kafka-topics.sh --create --zookeeper 172.17.80.219:2181 --replication-factor 2 --partitions 2 --topic topic1
Created topic topic1.
[root@iZ2zegzlkedbo3e64vkbefZ bin]# ./kafka-topics.sh --create --zookeeper 172.17.80.219:2181 --replication-factor 2 --partitions 2 --topic topic2
Created topic topic2.
当然我们也可以不手动创建topic,在执行代码kafkaTemplate.send("topic1", normalMessage)发送消息时,kafka会帮我们自动完成topic的创建工作,但这种情况下创建的topic默认只有一个分区,分区也没有副本。所以,我们可以在项目中新建一个配置类专门用来初始化topic,如下,
@Configuration
public class KafkaInitialConfiguration {
// 创建一个名为testtopic的Topic并设置分区数为8,分区副本数为2
@Bean
public NewTopic initialTopic() {
return new NewTopic("testtopic",8, (short) 2 );
}
// 如果要修改分区数,只需修改配置值重启项目即可
// 修改分区数并不会导致数据的丢失,但是分区数只能增大不能减小
@Bean
public NewTopic updateTopic() {
return new NewTopic("testtopic",10, (short) 2 );
}
}
3、新建SpringBoot项目
① 引入pom依赖
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
② application.propertise配置(本文用到的配置项这里全列了出来)
###########【Kafka集群】###########
spring.kafka.bootstrap-servers=112.126.74.249:9092,112.126.74.249:9093
###########【初始化生产者配置】###########
重试次数
spring.kafka.producer.retries=0
应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
spring.kafka.producer.acks=1
批量大小
spring.kafka.producer.batch-size=16384
提交延时
spring.kafka.producer.properties.linger.ms=0
当生产端积累的消息达到batch-size或接收到消息linger.ms后,生产者就会将消息提交给kafka
linger.ms为0表示每接收到一条消息就提交给kafka,这时候batch-size其实就没用了
生产端缓冲区大小
spring.kafka.producer.buffer-memory = 33554432
Kafka提供的序列化和反序列化类
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
自定义分区器
spring.kafka.producer.properties.partitioner.class=com.felix.kafka.producer.CustomizePartitioner
###########【初始化消费者配置】###########
默认的消费组ID
spring.kafka.consumer.properties.group.id=defaultConsumerGroup
是否自动提交offset
spring.kafka.consumer.enable-auto-commit=true
提交offset延时(接收到消息后多久提交offset)
spring.kafka.consumer.auto.commit.interval.ms=1000
当kafka中没有初始offset或offset超出范围时将自动重置offset
earliest:重置为分区中最小的offset;
latest:重置为分区中最新的offset(消费分区中新产生的数据);
none:只要有一个分区不存在已提交的offset,就抛出异常;
spring.kafka.consumer.auto-offset-reset=latest
消费会话超时时间(超过这个时间consumer没有发送心跳,就会触发rebalance操作)
spring.kafka.consumer.properties.session.timeout.ms=120000
消费请求超时时间
spring.kafka.consumer.properties.request.timeout.ms=180000
Kafka提供的序列化和反序列化类
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
消费端监听的topic不存在时,项目启动会报错(关掉)
spring.kafka.listener.missing-topics-fatal=false
设置批量消费
spring.kafka.listener.type=batch
批量消费每次最多消费多少条消息
spring.kafka.consumer.max-poll-records=50
二、Hello Kafka
1、简单生产者
@RestController
public class KafkaProducer {
@Autowired
private KafkaTemplate<String, Object> kafkaTemplate;
// 发送消息
@GetMapping("/kafka/normal/{message}")
public void sendMessage1(@PathVariable("message") String normalMessage) {
kafkaTemplate.send("topic1", normalMessage);
}
}
2、简单消费
@Component
public class KafkaConsumer {
// 消费监听
@KafkaListener(topics = {"topic1"})
public void onMessage1(ConsumerRecord<?, ?> record){
// 消费的哪个topic、partition的消息,打印出消息内容
System.out.println("简单消费:"+record.topic()+"-"+record.partition()+"-"+record.value());
}
}
上面示例创建了一个生产者,发送消息到topic1,消费者监听topic1消费消息。监听器用@KafkaListener注解,topics表示监听的topic,支持同时监听多个,用英文逗号分隔。启动项目,postman调接口触发生产者发送消息,
可以看到监听器消费成功.
三、生产者
1、带回调的生产者
kafkaTemplate提供了一个回调方法addCallback,我们可以在回调方法中监控消息是否发送成功 或 失败时做补偿处理,有两种写法,
@GetMapping("/kafka/callbackOne/{message}")
public void sendMessage2(@PathVariable("message") String callbackMessage) {
kafkaTemplate.send("topic1", callbackMessage).addCallback(success -> {
// 消息发送到的topic
String topic = success.getRecordMetadata().topic();
// 消息发送到的分区
int partition = success.getRecordMetadata().partition();
// 消息在分区内的offset
long offset = success.getRecordMetadata().offset();
System.out.println("发送消息成功:" + topic + "-" + partition + "-" + offset);
}, failure -> {
System.out.println("发送消息失败:" + failure.getMessage());
});
}
@GetMapping("/kafka/callbackTwo/{message}")
public void sendMessage3(@PathVariable("message") String callbackMessage) {
kafkaTemplate.send("topic1", callbackMessage).addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onFailure(Throwable ex) {
System.out.println("发送消息失败:"+ex.getMessage());
}
@Override
public void onSuccess(SendResult<String, Object> result) {
System.out.println("发送消息成功:" + result.getRecordMetadata().topic() + "-"
+ result.getRecordMetadata().partition() + "-" + result.getRecordMetadata().offset());
}
});
}
2、自定义分区器
我们知道,kafka中每个topic被划分为多个分区,那么生产者将消息发送到topic时,具体追加到哪个分区呢?这就是所谓的分区策略,Kafka 为我们提供了默认的分区策略,同时它也支持自定义分区策略。其路由机制为:
① 若发送消息时指定了分区(即自定义分区策略),则直接将消息append到指定分区;
② 若发送消息时未指定 patition,但指定了 key(kafka允许为每条消息设置一个key),则对key值进行hash计算,根据计算结果路由到指定分区,这种情况下可以保证同一个 Key 的所有消息都进入到相同的分区;
③ patition 和 key 都未指定,则使用kafka默认的分区策略,轮询选出一个 patition;
※ 我们来自定义一个分区策略,将消息发送到我们指定的partition,首先新建一个分区器类实现Partitioner接口,重写方法,其中partition方法的返回值就表示将消息发送到几号分区,
public class CustomizePartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
// 自定义分区规则(这里假设全部发到0号分区)
// ......
return 0;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> configs) {
}
}
在application.propertise中配置自定义分区器,配置的值就是分区器类的全路径名,
自定义分区器
spring.kafka.producer.properties.partitioner.class=com.felix.kafka.producer.CustomizePartitioner
3、kafka事务提交
如果在发送消息时需要创建事务,可以使用 KafkaTemplate 的 executeInTransaction 方法来声明事务,
@GetMapping("/kafka/transaction")
public void sendMessage7(){
// 声明事务:后面报错消息不会发出去
kafkaTemplate.executeInTransaction(operations -> {
operations.send("topic1","test executeInTransaction");
throw new RuntimeException("fail");
});
// 不声明事务:后面报错但前面消息已经发送成功了
kafkaTemplate.send("topic1","test executeInTransaction");
throw new RuntimeException("fail");
}
四、消费者
1、指定topic、partition、offset消费
前面我们在监听消费topic1的时候,监听的是topic1上所有的消息,如果我们想指定topic、指定partition、指定offset来消费呢?也很简单,@KafkaListener注解已全部为我们提供,
/**
* @Title 指定topic、partition、offset消费
* @Description 同时监听topic1和topic2,监听topic1的0号分区、topic2的 "0号和1号" 分区,指向1号分区的offset初始值为8
- @Author long.yuan
- @Date 2020/3/22 13:38
- @Param [record]
- @return void
**/
@KafkaListener(id = "consumer1",groupId = "felix-group",topicPartitions = {
@TopicPartition(topic = "topic1", partitions = { "0" }),
@TopicPartition(topic = "topic2", partitions = "0", partitionOffsets = @PartitionOffset(partition = "1", initialOffset = "8"))
})
public void onMessage2(ConsumerRecord record) {
System.out.println("topic:"+record.topic()+"|partition:"+record.partition()+"|offset:"+record.offset()+"|value:"+record.value());
}
属性解释:
① id:消费者ID;
② groupId:消费组ID;
③ topics:监听的topic,可监听多个;
④ topicPartitions:可配置更加详细的监听信息,可指定topic、parition、offset监听。
上面onMessage2监听的含义:监听topic1的0号分区,同时监听topic2的0号分区和topic2的1号分区里面offset从8开始的消息。
**注意:topics和topicPartitions不能同时使用;**
2、批量消费
设置application.prpertise开启批量消费即可,
设置批量消费
spring.kafka.listener.type=batch
批量消费每次最多消费多少条消息
spring.kafka.consumer.max-poll-records=50
接收消息时用List来接收,监听代码如下,
```java
@KafkaListener(id = "consumer2",groupId = "felix-group", topics = "topic1")
public void onMessage3(List<ConsumerRecord<?, ?>> records) {
System.out.println(">>>批量消费一次,records.size()="+records.size());
for (ConsumerRecord<?, ?> record : records) {
System.out.println(record.value());
}
}
3、ConsumerAwareListenerErrorHandler 异常处理器
通过异常处理器,我们可以处理consumer在消费时发生的异常。
新建一个 ConsumerAwareListenerErrorHandler 类型的异常处理方法,用@Bean注入,BeanName默认就是方法名,然后我们将这个异常处理器的BeanName放到@KafkaListener注解的errorHandler属性里面,当监听抛出异常的时候,则会自动调用异常处理器,
// 新建一个异常处理器,用@Bean注入
@Bean
public ConsumerAwareListenerErrorHandler consumerAwareErrorHandler() {
return (message, exception, consumer) -> {
System.out.println("消费异常:"+message.getPayload());
return null;
};
}
// 将这个异常处理器的BeanName放到@KafkaListener注解的errorHandler属性里面
@KafkaListener(topics = {"topic1"},errorHandler = "consumerAwareErrorHandler")
public void onMessage4(ConsumerRecord<?, ?> record) throws Exception {
throw new Exception("简单消费-模拟异常");
}
// 批量消费也一样,异常处理器的message.getPayload()也可以拿到各条消息的信息
@KafkaListener(topics = "topic1",errorHandler="consumerAwareErrorHandler")
public void onMessage5(List<ConsumerRecord<?, ?>> records) throws Exception {
System.out.println("批量消费一次...");
throw new Exception("批量消费-模拟异常");
}
执行看一下效果,
4、消息过滤器
消息过滤器可以在消息抵达consumer之前被拦截,在实际应用中,我们可以根据自己的业务逻辑,筛选出需要的信息再交由KafkaListener处理,不需要的消息则过滤掉。
配置消息过滤只需要为 监听器工厂 配置一个RecordFilterStrategy(消息过滤策略),返回true的时候消息将会被抛弃,返回false时,消息能正常抵达监听容器。
@Component
public class KafkaConsumer {
@Autowired
ConsumerFactory consumerFactory;
// 消息过滤器
@Bean
public ConcurrentKafkaListenerContainerFactory filterContainerFactory() {
ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory();
factory.setConsumerFactory(consumerFactory);
// 被过滤的消息将被丢弃
factory.setAckDiscarded(true);
// 消息过滤策略
factory.setRecordFilterStrategy(consumerRecord -> {
if (Integer.parseInt(consumerRecord.value().toString()) % 2 == 0) {
return false;
}
//返回true消息则被过滤
return true;
});
return factory;
}
// 消息过滤监听
@KafkaListener(topics = {"topic1"},containerFactory = "filterContainerFactory")
public void onMessage6(ConsumerRecord<?, ?> record) {
System.out.println(record.value());
}
}
上面实现了一个"过滤奇数、接收偶数"的过滤策略,我们向topic1发送0-99总共100条消息,看一下监听器的消费情况,可以看到监听器只消费了偶数。
5、消息转发
在实际开发中,我们可能有这样的需求,应用A从TopicA获取到消息,经过处理后转发到TopicB,再由应用B监听处理消息,即一个应用处理完成后将该消息转发至其他应用,完成消息的转发。
在SpringBoot集成Kafka实现消息的转发也很简单,只需要通过一个@SendTo注解,被注解方法的return值即转发的消息内容,如下,
/**
* @Title 消息转发
* @Description 从topic1接收到的消息经过处理后转发到topic2
* @Author long.yuan
* @Date 2020/3/23 22:15
* @Param [record]
* @return void
**/
@KafkaListener(topics = {"topic1"})
@SendTo("topic2")
public String onMessage7(ConsumerRecord<?, ?> record) {
return record.value()+"-forward message";
}
6.定时启动、停止监听器
默认情况下,当消费者项目启动的时候,监听器就开始工作,监听消费发送到指定topic的消息,那如果我们不想让监听器立即工作,想让它在我们指定的时间点开始工作,或者在我们指定的时间点停止工作,该怎么处理呢——使用KafkaListenerEndpointRegistry,下面我们就来实现:
① 禁止监听器自启动;
② 创建两个定时任务,一个用来在指定时间点启动定时器,另一个在指定时间点停止定时器;
新建一个定时任务类,用注解@EnableScheduling声明,KafkaListenerEndpointRegistry 在SpringIO中已经被注册为Bean,直接注入,设置禁止KafkaListener自启动,
@EnableScheduling
@Component
public class CronTimer {
/**
* @KafkaListener注解所标注的方法并不会在IOC容器中被注册为Bean,
* 而是会被注册在KafkaListenerEndpointRegistry中,
* 而KafkaListenerEndpointRegistry在SpringIOC中已经被注册为Bean
**/
@Autowired
private KafkaListenerEndpointRegistry registry;
@Autowired
private ConsumerFactory consumerFactory;
// 监听器容器工厂(设置禁止KafkaListener自启动)
@Bean
public ConcurrentKafkaListenerContainerFactory delayContainerFactory() {
ConcurrentKafkaListenerContainerFactory container = new ConcurrentKafkaListenerContainerFactory();
container.setConsumerFactory(consumerFactory);
//禁止KafkaListener自启动
container.setAutoStartup(false);
return container;
}
// 监听器
@KafkaListener(id="timingConsumer",topics = "topic1",containerFactory = "delayContainerFactory")
public void onMessage1(ConsumerRecord<?, ?> record){
System.out.println("消费成功:"+record.topic()+"-"+record.partition()+"-"+record.value());
}
// 定时启动监听器
@Scheduled(cron = "0 42 11 * * ? ")
public void startListener() {
System.out.println("启动监听器...");
// "timingConsumer"是@KafkaListener注解后面设置的监听器ID,标识这个监听器
if (!registry.getListenerContainer("timingConsumer").isRunning()) {
registry.getListenerContainer("timingConsumer").start();
}
//registry.getListenerContainer("timingConsumer").resume();
}
// 定时停止监听器
@Scheduled(cron = "0 45 11 * * ? ")
public void shutDownListener() {
System.out.println("关闭监听器...");
registry.getListenerContainer("timingConsumer").pause();
}
}
启动项目,触发生产者向topic1发送消息,可以看到consumer没有消费,因为这时监听器还没有开始工作,
11:42分监听器启动开始工作,消费消息,
11:45分监听器停止工作,
五、kafka命令
查询所有topic:(进入/bin目录)
./kafka-topics.sh --bootstrap-server ${你的kafka配置文件中listener的地址} --list
./kafka-topics.sh --bootstrap-server localhost:9092 --list
11.linux杀进程
ps -ef |grep repeat(服务名)
kill -9 26672(进程)
ll /proc/8077 查询某个端口所在路径
查看某个端口运行情况:
netstat -tunlp |grep 30684
nginx重启:/usr/local/nginx/sbin/nginx -s reload
提示没有命令:
-bash: nginx: command not found
新增脚本命令
vim /etc/profile
新增配置:
PATH=$PATH:/usr/local/nginx/sbin
export PATH
刷新环境变量:
source /etc/profile
netstat -tunlp |grep 9095
运营中心打包方式:
mvn clean package -DskipTests -Pgsdev
mvn clean package -DskipTests -Pgsgch
12.prometheus
1.简述:
Prometheus 是一款基于时序数据库的开源监控告警系统,非常适合Kubernetes集群的监控。Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的集成过程。这样做非常适合做虚拟化环境监控系统,比如VM、Docker、Kubernetes等。输出被监控组件信息的HTTP接口被叫做exporter 。目前互联网公司常用的组件大部分都有exporter可以直接使用,比如Varnish、Haproxy、Nginx、MySQL、Linux系统信息(包括磁盘、内存、CPU、网络等等)。Promethus有以下特点:
支持多维数据模型:由度量名和键值对组成的时间序列数据
内置时间序列数据库TSDB
支持PromQL查询语言,可以完成非常复杂的查询和分析,对图表展示和告警非常有意义
支持HTTP的Pull方式采集时间序列数据
支持PushGateway采集瞬时任务的数据
支持服务发现和静态配置两种方式发现目标
支持接入Grafana
2.组件说明:
* prometheus server是Prometheus组件中的核心部分,负责实现对监控数据的获取,存储以及查询。
* exporter简单说是采集端,通过http服务的形式保留一个url地址,prometheusserver通过访问该exporter提供的endpoint端点,即可获取到需要采集的监控数据。
* AlertManager在prometheus中,支持基于PromQL创建告警规则,如果满足定义的规则,则会产生一条告警信息,进入AlertManager进行处理。可以集成邮件,微信或者通过webhook自定义报警。
* Pushgateway由于Prometheus数据采集采用pull方式进行设置的,内置必须保证prometheusserver和对应的exporter必须通信,当网络情况无法直接满足时,可以使用pushgateway来进行中转,可以通过pushgateway将内部网络数据主动push到gateway里面去,而prometheus采用pull方式拉取pushgateway中的数据。
总结:
- prometheus负责从pushgateway和job中采集数据,存储到后端Storage中,可以通过PromQL进行查询,推送alerts信息到AlertManager。AlertManager根据不同的路由规则进行报警通知
3.prometheus.yml配置解析:
global:此片段指定的是prometheus的全局配置,比如采集间隔,抓取超时时间等。
rule_files:此片段指定报警规则文件,prometheus根据这些规则信息,会推送报警信息到alertmanager中。
scrape_configs:此片段指定抓取配置,prometheus的数据采集通过此片段配置。
alerting:此片段指定报警配置,这里主要是指定prometheus将报警规则推送到指定的alertmanager实例地址。
remote_write:指定后端的存储的写入api地址。
remote_read:指定后端的存储的读取api地址。
4.global参数:
#Howfrequentlytoscrapetargetsbydefault.[scrape_interval:<duration>|default=1m] #抓取间隔
#Howlonguntilascraperequesttimesout.[scrape_timeout:<duration>|default=10s] #抓取超时时间
#Howfrequentlytoevaluaterules.[evaluation_interval:<duration>|default=1m] #评估规则间隔
5.scrape_configs参数:
一个scrape_config片段指定一组目标和参数,目标就是实例,指定采集的端点,参数描述如何采集这些实例,主要参数如下:
scrape_interval:抓取间隔,默认继承global值。
scrape_timeout:抓取超时时间,默认继承global值。metric_path:抓取路径,默认是/metrics
*_sd_configs:指定服务发现配置
static_configs:静态指定服务job。
relabel_config:relabel设置。
6.检测语法,检测配置文件有没有问题?
[root@prometheus prometheus]# ./promtool check config prometheus.yml #检查配置文件语法有无问题
启动:
[root@prometheus prometheus]# systemctl restart prometheus
./prometheus &
7.AlterManager
安装在哪台机器都可以
[root@prometheus ~]# wget https://github.com/prometheus/alertmanager/releases/download/v0.23.0/alertmanager-0.23.0.linux-amd64.tar.gz
[root@prometheus ~]# tar -xvzf alertmanager-0.23.0.linux-amd64.tar.gz -C /usr/local/
[root@prometheus ~]# cd /usr/local/
[root@prometheus local]# mv alertmanager-0.23.0.linux-amd64/ alertmanager
[root@prometheus local]# vim alertmanager/alertmanager.yml
配置文件:
global:
resolve_timeout: 5m
smtp_from: '@163.com'
smtp_smarthost: 'smtp.163.com:25'
smtp_auth_username: '@163.com'
smtp_auth_password: '填自己的' #这里要开启邮箱SMTP/POP3/IMAP认证,记录授权码
smtp_require_tls: false
# smtp_hello: '163.com'
route:
group_by: ['alertname']
group_wait: 20s
group_interval: 5m
repeat_interval: 5m
receiver: 'email'
receivers:
- name: 'email'
email_configs:
- to: '@qq.com'
send_resolved: true
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
检测语法:
[root@prometheus alertmanager]# ./amtool check-config alertmanager.yml #检测语法
注:配置文件详解
# 全局配置项
global:
resolve_timeout: 5m #处理超时时间,默认为5min
smtp_from: '.com' # 发送邮箱名称
smtp_smarthost: 'smtp.163.com:25' # 邮箱smtp服务器代理
smtp_auth_username: '.com' # 邮箱名称
smtp_auth_password: '' # 邮箱授权码
# 定义路由树信息
route:
group_by: ['alertname'] # 报警分组依据
group_wait: 10s # 最初即第一次等待多久时间发送一组警报的通知
group_interval: 10s # 在发送新警报前的等待时间
repeat_interval: 1m # 发送重复警报的周期 对于email配置中,此项不可以设置过低,否则将会由于邮件发送太多频繁,被smtp服务器拒绝
receiver: 'email' # 发送警报的接收者的名称,以下receivers name的名称
# 定义警报接收者信息
receivers:
- name: 'email' # 警报
email_configs: # 邮箱配置
- to: '9@qq.com' # 接收警报的email配置
send_resolved: true
# 一个inhibition规则是在与另一组匹配器匹配的警报存在的条件下,使匹配一组匹配器的警报失效的规则。两个警报必须具有一组相同的标签.
inhibit_rules: #抑制规则
- source_match: #源标签
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
告警配置:
报警状态说明:
Prometheus Alert 告警状态有三种状态:Inactive、Pending、Firing。
1、Inactive:非活动状态,表示正在监控,但是还未有任何警报触发。
2、Pending:表示这个警报必须被触发。由于警报可以被分组、压抑/抑制或静默/静音,所 以等待验证,一旦所有的验证都通过,则将转到 Firing 状态。
3、Firing:将警报发送到 AlertManager,它将按照配置将警报的发送给所有接收者。一旦警 报解除,则将状态转到 Inactive,如此循环。
management:
endpoint:
metrics:
enabled: true
prometheus:
enabled: true
endpoints:
web:
exposure:
include: '*'
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
server:
port: 9898
13.容器
kubernets几种端口(port、targetport、contaierport、hostport、nodeport)的区别和关联
1.这两个一般用在应用yaml描述文件中,起到的作用类似于docker -p选项
containerport: 容器需要暴露的端口,service的targetPort会映射到pod里的这个containerPort。
hostport: 容器暴露的端口映射到的物理机的主机端口。
2.这两个一般用在service中,service 的类型为cluster ip时候:
port: service中clusterip 对应的端口
targetport: clusterIP作为负载均衡, 后端目标实例(容器)的端口。targetPort对应pod里的containerPort。
3.这一个一般用在service中,service的类型为nodeport:
cluster ip 只能集群内部访问(源与目标需要满足两个条件: kube-proxy正常运行,跨主机容器网络通信正常),nodeport会在每个kubelet节点的宿主机开启一个端口,用于应用集群外部访问。
4.hostport和nodeport的区别?
(1)hostport是将pod的端口映射到宿主机上。
(2)nodeport是将service的端口映射到集群中的每个宿主机上。
关于端口的总结
快速总结一下哪些端口和标签应该匹配:
Service selector 应该和 Pod 的标签匹配;
Service 的 targetPort 应该和 Pod 里面容器的 containerPort 匹配;
Service 端口可以是任意数字。多个 Service 可以使用同一个端口,因为不同的 Service 分配的 IP 地址不同;
Ingress 的 service.port 应该和 Service 的 port 匹配;
Service 的名称应该和 Ingress 中 service.name 字段匹配;
{
"code": 200,
"msg": "请求成功",
"data": {
"name": "ks-apiserver",
"namespace": "kubesphere-system",
"spec": {
"ports": [
{
"protocol": "TCP",
"port": 80,
"targetPort": 9090
}
],
"selector": {
"app": "ks-apiserver",
"tier": "backend"
},
"clusterIP": "10.233.23.116",
"clusterIPs": [
"10.233.23.116"
],
"type": "ClusterIP",
"sessionAffinity": "None",
"ipFamilies": [
"IPv4"
],
"ipFamilyPolicy": "SingleStack"
},
"deployments": [
{
"name": "ks-apiserver",
"namespace": "kubesphere-system",
"labels": {
"app": "ks-apiserver",
"app.kubernetes.io/managed-by": "Helm",
"tier": "backend",
"version": "v3.1.0"
},
"replicas": 1,
"availableReplicas": 1,
"unavailableReplicas": 0
}
],
"pods": [
{
"name": "ks-apiserver-5db774f4f-bcfg2",
"namespace": "kubesphere-system",
"labels": {
"app": "ks-apiserver",
"pod-template-hash": "5db774f4f",
"tier": "backend"
},
"nodeName": "k8s-hengtang2-m1",
"podIP": "10.233.94.20",
"hostIP": "172.30.33.246"
}
]
}
}
服务通过selector关联负载和容器
集群内部clusterip就能访问
regex used for validation is 'a-z0-9?'
确实不能大写
仅创建应用空间和应用空间配额,还是无法查询到,在应用空间有资源使用情况下才能查到
IfNotPresent 本地有就不会去拉
项目空间对应workspace,应用空间对应namespace,项目空间范围大于应用空间,应用空间里面有各种资源
查询接口,如果是NodePort类型,会有nodeport字段
集群内部clusterip就能访问,但只限集群内部
想外部能访问,就要NodePort类型或者LoadBalancer类型
返回没有nodePort参数,则展示clusterIP
外部访问--> port:nodePort/protocol
docker验证:
ewoJImF1dGhzIjogewoJCSIxNzIuMzAuMzUuMTc5IjogewoJCQkiYXV0aCI6ICJZV1J0YVc0eE9tZGlWRE5aV1NSRU1rdDVjUT09IgoJCX0KCX0sCgkiSHR0cEhlYWRlcnMiOiB7CgkJIlVzZXItQWdlbnQiOiAiRG9ja2VyLUNsaWVudC8xOS4wMy4xNCAobGludXgpIgoJfQp9
{
"name": "test",
"data": {
".dockerconfigjson": "ewoJImF1dGhzIjogewoJCSIxNzIuMzAuMzUuMTc5IjogewoJCQkiYXV0aCI6ICJZV1J0YVc0eE9tZGlWRE5aV1NSRU1rdDVjUT09IgoJCX0KCX0sCgkiSHR0cEhlYWRlcnMiOiB7CgkJIlVzZXItQWdlbnQiOiAiRG9ja2VyLUNsaWVudC8xOS4wMy4xNCAobGludXgpIgoJfQp9"
},
"type": "kubernetes.io/dockerconfigjson"
}
容器云、镜像及docker配置
第二步:
systemctl daemon-reload && systemctl restart docker
docker:
admin1:gbT3YY$D2Kyq
gbT3YY$D2Kyq
"172.31.129.33",

14.替换部分包
替换jar包部分依赖异常处理: window
jar -xvf dataqar-web-1.0.0-SNAPSHOT.jar
jar -cfM0 dataqar-web-1.0.0-SNAPSHOT.jar BOOT-INF/ META-INF/ org/
linux环境替换jar里的lib文件配置 api网关组件、云容器组件
unzip api-gateway-assembly-impl-1.0.0-SNAPSHOT.jar
rm -rf api-gateway-assembly-impl-1.0.0-SNAPSHOT.jar
cd BOOT-INT/lib/
vim api-gateway-assembly-sdk-1.0.0-SNAPSHOT.jar
/application.yml
修改完成后,退出 :wq
:quit
返回之前的解压目录
jar cvfm0 api-gateway-assembly-impl-1.0.0-SNAPSHOT.jar ./META-INF/MANIFEST.MF ./
15.服务器环境
[a-z0-9]([-a-z0-9]*[a-z0-9])?
16.运营中心规划
18.git
1.删除包:
git rm -rf .\yyzx-front\
git commit -m 'delete file'
git push
2.提交代码到远程:
git add .
git commit -m '提交信息'
git push (origin master)
拉取代码
git pull (origin master)
3.git基本操作:
git init: 初始化 Git 仓库
git clone: 克隆远程 Git 仓库到本地
git pull:从远程仓库拉取代码
git add: 添加文件到暂存区
git commit: 提交暂存区文件到 Git 仓库
git push:推送代码到远程仓库
git status:查看仓库状态
git diff: 查看工作区和暂存区的差异
git log:查看提交历史
git branch:查看本地分支列表
git checkout: 切换分支
git merge:合并分支
git remote: 查看远程仓库信息
4.git分支操作:
git branch
: 创建分支 git branch -d
: 删除本地分支
git merge --no-ff: 合并分支并保留分支历史
git push--delete : 删除远程分支(其中, <remote>是你要删除的远程仓库的名称,<branch>是要删除的分支的名称。)如:git push origin --delete gs
5.撤销操作:
git reset: 撤销暂存区的所有修改
git reset
: 撤销暂存区指定文件的修改
git checkout --: 撤销工作区指定文件的修改
git revert:撒销指定提交的修改 git reset --hard: 重置工作区、暂存区和Git 仓库的状态
6.配置操作:
git config:查看、添加或修改 Git 配置
git config --global user.name
: 设置Git全局用户名 git config --global user.email
:设置Git全局用户邮箱 git config --system:修改系统级别 Git 配置
git config --local: 修改当前仓库 Git 配置
git config --unset: 删除 Git 配置项
7.暂存操作:
git stash:将未提交的修改保存到 Git 的stash 中,以便之后恢复或者应用。
git stash save"message": 将未提交的修改保存到 Git 的 stash 中,并为当前的 stash设置一个描述信息。描述信息可以帮助我们更好地理解 stash 中保存的内容。
git stash list: 列出所有保存在 stash 中的修改。每个 stash 都有一个唯一的标识符,可以用来区分不同的 stash。
git stash apply
:将指定的 stash应用到当前分支中,但是不会删除该 stash。如果要删除该 stash,需要使用 git stash drop命令。 git stash pop: 将最近保存的 stash 应用到当前分支中,并删除该 stash。
git stash drop
:删除指定的 stash git stash clear:删除所有的 stash。
git stash branch
:基于 stash创建一个新的分支,并将该 stash 应用到新的分支中。新分支包含了 stash 中的所有修改。 git stash pop stash@
{0} 将指定版本保存的 stash 应用到当前分支中,并删除该 stash(注意大括号{需要加``)。
8.其它配置
从远端仓库克隆 dev 分支
git clone -b <分支名> <远程仓库地址>
git clone -b dev <git代码仓库地址>
从远端仓库拉取 dev 分支到本地。
## git fetch origin <分支名>
git fetch origin dev
从当前 master 分支上,拉取远端仓库 dev 分支到本地。
## git checkout -b <本地分支名> origin/<想要拉取的远程分支名>
git checkout -b master origin/dev
9.回滚操作
回滚到指定版本:git reset --hard 版本号
软合并 git reset --soft origin/develop
强行合并代码(用于回滚到指定版本)git push -f origin master
撤销commit还没push的数据(删除)
git reset --soft HEAD~1
10.合并分支
git push origin feature:master
这条命令将会将feature分支合并到master分支上。
19.读取nacos配置
springboot项目从nacos配置文件中读取配置信息(读取指定的某个配置)
1.导入依赖
<!-- 添加Nacos Config配置项 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!-- 读取bootstrap文件 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <!-- Nacos服务发现 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
工具类
/**
* yml文件转换为proper文件工具类
* @author: qcWang9
* @date: 2023/3/30 9:33
*/
public class YmlToPropertiesUtil {
public static Properties buildProperties(String path, Map<String, Object> map, Properties props) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Map) {
buildProperties(path + key + ".", (Map<String, Object>) value, props);
} else {
props.put(path + key, value.toString());
}
}
return props;
}
}
配置文件在nacos配的组名称和id
private static final String GROUP = "wfw";
private static final String DATA_ID = "elasticsearch-assembly.yml";
String serverAddr = properties.getProperty("server-addr"); String namespace = properties.getProperty("namespace"); Properties pro = new Properties(); pro.put("serverAddr", serverAddr); pro.put("namespace", namespace); ConfigService configService = NacosFactory.createConfigService(pro); String config = configService.getConfig(DATA_ID, GROUP, 5000); ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); Map<String, Object> map = mapper.readValue(config, Map.class); Properties props = new Properties(); properties = YmlToPropertiesUtil.buildProperties("", map, props);
读取需要的配置
properties.getProperty("common.elasticsearch.isSecureMode");
20.数据权限和功能权限
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
客户端认证信息:
package com.iflytek.zhcs.wfw.yyzx.om.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* @author: qcWang9
* @date: 2023/4/3 10:34
*/
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 该对象用来支持 password 模式
*/
@Autowired
AuthenticationManager authenticationManager;
/**
* 该对象用来将令牌信息存储到内存中
*/
@Autowired(required = false)
TokenStore inMemoryTokenStore;
/**
* 该对象将为刷新token提供支持
*/
@Autowired
UserDetailsService userDetailsService;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 配置 password 授权模式
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("wfwpt")
// 该client允许的授权类型 authorization_code,password,refresh_token
.authorizedGrantTypes("authorization_code","password", "refresh_token")
.accessTokenValiditySeconds(1800) // 配置access_token的过期时间
.resourceIds("wfwpt") //配置资源id
.scopes("all")
.secret(passwordEncoder().encode("Iflytek12#$"));
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(inMemoryTokenStore) //配置令牌的存储(这里存放在内存中)
//endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory)) //配置令牌的存储(这里存放在redis中)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
// 表示支持 client_id 和 client_secret 做登录认证
security.allowFormAuthenticationForClients();
}
}
配置访问权限:
package com.iflytek.zhcs.wfw.yyzx.om.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
/**
* @author: qcWang9
* @date: 2023/4/3 15:11
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
// 配置资源id,这里的资源id和授权服务器中的资源id一致
resources.resourceId("wfwpt")
// 设置这些资源仅基于令牌认证
.stateless(true);
}
/**
* 配置 URL 访问权限
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated();
}
}
配置web访问:
package com.iflytek.zhcs.wfw.yyzx.om.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* @author: qcWang9
* @date: 2023/4/3 15:12
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(new BCryptPasswordEncoder().encode("Iflytek12#$"))
.roles("admin")
.and()
.withUser("user")
.password(new BCryptPasswordEncoder().encode("Iflytek12#$"))
.roles("user");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/oauth/**").authorizeRequests()
//放行授权的请求
.antMatchers("/oauth/**").permitAll()
//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
.and().csrf().disable();
}
}
21.nginx
nginx启动命令
进入部署nginx的sbin目录
nginx
重启:
nginx -s reload
nginx:command not found
方法1
vim /etc/profile
编辑文件配置环境变量,添加以下的环境变量
PATH=$PATH: /usr/local/nginx/sbin //这个是你nginx的位置
export PATH
nginx -t,发现没有报错,说明配置成功了
方法2:使用软连接来连接(相当于在bin文件生成一个快捷入口)
ln -s /usr/local/nginx/sbin/nginx /usr/local/bin
22.策略模式+工厂模式
1.公共的接口,策略接口
public interface ActionService {
/**
* 警情中心下发消费
*/
void doAction();
}
2.具体的策略实现类
public class ActionServiceImpl1 implements ActionService {
/**
* 警情中心下发消费
*/
@Override
public void doAction() {
}
}
public class ActionServiceImpl2 implements ActionService {
/**
* 警情中心下发消费
*/
@Override
public void doAction() {
}
}
3.工厂类,统一调度,用来管理这些策略
public class ActionServiceFactory {
private ActionServiceFactory(){
}
private static class SingletonHolder{
private static ActionServiceFactory instance = new ActionServiceFactory();
}
public static ActionServiceFactory getInstance(){
return SingletonHolder.instance;
}
private static final Map<String, ActionService> ACTION_SERVICE_MAP = new HashMap<>();
static {
ACTION_SERVICE_MAP.put("action1", new ActionServiceImpl1());
ACTION_SERVICE_MAP.put("action2", new ActionServiceImpl2());
}
public static ActionService getActionService(String code) {
ActionService actionService = ACTION_SERVICE_MAP.get(code);
if (actionService == null) {
throw new RuntimeException("异常");
}
return actionService;
}
public void doAction(String code){
getActionService(code).doAction();
}
}
4.使用
ActionServiceFactory.getInstance().doAction(param);
23.redis持久化
背景
有个同学阿里二面,面试官问:redis宕机了,如何恢复数据? 这位同学当时一脸懵,不知道如何回答。
分析分析这个问题,redis宕机,要想恢复数据,首先redis的数据有没有做持久化,用的是哪种策略,这种策略的机制是什么,有趣点是什么,以及你们是从什么方面考虑用着中机制的
其实面试官就是想考察,你们业务中redis的持久化策略,以及你对持久化策略有没有了解过,还是就直接使用,不管数据会回丢失,反正丢失了都是运维的锅,那你这样基本上GG了
为什么要做持久化
Redis是个基于内存的数据库。那服务一旦宕机,内存中的数据将全部丢失。通常的解决方案是从后端数据库恢复这些数据,但后端数据库有性能瓶颈,如果是大数据量的恢复,
- 会对数据库带来巨大的压力,严重可能导致mysql宕机
- 数据库的性能不如Redis。导致程序响应慢。所以对Redis来说,实现数据的持久化,避免从后端数据库中恢复数据,是至关重要的。
持久化策略
官方支持的持久化有四种,如下:
- RDB(Redis 数据库):RDB 持久性以指定的时间间隔执行数据集的时间点快照。
- AOF(仅追加文件):AOF 持久性记录服务器接收到的每个写操作。然后可以在服务器启动时再次重播这些操作,从而重建原始数据集。命令使用与 Redis 协议本身相同的格式进行记录。
- RDB + AOF:您还可以在同一个实例中组合 AOF 和 RDB。
- 无持久性:您可以完全禁用持久性。这种策略,一般很少有人使用吧
下面我们对这几种策略,进行详细梳理下
RDB
RDB 就是 Redis DataBase 的缩写,中文名为快照/内存快照,RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值。
默认情况下,Redis 将数据集的快照保存在磁盘上名为 dump.rdb 的二进制文件中。
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。
- save:在主线程中执行,会导致阻塞;
- bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
一般通过 bgsave 命令来执行全量快照,这既提供了数据的可靠性保证,也避免了对 Redis 的性能影响。
redis.conf中配置RDB
内存快照虽然可以通过技术人员手动执行SAVE或BGSAVE命令来进行,但生产环境下多数情况都会设置其周期性执行条件。
bash复制代码# 周期性执行条件的设置格式为
save <seconds> <changes>
# 默认的设置为:
save 900 1
save 300 10
save 60 10000
# 以下设置方式为关闭RDB快照功能
save ""
以上三项默认信息设置代表的意义是:
- 如果900秒内有1条Key信息发生变化,则进行快照;
- 如果300秒内有10条Key信息发生变化,则进行快照;
- 如果60秒内有10000条Key信息发生变化,则进行快照。
Copy-On-Write, COW
redis在执行bgsave生成快照的期间,将内存中的数据同步到硬盘的过程可能就会持续比较长的时间,而实际情况是这段时间Redis服务一般都会收到数据写操作请求。那么如何保证快照的完整性呢?
可能会说,为了保证快照完整性,redis只能处理读操作,不能修改正在执行快照的数据。你想如果这样?为了快照而暂停写操作,同时候你的业务会受到很大的影响,是不可接受的,那有其他方案吗?
Redis 就会借助操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。
bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。
此时,如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本(键值对 C’)。然后,主线程在这个数据副本上进行修改。同时,bgsave 子进程可以继续把原来的数据(键值对 C)写入 RDB 文件。
写时复制机制保证快照期间数据可修改
这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。
快照的频率如何把握
对于快照来说,所谓“连拍”就是指连续地做快照。这样一来,快照的间隔时间变得很短,即使某一时刻发生宕机了,因为上一时刻快照刚执行,丢失的数据也不会太多。但是,这其中的快照间隔时间就很关键了。如下图:
为了尽可能保证在宕机的情况下,保证数据尽量不丢失,比如:一秒一次快照,那丢失的数据也是一秒。这看上去很美好,其实为带来很大的问题,如果频繁地执行全量快照,也会带来两方面的开销
- 一方面,频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。
- 另一方面,bgsave 子进程需要通过 fork 操作从主线程创建出来。虽然,子进程在创建后不会再阻塞主线程,但是,fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了
那这个频率怎么控制呢?这需要根据业务自身的情况,决定快照的频率。比如笔者:我们目前的使用的策略是,关闭系统的自动快照功能,就是 设置 save "" , 定时凌晨连接redis,手动执行bgsave,进行快照生成。可能有人说,如果执行这样的策略,数据丢失就是一天的,对,你说的对,但是我们的业务丢失一天的数据也没关系,这是业务能容忍的 ,在生产的情况下,redis的稳定性相当高,基本上不会宕机,出现宕机的情况,也是因为服务器自身的问题,导致机器重启,redis产生数据丢失。
优缺点
优点
- RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景;
- Redis加载RDB文件恢复数据要远远快于AOF方式;
缺点
- RDB方式实时性不够,无法做到秒级的持久化;
- 每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高;
- RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全;
总结:rdb数据恢复速度非常快,就是无法做到秒级的持久化
那有其他方式做到秒级的持久化吗?AOF
AOF
AOF 持久性记录服务器接收到的每个写操作。然后可以在服务器启动时再次重播这些操作,从而重建原始数据集。命令使用与 Redis 协议本身相同的格式进行记录
Redis 是先执行命令,把数据写入内存,然后才记录日志
AOF日志内容
我们以 Redis 收到“set testkey 1”命令后记录的日志为例,看看 AOF 日志的内容,
日志格式说明 *3表示当前命令有三个部分,每部分都是由$+数字开头,后面紧跟着具体的命令、键或值。这里,数字表示这部分中的命令、键或值一共有多少字节。例如,$3 set表示这部分有 3 个字节,也就是set命令
redis.conf中配置AOF
默认情况下,Redis是没有开启AOF的,可以通过配置redis.conf文件来开启AOF持久化,关于AOF的配置如下:
yaml复制代码# appendonly参数开启AOF持久化
appendonly no
# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"
# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir ./
# 同步策略
# appendfsync always
appendfsync everysec
# appendfsync no
# aof重写期间是否同步
no-appendfsync-on-rewrite no
# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 加载aof出错如何处理
aof-load-truncated yes
# 文件重写策略
aof-rewrite-incremental-fsync yes
写回策略
AOF 机制给我们提供了三个选择,也就是 AOF 配置项 appendfsync 的三个可选值。
- Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
- Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
- No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
针对避免主线程阻塞和减少数据丢失问题,这三种写回策略都无法做到两全其美。我们来分析下其中的原因。
- “同步写回”可以做到基本不丢数据,但是它在每一个写命令后都有一个慢速的落盘操作,不可避免地会影响主线程性能;
- 虽然“操作系统控制的写回”在写完缓冲区后,就可以继续执行后续的命令,但是落盘的时机已经不在 Redis 手中了,只要 AOF 记录没有写回磁盘,一旦宕机对应的数据就丢失了;
- “每秒写回”采用一秒写回一次的频率,避免了“同步写回”的性能开销,虽然减少了对系统性能的影响,但是如果发生宕机,上一秒内未落盘的命令操作仍然会丢失。所以,这只能算是,在避免影响主线程性能和避免数据丢失两者间取了个折中。
我把这三种策略的写回时机,以及优缺点汇总在了一张表格里,以方便你随时查看。
根据系统对高性能和高可靠性的要求,来选择使用哪种写回策略了。总结一下就是:
- 想要获得高性能,就选择 No 策略;
- 如果想要得到高可靠性保证,就选择 Always 策略;
- 如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择 Everysec 策略。
虽然AOF策略,能保证秒级数据丢失,但是随着redis的长时间运行,aof文件会越来越大,如果宕机,进行数据恢复的时候速度是特别慢,影响业务,那有什么好的发案处理吗?aof日志重写
AOF日志重写
AOF 文件是以追加的方式,逐一记录接收到的写命令的。当一个键值对被多条写命令反复修改时,AOF 文件会记录相应的多条命令。但是,在重写的时候,是根据这个键值对当前的最新状态,为它生成对应的写入命令。这样一来,一个键值对在重写日志中只用一条命令就行了,而且,在日志恢复时,只用执行这条命令,就可以直接完成这个键值对的写入了。
重写机制具有“多变一”功能。所谓的“多变一”,也就是说,旧日志文件中的多条命令,在重写后的新日志中变成了一条命令,例如:
我们对列表先后做了 6 次修改操作后,列表的最后状态是[“D”, “C”, “N”],此时,只用 LPUSH u:list “N”, “C”, “D”这一条命令就能实现该数据的恢复,这就节省了五条命令的空间。对于被修改过成百上千次的键值对来说,重写能节省的空间当然就更大了。
不过,虽然 AOF 重写后,日志文件会缩小,但是,要把整个数据库的最新数据的操作日志都写回磁盘,仍然是一个非常耗时的过程。那这个过程,会阻塞主线程吗
AOF重写会阻塞吗
AOF重写过程是由后台进程bgrewriteaof来完成的。主线程fork出后台的bgrewriteaof子进程,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
优缺点
优点
数据能做到秒级丢失,也就是说使用了aof这种机制,能做到最多丢失一秒的数据
缺点
恢复数据比较慢,虽然aof日志重写,可以减小文件,但是速度还是很慢
那有没有一种机制,能做到秒级丢失,恢复速度又比较快呢?RDB和AOF混合方式
RDB和AOF混合方式
Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
如下图所示,T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次做全量快照时,就可以清空 AOF 日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了。
内存快照和AOF混合使用
这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势,颇有点“鱼和熊掌可以兼得”的感觉,建议你在实践中用起来。
部署
wget https://download.redis.io/releases/redis-8.2.2.tar.gz # 下载redis
tar -zxvf redis-8.2.2.tar.gz # 将redis解压到当前目录
cd redis-8.2.2/ # 进入redis目录
make && make install # 执行make && make install 等待安装完成
# 等待命令执行完成,出现这句话则代表安装成功,it's a good idea to run 'make test';
redis-server -v # 可查看安装的redis版本
cp redis.conf redis.conf_bak # 将配置文件复制备份一份
# 修改配置
vim redis.conf
# 使用指定配置文件启动redis
edis-server redis.conf
redis-cli -h 127.0.0.1 -p 7379 -a Ztyf12#$ # 启动redis客户端
daemonize
redis.conf配置文件中daemonize守护线程,默认是NO。
daemonize是用来指定redis是否要用守护线程的方式启动.
yes:redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。
no: 当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭连接工具(putty,xshell等)都会导致redis进程退出。
通常都要讲daemonize no改为yes
protected-mode
redis保护模式,为no的话别的机器可以直接连接本机的redis服务
如果为yes的话,别的机器只能通过设置bind添加服务器ip才能进行访问,或者通过密码方式进行访问,也即是设置参数requirepass,从而达到可以从其他机器访问的目标。
暂时设置为no,方便实用,上线的时候一定设置为yes。
bind 127.0.0.1
直接注释掉(默认bind 127.0.0.1只能本机访问)或改成本机IP地址,否则影响远程IP连接
添加redis密码,requirepass
改为 requirepass 你自己设置的密码
总结
Rdb、Aof两种持久化机制各有优缺点,需要根据自己的实际业务来衡量,到底使用哪种机制,最能满足当下业务,我的建议
- 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;
- 如果允许分钟级别的数据丢失,可以只使用 RDB;
- 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。
24.k8s命令:
查看 metrics-server 或资源指标 API (metrics.k8s.io) 是否已经运行
kubectl get apiservices
创建命名空间:
kubectl create namespace mem-example
指定内存请求和限制
要为容器指定内存请求,请在容器资源清单中包含 resources: requests 字段。 同理,要指定内存限制,请包含 resources: limits。
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
namespace: mem-example
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
requests:
memory: "100Mi"
limits:
memory: "200Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
验证 Pod 中的容器是否已运行
kubectl get pod memory-demo --namespace=mem-example
查看 Pod 相关的详细信息
kubectl get pod memory-demo --output=yaml --namespace=mem-example
运行 kubectl top 命令,获取该 Pod 的指标数据
kubectl top pod memory-demo --namespace=mem-example
删除pod
kubectl delete pod memory-demo --namespace=mem-example
删除命名空间(同时删除该空间下所有的pod)
kubectl delete namespace mem-example
查询pod日志
kubectl logs -f <pod_name> -n
kubectl logs -f <pod_name> -n
--tail=100
总结:get命令能够确认的信息类别:
deployments(缩写deploy)
events (缩写ev)
namespaces (缩写ns)
nodes(缩写no)
pods (缩写po)
replicasets(缩写rs)
replicationcontrollers(缩写rc)
services(缩写svc)
使用describe查看k8s 中详细信息
describe描述.
语法:kubectl describe pod pod名字
语法:kubectl describe node node 名字。
语法:kubectl describe deployment deployment名字
保存镜像命令:
docker save : 保存一个或多个镜像至本地tar文件 (export 是容器打成 tar 文件),后缀为.tar的文件
docker save -o 【tar文件名】 【镜像名】:【版本号】
docker save -o nginx.tar nginx:latestdocker save -o wfw-base-17.tar 172.29.230.131:80/wfw/wfw-base-java17:v5.1
docker load : 把压缩包里面的内容直接导成镜像
-i : 代表读取文件
-q :
docker load -i nginx.tardocker tag wfw-base:v5 172.29.230.131:80/wfw/wfw-base:v5
docker push 172.29.230.131:80/wfw/wfw-base:v5

浙公网安备 33010602011771号