开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

一篇文章搞定mysql的 行转列(7种方法) 和 列转行 一

发表于 2021-11-13

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

一、行转列

即将原本同一列下多行的不同内容作为多个字段,输出对应内容。
建表语句

1
2
3
4
5
6
7
8
9
sql复制代码DROP TABLE IF EXISTS tb_score;

CREATE TABLE tb_score(
id INT(11) NOT NULL auto_increment,
userid VARCHAR(20) NOT NULL COMMENT '用户id',
subject VARCHAR(20) COMMENT '科目',
score DOUBLE COMMENT '成绩',
PRIMARY KEY(id)
)ENGINE = INNODB DEFAULT CHARSET = utf8;

插入数据

1
2
3
4
5
6
7
8
9
10
sql复制代码INSERT INTO tb_score(userid,subject,score) VALUES ('001','语文',90);
INSERT INTO tb_score(userid,subject,score) VALUES ('001','数学',92);
INSERT INTO tb_score(userid,subject,score) VALUES ('001','英语',80);
INSERT INTO tb_score(userid,subject,score) VALUES ('002','语文',88);
INSERT INTO tb_score(userid,subject,score) VALUES ('002','数学',90);
INSERT INTO tb_score(userid,subject,score) VALUES ('002','英语',75.5);
INSERT INTO tb_score(userid,subject,score) VALUES ('003','语文',70);
INSERT INTO tb_score(userid,subject,score) VALUES ('003','数学',85);
INSERT INTO tb_score(userid,subject,score) VALUES ('003','英语',90);
INSERT INTO tb_score(userid,subject,score) VALUES ('003','政治',82);

查询数据表中的内容(即转换前的结果)

1
sql复制代码SELECT * FROM tb_score

在这里插入图片描述

先来看一下转换后的结果:

在这里插入图片描述

可以看出,这里行转列是将原来的subject字段的多行内容选出来,作为结果集中的不同列,并根据userid进行分组显示对应的score。

1、使用case…when….then 进行行转列

1
2
3
4
5
6
7
sql复制代码SELECT userid,
SUM(CASE `subject` WHEN '语文' THEN score ELSE 0 END) as '语文',
SUM(CASE `subject` WHEN '数学' THEN score ELSE 0 END) as '数学',
SUM(CASE `subject` WHEN '英语' THEN score ELSE 0 END) as '英语',
SUM(CASE `subject` WHEN '政治' THEN score ELSE 0 END) as '政治'
FROM tb_score
GROUP BY userid

2、使用IF() 进行行转列:

1
2
3
4
5
6
7
sql复制代码SELECT userid,
SUM(IF(`subject`='语文',score,0)) as '语文',
SUM(IF(`subject`='数学',score,0)) as '数学',
SUM(IF(`subject`='英语',score,0)) as '英语',
SUM(IF(`subject`='政治',score,0)) as '政治'
FROM tb_score
GROUP BY userid

注意点:

(1)SUM() 是为了能够使用GROUP BY根据userid进行分组,因为每一个userid对应的subject=”语文”的记录只有一条,所以SUM() 的值就等于对应那一条记录的score的值。

假如userid =’001’ and subject=’语文’ 的记录有两条,则此时SUM() 的值将会是这两条记录的和,同理,使用Max()的值将会是这两条记录里面值最大的一个。但是正常情况下,一个user对应一个subject只有一个分数,因此可以使用SUM()、MAX()、MIN()、AVG()等聚合函数都可以达到行转列的效果。

(2)IF(subject=’语文’,score,0) 作为条件,即对所有subject=’语文’的记录的score字段进行SUM()、MAX()、MIN()、AVG()操作,如果score没有值则默认为0。

3、利用SUM(IF()) 生成列 + WITH ROLLUP 生成汇总行,并利用 IFNULL将汇总行标题显示为Total

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sql复制代码SELECT IFNULL(userid,'total') AS userid,
SUM(IF(`subject`='语文',score,0)) AS 语文,
SUM(IF(`subject`='数学',score,0)) AS 数学,
SUM(IF(`subject`='英语',score,0)) AS 英语,
SUM(IF(`subject`='政治',score,0)) AS 政治,
SUM(IF(`subject`='total',score,0)) AS total
FROM(
SELECT userid,IFNULL(`subject`,'total') AS `subject`,SUM(score) AS score
FROM tb_score
GROUP BY userid,`subject`
WITH ROLLUP
HAVING userid IS NOT NULL
)AS A
GROUP BY userid
WITH ROLLUP;

运行结果:

在这里插入图片描述

4、利用SUM(IF()) 生成列 + UNION 生成汇总行,并利用 IFNULL将汇总行标题显示为 Total

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql复制代码SELECT userid,
SUM(IF(`subject`='语文',score,0)) AS 语文,
SUM(IF(`subject`='数学',score,0)) AS 数学,
SUM(IF(`subject`='英语',score,0)) AS 英语,
SUM(IF(`subject`='政治',score,0)) AS 政治,
SUM(score) AS TOTAL
FROM tb_score
GROUP BY userid
UNION
SELECT 'TOTAL',SUM(IF(`subject`='语文',score,0)) AS 语文,
SUM(IF(`subject`='数学',score,0)) AS 数学,
SUM(IF(`subject`='英语',score,0)) AS 英语,
SUM(IF(`subject`='政治',score,0)) AS 政治,
SUM(score) FROM tb_score

运行结果:

在这里插入图片描述

5、利用SUM(IF()) 生成列,直接生成结果不再利用子查询

1
2
3
4
5
6
7
8
sql复制代码SELECT IFNULL(userid,'TOTAL') AS userid,
SUM(IF(`subject`='语文',score,0)) AS 语文,
SUM(IF(`subject`='数学',score,0)) AS 数学,
SUM(IF(`subject`='英语',score,0)) AS 英语,
SUM(IF(`subject`='政治',score,0)) AS 政治,
SUM(score) AS TOTAL
FROM tb_score
GROUP BY userid WITH ROLLUP;

运行结果:
在这里插入图片描述

6、动态,适用于列不确定情况

1
2
3
4
5
6
7
8
9
sql复制代码SET @EE='';
select @EE :=CONCAT(@EE,'sum(if(subject= \'',subject,'\',score,0)) as ',subject, ',') AS aa FROM (SELECT DISTINCT subject FROM tb_score) A ;

SET @QQ = CONCAT('select ifnull(userid,\'TOTAL\')as userid,',@EE,' sum(score) as TOTAL from tb_score group by userid WITH ROLLUP');
-- SELECT @QQ;

PREPARE stmt FROM @QQ;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

运行结果:
在这里插入图片描述
在这里插入图片描述

7、合并字段显示:利用group_concat()

1
2
sql复制代码SELECT userid,GROUP_CONCAT(`subject`,":",score)AS 成绩 FROM tb_score
GROUP BY userid

运行结果:
在这里插入图片描述

group_concat(),手册上说明:该函数返回带有来自一个组的连接的非NULL值的字符串结果。
比较抽象,难以理解。通俗点理解,其实是这样的:group_concat()会计算哪些行属于同一组,将属于同一组的列显示出来。要返回哪些列,由函数参数(就是字段名)决定。分组必须有个标准,就是根据group by指定的列进行分组。

结论:group_concat()函数可以很好的建属于同一分组的多个行转化为一个列。

二、列转行

建表语句:

1
2
3
4
5
6
7
8
9
sql复制代码CREATE TABLE tb_score1(
id INT(11) NOT NULL auto_increment,
userid VARCHAR(20) NOT NULL COMMENT '用户id',
cn_score DOUBLE COMMENT '语文成绩',
math_score DOUBLE COMMENT '数学成绩',
en_score DOUBLE COMMENT '英语成绩',
po_score DOUBLE COMMENT '政治成绩',
PRIMARY KEY(id)
)ENGINE = INNODB DEFAULT CHARSET = utf8;

插入数据:

1
2
3
sql复制代码INSERT INTO tb_score1(userid,cn_score,math_score,en_score,po_score) VALUES ('001',90,92,80,0);
INSERT INTO tb_score1(userid,cn_score,math_score,en_score,po_score) VALUES ('002',88,90,75.5,0);
INSERT INTO tb_score1(userid,cn_score,math_score,en_score,po_score) VALUES ('003',70,85,90,82);

查询数据表中的内容(即转换前的结果)

1
sql复制代码SELECT * FROM tb_score1

在这里插入图片描述

转换后:
在这里插入图片描述

本质是将userid的每个科目分数分散成一条记录显示出来。

直接上SQL:

1
2
3
4
5
6
7
8
sql复制代码SELECT userid,'语文' AS course,cn_score AS score FROM tb_score1
UNION ALL
SELECT userid,'数学' AS course,math_score AS score FROM tb_score1
UNION ALL
SELECT userid,'英语' AS course,en_score AS score FROM tb_score1
UNION ALL
SELECT userid,'政治' AS course,po_score AS score FROM tb_score1
ORDER BY userid

这里将每个userid对应的多个科目的成绩查出来,通过UNION ALL将结果集加起来,达到上图的效果。

附:UNION与UNION ALL的区别(摘)

  1. 对重复结果的处理:UNION会去掉重复记录,UNION ALL不会;
  2. 对排序的处理:UNION会排序,UNION ALL只是简单地将两个结果集合并;
  3. 效率方面的区别:因为UNION 会做去重和排序处理,因此效率比UNION ALL慢很多;

参考连接:
MySQL 如何实现行转列分级输出?
mysql行转列、列转行

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

ArrayList底层结构和源码分析

发表于 2021-11-13

注意事项

  1. ArrayList基本了解:
  • ArrayList可以加入null,并且多个;
  • ArrayList是由数组来实现的;
  • ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高),在多线程情况下,不建议使用ArrrayList;
  1. Idea Debug设置
  2. Java -version 1.8.0_221

底层操作机制

  • ArrayList中维护了一个Object类型的elementData数组。
1
java复制代码transient Object[] elementData; // transient 标识瞬间,短暂的,表示该属性不会被序列化
  • 当创建ArrayList对象时,如果使用的是无参构造器,则初始的elementData容量是0。第一次添加,则扩容elementData为10,如果需要再次扩容,则扩容elementData为原容量的1.5倍。
  • 如果使用指定大小的构造器,则初始elementData容量为指定大小。如果需要扩容,则扩容elementData为1.5倍。

解读源码

分析使用无参构造器,创建和使用ArrayList。有参构造器阅读源码过程差不多,不一样的是初次扩容!

源程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码@SuppressWarnings(value = "all")
public class ArrayListSource {
public static void main(String[] args) {
// 使用无参构造器创建ArrayList
ArrayList list = new ArrayList();
// 使用有参构造器创建ArrayList
// ArrayList list = new ArrayList(8);
// 向集合添加10条数据
for (int i = 1; i <= 10; i++) {
list.add(i);
}
// 再向集合添加5条数据
for (int i = 11; i <= 15; i++) {
list.add(i);
}
list.add(100);
list.add(200);
}
}

分析过程

1.创建一个空的数组【elementData】

image-20211112211822187

2.执行list.add(E e)

  • 先确保内部容量,是否要扩容ensureCapacityInternal
  • 然后再执行赋值

第一次添加的时候Debug F7 into进入源码!如果装填的数据是基本类型,塞入前会有一步装箱操作。

image-20211112212534426

3.确定最小容量【minCapacity】,第一次扩容为10

image-20211112213515703

4.确定是否需要扩容

  • modCount++ 记录集合被修改的次数
  • 如果elementData的大小不够,就调用grow()去扩容

image-20211112214100490

5.扩容,通过扩容机制来确定要扩容到多大

  • 第一次newCapacity=10
  • 第二次及其以后,按照数组原容量的1.5倍扩容
  • 扩容使用的是Arrays.copyOf

image-20211112214427260

6.完成扩容,数据放入数组

image-20211112222404029

grow()扩容函数详解

jdk 1.8.0_221源码片段

1
2
3
4
5
6
7
8
9
10
11
java复制代码private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
  1. 将 elementData.length 记录到 oldCapacity中,第一次值为0;
  2. newCapacity = oldCapacity + (oldCapacity >> 1); 执行扩容,扩容大小为 数组当前容量+数组当前大小右移1位(除以2),即扩容1.5倍;
  3. 因为第一次扩容 oldCapacity与newCapacity 都为0, 所以if (newCapacity - minCapacity < 0) 条件成立,第一次扩容大小为 10;
  4. Arrays.copyOf()方法可保留原先数据扩容;
  5. 如果容量超过2147483639,则调用hugeCapacity计算容量;

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【编程导航】如何利用nodejs体验后端工程师的日常呢?

发表于 2021-11-13

前言:

大家好,我是东东吖,从今天开始,我将带领大家一起学习Node.js,希望大家能够从一名前端工程师进阶成为全栈工程师。让我们一起来解锁Node.js吧!

一、Nodejs介绍:

1.什么是Nodejs?

简单的说 Node.js 就是运行在服务端的 JavaScript。
Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。
Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。

2.谁适合学习本教程?

如果你是一个前端工程师,你不懂得像PHP、Python或Ruby等动态编程语言,然后你想创建自己的服务,那么Node.js是一个非常好的选择。
Node.js 是运行在服务端的 JavaScript,如果你熟悉Javascript,那么你将会很容易的学会Node.js。
当然,如果你是后端程序员,想部署一些高性能的服务,那么学习Node.js也是一个非常好的选择。

3.学习本教程前你需要了解?

在继续本教程之前,你应该了解一些基本的计算机编程术语。如果你学习过Javascript,PHP,Java等编程语言,将有助于你更快的了解Node.js编程。

二、创建express后端服务器:

1.什么是express?

Express 是一个基于 Node.js 平台的极简、灵活的 web 应用开发框架,它提供一系列强大的特性,帮助你创建各种 Web 和移动设备应用。

2.全局下载express脚手架:

npm install -g express-generator

3.创建express项目:

express 项目名称

4.启动后端服务器:

①下载依赖:

npm i

②启动后端服务器

npm run start

当你能够看见这个页面,说明你的node服务已经创建并启动成功了。

image.png

5.express项目结构认识:

image.png
①bin:启动配置文件,在 www 里修改运行端口号

②node_modules:存放所有的项目依赖库,就像java存放架包

③public:用于存放静态资源文件 图片,CSS,JAVASCRIPT文件..

④routers:路由文件相当于springmvc中的Controller,ssh中的action

⑤views:存放页面的地方

⑥package.json:项目依赖配置及开发者信息。

⑦app.js:应用核心配置文件,项目入口

6.下载nodemon,自动重新启动:

当我们修改node代码后必须重启node服务器,这样十分影响我们的开发效率,我们需要下载一个可以帮我们自动重启的工具nodemon,当我们修改代码之后,他能帮我们自动重启。

①全局下载nodemon依赖包:

npm i nodemon -g

②修改为nodemon启动:

image.png

三、利用express体验get、post、put、delete四种请求:

1.get请求:

get请求一般用于获取,如果不设定请求方式,会默认为是get请求,像一些img,字体样式等利用媒体标签去请求,都会默认为是get方式,在地址栏就可以直接反问,要参数的话,可以直接在ulr的?后面拼接。
我们在user模块写一个git请求,模拟一个数组发送给客户端。
image.png

get请求可以直接在地址栏拼接参数,然后就能直接看见服务器返回的数据。

image.png

2.post请求:

我们继续在users模块编写一个post请求,这个请求三个参数必传,不然就会返回给客户端参数错误,只有当name,age,city这三个参数都传的时候,这个请求才会返回添加成功。post请求一般用于新增,但是有一些数据,涉及到安全,用get请求是不可取的,所有也会采用post请求。

image.png

post请求我们是不能在浏览地址栏访问的。不信你可以尝试一下,就像下面这样:

image.png

我们需要利用一个接口测试工具postman,当我们不传或者少传必传参数,就会返回参数错误:

image.png

只有当我们把name,age,city三个产生都传了才会返回成功

image.png

我们在服务器端打印一下接收的产生

image.png

打开终端,我们就能看见客户端发送来的参数,就能对这些数据做一些处理,考虑到同学们的学习成本,这篇文章我们就暂不讲了,大家知道原理就OK了。

image.png

3.put请求:

put请求与post类似,一般用于编辑更新数据,我们把请求方式改一下,再加一个必传参数id。

image.png

用postman测试一下,返回编辑成功

image.png

打开终端:

image.png

4.delete请求:

delete请求一般用于删除,我们设置一个必传参数id就可以了。

image.png

用postman测试一下,返回删除成功
image.png

打开终端:

image.png

四、利用前端项目来请求数据,渲染到页面上。

我们用一个实际前端项目来调一下这个接口。

image.png

前端把数据渲染到页面上,完美!!!

image.png

以上就是后端工程师和我们对接api接口的大致流程,后端就是操作数据库,写一些后端的业务逻辑;而前端就是写一些页面交互的逻辑,处理数据渲染到页面上。

笔者往期文章:

微信支付其实很简单,没做过微信支付的你是否有这些疑惑?

小程序蓝牙开锁,前端是如何直接和硬件进行数据交互的?

父子组件的生命周期执行流程是怎么样的呢?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

windows详细安装mysql步骤

发表于 2021-11-13

这是我参与11月更文挑战的第N天,活动详情查看:2021最后一次更文挑战

MySQL详细安装过程及步骤

  1.1、双击运行下载好的mysql-installer-community-8.0.25.0.msi,程序运行需要一些时间,请等待一下,如图所示。

  img

  1.2、运行成功之后,进入类型选择页面,选择了Developer Default(默认安装的步骤),点击“Next”按钮,如图所示。

  • developer default(开发者默认):安装mysql开发所需的所有产品
  • server only(服务器):只安装mysql服务器产品
  • client only(客户端):只安装没有服务器的
  • mysql客户端产品 full(完全):安装所有包含的mysql产品和功能
  • custom(手动):手动选择系统上应安装的产品

  img

  1.3、开发者默认模式检测以下程序会安装不成功,安装下图步骤依次点击,最后点击“Next”按钮,选择“yes”,进入下一个安装流程,如图所示。 check:以下产品的请求失败,安装程序将自动尝试解决其中一些问题。标记为手动的要求无法自动解决。单击这些项目以尝试手动恢复。检测到不可安装的程序说明: MySQL for Visual Studio:是一款代码编辑工具(可编写C#、Visual Basic、C++、TypeScript、F# ),如果你安装的话,就要求去安装Visual Studio version:2015、2017、2019其中一个版本 ;Connector/pyton:电脑有python3.6了就没选择3.4版本的。如果你没安装有python可按要求去安装一些内容。

   img

   img

1.4、在安装所选界面能看到我们接下来所需要安装的程序,点击“Execute”按钮,如图所示。

   img

  1.5、安装程序进度界面,安装需要一些时间。点击Show Detail能看到安装日志,如图所示。

   img

  1.6、程序安装完成之后,点击“Next”按钮,如图所示。

   img

  1.7、在Product Configutration(产品配置)页面能看到需要配置的程序,点击“Next”按钮,如图所示。(页面英语介绍:现在我们将逐一介绍以下产品的配置向导。您可以随时取消,如果您希望离开此向导,而不必配置所有产品)

  img

  1.8、设置服务器配置类型以及连接端口:(这里保持默认就好,不需要修改),点击“Next”按钮,如图所示。

    Config Type:选择Development Machine。

    Port:输入3306,也可以输入其他最好是3306-3309之间。

    X Protocol Port:输入33060

   img

  1.9、进入Authentication Method(身份认证方法)选择界面,保持默认就好,点击“Next”按钮,如图所示。

  img

  1.10、配置root的密码(该密码一定要记住),如图所示。

   img

  1.11、添加其他管理员,点击“Add User”按钮,输入账号密码点击ok(如果添加的管理员只允许在本地登录就将host改成local),回到界面之后点击“Next”按钮,如图所示。

  img

  1.12、配置MySQL在Windows系统中的名字,是否选择开机启动MySQL服务(建议保持默认,个人觉得没必要修改),其它的没进行修改,点击”Next”按钮,如图所示。

   img

  1.13、Mysql server :Apply Configuration(应用配置页面),点击“Execute”按钮,进行安装配置,安装完成之后,点击“Finish”按钮,如图所示。

  img

  1.14、安装程序又回到了Product Configutration(产品配置)页面,此时我们看到MySQL server安装成功的显示,点击“Next”按钮,如图所示。

  img

  1.15、配置MySQL Router Configuration:勾选“Bootstrap MySQL Router for user with innoDB cluster”,Hostname:输入localhost,Password:就是之前设置的root密码,(如果不想输入密码可直接点击下一步)点击下一步,如图所示。

   img

  1.16、Mysql router :apply configuration(应用配置页面),点击“Execute”按钮,如图所示。

  img

  1.17、检测root密码,输入密码,点击“Check”按钮,点击“Next”按钮,如图所示。

  img

  1.18、点击“Execute”,完成之后点击“finish”按钮,如图所示。

  img

  img

  1.19、安装程序又回到了Product Configutration(产品配置)页面,继续点击“Next”按钮,如图所示。

  img

 1.20、安装程序完成界面,点击“Finish”按钮,如图所示。

  img

  1.21、双击运行之前下载的安装包,能看到我们所安装的产品,如图所示。

  img

  1.22、配置MySQL环境变量,MySQL默认安装路径是:C:\Program Files\MySQL\MySQL Server 8.0,右击“我的电脑”—>高级系统设置→高级→环境变量→用户变量,点击“新建”,变量名:MYSQL_HOME,变量值:C:\Program Files\MySQL\MySQL Server 8.0,点击“确定”,如图所示。

  img

  在用户变量下找到Path,点击“编辑”,点击“新建”,输入%MYSQL_HOME%\bin,点击“确定”,如图所示。

  img

  打开cmd输入mysql –u root –p,输入root的密码,此时成功登录MySQL数据库,如图所示。

  img
  img

我是白又白i,一名喜欢分享知识的程序媛❤️
感兴趣的可以关注我的公众号:白又白学Python【非常感谢你的点赞、收藏、关注、评论,一键三连支持】

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Golang实践录:利用反射reflect构建通用打印结构体

发表于 2021-11-13

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

本文针对 Golang 的结构体字段的打印进行一些研究。其中涉及到一些反射的知识。实际上本文是基于前面积累的反射进行综合使用的一个示例,也在工作中使用着。

问题提出

总结一些实践情况,结构体字段值的输出还是比较常见的,至少笔者目前常用。比如输出某些数据表的数据(代码中会转换为结构体),对比不同版本数据表的数据,对比某些不同版本但格式相同的 json 文件,等。为了优化代码,减少开发维护工作量,需寻找一种高效的方法,打印结构体。初步需求如下:

  • 格式化,目前需迎合 markdown 表格的格式。
  • 接口可通用于数组、map等结构,原则上直接传递某个变量,即可自行输出格式化后的所需内容。
  • 输出方式多样化,如输出到终端或文件。

使用 markdown 是因为笔者需要将输出的数据表内容通过 vuepress 发布到内部 web 服务器上,以便随时查阅。

测试数据

本文使用的测试数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
go复制代码type TestObj struct {
  Name string
  Value uint64
  Size int32
  Guard float32
}
​
var objects []TestObj
​
  object1 := TestObj{
      Name: "Jim | Kent",
      Value: 128,
      Size: 256,
      Guard: 56.4,
  }
  object2 := TestObj{
      Name: "James1",
      Value: 128,
      Size: 259,
      Guard: 56.4,
  }
​
  objects = append(objects, object1)
  objects = append(objects, object2)
   
  var myMap map[string]TestObj
  myMap = make(map[string]TestObj)
  myMap["obj3"] = TestObj{"Jim Kent", 103, 201, 102.56}
  myMap["obj1"] = TestObj{"Kent", 101, 201, 102.56}
  myMap["obj2"] = TestObj{"Kent", 102, 201, 102.56}

效果

对于可识别渲染 markdown 的平台来说,输出的如下结果:

1
2
3
4
5
6
7
arduino复制代码print by line - slice default  
total: 2  
​
| Name         | Value | Size | Guard |
| ------------- | ----- | ---- | ----- |
| Jim <br> Kent | 128   | 256 | 56.4 |
| James1       | 128   | 259 | 56.4 |

就能正常显示表格形式。如下:

print by line - slice default total: 2

Name Value Size Guard
Jim Kent 128 256 56.4
James1 128 259 56.4

简单版本

遍历结构体数据,并打印之:

1
2
3
4
css复制代码    for a, b := range objects {
      fmt.Printf("%v %v\n", a, b)
      // fmt.Printf("%v %+v\n", a, b)
  }

如果需要格式化,需显式给出结构体字段和格式化形式。如下:

1
2
3
perl复制代码    for a, b := range objects {
      fmt.Printf("%d: %v | %v | %v | %v\n", a, b.Name, b.Value, b.Size, b.Guard)
  }

以上结果分别如下:

1
2
3
4
5
makefile复制代码0 {Jim | Kent 128 256 56.4}
1 {James1 128 259 56.4}
​
0: Jim | Kent | 128 | 256 | 56.4
1: James1 | 128 | 259 | 56.4

由于此版本非吾所用,因此只具大致形式。

可以看到,前者简单,不用理会结构体内容,直接使用%v即可打印,如需要输出结构体字段名,则用%+v。但其形式固定的,类似{xx xx xx}这样。后者使用竖线|将各字段隔开,需一一写出字段(当然也可忽略部分字段)。

reflect版本

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
go复制代码func checkSkipNames(a string, b []string) bool {
  for _, item := range b {
      if item == a {
          return true
      }
  }
  return false
}
​
// 结构体的字段名称
func GetStructName(myref reflect.Value, names []string) (buffer string) {
  // 注:有可能传递string数组,此时没有“标题”一说,返回
  if myref.Type().Name() == "string" {
      return
  }
  for i := 0; i < myref.NumField(); i++ {
      if ok := checkSkipNames(myref.Type().Field(i).Name, names); ok {
          continue
      }
      buffer += fmt.Sprintf("| %v ", myref.Type().Field(i).Name)
  }
  buffer += fmt.Sprintf("|\n")
  for i := 0; i < myref.NumField(); i++ {
      if ok := checkSkipNames(myref.Type().Field(i).Name, names); ok {
          continue
      }
      buffer += fmt.Sprintf("| --- ")
  }
  buffer += fmt.Sprintf("|\n")
  return
}
​
// 将 | 替换为 <br>
func replaceI(text string) (ret string) {
  // 下面2种方法都可以
  // reg := regexp.MustCompile(`|`)
  // ret = reg.ReplaceAllString(text, `${1}<br/>`)
  ret = strings.Replace(text, "|", "<br>", -1)
  // fmt.Printf("!!! %q\n", ret)
  return ret
}
​
// 结构体的值
func GetStructValue(myref reflect.Value, names []string) (buffer string) {
  // 注:有可能传递string数组,此时没有“字段”一说,返回原本的内容
  if myref.Type().Name() == "string" {
      return myref.Interface().(string)
  }
​
  for i := 0; i < myref.NumField(); i++ {
      if ok := checkSkipNames(myref.Type().Field(i).Name, names); ok {
          continue
      }
      // 判断是否包含|,有则替换,其必须是string类型,其它保持原有的
      t := myref.Field(i).Type().Name()
      if t == "string" {
          var str string = myref.Field(i).Interface().(string)
          str = replaceI(str)
          buffer += fmt.Sprintf("| %v ", str)
      } else {
          buffer += fmt.Sprintf("| %v ", myref.Field(i).Interface())
      }
  }
  buffer += fmt.Sprintf("|\n")
​
  return
}
​
func PrintStructTable(data interface{}, title string, skipNames ...string) {
  var w io.Writer
  w = os.Stdout // set to stdout
  buffer, num := PrintStructTable2Buffer(data, title, skipNames...)
  fmt.Fprintf(w, "total: %v\n", num)
  fmt.Fprintf(w, "%v\n", buffer)
}
​
/*
功能:指定结构体data,其可为slice map 单独结构体
    指定自定义标题,为空则使用结构体字段
    指定忽略的字段名称(即结构体字段的变量)
    按结构体定义的顺序列出,如自定义标题,则必须保证一致。
*/
func PrintStructTable2Buffer(data interface{}, title string, skipNames ...string) (buffer string, num int) {
  buffer = ""
​
  t := reflect.TypeOf(data)
  v := reflect.ValueOf(data)
​
  var skipNamess []string
  for _, item := range skipNames {
      skipNamess = append(skipNamess, item)
  }
​
  // 打印结构体字段标志
  innertitle := false
  printHead := false
  if len(title) == 0 {
      innertitle = true
  }
​
  // 不同类型的,其索引方式不同,故一一判断使用
  switch t.Kind() {
  case reflect.Slice, reflect.Array:
      num = v.Len()
      if innertitle {
          buffer += GetStructName(v.Index(0), skipNamess)
      } else {
          buffer += fmt.Sprintln(title)
      }
      for i := 0; i < v.Len(); i++ {
          buffer += GetStructValue(v.Index(i), skipNamess)
      }
  case reflect.Map:
      num = v.Len()
      iter := v.MapRange()
      for iter.Next() {
          if !printHead {
              if innertitle {
                  buffer += GetStructName(iter.Value(), skipNamess)
              } else {
                  buffer += fmt.Sprintln(title)
              }
              printHead = true
          }
          buffer += GetStructValue(iter.Value(), skipNamess)
      }
  default:
      num = 1 // 单独结构体不能用Len,单独赋值
      if !printHead {
          if innertitle {
              buffer += GetStructName(v, skipNamess)
          } else {
              buffer += fmt.Sprintln(title)
          }
          printHead = true
      }
      buffer += GetStructValue(v, skipNamess)
  }
​
  return
}

上述代码提供的对外接口为PrintStructTable2Buffer和PrintStructTable,因为默认格式为markdown表格形式,故加上Table。前者输出到缓冲区的(可继续写到文件中),后者直接输出终端。真正实现的接口为PrintStructTable2Buffer,其提供了自定义标题,和忽略的字段参数,如果不指定标题,必须将title置为空,因为最后的参数是可变参数,只能有一个,如不写,则输出所有字段。

至于内部实现,因为需要根据用户输入忽略某些字段,因此定义checkSkipNames检查参数,利用GetStructName获取结构体名称,GetStructValue获取结构体的值。不管获取字段还是值,均使用传递的interface{},不需额外传递结构体本身。 注意,由于默认使用竖线分隔,如果字段值本身有竖线,则使用<br>替换——即让该字段的值换行。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
scss复制代码    // 数组,默认形式
  fmt.Println("print by line - slice default")
  buf, num := PrintStructTable2Buffer(objects, "")
  fmt.Println("total:", num)
  fmt.Println(buf)
​
  // 数组,自定义标题
  fmt.Println("print by line - slice")
  buf, num = PrintStructTable2Buffer(objects, "| Name | Value | Size | Guard |\n| --- | --- | --- | ++++ |")
  fmt.Println("total:", num)
  fmt.Println(buf)
  // 单个对象
  fmt.Println("print by line - single object")
  buf, num = PrintStructTable2Buffer(object1, "| Name | Value | Guard |\n| +++ | +++ | +++ |", "Size")
  fmt.Println("total:", num)
  fmt.Println(buf)
  // map
  fmt.Println("print by line - map")
  buf, num = PrintStructTable2Buffer(myMap, "aaa")
  fmt.Println(buf)

测试结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
arduino复制代码print by line - slice default
total: 2
| Name | Value | Size | Guard |
| --- | --- | --- | --- |
| Jim <br> Kent | 128 | 256 | 56.4 |
| James1 | 128 | 259 | 56.4 |
​
print by line - slice
total: 2
| Name | Value | Size | Guard |
| --- | --- | --- | ++++ |
| Jim <br> Kent | 128 | 256 | 56.4 |
| James1 | 128 | 259 | 56.4 |
​
print by line - single object
total: 1
| Name | Value | Guard |
| +++ | +++ | +++ |
| Jim <br> Kent | 128 | 56.4 |
​
print by line - map
aaa
| Jim Kent | 103 | 201 | 102.56 |
| Kent | 101 | 201 | 102.56 |
| Kent | 102 | 201 | 102.56 |
​

观察结果,可达到预期目的。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

k8s series 24 calico初级(监控)

发表于 2021-11-13

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」。

前言

calico官方提供了prometheus的监控,且还详细说明了相关指标

因为我们是默认安装的calico,并没有启用Typha组件,所以kubernetes集群中就只有Felix和kube-controlles两大组件在运行

Felix的详细指标: docs.projectcalico.org/reference/f…

kube-controlles的详细指标:docs.projectcalico.org/reference/k…

calico组件配置

虽然官方提供了相关prometheus的监控,但默认配置是禁用的,需要手动开启,且还需要提供端点供prometheus拉取监控数据

Felix配置

启用Felix的prometheus指标

1
js复制代码calicoctl patch felixConfiguration default  --patch '{"spec":{"prometheusMetricsEnabled": true}}'

创建Felix指标端点

1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: felix-metrics-svc
namespace: kube-system
spec:
selector:
k8s-app: calico-node
ports:
- port: 9091
targetPort: 9091
EOF

kube-controlles配置

kube-controlles的prometheus指标默认是启用的,需要无须改动,如果想更改它的监控端口,可以使用如下命令,如果端口改为0,则为禁用

1
2
js复制代码#默认端口监控在9094
calicoctl patch kubecontrollersconfiguration default --patch '{"spec":{"prometheusMetricsPort": 9094}}'

创建kube-controlles指标端点

1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: kube-controllers-metrics-svc
namespace: kube-system
spec:
selector:
k8s-app: calico-kube-controllers
ports:
- port: 9094
targetPort: 9094
EOF

两个组件的Service创建成功后,查看一下

image.png

prometheus安装配置

在安装prometheus之前需要提前 创建相关服务账号和权限

创建namespace

创建一个独立的命令空间,供监控使用

1
2
3
4
5
6
7
8
9
js复制代码kubectl apply -f -<<EOF
apiVersion: v1
kind: Namespace
metadata:
name: calico-monitoring
labels:
app: ns-calico-monitoring
role: monitoring
EOF

创建服务账号

创建一个具从calico采集数据的账号,然后授于相关权限

下面配置分为三部分,创建角色,创建账号,绑定角色账号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
js复制代码kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: calico-prometheus-user
rules:
- apiGroups: [""]
resources:
- endpoints
- services
- pods
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: calico-prometheus-user
namespace: calico-monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: calico-prometheus-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: calico-prometheus-user
subjects:
- kind: ServiceAccount
name: calico-prometheus-user
namespace: calico-monitoring
EOF

prometheus配置文件

创建prometheus的配置文件,如果二制进安装过prometheus,应该发现下列的配置几乎是一样的,后期想修改相关配置,直接编辑既可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
js复制代码kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: calico-monitoring
data:
prometheus.yml: |-
global:
scrape_interval: 15s
external_labels:
monitor: 'tutorial-monitor'
scrape_configs:
- job_name: 'prometheus'
scrape_interval: 5s
static_configs:
- targets: ['localhost:9090']
- job_name: 'felix_metrics'
scrape_interval: 5s
scheme: http
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_name]
regex: felix-metrics-svc
replacement: $1
action: keep
- job_name: 'typha_metrics'
scrape_interval: 5s
scheme: http
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_name]
regex: typha-metrics-svc
replacement: $1
action: keep
- job_name: 'kube_controllers_metrics'
scrape_interval: 5s
scheme: http
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_name]
regex: kube-controllers-metrics-svc
replacement: $1
action: keep
EOF

安装prometheus

以上步骤成功后,执行下列安装步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
js复制代码kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: prometheus-pod
namespace: calico-monitoring
labels:
app: prometheus-pod
role: monitoring
spec:
serviceAccountName: calico-prometheus-user
containers:
- name: prometheus-pod
image: prom/prometheus
resources:
limits:
memory: "128Mi"
cpu: "500m"
volumeMounts:
- name: config-volume
mountPath: /etc/prometheus/prometheus.yml
subPath: prometheus.yml
ports:
- containerPort: 9090
volumes:
- name: config-volume
configMap:
name: prometheus-config
EOF

查看安装进度,如果返回的状态是Running说明安装完成

1
js复制代码kubectl get pods prometheus-pod -n calico-monitoring

访问prometheus

因为我们没有给promethesu创建Service,所以这里先使用端口转发,简单验证一下prometheus是否获取到了calico的数据

1
js复制代码kubectl port-forward --address 0.0.0.0 pod/prometheus-pod 9090:9090 -n calico-monitoring

访问 http://ip:9090 端口

Grafana安装配置

在配置Grafana之前,需要声明prometheus访问方式,便于访问数据显示图表

创建prometheus Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js复制代码kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: prometheus-dashboard-svc
namespace: calico-monitoring
spec:
selector:
app: prometheus-pod
role: monitoring
ports:
- port: 9090
targetPort: 9090
EOF

创建grafana配置

创建grafana连接数据库的类型,地址,端口,以及连接方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
js复制代码kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-config
namespace: calico-monitoring
data:
prometheus.yaml: |-
{
"apiVersion": 1,
"datasources": [
{
"access":"proxy",
"editable": true,
"name": "calico-demo-prometheus",
"orgId": 1,
"type": "prometheus",
"url": "http://prometheus-dashboard-svc.calico-monitoring.svc:9090",
"version": 1
}
]
}
EOF

Felix的仪表盘配置

1
js复制代码kubectl apply -f https://docs.projectcalico.org/manifests/grafana-dashboards.yaml

安装Grafana

直接应用如下配置,会从grafana官方下载最新的镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
js复制代码kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: grafana-pod
namespace: calico-monitoring
labels:
app: grafana-pod
role: monitoring
spec:
containers:
- name: grafana-pod
image: grafana/grafana:latest
resources:
limits:
memory: "128Mi"
cpu: "500m"
volumeMounts:
- name: grafana-config-volume
mountPath: /etc/grafana/provisioning/datasources
- name: grafana-dashboards-volume
mountPath: /etc/grafana/provisioning/dashboards
- name: grafana-storage-volume
mountPath: /var/lib/grafana
ports:
- containerPort: 3000
volumes:
- name: grafana-storage-volume
emptyDir: {}
- name: grafana-config-volume
configMap:
name: grafana-config
- name: grafana-dashboards-volume
configMap:
name: grafana-dashboards-config
EOF

访问grafana

因暂时没有写Service配置,先转发端口来访问,验证一下监控是否正常

1
js复制代码kubectl port-forward --address 0.0.0.0 pod/grafana-pod 3000:3000 -n calico-monitoring

访问http://IP:3000 访问Grafana的web-ui登陆页,默认账号密码都是: admin/admin

登陆成功后,会提示修改密码或跳过,后续在设置中修改

image.png

登陆好之看,是没有任何东西的,需要访问一下这个地址: http://ip:3000/d/calico-felix-dashboard/felix-dashboard-calico?orgId=1

会打开calico给我们提供的Dashborad,这里点一下加星,后面就可以在主页上找到该面版了

image.png

创建Service

直接使用expose命令创建一个NodePort类型的Service

1
2
3
4
js复制代码#创建Service
kubectl expose pod grafana-pod --port=3000 --target-port=3000 --type=NodePort -n calico-monitoring
#查看暴露的端口
kubectl get svc -n calico-monitoring

访问集群节点ip+30538端口 就可以打开grafana了
image.png

卸载

如果觉得该套监控比较占用集群资源,如果单纯的只是想看看效果,可执行下列命令来删除这套监控

1
2
3
4
5
6
7
8
js复制代码kubectl delete service felix-metrics-svc -n kube-system
kubectl delete service typha-metrics-svc -n kube-system
kubectl delete service kube-controllers-metrics-svc -n kube-system
kubectl delete namespace calico-monitoring
kubectl delete ClusterRole calico-prometheus-user
kubectl delete clusterrolebinding calico-prometheus-user

kubectl delete namespace calico-monitoring

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

抽奖过程公布,我用了一款有故事的抽奖工具 prize 工具文

发表于 2021-11-13

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」

之前学委发表了一篇文末抽奖的文章:Python中处理字符串的常用函数汇总【文末送书】

学委喜欢下面这句话:

生活不尽如人意 但总有美好事情发生

抽奖就是这样一件美妙的事情,也是一个充满期待的时刻,不是吗?

学委花了几天把抽奖过程和结果全网公开,配上了动感的🎵,我们看看视频吧:

[video(video-dKEQ7AcP-1636775602134)(type-bilibili)(url-player.bilibili.com/player.html…(image-https://img-blog.csdnimg.cn/img_convert/9aa54a24628c8f389208d0db45df754f.png)(title-%E7%A6%BB%E8%B0%B1%EF%BC%81%E6%80%92%E6%94%B9%E6%8A%BD%E5%A5%96%E7%A8%8B%E5%BA%8F%E8%83%8C%E5%90%8E%E5%8E%9F%E5%9B%A0%E4%BB%A4%E4%BA%BA%E6%9A%96%E5%BF%83%EF%BC%81))]

最后恭喜 IT莫扎特 喜提Python好书。

(PS:视频情节纯属玩梗硬编,若李杜在世,他们必是顶尖程序玩家,大家都喜欢这两位著名诗人,俺也一样!)

prize 工具文章介绍

【开源项目】一款prize万能抽奖小工具发布

在这篇发布中,学委定了一个抽奖时间11月10号晚上10点公布,视频中时手动的

在这里插入图片描述
前文贴图的prize python库是周日发布的【0.0.2】 版本

这次,重大更新推出之【定时抽奖】

特地追加了一个**【定时抽奖】**功能!

更多说明看下图:

在这里插入图片描述

再温习一遍【prize】工具如何进行抽奖操作?

第一步: 打开prize:创建了桌面快捷方式,可以双击prize即可打开。(否则打开终端/command,输入: prize)

第二步:在弹出的主界面内,复制黏贴信息,根据情况选择按行解析还是其他格式,然后点击生成【卡片格子】

第三步:点击【重新抽奖】

定时抽奖如何进行

前面两步跟上面的即时抽奖别无二致,下面是第三步。

第三步:进入菜单【更多配置】-> 【定时抽奖】

在这里插入图片描述

第四步:再弹出的字窗口内设置时/分/秒 ,然后点击【预约抽奖】,最后就是等待prize工具自动准点抽奖了。

在这里插入图片描述

懒得看文字步骤的,看看上面的视频吧

视频内介绍了:

  • 安装/操作/定时等等操作。
  • 包括了Windows操作系统和MacOS上如何操作prize
  • “重现”了李白和杜甫的深厚情谊!

好,对于这个工具有其他改进意见可以评论提出。

对了,喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏

持续学习持续开发,我是雷学委!

编程很有趣,关键是把技术搞透彻讲明白。

欢迎关注微信,点赞支持收藏!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python爬虫实战,pyecharts模块,Python实

发表于 2021-11-13

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」。

前言

利用利用Python 自动化来获取某类商品中最好卖的商品以供参考。废话不多说。

让我们愉快地开始吧~

开发工具

Python版本: 3.6.4

相关模块:

pyecharts模块;

以及一些Python自带的模块。

环境搭建

安装Python并添加到环境变量,pip安装需要的相关模块即可。

准备工作

1、配置好 Android ADB 开发环境

2、Python 虚拟环境内安装 pocoui 依赖库

1
2
3
4
5
python复制代码# pocoui\
pip3 install pocoui

# 数据可视化图表
pip3 install pyecharts -U

步骤

我们分 7 个步骤来实现这个功能,分别是:打开目标应用客户端、检索关键字到商品列表界面、计算最佳滑动距离、筛选商品、获取商品链接地址、写入文件排序并统计商品、配置参数。

第 1 步,使用 pocoui 自动化打开目标应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
python复制代码def __pre(self):
    """
    准备工作
    :return:
    """
    home()
    stop_app(package_name)
    start_my_app(package_name, activity)


    # 等待到达桌面
    self.poco(text='闲鱼').wait_for_appearance()
    self.poco(text='鱼塘').wait_for_appearance()
    self.poco(text='消息').wait_for_appearance()
    self.poco(text='我的').wait_for_appearance()

    print('进入闲鱼主界面')

进入到闲鱼首页之后,应用端会拿到剪切板的数据,当存在特定规律的口令的时,会立马弹出一个对话框, 因此需要模拟关闭对话框的操作。

1
2
3
4
5
6
7
python复制代码# 如果指定时间内内有淘口令,就关闭\
for i in range(10, -1, -1):\
      close_element = self.poco('com.taobao.idlefish:id/ivClose')\
      if close_element.exists():\
            close_element.click()\
            break\
      time.sleep(1)

第 2 步,检索关键字到商品列表界面

通过要检索的关键字,模拟输入到输入框内,然后点击搜索按钮,一直等待搜过列表出现为止。

列表

另外,为了更加方便地处理数据,商品列表切换到列表模式,即一行只显示一个商品。

1
2
3
4
5
6
7
8
9
10
python复制代码def __input_key_word(self):
    """
    输入关键字
    :return:
    """
    # 进入搜索界面
    perform_click(self.poco('com.taobao.idlefish:id/bar_tx'))

    # 搜索框内输入文本
    self.poco('com.taobao.idlefish:id/search_term').set_text(self.good_msg)

第 3 步,计算最佳滑动距离。

为了保证爬取数据的高效性,获取计算出每次滑动的最佳距离。

首先先拿到当前界面的 UI 控件树,然后通过控件的属性 ID 拿到商品的坐标,进而得到每一项商品的高度。

最后,通过观察屏幕中出现商品的数目得到最佳滑动距离。

1
2
3
4
5
6
7
8
python复制代码def __get_good_swipe_distance(self):
    """
    获取每次滑动,最合适的距离
    :return:
    """
    element = Element()
    # 保存当前的UI树到本地
    element.get_current_ui_tree()

第 4 步,筛选商品。

上面的步骤拿到最佳的滑动距离,不停的滑动页面遍历列表元素的子 Item。

需要注意的是,为了避免滑动惯性导致的误差,每一次的滑动时长最好设置为 2s 以上。

通过商品 Item 筛选出想要数目大于预设数字的商品。

1
2
3
4
5
6
7
8
python复制代码# 多少人想要
want_element_parent = item.offspring('com.taobao.idlefish:id/search_item_flowlayout')

if want_element_parent.exists():
     # 想要数/已付款数目
     want_element = want_element_parent.children()[0]

     want_content = want_element.get_text()

第 5 步,获取商品链接地址

对于上一步满足条件的商品,点击商品 Item 进入到商品详情页面。

接着点击右上角的分享按钮,会立即弹出分享对话框。

分享对话框

然后点击口令控件,会提示口令复制到系统剪切板成功。

1
2
3
4
5
6
python复制代码# 点击更多
while True:
     if self.poco('com.taobao.idlefish:id/ftShareName').exists():
          break
     print('点击更多~')
     perform_click(self.poco(text='更多'))

第 6 步,写入商品、排序并统计数据

排序

将上面获取到的商品标题、想要数、分享地址写入到 CSV 文件中。

然后读取数据文件,通过对表格中的第二列进行反向排序, 使商品按照想要数进行降序排列。

1
2
3
4
5
6
7
8
python复制代码def __sort_result(self):
    """
    对爬取的结果进行排序
    :return:
    """
    reader = csv.reader(open(self.file_path), delimiter=",")

    return sortedlist

最后拿到前 10 项数据,利用 pyecharts 生成统计图表。

第 7 步,配置参数

编写 yaml 文件,指定要爬取商品的关键字、爬取时间、想要数考核指标数、筛选商品数目。

1
2
3
4
5
6
7
python复制代码goods:
  # 搜索商品1,包含搜索关键字、爬取时间
  good1:
    key_word: '资料'   # 搜索关键字
    key_num: 100  # 筛选【想要数】的临界点
    num: 10      # 只筛选爆款
    time: 600   # 爬取时间(秒)

效果展示

提前配置好商品关键字、爬取时间等参数,即可以爬取到符合要求的、最好卖的商品数据,最终以图表的方式展示出来。

效果

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringCloud升级之路20200x版-34验证

发表于 2021-11-13

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

本系列代码地址:github.com/JoJoTec/spr…

在前面一节,我们利用 resilience4j 粘合了 OpenFeign 实现了断路器、重试以及线程隔离,并使用了新的负载均衡算法优化了业务激增时的负载均衡算法表现。这一节,我们开始编写单元测试验证这些功能的正确性,以便于日后升级依赖,修改的时候能保证正确性。同时,通过单元测试,我们更能深入理解 Spring Cloud。

验证重试配置

对于我们实现的重试,我们需要验证:

  1. 验证配置正确加载:即我们在 Spring 配置(例如 application.yml)中的加入的 Resilience4j 的配置被正确加载应用了。
  2. 验证针对 ConnectTimeout 重试正确:FeignClient 可以配置 ConnectTimeout 连接超时时间,如果连接超时会有连接超时异常抛出,对于这种异常无论什么请求都应该重试,因为请求并没有发出。
  3. 验证针对断路器异常的重试正确:断路器是微服务实例方法级别的,如果抛出断路器打开异常,应该直接重试下一个实例。
  4. 验证针对限流器异常的重试正确:当某个实例线程隔离满了的时候,抛出线程限流异常应该直接重试下一个实例。
  5. 验证针对非 2xx 响应码可重试的方法重试正确
  6. 验证针对非 2xx 响应码不可重试的方法没有重试
  7. 验证针对可重试的方法响应超时异常重试正确:FeignClient 可以配置 ReadTimeout 即响应超时,如果方法可以重试,则需要重试。
  8. 验证针对不可重试的方法响应超时异常不能重试:FeignClient 可以配置 ReadTimeout 即响应超时,如果方法不可以重试,则不能重试。

验证配置正确加载

我们可以定义不同的 FeignClient,之后检查 resilience4j 加载的重试配置来验证重试配置的正确加载。

首先定义两个 FeignClient,微服务分别是 testService1 和 testService2,contextId 分别是 testService1Client 和 testService2Client

1
2
3
4
5
6
7
8
9
10
less复制代码@FeignClient(name = "testService1", contextId = "testService1Client")
public interface TestService1Client {
@GetMapping("/anything")
HttpBinAnythingResponse anything();
}
@FeignClient(name = "testService2", contextId = "testService2Client")
public interface TestService2Client {
@GetMapping("/anything")
HttpBinAnythingResponse anything();
}

然后,我们增加 Spring 配置,使用 SpringExtension 编写单元测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
less复制代码//SpringExtension也包含了 Mockito 相关的 Extension,所以 @Mock 等注解也生效了
@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = {
//默认请求重试次数为 3
"resilience4j.retry.configs.default.maxAttempts=3",
// testService2Client 里面的所有方法请求重试次数为 2
"resilience4j.retry.configs.testService2Client.maxAttempts=2",
})
@Log4j2
public class OpenFeignClientTest {
@SpringBootApplication
@Configuration
public static class App {
}
}

编写测试代码,验证配置加载正确性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
scss复制代码@Test
public void testConfigureRetry() {
//读取所有的 Retry
List<Retry> retries = retryRegistry.getAllRetries().asJava();
//验证其中的配置是否符合我们填写的配置
Map<String, Retry> retryMap = retries.stream().collect(Collectors.toMap(Retry::getName, v -> v));
//我们初始化 Retry 的时候,使用 FeignClient 的 ContextId 作为了 Retry 的 Name
Retry retry = retryMap.get("testService1Client");
//验证 Retry 配置存在
Assertions.assertNotNull(retry);
//验证 Retry 配置符合我们的配置
Assertions.assertEquals(retry.getRetryConfig().getMaxAttempts(), 3);
retry = retryMap.get("testService2Client");
//验证 Retry 配置存在
Assertions.assertNotNull(retry);
//验证 Retry 配置符合我们的配置
Assertions.assertEquals(retry.getRetryConfig().getMaxAttempts(), 2);
}

验证针对 ConnectTimeout 重试正确

我们可以通过针对一个微服务注册两个实例,一个实例是连接不上的,另一个实例是可以正常连接的,无论怎么调用 FeignClient,请求都不会失败,来验证重试是否生效。我们使用 HTTP 测试网站来测试,即 httpbin.org 。这个网站的 api 可以用来模拟各种调用。其中 /status/{status} 就是将发送的请求原封不动的在响应中返回。在单元测试中,我们不会单独部署一个注册中心,而是直接 Mock spring cloud 中服务发现的核心接口 DiscoveryClient,并且将我们 Eureka 的服务发现以及注册通过配置都关闭,即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
less复制代码//SpringExtension也包含了 Mockito 相关的 Extension,所以 @Mock 等注解也生效了
@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = {
//关闭 eureka client
"eureka.client.enabled=false",
//默认请求重试次数为 3
"resilience4j.retry.configs.default.maxAttempts=3"
})
@Log4j2
public class OpenFeignClientTest {
@SpringBootApplication
@Configuration
public static class App {
@Bean
public DiscoveryClient discoveryClient() {
//模拟两个服务实例
ServiceInstance service1Instance1 = Mockito.spy(ServiceInstance.class);
ServiceInstance service1Instance4 = Mockito.spy(ServiceInstance.class);
Map<String, String> zone1 = Map.ofEntries(
Map.entry("zone", "zone1")
);
when(service1Instance1.getMetadata()).thenReturn(zone1);
when(service1Instance1.getInstanceId()).thenReturn("service1Instance1");
when(service1Instance1.getHost()).thenReturn("httpbin.org");
when(service1Instance1.getPort()).thenReturn(80);
when(service1Instance4.getInstanceId()).thenReturn("service1Instance4");
when(service1Instance4.getHost()).thenReturn("www.httpbin.org");
//这个port连不上,测试 IOException
when(service1Instance4.getPort()).thenReturn(18080);
DiscoveryClient spy = Mockito.spy(DiscoveryClient.class);
//微服务 testService3 有两个实例即 service1Instance1 和 service1Instance4
Mockito.when(spy.getInstances("testService3"))
.thenReturn(List.of(service1Instance1, service1Instance4));
return spy;
}
}
}

编写 FeignClient:

1
2
3
4
5
kotlin复制代码@FeignClient(name = "testService3", contextId = "testService3Client")
public interface TestService3Client {
@PostMapping("/anything")
HttpBinAnythingResponse anything();
}

调用 TestService3Client 的 anything 方法,验证是否有重试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
scss复制代码@SpyBean
private TestService3Client testService3Client;

/**
* 验证对于有不正常实例(正在关闭的实例,会 connect timeout)请求是否正常重试
*/
@Test
public void testIOExceptionRetry() {
//防止断路器影响
circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset);
for (int i = 0; i < 5; i++) {
Span span = tracer.nextSpan();
try (Tracer.SpanInScope cleared = tracer.withSpanInScope(span)) {
//不抛出异常,则正常重试了
testService3Client.anything();
testService3Client.anything();
}
}
}

这里强调一点,由于我们在这个类中还会测试其他异常,以及断路器,我们需要避免这些测试一起执行的时候,断路器打开了,所以我们在所有测试调用 FeignClient 的方法开头,清空所有断路器的数据,通过:

1
scss复制代码circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset);

并且通过日志中可以看出由于 connect timeout 进行重试:

1
2
3
4
ruby复制代码call url: POST -> http://www.httpbin.org:18080/anything, ThreadPoolStats(testService3Client:www.httpbin.org:18080): {"coreThreadPoolSize":10,"maximumThreadPoolSize":10,"queueCapacity":100,"queueDepth":0,"remainingQueueCapacity":100,"threadPoolSize":1}, CircuitBreakStats(testService3Client:www.httpbin.org:18080:public abstract com.github.jojotech.spring.cloud.webmvc.test.feign.HttpBinAnythingResponse com.github.jojotech.spring.cloud.webmvc.test.feign.OpenFeignClientTest$TestService3Client.anything()): {"failureRate":-1.0,"numberOfBufferedCalls":0,"numberOfFailedCalls":0,"numberOfNotPermittedCalls":0,"numberOfSlowCalls":0,"numberOfSlowFailedCalls":0,"numberOfSlowSuccessfulCalls":0,"numberOfSuccessfulCalls":0,"slowCallRate":-1.0}
TestService3Client#anything() response: 582-Connect to www.httpbin.org:18080 [www.httpbin.org/34.192.79.103, www.httpbin.org/18.232.227.86, www.httpbin.org/3.216.167.140, www.httpbin.org/54.156.165.4] failed: Connect timed out, should retry: true
call url: POST -> http://httpbin.org:80/anything, ThreadPoolStats(testService3Client:httpbin.org:80): {"coreThreadPoolSize":10,"maximumThreadPoolSize":10,"queueCapacity":100,"queueDepth":0,"remainingQueueCapacity":100,"threadPoolSize":1}, CircuitBreakStats(testService3Client:httpbin.org:80:public abstract com.github.jojotech.spring.cloud.webmvc.test.feign.HttpBinAnythingResponse com.github.jojotech.spring.cloud.webmvc.test.feign.OpenFeignClientTest$TestService3Client.anything()): {"failureRate":-1.0,"numberOfBufferedCalls":0,"numberOfFailedCalls":0,"numberOfNotPermittedCalls":0,"numberOfSlowCalls":0,"numberOfSlowFailedCalls":0,"numberOfSlowSuccessfulCalls":0,"numberOfSuccessfulCalls":0,"slowCallRate":-1.0}
response: 200 - OK

验证针对断路器异常的重试正确

通过系列前面的源码分析,我们知道 spring-cloud-openfeign 的 FeignClient 其实是懒加载的。所以我们实现的断路器也是懒加载的,需要先调用,之后才会初始化断路器。所以这里如果我们要模拟断路器打开的异常,需要先手动读取载入断路器,之后才能获取对应方法的断路器,修改状态。

我们先定义一个 FeignClient:

1
2
3
4
5
kotlin复制代码@FeignClient(name = "testService1", contextId = "testService1Client")
public interface TestService1Client {
@GetMapping("/anything")
HttpBinAnythingResponse anything();
}

使用前面同样的方式,给这个微服务添加实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
less复制代码//SpringExtension也包含了 Mockito 相关的 Extension,所以 @Mock 等注解也生效了
@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = {
//关闭 eureka client
"eureka.client.enabled=false",
//默认请求重试次数为 3
"resilience4j.retry.configs.default.maxAttempts=3",
//增加断路器配置
"resilience4j.circuitbreaker.configs.default.failureRateThreshold=50",
"resilience4j.circuitbreaker.configs.default.slidingWindowType=COUNT_BASED",
"resilience4j.circuitbreaker.configs.default.slidingWindowSize=5",
"resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=2",
})
@Log4j2
public class OpenFeignClientTest {
@SpringBootApplication
@Configuration
public static class App {
@Bean
public DiscoveryClient discoveryClient() {
//模拟两个服务实例
ServiceInstance service1Instance1 = Mockito.spy(ServiceInstance.class);
ServiceInstance service1Instance3 = Mockito.spy(ServiceInstance.class);
Map<String, String> zone1 = Map.ofEntries(
Map.entry("zone", "zone1")
);
when(service1Instance1.getMetadata()).thenReturn(zone1);
when(service1Instance1.getInstanceId()).thenReturn("service1Instance1");
when(service1Instance1.getHost()).thenReturn("httpbin.org");
when(service1Instance1.getPort()).thenReturn(80);
when(service1Instance3.getMetadata()).thenReturn(zone1);
when(service1Instance3.getInstanceId()).thenReturn("service1Instance3");
//这其实就是 httpbin.org ,为了和第一个实例进行区分加上 www
when(service1Instance3.getHost()).thenReturn("www.httpbin.org");
DiscoveryClient spy = Mockito.spy(DiscoveryClient.class);
//微服务 testService3 有两个实例即 service1Instance1 和 service1Instance4
Mockito.when(spy.getInstances("testService1"))
.thenReturn(List.of(service1Instance1, service1Instance3));
return spy;
}
}
}

然后,编写测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
csharp复制代码@Test
public void testRetryOnCircuitBreakerException() {
//防止断路器影响
circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset);
CircuitBreaker testService1ClientInstance1Anything;
try {
testService1ClientInstance1Anything = circuitBreakerRegistry
.circuitBreaker("testService1Client:httpbin.org:80:public abstract com.github.jojotech.spring.cloud.webmvc.test.feign.HttpBinAnythingResponse com.github.jojotech.spring.cloud.webmvc.test.feign.OpenFeignClientTest$TestService1Client.anything()", "testService1Client");
} catch (ConfigurationNotFoundException e) {
//找不到就用默认配置
testService1ClientInstance1Anything = circuitBreakerRegistry
.circuitBreaker("testService1Client:httpbin.org:80:public abstract com.github.jojotech.spring.cloud.webmvc.test.feign.HttpBinAnythingResponse com.github.jojotech.spring.cloud.webmvc.test.feign.OpenFeignClientTest$TestService1Client.anything()");
}
//将断路器打开
testService1ClientInstance1Anything.transitionToOpenState();
//调用多次,调用成功即对断路器异常重试了
for (int i = 0; i < 10; i++) {
this.testService1Client.anything();
}
}

运行测试,日志中可以看出,针对断路器打开的异常进行重试了:

1
yaml复制代码2021-11-13 03:40:13.546  INFO [,,] 4388 --- [           main] c.g.j.s.c.w.f.DefaultErrorDecoder        : TestService1Client#anything() response: 581-CircuitBreaker 'testService1Client:httpbin.org:80:public abstract com.github.jojotech.spring.cloud.webmvc.test.feign.HttpBinAnythingResponse com.github.jojotech.spring.cloud.webmvc.test.feign.OpenFeignClientTest$TestService1Client.anything()' is OPEN and does not permit further calls, should retry: true

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

ElasticSearch 概念介绍和部署 1Elasti

发表于 2021-11-13

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」

1.ElasticSearch简介

1.1ElasticSearch(简称ES)

​

Elasticsearch是用Java开发并且是当前最流行的开源的企业级搜索引擎。能够达到实时搜索,稳定,可靠,快速,安装使用方便。
客户端支持Java、.NET(C#)、PHP、Python、Ruby等多种语言。

官方网站:www.elastic.co
下载地址:www.elastic.co/cn/start

创始人: Shay Banon(谢巴农)

应用场景
image.png

1.2ElasticSearch与Lucene的关系

Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库(框架)

但是想要使用Lucene,必须使用Java来作为开发语言并将其直接集成到你的应用 中,并且Lucene的配置及使用非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。

Lucene缺点:
1)只能在Java项目中使用,并且要以jar包的方式直接集成项目中.
2)使用非常复杂-创建索引和搜索索引代码繁杂
3)不支持集群环境-索引数据不同步(不支持大型项目)
4)索引数据如果太多就不行,索引库和应用所在同一个服务器,共同占用硬盘.共用空间少.

上述Lucene框架中的缺点,ES全部都能解决.

1.3哪些公司在使用Elasticsearch

image.png
国内公司:

1
2
3
4
5
6
7
8
9
shell复制代码1. 京东 
2. 携程
3. 去哪儿
4. 58同城
5. 滴滴
6. 今日头条
7. 小米
8. 哔哩哔哩
9. 联想

1.4ES vs Solr比较

1.4.1ES vs Solr 检索速度

当单纯的对已有数据进行搜索时,Solr更快。
image.png
当实时建立索引时, Solr会产生io阻塞,查询性能较差, Elasticsearch具有明显的优势。
image.png

大型互联网公司,实际生产环境测试,将搜索引擎从Solr转到 Elasticsearch以后的平均查询速度有了50倍的提升。

总结:
二者安装都很简单。
1、Solr 利用 Zookeeper 进行分布式管理,而Elasticsearch 自身带有分布式协调管理功能。
2、Solr 支持更多格式的数据,比如JSON、XML、CSV,而 Elasticsearch 仅支持json文件格式。
3、Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch。
4、Solr 是传统搜索应用的有力解决方案,但 Elasticsearch更适用于新兴的实时搜索应用。

1.4.2ES vs 关系型数据库

Elasticsearch VS RDBMS

RDBMS Database Table Row Column Schema SQL
Elasticsearch Index Index(Type) Document Filed Mapping DSL

说明:

  1. 在 7.0 之前, 一个 index 可以设置多个 types
  2. 目前 type 已经被 Deprecated, 7.0 开始,一个索引只能创建一个 Type -> “_doc”
  3. 传统关系型数据库和 Elasticesarch 的区别
  • Elasticsearch- Schemaless / 相关性 / 高性能全文检索
  • RDMS - 事务性 / Join

2.Lucene全文检索框架

2.1什么是全文检索

全文检索是指:
通过一个程序扫描文本中的每一个单词,针对单词建立索引,并保存该单词在文本中的位置、以及出现的次数
用户查询时,通过之前建立好的索引来查询,将索引中单词对应的文本位置、出现的次数返回给用户,因为有了具体文本的位置,所以就可以将具体内容读取出来了

2.2分词原理之倒排索引

传统线性查找一个 10 MB 的 word ,查找关键字如果在文档的最后,大约需要 3 秒钟
​

倒排索引:记录每个词条出现在哪些文档,以及文档中的位置,可以根据词条快速定位到包含这些词条的文档以及文档的位置

  • 文档 (Document):索引库中每一条原始护具,例如一个网页信息,一件商品信息
  • 词条:原始数据按照算法进行分词,得到每一个词

创建倒排索引,分为以下几步:
1)创建文档列表:
l lucene首先对原始文档数据进行编号(DocID),形成列表,就是一个文档列表
image.png
2)创建倒排索引列表
l 然后对文档中数据进行分词,得到词条。对词条进行编号,以词条创建索引。然后记录下包含该词条的所有文档编号(及其它信息)。
image.png
谷歌之父–> 谷歌、之父

倒排索引创建索引的流程:
1) 首先把所有的原始数据进行编号,形成文档列表
2) 把文档数据进行分词,得到很多的词条,以词条为索引。保存包含这些词条的文档的编号信息。

搜索的过程:
当用户输入任意的词条时,首先对用户输入的数据进行分词,得到用户要搜索的所有词条,然后拿着这些词条去倒排索引列表中进行匹配。找到这些词条就能找到包含这些词条的所有文档的编号。
然后根据这些编号去文档列表中找到文档

3.Elasticsearch中的核心概念

3.1索引 index

一个索引就是一个拥有几分相似特征的文档的集合。比如说,可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引
一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字

3.2映射 mapping

ElasticSearch中的映射(Mapping)用来定义一个文档
mapping是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分词器、是否被索引等等,这些都是映射里面可以设置的

3.3字段Field

相当于是数据表的字段/列

3.4字段类型 Type

每一个字段都应该有一个对应的类型,例如:Text、Keyword、Byte等

3.5文档 document

一个文档是一个可被索引的基础信息单元,类似一条记录。文档以JSON(Javascript Object Notation)格式来表示;

3.6集群 cluster

一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能

3.7节点 node

一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中这意味着,如果在网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中在一个集群里,可以拥有任意多个节点。而且,如果当前网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。

3.8分片和副本 shards&replicas

3.8.1分片

一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10 亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片当创建一个索引的时候,可以指定你想要的分片的数量每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上分片很重要,主要有两方面的原因允许水平分割/扩展你的内容容量允许在分片之上进行分布式的、并行的操作,进而提高性能/吞吐量至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户来说,这些都是透明的

3.8.2副本

在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做副本分片,或者直接叫副本
副本之所以重要,有两个主要原因
1)在分片/节点失败的情况下,提供了高可用性。
注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的
2)扩展搜索量/吞吐量,因为搜索可以在所有的副本上并行运行每个索引可以被分成多个分片。一个索引有0个或者多个副本
一旦设置了副本,每个索引就有了主分片和副本分片,分片和副本的数量可以在索引创建的时候指定
在索引创建之后,可以在任何时候动态地改变副本的数量,但是不能改变分片的数量

4.安装Elasticsearch

4.1安装Elasticsearch

4.1.1创建普通用户

**ES不能使用root用户来启动,必须使用普通用户来安装启动。**这里我们创建一个普通用户以及定义一些常规目录用于存放我们的数据文件以及安装包等。
创建一个es专门的用户(必须)
使用root用户在服务器执行以下命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
shell复制代码# 创建用户组
groupadd elasticsearch

# 创建用户 hxl, 且设置密码
useradd hxl
passwd hxl

# 创建 es 文件夹
mkdir -p /usr/local/es

# 用户 hxl 添加到 elasticsearch 用户组
usermod ‐G elasticsearch hxl
chown ‐R hxl /usr/local/es/elasticsearch‐7.6.1

# 设置 sudo 全县
# 为了让普通用户有更大的操作权限,我们一般都会给普通用户设置sudo权限,方便普通用户的 操作
# 三台机器使用 root 用户执行 visudo 命令然后为 es 用户添加权限
visudo

# 在 root ALL=(ALL)ALL一行下面
# 添加 hxl 用户如下:
hxl ALL=(ALL)ALL

# 添加成功保存后切换用户到 hxl 进行操作
su hxl

4.1.2上传压缩包并解压

将es的安装包下载并上传到服务器的/user/local/es路径下,然后进行解压
使用 hxl 用户来执行以下操作,将es安装包上传到指定服务器,并使用es用户执行以下命令解压。

1
2
3
4
5
6
shell复制代码# 解压 elasticsearch

su hxl
cd /user/local

tar ‐zvxf elasticsearch‐7.6.1‐linux‐x86_64.tar.gz ‐C /usr/local/es/

4.1.3修改配置文件

4.1.3.1修改elasticsearch.yml

进入服务器使用 hxl 用户来修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
shell复制代码cd /usr/local/es/elasticsearch‐7.6.1/config
# 创建日志目录
mkdir ‐p /usr/local/es/elasticsearch‐7.6.1/log
# 创建数据目录
mkdir ‐p /usr/local/es/elasticsearch‐7.6.1/data

# 备份配置文件
cp elasticsearch.yml elasticsearch.yml.bak

# 编辑配置
vim elasticsearch.yml

cluster.name: hxl‐es
node.name: node1
path.data: /usr/local/es/elasticsearch‐7.6.1/data
path.logs: /usr/local/es/elasticsearch‐7.6.1/log
network.host: 0.0.0.0
http.port: 9200
discovery.seed_hosts: ["服务器IP"]
cluster.initial_master_nodes: ["节点名"]
bootstrap.system_call_filter: false
bootstrap.memory_lock: false
http.cors.enabled: true
http.cors.allow‐origin:"*"

4.1.3.2修改jvm.option

修改jvm.option配置文件,调整jvm堆内存大小
node1.hxl.cn使用 hxl 用户执行以下命令调整jvm堆内存大小,每个人根据自己服务器的内存大小来进行调整。

1
2
3
4
5
shell复制代码cd /usr/local/es/elasticsearch‐7.6.1/config

vim jvm.option
-Xms2g
-Xmx2g

4.2修改系统配置,解决启动时候的问题

由于现在使用普通用户来安装es服务,且es服务对服务器的资源要求比较多,包括内存大小,线程数等。所以我们需要给普通用户解开资源的束缚

4.2.1普通用户打开文件的最大数限制

问题错误信息描述:
max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536]
ES因为需要大量的创建索引文件,需要大量的打开系统的文件,所以我们需要解除linux系统当中打开文件最大数目的限制,不然ES启动就会抛错
三台机器使用 hxl 用户执行以下命令解除打开文件数据的限制
sudo vi /etc/security/limits.conf
添加如下内容: 注意*不要去掉了

1
2
3
4
shell复制代码* soft	nofile	65536
* hard nofile 131072
* soft nproc 2048
* hard nproc 4096

4.2.2此文件修改后需要重新登录用户,才会生效

普通用户启动线程数限制
问题错误信息描述
max number of threads [1024] for user [es] likely too low, increase to at least [4096]
修改普通用户可以创建的最大线程数
max number of threads [1024] for user [es] likely too low, increase to at least [4096]原因:无法创建本地线程问题,用户最大可创建线程数太小解决方案: 修改90-nproc.conf 配置文件。
三台机器使用 hxl 用户执行以下命令修改配置文件

1
2
3
4
5
shell复制代码# CentOS6
sudo vi /etc/security/limits.d/90‐nproc.conf

# CentOS7
sudo vi /etc/security/limits.d/20‐nproc.conf

找到如下内容:

1
2
shell复制代码* soft	nproc	1024#修改为
* soft nproc 4096

4.2.3普通用户调大虚拟内存

错误信息描述:
max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
调大系统的虚拟内存
原因:最大虚拟内存太小
每次启动机器都手动执行下。三台机器执行以下命令

1
2
3
4
5
6
7
8
9
shell复制代码
# 编辑 sysctl
vim /etc/sysctl.conf

# 追加以下内容:
vm.max_map_count=262144

# 保存后,执行:
sysctl ‐p

备注:以上三个问题解决完成之后,重新连接secureCRT或者重新连接xshell生效

4.3启动ES服务

三台机器使用baiqi用户执行以下命令启动es服务

1
shell复制代码nohup /usr/local/es/elasticsearch‐7.6.1/bin/elasticsearch 2>&1 &

启动成功之后jsp即可看到es的服务进程,并且访问页面

1
shell复制代码http://127.0.0.1:9200/?pretty

能够看到es启动之后的一些信息
注意:如果哪一台机器服务启动失败,那么就到哪一台机器的

1
shell复制代码/usr/local/es/elasticsearch‐7.6.1/log

这个路径下面去查看错误日志

1
2
3
4
5
6
7
8
9
10
11
shell复制代码
# 注意:防火墙涉及到系统安全,如有必要请咨询 devops, ops
# 关闭 Linux 防火墙

# 永久性生效,重启后不会复原
开启:chkconfig iptables on
关闭:chkconfig iptables off

# 即时生效,重启后复原
开启:service iptables start
关闭:service iptables stop

注意:启动ES的时候出现 Permission denied
原因:当前的用户没有对XX文件或目录的操作权限

5.客户端Kibana安装

5.1客户端可以分为图形界面客户端,和代码客户端.

5.2ES主流客户端Kibana,开放9200端口与图形界面客户端交互

1)下载Kibana放之/usr/local/es目录中
2)解压文件:tar -zxvf kibana-X.X.X-linux-x86_64.tar.gz
3)进入/usr/local/es/kibana-X.X.X-linux-x86_64/config目录
4)使用vi编辑器:vi kibana.yml

1
2
3
4
shell复制代码server.port: 5601
server.host: "0.0.0.0"
# es 服务器的 ip 和端口
elasticsearch.hosts: ["http://localhost:9200"]

5)启动Kibana

1
shell复制代码./kibana

6)访问Kibana

1
shell复制代码http://127.0.0.1:5601/app/kibana

6.安装IK分词器

我们后续也需要使用Elasticsearch来进行中文分词,所以需要单独给Elasticsearch 安装IK分词器插件。以下为具体安装步骤:

6.1下载Elasticsearch IK分词器

下载地址:github.com/medcl/elast…

6.2切换到 hxl 用户,并在es的安装目录下/plugins创建ik

1
shell复制代码mkdir ‐p /usr/local/es/elasticsearch‐7.6.1/plugins/ik

6.3将下载的ik分词器上传并解压到该目录

1
2
shell复制代码cd /usr/local/es/elasticsearch‐7.6.1/plugins/ik
unzip elasticsearch‐analysis‐ik‐7.6.1.zip

6.4重启Elasticsearch

6.5 测试分词效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
shell复制代码POST _analyze
{
"analyzer":"standard",
"text":"我爱你中国"
}


#ik_smart:会做最粗粒度的拆分
POST _analyze
{
"analyzer":
"ik_smart",
"text":"中华人民共和国"
}

#ik_max_word: 会将文本做最细粒度的拆分
POST _analyze
{
"analyzer":"ik_max_word",
"text":"我爱你中国"
}

7.指定IK分词器作为默认分词器

ES的默认分词设置是standard,这个在中文分词时就比较尴尬了,会单字拆分,比如我搜索关键词“清华大学”,这时候会按“清”,“华”,“大”,“学”去分词,然后搜出来的都是些“清清的河水”,“中华儿女”,“地大物博”,“学而不思则罔”之类的莫名其妙的结果,这里我们就想把这个分词方式修改一下,于是呢,就想到了ik分词器,有两种ik_smart和ik_max_word。
ik_smart会将“清华大学”整个分为一个词,而ik_max_word会将“清华大学”分为“清华大学”,“清华”和“大学”,按需选其中之一就可以了。
修改默认分词方法(这里修改school_index索引的默认分词为:ik_max_word):

1
2
3
4
5
6
7
8
shell复制代码PUT	/school_index
{
"settings" : {
"index" : {
"analysis.analyzer.default.type": "ik_max_word"
}
}
}

8.ES数据管理

8.1ES数据管理概述
ES是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。
然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。
在ES中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。ES使用JSON作为文档序列化格式。
JSON现在已经被大多语言所支持,而且已经成为NoSQL领域的标准格式。ES存储的一个员工文档的格式示例:

1
2
3
4
5
6
7
json复制代码{

"email" : "zhangsan@hw.cn",
"name" : "张三",
"age" : 32,
"interests" : ["篮球", "足球"]
}

8.2基本操作
1)创建索引
格式: PUT /索引名称

1
json复制代码PUT /es_db

2)查询索引
格式: GET /索引名称

1
json复制代码GET /es_db

3)删除索引
格式: DELETE /索引名称

1
json复制代码DELETE /es_db

4)添加文档
格式: PUT /索引名称/类型/id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
shell复制代码PUT /es_db/_doc/1
{
"name":"张三",
"sex":1,
"age":25,
"address":"广州天河公园",
"remark":"java developer"
}


PUT /es_db/_doc/2
{
"name": "李四",
"sex": 1,
"age": 28,
"address": "广州荔湾大厦",
"remark": "java assistant"
}


PUT /es_db/_doc/3
{
"name": "rod",
"sex": 0,
"age": 26,
"address": "广州白云山公园",
"remark": "php developer"
}

PUT /es_db/_doc/4
{
"name": "admin",
"sex": 0,
"age": 22,
"address": "长沙橘子洲头",
"remark": "python assistant"
}


PUT /es_db /_doc /5
{
"name": "小明",
"sex": 0,
"age": 19,
"address": "长沙岳麓山",
"remark": "java architect assistant"
}

5) 修改文档

1
2
3
4
5
6
7
8
9
10
shell复制代码格式: PUT /索引名称/类型/id 
举例:
PUT /es_db/_doc/1
{
"name": "张飞",
"sex": 1,
"age": 25,
"address": "张家界森林公园",
"remark": "php developer assistant"
}

**可能出现的问题: **
​

重启服务可能遇到的问题:TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block,可以参考下面配置。
关键的参数cluster.routing.allocation.disk.threshold_enabled
(es可以根据磁盘使用情况来决定是否继续分配shard。默认设置是开启的).
为了在本地单机上测试,我自己电脑磁盘空间剩下没多少了,修改elasticsearch.yml,设置cluster.routing.allocation.disk.threshold_enabled: false。
如果后续虚拟机磁盘空间不够,启动失败了可以删除了data,logs里的文件。
​

问题:
ES插入大量的数据时报错:TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block 的解决方法
原因:
是因为一次请求中批量插入的数据条数巨多,以及短时间内的请求次数巨多引起ES节点服务器内存超过限制,ES主动给索引上锁。
​

解决办法:

1
2
3
4
shell复制代码PUT _all/_settings
{
"index.blocks.read_only_allow_delete": null
}

6) 查询文档

1
2
3
shell复制代码格式: GET /索引名称/类型/id

GET /es_db/_doc/1

7)删除文档

1
2
3
4
5
6
shell复制代码格式: DELETE /索引名称/类型/id

DELETE /es_db/_doc/2

# 如果是 PowerShell 可以使用下面的请求命令
curl -uri 'http://localhost:9200/es_db/_doc/2' -method 'DELETE'

9.Restful认识

Restful是一种面向资源的架构风格,可以简单理解为:使用URL定位资源,用HTTP 动词(GET,POST,DELETE,PUT)描述操作。 基于Restful API ES和所有客户端的交互都是使用JSON格式的数据.
其他所有程序语言都可以使用RESTful API,通过9200端口的与ES进行通信
GET 查询
PUT 添加
POST 修改
DELETE 删除
​

用户做 crud

1
2
3
4
5
6
7
8
9
10
11
12
13
shell复制代码 Get http://localhost:8080/employee/1

Get http://localhost:8080/employees

put http://localhost:8080/employee
{
}

delete http://localhost:8080/employee/1

Post http://localhost:8080/employee/1
{
}

使用Restful的好处:
透明性,暴露资源存在。
充分利用 HTTP 协议本身语义,不同请求方式进行不同的操作

10.查询操作

10.1查询当前类型中的所有文档 _search

1
2
3
shell复制代码格式: GET /索引名称/类型/_search 
举例: GET /es_db/_doc/_search
SQL: select * from student

10.2条件查询, 如要查询age等于28岁的 _search?q=:**

1
2
3
shell复制代码格式: GET /索引名称/类型/_search?q=*:*** 
举例: GET /es_db/_doc/_search?q=age:28
SQL: select * from student where age = 28

10.3范围查询, 如要查询age在25至26岁之间的 _search?q=[** TO **] 注意: TO 必须为大写

1
2
3
shell复制代码格式: GET /索引名称/类型/_search?q=***[1 TO 100] 
举例: GET /es_db/_doc/_search?q=age[1 TO 100]
SQL: select * from student where age between 1 and 100

10.4根据多个ID进行批量查询 _mget

1
2
3
4
5
6
7
shell复制代码格式: GET /索引名称/类型/_mget 2 
举例:
GET /es_db/_doc/_mget
{
"ids":["1","2"]
}
SQL: select * from student where id in (1,2)

10.5查询年龄小于等于28岁的 :<=

1
2
3
shell复制代码格式: GET /索引名称/类型/_search?q=age:<=** 
举例: GET /es_db/_doc/_search?q=age:<=28
SQL: select * from student where age <= 28

10.6查询年龄大于28前的 :>

1
2
3
shell复制代码格式: GET /索引名称/类型/_search?q=age:>** 
举例: GET /es_db/_doc/_search?q=age:>28
SQL: select * from student where age > 28

10.7分页查询 from=&size=

1
2
3
shell复制代码格式: GET /索引名称/类型/_search?q=age[25 TO 26]&from=0&size=1 
举例: GET /es_db/_doc/_search?q=age[25 TO 26]&from=0&size=1
SQL: select * from student where age between 25 and 26 limit 0, 1

10.8对查询结果只输出某些字段 _source=字段,字段

1
2
3
shell复制代码格式: GET /索引名称/类型/_search?_source=字段,字段 
举例: GET /es_db/_doc/_search?_source=name,age
SQL: select name,age from student

10.9对查询结果排序 sort=字段:desc/asc

1
2
3
shell复制代码格式: GET /索引名称/类型/_search?sort=字段 desc 
举例: GET /es_db/_doc/_search?sort=age:desc
SQL: select * from student order by age desc

Reference

  • blog.csdn.net/qq_36811160…
  • www.jianshu.com/p/233bf9a95…
  • blog.csdn.net/weixin_3226…
  • zhuanlan.zhihu.com/p/33671444
  • www.zhihu.com/question/23…
  • www.elastic.co/guide/cn/el…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…356357358…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%