知识

learn!!!

1.Hive

用作离线数据分析

image-20220516110401752

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');

image-20220613144430195

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的字段在各自表当中进行分桶操作即可。

image-20220613152701098

image-20220613152516857

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.显示命令

image-20220613154744646

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,ResponseEntity是 Spring 对 HTTP 请求响应的封装
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

  1. ls:列出目录中的文件和子目录。
  2. cd:切换当前工作目录。
  3. pwd:显示当前工作目录的路径。
  4. mkdir:创建一个新目录。
  5. rmdir:删除一个空目录。
  6. cp:复制文件或目录。
  7. mv:移动或重命名文件或目录。
  8. touch:创建一个新文件或更新现有文件的时间戳。
  9. cat:查看文件内容。
  10. grep:在文件中查找指定的字符串。
  11. chmod:更改文件或目录的权限。
  12. chown:更改文件或目录的所有者。
  13. df:显示磁盘空间使用情况。
  14. du:显示目录空间使用情况。
  15. ps:显示当前正在运行的进程信息。
  16. kill:终止指定的进程。
  17. top:实时显示系统资源的使用情况,包括 CPU、内存等。
  18. ifconfig:显示网络接口的信息,包括 IP 地址、MAC 地址等。
  19. netstat:显示网络连接状态和统计信息。
  20. 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方法创建

PUT http://localhost:9200/shopping/_create/1001

获取索引数据:

GET http://localhost:9200/shopping/_doc/1001 获取主键id为1001的数据

获取这个索引下所有的数据:

GET http://localhost:9200/shopping/_search

修改数据:

全量数据更新:

在请求body里放入修改的数据,使用put请求

PUT http://localhost:9200/shopping/_doc/1001

局部数量更新:

请求body里写入需要修改的属性及属性值

{
	"doc" :{
		"title" : "华为手机"
	}
}

POST http://localhost:9200/shopping/_update/1001

删除:

DELETE http://localhost:9200/shopping/_doc/1001

多个条件的查询:

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

image-20260303142857494

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

image-20220624090011461

8.redis hash

hash: key value(key value) -->key field value

image-20220625180828882

image-20220625182208800

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调接口触发生产者发送消息,

img

可以看到监听器消费成功.

img

三、生产者

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("批量消费-模拟异常");
}

执行看一下效果,

img

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条消息,看一下监听器的消费情况,可以看到监听器只消费了偶数。

img

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没有消费,因为这时监听器还没有开始工作,

img

11:42分监听器启动开始工作,消费消息,

img

img

11:45分监听器停止工作,

img

五、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']

告警配置:

image-20220725103346114

报警状态说明:

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就能访问

image-20220824191256259

regex used for validation is 'a-z0-9?'

确实不能大写

仅创建应用空间和应用空间配额,还是无法查询到,在应用空间有资源使用情况下才能查到

image-20220824191656003

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配置

image-20220921111756437

第二步:

systemctl daemon-reload && systemctl restart docker

docker:

admin1:gbT3YY$D2Kyq

gbT3YY$D2Kyq

"172.31.129.33",

preload

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])?

image-20220825095039214

image-20220914180740633

16.运营中心规划

image-20221031164938604

image-20221129095551967

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是个基于内存的数据库。那服务一旦宕机,内存中的数据将全部丢失。通常的解决方案是从后端数据库恢复这些数据,但后端数据库有性能瓶颈,如果是大数据量的恢复,

  1. 会对数据库带来巨大的压力,严重可能导致mysql宕机
  2. 数据库的性能不如Redis。导致程序响应慢。所以对Redis来说,实现数据的持久化,避免从后端数据库中恢复数据,是至关重要的。

持久化策略

官方支持的持久化有四种,如下:

  1. RDB(Redis 数据库):RDB 持久性以指定的时间间隔执行数据集的时间点快照。
  2. AOF(仅追加文件):AOF 持久性记录服务器接收到的每个写操作。然后可以在服务器启动时再次重播这些操作,从而重建原始数据集。命令使用与 Redis 协议本身相同的格式进行记录。
  3. RDB + AOF:您还可以在同一个实例中组合 AOF 和 RDB。
  4. 无持久性:您可以完全禁用持久性。这种策略,一般很少有人使用吧

下面我们对这几种策略,进行详细梳理下

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 文件。

img

写时复制机制保证快照期间数据可修改

这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。

快照的频率如何把握

对于快照来说,所谓“连拍”就是指连续地做快照。这样一来,快照的间隔时间变得很短,即使某一时刻发生宕机了,因为上一时刻快照刚执行,丢失的数据也不会太多。但是,这其中的快照间隔时间就很关键了。如下图:

img

为了尽可能保证在宕机的情况下,保证数据尽量不丢失,比如:一秒一次快照,那丢失的数据也是一秒。这看上去很美好,其实为带来很大的问题,如果频繁地执行全量快照,也会带来两方面的开销

  • 一方面,频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。
  • 另一方面,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 是先执行命令,把数据写入内存,然后才记录日志

img

AOF日志内容

我们以 Redis 收到“set testkey 1”命令后记录的日志为例,看看 AOF 日志的内容,

img

日志格式说明 *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 记录没有写回磁盘,一旦宕机对应的数据就丢失了;
  • “每秒写回”采用一秒写回一次的频率,避免了“同步写回”的性能开销,虽然减少了对系统性能的影响,但是如果发生宕机,上一秒内未落盘的命令操作仍然会丢失。所以,这只能算是,在避免影响主线程性能和避免数据丢失两者间取了个折中。

我把这三种策略的写回时机,以及优缺点汇总在了一张表格里,以方便你随时查看。

img
根据系统对高性能和高可靠性的要求,来选择使用哪种写回策略了。总结一下就是:

  • 想要获得高性能,就选择 No 策略;
  • 如果想要得到高可靠性保证,就选择 Always 策略;
  • 如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择 Everysec 策略。

虽然AOF策略,能保证秒级数据丢失,但是随着redis的长时间运行,aof文件会越来越大,如果宕机,进行数据恢复的时候速度是特别慢,影响业务,那有什么好的发案处理吗?aof日志重写

AOF日志重写

AOF 文件是以追加的方式,逐一记录接收到的写命令的。当一个键值对被多条写命令反复修改时,AOF 文件会记录相应的多条命令。但是,在重写的时候,是根据这个键值对当前的最新状态,为它生成对应的写入命令。这样一来,一个键值对在重写日志中只用一条命令就行了,而且,在日志恢复时,只用执行这条命令,就可以直接完成这个键值对的写入了。
重写机制具有“多变一”功能。所谓的“多变一”,也就是说,旧日志文件中的多条命令,在重写后的新日志中变成了一条命令,例如:

img

我们对列表先后做了 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 日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了。

img

内存快照和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:latest

docker 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.tar

docker tag wfw-base:v5 172.29.230.131:80/wfw/wfw-base:v5

docker push 172.29.230.131:80/wfw/wfw-base:v5

99.测试

posted @ 2026-04-24 17:09  回眸23  阅读(3)  评论(0)    收藏  举报