首页> 搜索结果页
"Javascript中内建函数reduce的应用详解" 检索
共 8 条结果
ES6详解 快速上手!
一、Es61.1、ES6的概述ECMAScript的快速发展:编程语言JavaScript是ECMAScript的实现和扩展 。ECMAScript是由ECMA(一个类似W3C的标准组织)参与进行标准化的语法规范。ECMAScript定义了:[语言语法] – 语法解析规则、关键字、语句、声明、运算符等。[类型]– 布尔型、数字、字符串、对象等。[原型和继承]内建对象和函数的[标准库] – [JSON]、[Math]、[数组方法]、[对象自省方法]等。ECMAScript标准不定义HTML或CSS的相关功能,也不定义类似DOM(文档对象模型)的[Web API],这些都在独立的标准中进行定义。ECMAScript涵盖了各种环境中JS的使用场景,无论是浏览器环境还是类似[node.js]的非浏览器环境。ECMAScript标准的历史版本分别是1、2、3、5。那么为什么没有第4版?其实,在过去确实曾计划发布提出巨量新特性的第4版,但最终却因想法太过激进而惨遭废除(这一版标准中曾经有一个极其复杂的支持泛型和类型推断的内建静态类型系统)。ES4饱受争议,当标准委员会最终停止开发ES4时,其成员同意发布一个相对谦和的ES5版本,随后继续制定一些更具实质性的新特性。这一明确的协商协议最终命名为“Harmony”,因此,ES5规范中包含这样两句话ECMAScript是一门充满活力的语言,并在不断进化中。未来版本的规范中将持续进行重要的技术改进2009年发布的改进版本ES5,引入了[Object.create()]、[Object.defineProperty()]、[getters]和[setters]、[严格模式]以及[JSON]对象。ES6: 是JavaScript语言的下一代标准,2015年6月正式发布。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。小结:ECMAScript是前端js的语法规范;可以应用在各种js环境中。如:浏览器或者node.js环境。它有很多版本:es1/2/3/5/6,很多新特性,可以在js环境中使用这些新特性。1.2、ES6的语法:let和const命令变量和常量的严格区分。核心代码:新建espro6\demo01.html如下<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <script> // 1: 在javascrit定义数据类型只有一种标识 - var // 灵活拷问:js有数据类型吗? // 有,string,number,Object, boolean, undefined // 什么是语言:电脑内存中,人通过代码和电脑沟通。 let name = "zhangsan"; let age = 100; let flag = false; //js被骂没常量 const PI = Math.PI; // 修改会报错 //PI = 1245; console.log(PI) //var或造成变量穿透 for(let i=0;i<5;i++){ console.log(i); }; //console.log("===这里就是变量穿透===>" + i) </script> </body> </html>双击espro6/demo01.html运行如下:小结let : 可变变量const 是常量var:最原始。1.3、ES6的语法:模板字符串以前: 我们都是使用 ‘’ 或者 “” 来把字符串套起来现在: `` 【反引号】第一个用途:基本的字符串格式化。将表达式嵌入字符串中进行拼接。用${}来界定。//es5 let name = 'itcast' console.log('hello ' + name) //es6 const name = 'itcast' console.log(`hello ${name}`) //hello itcast第二个用途:在ES5时我们通过反斜杠()来做多行字符串或者字符串一行行拼接。ES6反引号(``)直接搞定。新建一个espro6\demo02.html如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>模板字符串</title> </head> <body> <script> var username = "张三"; var age = 30; // 1: 原始的做法就是去拼接字符串 var str = "我名字叫 " + username+",年龄是: "+age; console.log(str); // 2:用模板字符串来拯救 注意:这里是 `(飘键) (tab键盘的上面那个键) // jdk1.9 var str2 = `我名字叫 ${username},年龄是: ${age}`; console.log(str2); </script> </body> </html>1.4、ES6的语法:函数默认参数与箭头函数函数默认参数在方法的参数后面加上一个默认值即可核心代码双击espro6/demo03.html运行如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <script> // 默认参数 给参数列表设定初始值 function add(a =100,b=100) { console.log(a,b); } // 执行方法,会用默认值填充,打印出来100,200 add(); // 覆盖默认值打印 结果是1,2 add(1,2); </script> </body> </html>==箭头函数==箭头函数简化函数的定义,可以让我们不用使用function关键字/* 箭头函数最直观的三个特点。 1不需要function关键字来创建函数 2省略return关键字 3继承当前上下文的 this 关键字 */核心代码双击espro6/demo04.html运行如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>箭头函数</title> </head> <body> <script> // 箭头函数 // 它也是一种函数的定义,它简化定义仅此而已。 // 步骤:1:去掉function 2: 括号后面加箭头。 // 1:声明式的定义 function add (){ }; // 2:表达式的定义 var add2 = function(){ } // 3:箭头函数的定义 var sum = (a = 100,b = 300)=>{ console.log(a+b); }; // 这里执行箭头函数 sum(50,50); // 这里执行箭头函数 sum(); // 这里执行箭头函数 sum(400); </script> </body> </html>箭头函数深度学习双击espro6/demo05.html运行如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>箭头函数</title> </head> <body> <script> // 箭头函数 // 它也是一种函数的定义,它简化定义仅此而已。 // 步骤:1:去掉function 2: 括号后面加箭头。 // 无参数的函数 //var sum = function(){ //} // 箭头改造如下 //var sum = ()=>{} // 有参数 // 第一种情况 一个参数的如下 //var sum2 = function(a){ //}; // 箭头改造如下 var sum2 = (a)=>{}; var sum2 = a=>{ return a; }; // 第二种情况 二个参数的以上,记住括号一定要加 //var sum3 = function(a,b){ // return a + b; //}; // 箭头改造如下 var sum3 = (a,b)=>{ return a + b; }; // 第三种情况,如果没有逻辑体,只有返回值可以简化如下 //var sum4 = function(a,b){ // return a + b; //}; // 箭头改造如下 var sum4 = (a,b)=>a+b // 执行 console.log(sum2(100)); console.log(sum3(100,100)); console.log(sum4(100,100)); </script> </body> </html>1.5、ES6的语法:对象初始化简写核心代码它是指:如果一个对象中的key和value的名字一样的情况下可以定义成一个。双击espro6\demo06.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>对象简写</title> </head> <body> <script> function person(name, age) { //return {name:name,age:age}; // 对象简写 return { name, age }; }; // 调用和执行 var json = person("小花花美美", 20); console.log(json.name, json.age); //========================= 实战应用 ========================= //<button onclick="login()">登录</button> function login() { var username = $("#username").val(); var password = $("#password").val(); // 发送ajax $.ajax({ type: "post", // 对象简写 data: { username, password }, // 原始写分 //data:{username:username,password:password}, success() { } }); } </script> </body> </html>1.6、ES6的语法:对象解构核心代码​ 对象解构 —- es6提供一些获取快捷获取对象属性和行为方式双击espro6\demo07.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>对象解构</title> </head> <body> <script> // 对象解构 --- es6提供一些获取快捷获取对象属性和行为方式 var person = { name:'zhangsan', age:32, language:"cn", // 函数也有处理 /* say:function(){ console.log(this.name+"年龄是:" + this.age); } */ /* say:()=>{ console.log(this.name+"年龄是:" + this.age); } */ say(){ console.log(this.name+"年龄是:" + this.age); } }; // ===========================传统的做法======================== var name = person.name; var age = person.age; person.say(); // ===========================对象解构做法======================== //es6的做法 前提:默认情况name,age必须是jsonkey. var {name,age} = person; console.log(name,age); // 可以用冒号取小名 var {name,age,language:lan} = person; console.log(name,age,lan); </script> </body> </html>1.7、ES6的语法:传播操作符【…】把一个对象的属性传播到另外一个对象中Spread Operator核心代码双击espro6\demo08.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>ES6的语法:传播操作符【...】</title> </head> <body> <script> // 1: 定义一个对象 var person1 = { name: '张三', age: 16, }; // 2: 对象解构 var {name,age} = person1; // =========================== ... 对象融合===================== var person2 = { ...person1, gender:1, tel:13478900 }; console.log(person2); // =========================== ... 对象取值===================== // ... 对象取值 var person3 = { name:"李四", gender:1, tel:"11111", address:'广州' }; // ...person4 把剩下没取走给我。 var {name,gender,...person4} = person3; console.log(name) console.log(age) console.log(person4) // =================场景分析 -----伪代码======================== // 模拟后台:异步查询返回用户数据 如下: function findUsers(){ $.get("xxxxx",function(res){ var res = { pages:11, pageSize:10, pageNo:1, firstFlag:true, lastFlag:false, total:123, data:[{},{},{},{}], }; // ==========================对象 ... 取值=============== var {data:users,...pagesjon} = res; //等价于 /* var users = res.data; var pagesjon = { res = { pages:11, pageSize:10, pageNo:1, firstFlag:true, lastFlag:false, total:123, }; */ }) } </script> </body> </html>1.8、ES6的语法:数组map和reduce方法使用(了解)目标:数组中map方法的应用场景讲解:数组中新增了map和reduce方法。map()方法可以将原数组中的所有元素通过一个函数进行处理并放入到一个新数组中并返回该新数组。举例:有一个字符串数组,希望转为int数组<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>数组方法的扩展-map reduce</title> </head> <body> <script> /*********************************map*********************/ let arr = ['1', '20', '-5', '3']; console.log(arr) // 1:数据类型的更改 map自身循环的功能 // 2:map方法可以将原数组中的所有元素通过一个函数进行处理并放入到一个新数组中并返回该新数组。 var newarr = arr.map(function (value) { return parseInt(value) * 2; }); /* // 箭头简化01 var newarr = arr.map(value=>{ return parseInt(value) * 2; }); // 箭头简化02 var newarr = arr.map(value => parseInt(value) * 2); */ console.log("原数组:", arr); console.log("map的newarr数组:", newarr); // 2 40 -10 6 /*********************************end map*********************/ /*********************************reduce*********************/ //reduce 计算方法 let arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var result = arr2.reduce((a, b) => a + b); console.log(result); /*********************************end reduce*********************/ </script> </body> </html>reduce()reduce(function(),初始值(可选)) :接收一个函数(必须)和一个初始值(可选),该函数接收两个参数:第一个参数是上一次reduce处理的结果第二个参数是数组中要处理的下一个元素reduce() 会从左到右依次把数组中的元素用reduce处理,并把处理的结果作为下次reduce的第一个参数。如果是 第一次,会把前两个元素作为计算参数,或者把用户指定的初始值作为起始参数<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>数组方法的扩展-map reduce</title> </head> <body> <script> /*********************************reduce*********************/ //reduce 计算方法 let arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // 箭头写法 -- 1到10相加 var result = arr2.reduce((a, b) => a + b); // 原始写法 -- 1到10相加 //var result = arr2.reduce(function(a,b){ // return a+b; //}) console.log(result); /*********************************end reduce*********************/ </script> </body> </html>
文章
机器学习/深度学习  ·  JSON  ·  JavaScript  ·  前端开发  ·  API  ·  数据格式
2023-01-12
2019 Python 面试 必备!100 问,你会几道?
来源商业新知网,原标题:2019 Python 面试 100 问,你会几道? 0 遇到过得反爬虫策略以及解决方法? 1.通过headers反爬虫 2.基于用户行为的发爬虫:(同一IP短时间内访问的频率) 3.动态网页反爬虫(通过ajax请求数据,或者通过JavaScript生成) 4.对部分数据进行加密处理的(数据是乱码) 解决方法:对于基本网页的抓取可以自定义headers,添加headers的数据 使用多个代理ip进行抓取或者设置抓取的频率降低一些, 动态网页的可以使用selenium + phantomjs 进行抓取 对部分数据进行加密的,可以使用selenium进行截图,使用python自带的pytesseract库进行识别,但是比较慢最直接的方法是找到加密的方法进行逆向推理。 1 urllib 和 urllib2 的区别?urllib 和urllib2都是接受URL请求的相关模块,但是urllib2可以接受一个Request类的实例来设置URL请求的headers,urllib仅可以接受URL。urllib不可以伪装你的User-Agent字符串。 urllib提供urlencode()方法用来GET查询字符串的产生,而urllib2没有。这是为何urllib常和urllib2一起使用的原因。 2 列举网络爬虫所用到的网络数据包,解析包?网络数据包 urllib、urllib2、requests 解析包 re、xpath、beautiful soup、lxml 3 简述一下爬虫的步骤?确定需求;确定资源;通过url获取网站的返回数据;定位数据;存储数据。4 遇到反爬机制怎么处理?反爬机制:headers方向 判断User-Agent、判断Referer、判断Cookie。 将浏览器的headers信息全部添加进去 注意:Accept-Encoding;gzip,deflate需要注释掉 5 常见的HTTP方法有哪些?GET:请求指定的页面信息,返回实体主体; HEAD:类似于get请求,只不过返回的响应中没有具体的内容,用于捕获报头; POST:向指定资源提交数据进行处理请求(比如表单提交或者上传文件),。数据被包含在请求体中。 PUT:从客户端向服务端传送数据取代指定的文档的内容; DELETE:请求删除指定的页面; CONNNECT:HTTP1.1协议中预留给能够将连接方式改为管道方式的代理服务器; OPTIONS:允许客户端查看服务器的性能; TRACE:回显服务器的请求,主要用于测试或者诊断。6 说一说redis-scrapy中redis的作用?它是将scrapy框架中Scheduler替换为redis数据库,实现队列管理共享。 优点:可以充分利用多台机器的带宽;可以充分利用多台机器的IP地址。7 遇到的反爬虫策略以及解决方法?通过headers反爬虫:自定义headers,添加网页中的headers数据。基于用户行为的反爬虫(封IP):可以使用多个代理IP爬取或者将爬取的频率降低。动态网页反爬虫(JS或者Ajax请求数据):动态网页可以使用 selenium + phantomjs 抓取。对部分数据加密处理(数据乱码):找到加密方法进行逆向推理。8 如果让你来防范网站爬虫,你应该怎么来提高爬取的难度 ?判断headers的User-Agent;检测同一个IP的访问频率;数据通过Ajax获取;爬取行为是对页面的源文件爬取,如果要爬取静态网页的html代码,可以使用jquery去模仿写html。9 scrapy分为几个组成部分?分别有什么作用?分为5个部分;Spiders(爬虫类),Scrapy Engine(引擎),Scheduler(调度器),Downloader(下载器),Item Pipeline(处理管道)。 Spiders:开发者自定义的一个类,用来解析网页并抓取指定url返回的内容。 Scrapy Engine:控制整个系统的数据处理流程,并进行事务处理的触发。 Scheduler:接收Engine发出的requests,并将这些requests放入到处理列队中,以便之后engine需要时再提供。 Download:抓取网页信息提供给engine,进而转发至Spiders。 Item Pipeline:负责处理Spiders类提取之后的数据。 比如清理HTML数据、验证爬取的数据(检查item包含某些字段)、查重(并丢弃)、将爬取结果保存到数据库中10 简述一下scrapy的基本流程?知识图谱,2019 Python 面试 100 问,你会几道?scrapy分为9个步骤:Spiders需要初始的start_url或则函数stsrt_requests,会在内部生成Requests给Engine;Engine将requests发送给Scheduler;Engine从Scheduler那获取requests,交给Download下载;在交给Dowmload过程中会经过Downloader Middlewares(经过process_request函数);Dowmloader下载页面后生成一个response,这个response会传给Engine,这个过程中又经过了Downloader Middlerwares(经过process_request函数),在传送中出错的话经过process_exception函数;Engine将从Downloader那传送过来的response发送给Spiders处理,这个过程经过Spiders Middlerwares(经过process_spider_input函数);Spiders处理这个response,返回Requests或者Item两个类型,传给Engine,这个过程又经过Spiders Middlewares(经过porcess_spider_output函数);Engine接收返回的信息,如果使Item,将它传给Items Pipeline中;如果是Requests,将它传给Scheduler,继续爬虫;重复第三步,直至没有任何需要爬取的数据11 python3.5语言中enumerate的意思是对于一个可迭代的(iterable)/可遍历的对象(如列表、字符串),enumerate将其组成一个索引序列,利用它可以同时获得索引和值 enumerate多用于在for循环中得到计数 12 你是否了解谷歌的无头浏览器?无头浏览器即headless browser,是一种没有界面的浏览器。既然是浏览器那么浏览器该有的东西它都应该有,只是看不到界面而已。 Python中selenium模块中的PhantomJS即为无界面浏览器(无头浏览器):是基于QtWebkit的无头浏览器。 13 scrapy和scrapy-redis的区别?scrapy是一个爬虫通用框架,但不支持分布式,scrapy-redis是为了更方便的实现scrapy分布式爬虫,而提供了一些以redis为基础的组件 为什么会选择redis数据库?因为redis支持主从同步,而且数据都是缓存在内存中,所以基于redis的分布式爬虫,对请求和数据的高频读取效率非常高 什么是主从同步?在Redis中,用户可以通过执行SLAVEOF命令或者设置slaveof选项,让一个服务器去复制(replicate)另一个服务器,我们称呼被复制的服务器为主服务器(master),而对主服务器进行复制的服务器则被称为从服务器(slave),当客户端向从服务器发送SLAVEOF命令,要求从服务器复制主服务器时,从服务器首先需要执行同步操作,也即是,将从服务器的数据库状态更新至主服务器当前所处的数据库状态 14 scrapy的优缺点?为什么要选择scrapy框架?优点:采取可读性更强的xpath代替正则 强大的统计和log系统 同时在不同的url上爬行 支持shell方式,方便独立调试 写middleware,方便写一些统一的过滤器 通过管道的方式存入数据库 缺点:基于python爬虫框架,扩展性比较差,基于twisted框架,运行中exception是不会干掉reactor,并且异步框架出错后是不会停掉其他任务的,数据出错后难以察觉 15 scrapy和requests的使用情况?requests 是 polling 方式的,会被网络阻塞,不适合爬取大量数据 scapy 底层是异步框架 twisted ,并发是最大优势 16 描述一下scrapy框架的运行机制?从start_urls里面获取第一批url发送请求,请求由请求引擎给调度器入请求对列,获取完毕后,调度器将请求对列交给下载器去获取请求对应的响应资源,并将响应交给自己编写的解析方法做提取处理,如果提取出需要的数据,则交给管道处理,如果提取出url,则继续执行之前的步骤,直到多列里没有请求,程序结束。 17 写爬虫使用多进程好,还是用多线程好?IO密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。在实际的数据采集过程中,既考虑网速和响应的问题,也需要考虑自身机器的硬件情况,来设置多进程或多线程 18 常见的反爬虫和应对方法?基于用户行为,同一个ip段时间多次访问同一页面 利用代理ip,构建ip池请求头里的user-agent 构建user-agent池(操作系统、浏览器不同,模拟不同用户)动态加载(抓到的数据和浏览器显示的不一样),js渲染 模拟ajax请求,返回json形式的数据selenium / webdriver 模拟浏览器加载对抓到的数据进行分析加密参数字段 会话跟踪【cookie】 防盗链设置【Referer19 分布式爬虫主要解决什么问题?面对海量待抓取网页,只有采用分布式架构,才有可能在较短时间内完成一轮抓取工作。 它的开发效率是比较快而且简单的。 20 如何提高爬取效率?爬虫下载慢主要原因是阻塞等待发往网站的请求和网站返回 1,采用异步与多线程,扩大电脑的cpu利用率; 2,采用消息队列模式 3,提高带宽 21 说说什么是爬虫协议?Robots协议(也称为爬虫协议、爬虫规则、机器人协议等)也就是robots.txt,网站通过robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。 Robots协议是网站国际互联网界通行的道德规范,其目的是保护网站数据和敏感信息、确保用户个人信息和隐私不被侵犯。因其不是命令,故需要搜索引擎自觉遵守。 22 如果对方网站反爬取,封IP了怎么办?放慢抓取熟速度,减小对目标网站造成的压力,但是这样会减少单位时间内的数据抓取量使用代理IP(免费的可能不稳定,收费的可能不划算)23 有一个jsonline格式的文件filedef get_lines(): with open('file.txt','rb') as f: return f.readlines() if name == '__main__': for e in get_lines(): process(e) # 处理每一行数据 现在要处理一个大小为10G的文件,但是内存只有4G,如果在只修改get_lines 函数而其他代码保持不变的情况下,应该如何实现?需要考虑的问题都有那些? def get_lines(): with open('file.txt','rb') as f: for i in f: yield i Pandaaaa906提供的方法 from mmap import mmap def get_lines(fp): with open(fp,"r+") as f: m = mmap(f.fileno(), 0) tmp = 0 for i, char in enumerate(m): if char==b"n": yield m[tmp:i+1].decode() tmp = i+1 if __name__=="__main__": for i in get_lines("fp_some_huge_file"): print(i) 要考虑的问题有:内存只有4G无法一次性读入10G文件,需要分批读入分批读入数据要记录每次读入数据的位置。分批每次读取数据的大小,太小会在读取操作花费过多时间。 https://stackoverflow.com/questions/30294146/python-fastest-way-to-process-large-file 24 补充缺失的代码def print_directory_contents(sPath):"""这个函数接收文件夹的名称作为输入参数返回该文件夹中文件的路径以及其包含文件夹中文件的路径"""import osfor s_child in os.listdir(s_path): s_child_path = os.path.join(s_path, s_child) if os.path.isdir(s_child_path): print_directory_contents(s_child_path) else: print(s_child_path) 25 输入日期, 判断这一天是这一年的第几天?import datetimedef dayofyear(): year = input("请输入年份: ") month = input("请输入月份: ") day = input("请输入天: ") date1 = datetime.date(year=int(year),month=int(month),day=int(day)) date2 = datetime.date(year=int(year),month=1,day=1) return (date1-date2).days+1 26 打乱一个排好序的list对象alist?import randomalist = [1,2,3,4,5]random.shuffle(alist)print(alist)27 现有字典 d= {'a':24,'g':52,'i':12,'k':33}请按value值进行排序?sorted(d.items(),key=lambda x:x[1])28 字典推导式d = {key:value for (key,value) in iterable}29 请反转字符串 "aStr"?print("aStr"[::-1])30 将字符串 "k:1 |k1:2|k2:3|k3:4",处理成字典str1 = "k:1|k1:2|k2:3|k3:4"def str2dict(str1): dict1 = {} for iterms in str1.split('|'): key,value = iterms.split(':') dict1[key] = value return dict1 字典推导式 d = {k:int(v) for t in str1.split("|") for k, v in (t.split(":") )}31 请按alist中元素的age由大到小排序alist = [{'name':'a','age':20},{'name':'b','age':30},{'name':'c','age':25}]def sort_by_age(list1): return sorted(alist,key=lambda x:x['age'],reverse=True) 32 下面代码的输出结果将是什么?list = ['a','b','c','d','e']print(list[10:])代码将输出[],不会产生IndexError错误,就像所期望的那样,尝试用超出成员的个数的index来获取某个列表的成员。例如,尝试获取list[10]和之后的成员,会导致IndexError。然而,尝试获取列表的切片,开始的index超过了成员个数不会产生IndexError,而是仅仅返回一个空列表。这成为特别让人恶心的疑难杂症,因为运行的时候没有错误产生,导致Bug很难被追踪到。 33 写一个列表生成式,产生一个公差为11的等差数列print([x*11 for x in range(10)])34 给定两个列表,怎么找出他们相同的元素和不同的元素?list1 = [1,2,3]list2 = [3,4,5]set1 = set(list1)set2 = set(list2)print(set1 & set2)print(set1 ^ set2)35 请写出一段python代码实现删除list里面的重复元素?l1 = ['b','c','d','c','a','a']l2 = list(set(l1))print(l2)用list类的sort方法: l1 = ['b','c','d','c','a','a']l2 = list(set(l1))l2.sort(key=l1.index)print(l2)也可以这样写: l1 = ['b','c','d','c','a','a']l2 = sorted(set(l1),key=l1.index)print(l2)也可以用遍历: l1 = ['b','c','d','c','a','a']l2 = []for i in l1: if not i in l2: l2.append(i) print(l2)36 给定两个list A,B ,请用找出A,B中相同与不同的元素A,B 中相同元素: print(set(A)&set(B))A,B 中不同元素: print(set(A)^set(B))37 python新式类和经典类的区别? 在python里凡是继承了object的类,都是新式类 Python3里只有新式类 Python2里面继承object的是新式类,没有写父类的是经典类 经典类目前在Python里基本没有应用 38 python中内置的数据结构有几种? 整型 int、 长整型 long、浮点型 float、 复数 complex 字符串 str、 列表 list、 元祖 tuple 字典 dict 、 集合 set Python3 中没有 long,只有无限精度的 int 39 python如何实现单例模式?请写出两种实现方式?第一种方法:使用装饰器 def singleton(cls): instances = {} def wrapper(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper @singletonclass Foo(object): pass foo1 = Foo()foo2 = Foo()print(foo1 is foo2) # True第二种方法:使用基类 New 是真正创建实例对象的方法,所以重写基类的new 方法,以此保证创建对象的时候只生成一个实例 class Singleton(object): def __new__(cls, *args, **kwargs): if not hasattr(cls, '_instance'): cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs) return cls._instance class Foo(Singleton): pass foo1 = Foo()foo2 = Foo() print(foo1 is foo2) # True第三种方法:元类,元类是用于创建类对象的类,类对象创建实例对象时一定要调用call方法,因此在调用call时候保证始终只创建一个实例即可,type是python的元类 class Singleton(type): def __call__(cls, *args, **kwargs): if not hasattr(cls, '_instance'): cls._instance = super(Singleton, cls).__call__(*args, **kwargs) return cls._instance Python2 class Foo(object): __metaclass__ = Singleton Python3 class Foo(metaclass=Singleton): pass foo1 = Foo()foo2 = Foo()print(foo1 is foo2) # True40 反转一个整数,例如-123 --> -321class Solution(object): def reverse(self,x): if -1010: return x str_x = str(x) if str_x[0] !="-": str_x = str_x[::-1] x = int(str_x) else: str_x = str_x[1:][::-1] x = int(str_x) x = -x return x if -21474836482147483647 else 0 if name == '__main__': s = Solution() reverse_int = s.reverse(-120) print(reverse_int) 41 设计实现遍历目录与子目录,抓取.pyc文件?第一种方法: import os def get_files(dir,suffix): res = [] for root,dirs,files in os.walk(dir): for filename in files: name,suf = os.path.splitext(filename) if suf == suffix: res.append(os.path.join(root,filename)) print(res) get_files("./",'.pyc')第二种方法: import os def pick(obj): if ob.endswith(".pyc"): print(obj) def scan_path(ph): file_list = os.listdir(ph) for obj in file_list: if os.path.isfile(obj): pick(obj) elif os.path.isdir(obj): scan_path(obj) if __name__=='__main__': path = input('输入目录') scan_path(path) 第三种方法 from glob import iglob def func(fp, postfix): for i in iglob(f"{fp}/**/*{postfix}", recursive=True): print(i) if name == "__main__": postfix = ".pyc" func("K:Python_script", postfix) 42 Python-遍历列表时删除元素的正确做法遍历在新在列表操作,删除时在原来的列表操作 a = [1,2,3,4,5,6,7,8]print(id(a))print(id(a[:]))for i in a[:]: if i>5: pass else: a.remove(i) print(a) print('-----------')print(id(a)) filter a=[1,2,3,4,5,6,7,8]b = filter(lambda x: x>5,a)print(list(b))列表解析 a=[1,2,3,4,5,6,7,8]b = [i for i in a if i>5]print(b)倒序删除 因为列表总是‘向前移’,所以可以倒序遍历,即使后面的元素被修改了,还没有被遍历的元素和其坐标还是保持不变的 a=[1,2,3,4,5,6,7,8]print(id(a))for i in range(len(a)-1,-1,-1): if a[i]>5: pass else: a.remove(a[i]) print(id(a))print('-----------')print(a)43 字符串的操作题目全字母短句 PANGRAM 是包含所有英文字母的句子,比如:A QUICK BROWN FOX JUMPS OVER THE LAZY DOG. 定义并实现一个方法 get_missing_letter, 传入一个字符串采纳数,返回参数字符串变成一个 PANGRAM 中所缺失的字符。应该忽略传入字符串参数中的大小写,返回应该都是小写字符并按字母顺序排序(请忽略所有非 ACSII 字符) 下面示例是用来解释,双引号不需要考虑: (0)输入: "A quick brown for jumps over the lazy dog" 返回: "" (1)输入: "A slow yellow fox crawls under the proactive dog" 返回: "bjkmqz" (2)输入: "Lions, and tigers, and bears, oh my!" 返回: "cfjkpquvwxz" (3)输入: "" 返回:"abcdefghijklmnopqrstuvwxyz" def get_missing_letter(a): s1 = set("abcdefghijklmnopqrstuvwxyz") s2 = set(a) ret = "".join(sorted(s1-s2)) return ret print(get_missing_letter("python"))44 可变类型和不可变类型1,可变类型有list,dict.不可变类型有string,number,tuple. 2,当进行修改操作时,可变类型传递的是内存中的地址,也就是说,直接修改内存中的值,并没有开辟新的内存。 3,不可变类型被改变时,并没有改变原内存地址中的值,而是开辟一块新的内存,将原地址中的值复制过去,对这块新开辟的内存中的值进行操作。 45 is和==有什么区别?is:比较的是两个对象的id值是否相等,也就是比较俩对象是否为同一个实例对象。是否指向同一个内存地址 == : 比较的两个对象的内容/值是否相等,默认会调用对象的eq()方法 46 求出列表所有奇数并构造新列表a = [1,2,3,4,5,6,7,8,9,10]res = [ i for i in a if i%2==1]print(res)47 用一行python代码写出1+2+3+10248from functools import reduce 1.使用sum内置求和函数 num = sum([1,2,3,10248])print(num) 2.reduce 函数 num1 = reduce(lambda x,y :x+y,[1,2,3,10248])print(num1)48 Python中变量的作用域?(变量查找顺序)函数作用域的LEGB顺序 1.什么是LEGB? L: local 函数内部作用域 E: enclosing 函数内部与内嵌函数之间 G: global 全局作用域 B: build-in 内置作用 python在函数里面的查找分为4种,称之为LEGB,也正是按照这是顺序来查找的 49 字符串 "123" 转换成 123,不使用内置api,例如 int()方法一: 利用 str 函数 def atoi(s): num = 0 for v in s: for j in range(10): if v == str(j): num = num * 10 + j return num 方法二: 利用 ord 函数 def atoi(s): num = 0 for v in s: num = num * 10 + ord(v) - ord('0') return num 方法三: 利用 eval 函数 def atoi(s): num = 0 for v in s: t = "%s * 1" % v n = eval(t) num = num * 10 + n return num 方法四: 结合方法二,使用 reduce ,一行解决 from functools import reducedef atoi(s): return reduce(lambda num, v: num * 10 + ord(v) - ord('0'), s, 0) 50 Given an array of integers给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。示例:给定nums = [2,7,11,15],target=9 因为 nums[0]+nums[1] = 2+7 =9,所以返回[0,1] class Solution: def twoSum(self,nums,target): """ :type nums: List[int] :type target: int :rtype: List[int] """ d = {} size = 0 while size < len(nums): if target-nums[size] in d: if d[target-nums[size]] return [d[target-nums[size]],size] else: d[nums[size]] = size size = size +1 solution = Solution()list = [2,7,11,15]target = 9nums = solution.twoSum(list,target)print(nums)给列表中的字典排序:假设有如下list对象,alist=[{"name":"a","age":20},{"name":"b","age":30},{"name":"c","age":25}],将alist中的元素按照age从大到小排序 alist=[{"name":"a","age":20},{"name":"b","age":30},{"name":"c","age":25}] alist_sort = sorted(alist,key=lambda e: e.__getitem__('age'),reverse=True)51 python代码实现删除一个list里面的重复元素def distFunc1(a): """使用集合去重""" a = list(set(a)) print(a) def distFunc2(a): """将一个列表的数据取出放到另一个列表中,中间作判断""" list = [] for i in a: if i not in list: list.append(i) #如果需要排序的话用sort list.sort() print(list) def distFunc3(a): """使用字典""" b = {} b = b.fromkeys(a) c = list(b.keys()) print(c) if name == "__main__": a = [1,2,4,2,4,5,7,10,5,5,7,8,9,0,3] distFunc1(a) distFunc2(a) distFunc3(a) 52 统计一个文本中单词频次最高的10个单词?import re 方法一 def test(filepath): distone = {} with open(filepath) as f: for line in f: line = re.sub("W+", " ", line) lineone = line.split() for keyone in lineone: if not distone.get(keyone): distone[keyone] = 1 else: distone[keyone] += 1 num_ten = sorted(distone.items(), key=lambda x:x[1], reverse=True)[:10] num_ten =[x[0] for x in num_ten] return num_ten 方法二 使用 built-in 的 Counter 里面的 most_common import refrom collections import Counter def test2(filepath): with open(filepath) as f: return list(map(lambda c: c[0], Counter(re.sub("W+", " ", f.read()).split()).most_common(10))) 53 请写出一个函数满足以下条件该函数的输入是一个仅包含数字的list,输出一个新的list,其中每一个元素要满足以下条件: 1、该元素是偶数 2、该元素在原list中是在偶数的位置(index是偶数) def num_list(num): return [i for i in num if i %2 ==0 and num.index(i)%2==0] num = [0,1,2,3,4,5,6,7,8,9,10]result = num_list(num)print(result)54 使用单一的列表生成式来产生一个新的列表该列表只包含满足以下条件的值,元素为原始列表中偶数切片 list_data = [1,2,5,8,10,3,18,6,20]res = [x for x in list_data[::2] if x %2 ==0]print(res)55 用一行代码生成[1,4,9,16,25,36,49,64,81,100][x * x for x in range(1,11)]56 输入某年某月某日,判断这一天是这一年的第几天?import datetime y = int(input("请输入4位数字的年份:"))m = int(input("请输入月份:"))d = int(input("请输入是哪一天")) targetDay = datetime.date(y,m,d)dayCount = targetDay - datetime.date(targetDay.year -1,12,31)print("%s是 %s年的第%s天。"%(targetDay,y,dayCount.days))57 两个有序列表,l1,l2,对这两个列表进行合并不可使用extenddef loop_merge_sort(l1,l2): tmp = [] while len(l1)>0 and len(l2)>0: if l1[0] 0]: tmp.append(l1[0]) del l1[0] else: tmp.append(l2[0]) del l2[0] while len(l1)>0: tmp.append(l1[0]) del l1[0] while len(l2)>0: tmp.append(l2[0]) del l2[0] return tmp 58 给定一个任意长度数组,实现一个函数让所有奇数都在偶数前面,而且奇数升序排列,偶数降序排序,如字符串'1982376455',变成'1355798642' 方法一 def func1(l): if isinstance(l, str): l = [int(i) for i in l] l.sort(reverse=True) for i in range(len(l)): if l[i] % 2 > 0: l.insert(0, l.pop(i)) print(''.join(str(e) for e in l)) 方法二 def func2(l): print("".join(sorted(l, key=lambda x: int(x) % 2 == 0 and 20 - int(x) or int(x)))) 59 写一个函数找出一个整数数组中,第二大的数def find_second_large_num(num_list): """ 找出数组第2大的数字 """ # 方法一 # 直接排序,输出倒数第二个数即可 tmp_list = sorted(num_list) print("方法一nSecond_large_num is :", tmp_list[-2]) # 方法二 # 设置两个标志位一个存储最大数一个存储次大数 # two 存储次大值,one 存储最大值,遍历一次数组即可,先判断是否大于 one,若大于将 one 的值给 two,将 num_list[i] 的值给 one,否则比较是否大于two,若大于直接将 num_list[i] 的值给two,否则pass one = num_list[0] two = num_list[0] for i in range(1, len(num_list)): if num_list[i] > one: two = one one = num_list[i] elif num_list[i] > two: two = num_list[i] print("方法二nSecond_large_num is :", two) # 方法三 # 用 reduce 与逻辑符号 (and, or) # 基本思路与方法二一样,但是不需要用 if 进行判断。 from functools import reduce num = reduce(lambda ot, x: ot[1] < x and (ot[1], x) or ot[0] < x and (x, ot[1]) or ot, num_list, (0, 0))[0] print("方法三nSecond_large_num is :", num) if name == '__main___': num_list = [34, 11, 23, 56, 78, 0, 9, 12, 3, 7, 5] find_second_large_num(num_list) 60 阅读一下代码他们的输出结果是什么?def multi(): return [lambda x : i*x for i in range(4)] print([m(3) for m in multi()])正确答案是[9,9,9,9],而不是[0,3,6,9]产生的原因是Python的闭包的后期绑定导致的,这意味着在闭包中的变量是在内部函数被调用的时候被查找的,因为,最后函数被调用的时候,for循环已经完成, i 的值最后是3,因此每一个返回值的i都是3,所以最后的结果是[9,9,9,9] 61 统计一段字符串中字符出现的次数 方法一 def count_str(str_data): """定义一个字符出现次数的函数""" dict_str = {} for i in str_data: dict_str[i] = dict_str.get(i, 0) + 1 return dict_str dict_str = count_str("AAABBCCAC")str_count_data = ""for k, v in dict_str.items(): str_count_data += k + str(v) print(str_count_data) 方法二 from collections import Counter print("".join(map(lambda x: x[0] + str(x[1]), Counter("AAABBCCAC").most_common())))62 Python中类方法、类实例方法、静态方法有何区别?类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行装饰,形参为cls,表示类对象,类对象和实例对象都可调用 类实例方法: 是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身; 静态方法: 是一个任意函数,在其上方使用 @staticmethod 进行装饰,可以用对象直接调用,静态方法实际上跟该类没有太大关系 63 遍历一个object的所有属性,并print每一个属性名?class Car: def __init__(self,name,loss): # loss [价格,油耗,公里数] self.name = name self.loss = loss def getName(self): return self.name def getPrice(self): # 获取汽车价格 return self.loss[0] def getLoss(self): # 获取汽车损耗值 return self.loss[1] * self.loss[2] Bmw = Car("宝马",[60,9,500]) # 实例化一个宝马车对象print(getattr(Bmw,"name")) # 使用getattr()传入对象名字,属性值。print(dir(Bmw)) # 获Bmw所有的属性和方法64 写一个类,并让它尽可能多的支持操作符?class Array: __list = [] def __init__(self): print "constructor" def __del__(self): print "destruct" def __str__(self): return "this self-defined array class" def __getitem__(self,key): return self.__list[key] def __len__(self): return len(self.__list) def Add(self,value): self.__list.append(value) def Remove(self,index): del self.__list[index] def DisplayItems(self): print "show all items---" for item in self.__list: print item 65 关于Python内存管理,下列说法错误的是 BA,变量不必事先声明 B,变量无须先创建和赋值而直接使用 C,变量无须指定类型 D,可以使用del释放资源 66 Python的内存管理机制及调优手段?内存管理机制: 引用计数、垃圾回收、内存池 引用计数:引用计数是一种非常高效的内存管理手段,当一个Python对象被引用时其引用计数增加1, 当其不再被一个变量引用时则计数减1,当引用计数等于0时对象被删除。弱引用不会增加引用计数 垃圾回收: 1.引用计数 引用计数也是一种垃圾收集机制,而且也是一种最直观、最简单的垃圾收集技术。当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为1,如果引用被删除,对象的引用计数为0,那么该对象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了。 2.标记清除 调优手段 1.手动垃圾回收 2.调高垃圾回收阈值 3.避免循环引用 67 内存泄露是什么?如何避免?内存泄漏 指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。 有 __del__() 函数的对象间的循环引用是导致内存泄露的主凶。不使用一个对象时使用: del object 来删除一个对象的引用计数就可以有效防止内存泄露问题。 通过Python扩展模块gc 来查看不能回收的对象的详细信息。 可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为0来判断是否内存泄露 68 python常见的列表推导式?[表达式 for 变量 in 列表] 或者 [表达式 for 变量 in 列表 if 条件] 69 简述read、readline、readlines的区别?read 读取整个文件 readline 读取下一行 readlines 读取整个文件到一个迭代器以供我们遍历 70 什么是Hash(散列函数)?散列函数 (英语:Hash function)又称 散列算法 、 哈希函数 ,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做 散列值 (hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表 71 python函数重载机制?函数重载主要是为了解决两个问题。 1。可变参数类型。 2。可变参数个数。 另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。 好吧,那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。 那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。 好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。 72 手写一个判断时间的装饰器import datetime class TimeException(Exception): def __init__(self, exception_info): super().__init__() self.info = exception_info def __str__(self): return self.info def timecheck(func): def wrapper(*args, **kwargs): if datetime.datetime.now().year == 2019: func(*args, **kwargs) else: raise TimeException("函数已过时") return wrapper @timecheckdef test(name): print("Hello {}, 2019 Happy".format(name)) if name == "__main__": test("backbp") 73 使用Python内置的filter()方法来过滤?list(filter(lambda x: x % 2 == 0, range(10)))74 编写函数的4个原则1.函数设计要尽量短小 2.函数声明要做到合理、简单、易于使用 3.函数参数设计应该考虑向下兼容 4.一个函数只做一件事情,尽量保证函数语句粒度的一致性 75 函数调用参数的传递方式是值传递还是引用传递?Python的参数传递有:位置参数、默认参数、可变参数、关键字参数。 函数的传值到底是值传递还是引用传递、要分情况: 不可变参数用值传递:像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象。 可变参数是引用传递:比如像列表,字典这样的对象是通过引用传递、和C语言里面的用指针传递数组很相似,可变对象能在函数内部改变。 76 如何在function里面设置一个全局变量globals() # 返回包含当前作用余全局变量的字典。global 变量 设置使用全局变量77 对缺省参数的理解 ?缺省参数指在调用函数的时候没有传入参数的情况下,调用默认的参数,在调用函数的同时赋值时,所传入的参数会替代默认参数。 *args是不定长参数,它可以表示输入参数是不确定的,可以是任意多个。 **kwargs是关键字参数,赋值的时候是以键值对的方式,参数可以是任意多对在定义函数的时候 不确定会有多少参数会传入时,就可以使用两个参数 78 带参数的装饰器?带定长参数的装饰器 def new_func(func): def wrappedfun(username, passwd): if username == 'root' and passwd == '123456789': print('通过认证') print('开始执行附加功能') return func() else: print('用户名或密码错误') return return wrappedfun @new_funcdef origin(): print('开始执行函数') origin('root','123456789')带不定长参数的装饰器 def new_func(func): def wrappedfun(*parts): if parts: counts = len(parts) print('本系统包含 ', end='') for part in parts: print(part, ' ',end='') print('等', counts, '部分') return func() else: print('用户名或密码错误') return func() return wrappedfun79 为什么函数名字可以当做参数用?Python中一切皆对象,函数名是函数在内存中的空间,也是一个对象 80 Python中pass语句的作用是什么?在编写代码时只写框架思路,具体实现还未编写就可以用pass进行占位,是程序不报错,不会进行任何操作。 81 有这样一段代码,print c会输出什么,为什么?a = 10b = 20c = [a]a = 15答:10对于字符串,数字,传递是相应的值 82 交换两个变量的值?a, b = b, a83 map函数和reduce函数?map(lambda x: x * x, [1, 2, 3, 4]) # 使用 lambda [1, 4, 9, 16] reduce(lambda x, y: x y, [1, 2, 3, 4]) # 相当于 ((1 2) 3) 4 24 84 回调函数,如何通信的?回调函数是把函数的指针(地址)作为参数传递给另一个函数,将整个函数当作一个对象,赋值给调用的函数。 85 Python主要的内置数据类型都有哪些? print dir( ‘a ’) 的输出?内建类型:布尔类型,数字,字符串,列表,元组,字典,集合 输出字符串'a'的内建方法 86 map(lambda x:xx,[y for y in range(3)])的输出?[0, 1, 4]87 hasattr() getattr() setattr() 函数使用详解?hasattr(object,name)函数: 判断一个对象里面是否有name属性或者name方法,返回bool值,有name属性(方法)返回True,否则返回False。 class function_demo(object): name = 'demo' def run(self): return "hello function" functiondemo = function_demo()res = hasattr(functiondemo, "name") # 判断对象是否有name属性,Trueres = hasattr(functiondemo, "run") # 判断对象是否有run方法,Trueres = hasattr(functiondemo, "age") # 判断对象是否有age属性,Falseprint(res)getattr(object, name[,default])函数: 获取对象object的属性或者方法,如果存在则打印出来,如果不存在,打印默认值,默认值可选。注意:如果返回的是对象的方法,则打印结果是:方法的内存地址,如果需要运行这个方法,可以在后面添加括号(). functiondemo = function_demo()getattr(functiondemo, "name")# 获取name属性,存在就打印出来 --- demogetattr(functiondemo, "run") # 获取run 方法,存在打印出方法的内存地址getattr(functiondemo, "age") # 获取不存在的属性,报错getattr(functiondemo, "age", 18)# 获取不存在的属性,返回一个默认值setattr(object, name, values)函数: 给对象的属性赋值,若属性不存在,先创建再赋值 class function_demo(object): name = "demo" def run(self): return "hello function" functiondemo = function_demo()res = hasattr(functiondemo, "age") # 判断age属性是否存在,Falseprint(res)setattr(functiondemo, "age", 18) # 对age属性进行赋值,无返回值res1 = hasattr(functiondemo, "age") # 再次判断属性是否存在,True综合使用 class function_demo(object): name = "demo" def run(self): return "hello function" functiondemo = function_demo()res = hasattr(functiondemo, "addr") # 先判断是否存在if res: addr = getattr(functiondemo, "addr") print(addr) else: addr = getattr(functiondemo, "addr", setattr(functiondemo, "addr", "北京首都")) print(addr) 88 一句话解决阶乘函数?reduce(lambda x,y : x*y,range(1,n+1))89 对设计模式的理解,简述你了解的设计模式?设计模式是经过总结,优化的,对我们经常会碰到的一些编程问题的可重用解决方案。一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码,反之,设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板。 常见的是工厂模式和单例模式 90 请手写一个单例 python2 class A(object): __instance = None def __new__(cls,*args,**kwargs): if cls.__instance is None: cls.__instance = objecet.__new__(cls) return cls.__instance else: return cls.__instance 91 单例模式的应用场景有那些?单例模式应用的场景一般发现在以下条件下: 资源共享的情况下,避免由于资源操作时导致的性能或损耗等,如日志文件,应用配置。 控制资源的情况下,方便资源之间的互相通信。如线程池等,1,网站的计数器 2,应用配置 3.多线程池 4数据库配置 数据库连接池 5.应用程序的日志应用… 92 用一行代码生成[1,4,9,16,25,36,49,64,81,100]print([x*x for x in range(1, 11)])93 对装饰器的理解,并写出一个计时器记录方法执行性能的装饰器?装饰器本质上是一个callable object ,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。 import timefrom functools import wraps def timeit(func): @wraps(func) def wrapper(*args, **kwargs): start = time.clock() ret = func(*args, **kwargs) end = time.clock() print('used:',end-start) return ret return wrapper @timeitdef foo(): print('in foo()'foo()) 94 解释以下什么是闭包?在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。 95 函数装饰器有什么作用?装饰器本质上是一个callable object,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能。装饰器的返回值也是一个函数的对象,它经常用于有切面需求的场景。比如:插入日志,性能测试,事务处理,缓存。权限的校验等场景,有了装饰器就可以抽离出大量的与函数功能本身无关的雷同代码并发并继续使用。 详细参考:https://manjusaka.itscoder.com/2018/02/23/something-about-decorator/ 96 生成器,迭代器的区别?迭代器是遵循迭代协议的对象。用户可以使用 iter() 以从任何序列得到迭代器(如 list, tuple, dictionary, set 等)。另一个方法则是创建一个另一种形式的迭代器 —— generator 。要获取下一个元素,则使用成员函数 next()(Python 2)或函数 next() function (Python 3) 。当没有元素时,则引发 StopIteration 此例外。若要实现自己的迭代器,则只要实现 next()(Python 2)或 next ()( Python 3) 生成器(Generator),只是在需要返回数据的时候使用yield语句。每次next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值) 区别: 生成器能做到迭代器能做的所有事,而且因为自动创建iter()和next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出StopIteration异常。 97 X是什么类型?X= (i for i in range(10)) X是 generator类型 98 请用一行代码 实现将1-N 的整数列表以3为单位分组N =100print ([[x for x in range(1,100)] [i:i+3] for i in range(0,100,3)])99 Python中yield的用法?yield就是保存当前程序执行状态。你用for循环的时候,每次取一个元素的时候就会计算一次。用yield的函数叫generator,和iterator一样,它的好处是不用一次计算所有元素,而是用一次算一次,可以节省很多空间,generator每次计算需要上一次计算结果,所以用yield,否则一return,上次计算结果就没了。
文章
2019-06-12
MongoDB、Hbase、Redis等NoSQL优劣势、应用场景
NoSQL的四大种类 NoSQL数据库在整个数据库领域的江湖地位已经不言而喻。在大数据时代,虽然RDBMS很优秀,但是面对快速增长的数据规模和日渐复杂的数据模型,RDBMS渐渐力不从心,无法应对很多数据库处理任务,这时NoSQL凭借易扩展、大数据量和高性能以及灵活的数据模型成功的在数据库领域站稳了脚跟。 目前大家基本认同将NoSQL数据库分为四大类:键值存储数据库,文档型数据库,列存储数据库和图形数据库,其中每一种类型的数据库都能够解决关系型数据不能解决的问题。在实际应用中,NoSQL数据库的分类界限其实没有那么明显,往往会是多种类型的组合体。 主流nosql的详解:MongoDB、Hbase、Redis MongoDB MongoDB 是一个高性能,开源,无模式的文档型数据库,开发语言是C++。它在许多场景下可用于替代统的关系型数据库或键/值存储方式。 1.MongoDB特点 所用语言:C++ 特点:保留了SQL一些友好的特性(查询,索引)。 使用许可: AGPL(发起者: Apache) 协议: Custom, binary( BSON) Master/slave复制(支持自动错误恢复,使用 sets 复制) 内建分片机制 支持 javascript表达式查询 可在服务器端执行任意的 javascript函数 update-in-place支持比CouchDB更好 在数据存储时采用内存到文件映射 对性能的关注超过对功能的要求 建议最好打开日志功能(参数 --journal) 在32位操作系统上,数据库大小限制在约2.5Gb 空数据库大约占 192Mb 采用 GridFS存储大数据或元数据(不是真正的文件系统) 2.MongoDB优点: 1)更高的写负载,MongoDB拥有更高的插入速度。 2)处理很大的规模的单表,当数据表太大的时候可以很容易的分割表。 3)高可用性,设置M-S不仅方便而且很快,MongoDB还可以快速、安全及自动化的实现节点 (数据中心)故障转移。 4)快速的查询,MongoDB支持二维空间索引,比如管道,因此可以快速及精确的从指定位置 获取数据。MongoDB在启动后会将数据库中的数据以文件映射的方式加载到内存中。如果内 存资源相当丰富的话,这将极大地提高数据库的查询速度。 5)非结构化数据的爆发增长,增加列在有些情况下可能锁定整个数据库,或者增加负载从而 导致性能下降,由于MongoDB的弱数据结构模式,添加1个新字段不会对旧表格有任何影响, 整个过程会非常快速。 3.MongoDB缺点: 1)不支持事务。 2)MongoDB占用空间过大 。 3)MongoDB没有成熟的维护工具。 4.MongoDB应用场景 1.)适用于实时的插入、更新与查询的需求,并具备应用程序实时数据存储所需的复制及高度伸缩性; 2) 非常适合文档化格式的存储及查询; 3.)高伸缩性的场景:MongoDB 非常适合由数十或者数百台服务器组成的数据库。 4.)对性能的关注超过对功能的要求。 HBase HBase 是 Apache Hadoop 中的一个子项目,属于 bigtable 的开源版本,所实现的语言为Java(故依赖 Java SDK)。HBase 依托于 Hadoop 的 HDFS(分布式文件系统)作为最基本存储基础单元。 1.HBase 特点: 所用语言: Java 特点:支持数十亿行X上百万列 使用许可: Apache 协议:HTTP/REST (支持 Thrift,见编注4) 在 BigTable之后建模 采用分布式架构 Map/reduce 对实时查询进行优化 高性能 Thrift网关 通过在server端扫描及过滤实现对查询操作预判 支持 XML, Protobuf, 和binary的HTTP Cascading, hive, and pig source and sink modules 基于 Jruby( JIRB)的shell 对配置改变和较小的升级都会重新回滚 不会出现单点故障 堪比MySQL的随机访问性能 3. HBase 优点 1) 存储容量大,一个表可以容纳上亿行,上百万列; 2.)可通过版本进行检索,能搜到所需的历史版本数据; 3.)负载高时,可通过简单的添加机器来实现水平切分扩展,跟Hadoop的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性能(MapReduce); 4.)在第3点的基础上可有效避免单点故障的发生。 4.HBase 缺点 1. 基于Java语言实现及Hadoop架构意味着其API更适用于Java项目; 2. node开发环境下所需依赖项较多、配置麻烦(或不知如何配置,如持久化配置),缺乏文档; 3. 占用内存很大,且鉴于建立在为批量分析而优化的HDFS上,导致读取性能不高; 4. API相比其它 NoSql 的相对笨拙。 5.HBase 适用场景 1)bigtable类型的数据存储; 2)对数据有版本查询需求; 3)应对超大数据量要求扩展简单的需求。 Redis Redis 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。目前由VMware主持开发工作。 1.Redis 特点: 所用语言:C/C++ 特点:运行异常快 使用许可: BSD 协议:类 Telnet 有硬盘存储支持的内存数据库, 但自2.0版本以后可以将数据交换到硬盘(注意, 2.4以后版本不支持该特性!) Master-slave复制(见编注3) 虽然采用简单数据或以键值索引的哈希表,但也支持复杂操作,例如 ZREVRANGEBYSCORE。 INCR & co (适合计算极限值或统计数据) 支持 sets(同时也支持 union/diff/inter) 支持列表(同时也支持队列;阻塞式 pop操作) 支持哈希表(带有多个域的对象) 支持排序 sets(高得分表,适用于范围查询) Redis支持事务 支持将数据设置成过期数据(类似快速缓冲区设计) Pub/Sub允许用户实现消息机制 2. Redis 优势 1)非常丰富的数据结构; 2.)Redis提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断; 3.)数据存在内存中,读写非常的高速,可以达到10w/s的频率。 3.Redis 缺点 1) Redis3.0后才出来官方的集群方案,但仍存在一些架构上的问题; 2.)持久化功能体验不佳——通过快照方法实现的话,需要每隔一段时间将整个数据库的数据写到磁盘上,代价非常高;而aof方法只追踪变化的数据,类似于mysql的binlog方法,但追加log可能过大,同时所有操作均要重新执行一遍,恢复速度慢; 3)由于是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然redis本身有key过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。 4.Redis 应用场景: 最佳应用场景:适用于数据变化快且数据库大小可遇见(适合内存容量)的应用程序。 例如:微博、数据分析、实时数据搜集、实时通讯等。 参考 技术精华总结,说说我上半年都干了什么 非科班出身程序员:如何获取职业资源、进入好公司? 【Android】一次面试总结 练就Java24章真经—你所不知道的工厂方法
文章
Java  ·  数据库  ·  NoSQL  ·  MongoDB  ·  存储  ·  分布式数据库  ·  Redis  ·  Hbase  ·  分布式计算  ·  Hadoop
2018-10-11
NoSQL的四大种类
NoSQL数据库在整个数据库领域的江湖地位已经不言而喻。在大数据时代,虽然RDBMS很优秀,但是面对快速增长的数据规模和日渐复杂的数据模型,RDBMS渐渐力不从心,无法应对很多数据库处理任务,这时NoSQL凭借易扩展、大数据量和高性能以及灵活的数据模型成功的在数据库领域站稳了脚跟。 目前大家基本认同将NoSQL数据库分为四大类:键值存储数据库,文档型数据库,列存储数据库和图形数据库,其中每一种类型的数据库都能够解决关系型数据不能解决的问题。在实际应用中,NoSQL数据库的分类界限其实没有那么明显,往往会是多种类型的组合体。 主流nosql的详解:MongoDB、Hbase、Redis MongoDB MongoDB 是一个高性能,开源,无模式的文档型数据库,开发语言是C++。它在许多场景下可用于替代统的关系型数据库或键/值存储方式。 1.MongoDB特点 所用语言:C++特点:保留了SQL一些友好的特性(查询,索引)。使用许可: AGPL(发起者: Apache)协议: Custom, binary( BSON)Master/slave复制(支持自动错误恢复,使用 sets 复制)内建分片机制支持 javascript表达式查询可在服务器端执行任意的 javascript函数update-in-place支持比CouchDB更好在数据存储时采用内存到文件映射对性能的关注超过对功能的要求建议最好打开日志功能(参数 --journal)在32位操作系统上,数据库大小限制在约2.5Gb空数据库大约占 192Mb采用 GridFS存储大数据或元数据(不是真正的文件系统) 2.MongoDB优点 1)更高的写负载,MongoDB拥有更高的插入速度。 2)处理很大的规模的单表,当数据表太大的时候可以很容易的分割表。 3)高可用性,设置M-S不仅方便而且很快,MongoDB还可以快速、安全及自动化的实现节点 (数据中心)故障转移。 4)快速的查询,MongoDB支持二维空间索引,比如管道,因此可以快速及精确的从指定位置 获取数据。MongoDB在启动后会将数据库中的数据以文件映射的方式加载到内存中。如果内 存资源相当丰富的话,这将极大地提高数据库的查询速度。 5)非结构化数据的爆发增长,增加列在有些情况下可能锁定整个数据库,或者增加负载从而 导致性能下降,由于MongoDB的弱数据结构模式,添加1个新字段不会对旧表格有任何影响, 整个过程会非常快速。 3.MongoDB缺点 1)不支持事务。 2)MongoDB占用空间过大 。 3)MongoDB没有成熟的维护工具。 4.MongoDB应用场景 1.)适用于实时的插入、更新与查询的需求,并具备应用程序实时数据存储所需的复制及高度伸缩性; 2) 非常适合文档化格式的存储及查询; 3.)高伸缩性的场景:MongoDB 非常适合由数十或者数百台服务器组成的数据库。 4.)对性能的关注超过对功能的要求。 HBase HBase 是 Apache Hadoop 中的一个子项目,属于 bigtable 的开源版本,所实现的语言为Java(故依赖 Java SDK)。HBase 依托于 Hadoop 的 HDFS(分布式文件系统)作为最基本存储基础单元。 1.HBase 特点 所用语言: Java特点:支持数十亿行X上百万列使用许可: Apache协议:HTTP/REST (支持 Thrift,见编注4)在 BigTable之后建模采用分布式架构 Map/reduce对实时查询进行优化高性能 Thrift网关通过在server端扫描及过滤实现对查询操作预判支持 XML, Protobuf, 和binary的HTTPCascading, hive, and pig source and sink modules基于 Jruby( JIRB)的shell对配置改变和较小的升级都会重新回滚不会出现单点故障堪比MySQL的随机访问性能 2. HBase 优点 1) 存储容量大,一个表可以容纳上亿行,上百万列; 2.)可通过版本进行检索,能搜到所需的历史版本数据; 3.)负载高时,可通过简单的添加机器来实现水平切分扩展,跟Hadoop的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性能(MapReduce); 4.)在第3点的基础上可有效避免单点故障的发生。 3.HBase 缺点 基于Java语言实现及Hadoop架构意味着其API更适用于Java项目; node开发环境下所需依赖项较多、配置麻烦(或不知如何配置,如持久化配置),缺乏文档; 占用内存很大,且鉴于建立在为批量分析而优化的HDFS上,导致读取性能不高; API相比其它 NoSql 的相对笨拙。 4.HBase 适用场景 1)bigtable类型的数据存储; 2)对数据有版本查询需求; 3)应对超大数据量要求扩展简单的需求。 Redis Redis 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。目前由VMware主持开发工作。 1.Redis 特点 所用语言:C/C++特点:运行异常快使用许可: BSD协议:类 Telnet有硬盘存储支持的内存数据库,但自2.0版本以后可以将数据交换到硬盘(注意, 2.4以后版本不支持该特性!)Master-slave复制(见编注3)虽然采用简单数据或以键值索引的哈希表,但也支持复杂操作,例如 ZREVRANGEBYSCORE。INCR & co (适合计算极限值或统计数据)支持 sets(同时也支持 union/diff/inter)支持列表(同时也支持队列;阻塞式 pop操作)支持哈希表(带有多个域的对象)支持排序 sets(高得分表,适用于范围查询)Redis支持事务支持将数据设置成过期数据(类似快速缓冲区设计)Pub/Sub允许用户实现消息机制 2. Redis 优势 1)非常丰富的数据结构; 2.)Redis提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断; 3.)数据存在内存中,读写非常的高速,可以达到10w/s的频率。 3.Redis 缺点 1) Redis3.0后才出来官方的集群方案,但仍存在一些架构上的问题; 2.)持久化功能体验不佳——通过快照方法实现的话,需要每隔一段时间将整个数据库的数据写到磁盘上,代价非常高;而aof方法只追踪变化的数据,类似于mysql的binlog方法,但追加log可能过大,同时所有操作均要重新执行一遍,恢复速度慢; 3)由于是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然redis本身有key过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。 4.Redis 应用场景 最佳应用场景:适用于数据变化快且数据库大小可遇见(适合内存容量)的应用程序。 例如:微博、数据分析、实时数据搜集、实时通讯等。
文章
Java  ·  数据库  ·  NoSQL  ·  MongoDB  ·  存储  ·  分布式数据库  ·  Redis  ·  分布式计算  ·  Hbase  ·  Hadoop
2018-08-29
MongoDB、Hbase、Redis等NoSQL优劣势、应用场景
NoSQL的四大种类 NoSQL数据库在整个数据库领域的江湖地位已经不言而喻。在大数据时代,虽然RDBMS很优秀,但是面对快速增长的数据规模和日渐复杂的数据模型,RDBMS渐渐力不从心,无法应对很多数据库处理任务,这时NoSQL凭借易扩展、大数据量和高性能以及灵活的数据模型成功的在数据库领域站稳了脚跟。 目前大家基本认同将NoSQL数据库分为四大类:键值存储数据库,文档型数据库,列存储数据库和图形数据库,其中每一种类型的数据库都能够解决关系型数据不能解决的问题。在实际应用中,NoSQL数据库的分类界限其实没有那么明显,往往会是多种类型的组合体。 主流nosql的详解:MongoDB、Hbase、Redis MongoDB MongoDB 是一个高性能,开源,无模式的文档型数据库,开发语言是C++。它在许多场景下可用于替代统的关系型数据库或键/值存储方式。 1.MongoDB特点 所用语言:C++ 特点:保留了SQL一些友好的特性(查询,索引)。 使用许可: AGPL(发起者: Apache) 协议: Custom, binary( BSON) Master/slave复制(支持自动错误恢复,使用 sets 复制) 内建分片机制 支持 javascript表达式查询 可在服务器端执行任意的 javascript函数 update-in-place支持比CouchDB更好 在数据存储时采用内存到文件映射 对性能的关注超过对功能的要求 建议最好打开日志功能(参数 —journal) 在32位操作系统上,数据库大小限制在约2.5Gb 空数据库大约占 192Mb 采用 GridFS存储大数据或元数据(不是真正的文件系统) 2.MongoDB优点: 1)更高的写负载,MongoDB拥有更高的插入速度。 2)处理很大的规模的单表,当数据表太大的时候可以很容易的分割表。 3)高可用性,设置M-S不仅方便而且很快,MongoDB还可以快速、安全及自动化的实现节点 (数据中心)故障转移。 4)快速的查询,MongoDB支持二维空间索引,比如管道,因此可以快速及精确的从指定位置 获取数据。MongoDB在启动后会将数据库中的数据以文件映射的方式加载到内存中。如果内 存资源相当丰富的话,这将极大地提高数据库的查询速度。 5)非结构化数据的爆发增长,增加列在有些情况下可能锁定整个数据库,或者增加负载从而 导致性能下降,由于MongoDB的弱数据结构模式,添加1个新字段不会对旧表格有任何影响, 整个过程会非常快速。 3.MongoDB缺点: 1)不支持事务。 2)MongoDB占用空间过大 。 3)MongoDB没有成熟的维护工具。 4.MongoDB应用场景 1.)适用于实时的插入、更新与查询的需求,并具备应用程序实时数据存储所需的复制及高度伸缩性; 2) 非常适合文档化格式的存储及查询; 3.)高伸缩性的场景:MongoDB 非常适合由数十或者数百台服务器组成的数据库。 4.)对性能的关注超过对功能的要求。 HBase HBase 是 Apache Hadoop 中的一个子项目,属于 bigtable 的开源版本,所实现的语言为Java(故依赖 Java SDK)。HBase 依托于 Hadoop 的 HDFS(分布式文件系统)作为最基本存储基础单元。 1.HBase 特点: 所用语言: Java 特点:支持数十亿行X上百万列 使用许可: Apache 协议:HTTP/REST (支持 Thrift,见编注4) 在 BigTable之后建模 采用分布式架构 Map/reduce 对实时查询进行优化 高性能 Thrift网关 通过在server端扫描及过滤实现对查询操作预判 支持 XML, Protobuf, 和binary的HTTP Cascading, hive, and pig source and sink modules 基于 Jruby( JIRB)的shell 对配置改变和较小的升级都会重新回滚 不会出现单点故障 堪比MySQL的随机访问性能 3. HBase 优点 1) 存储容量大,一个表可以容纳上亿行,上百万列; 2.)可通过版本进行检索,能搜到所需的历史版本数据; 3.)负载高时,可通过简单的添加机器来实现水平切分扩展,跟Hadoop的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性能(MapReduce); 4.)在第3点的基础上可有效避免单点故障的发生。 4.HBase 缺点 1. 基于Java语言实现及Hadoop架构意味着其API更适用于Java项目; 2. node开发环境下所需依赖项较多、配置麻烦(或不知如何配置,如持久化配置),缺乏文档; 3. 占用内存很大,且鉴于建立在为批量分析而优化的HDFS上,导致读取性能不高; 4. API相比其它 NoSql 的相对笨拙。 5.HBase 适用场景 1)bigtable类型的数据存储; 2)对数据有版本查询需求; 3)应对超大数据量要求扩展简单的需求。 Redis Redis 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。目前由VMware主持开发工作。 1.Redis 特点: 所用语言:C/C++ 特点:运行异常快 使用许可: BSD 协议:类 Telnet 有硬盘存储支持的内存数据库, 但自2.0版本以后可以将数据交换到硬盘(注意, 2.4以后版本不支持该特性!) Master-slave复制(见编注3) 虽然采用简单数据或以键值索引的哈希表,但也支持复杂操作,例如 ZREVRANGEBYSCORE。 INCR & co (适合计算极限值或统计数据) 支持 sets(同时也支持 union/diff/inter) 支持列表(同时也支持队列;阻塞式 pop操作) 支持哈希表(带有多个域的对象) 支持排序 sets(高得分表,适用于范围查询) Redis支持事务 支持将数据设置成过期数据(类似快速缓冲区设计) Pub/Sub允许用户实现消息机制 2. Redis 优势 1)非常丰富的数据结构; 2.)Redis提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断; 3.)数据存在内存中,读写非常的高速,可以达到10w/s的频率。 3.Redis 缺点 1) Redis3.0后才出来官方的集群方案,但仍存在一些架构上的问题; 2.)持久化功能体验不佳——通过快照方法实现的话,需要每隔一段时间将整个数据库的数据写到磁盘上,代价非常高;而aof方法只追踪变化的数据,类似于mysql的binlog方法,但追加log可能过大,同时所有操作均要重新执行一遍,恢复速度慢; 3)由于是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然redis本身有key过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。 4.Redis 应用场景: 最佳应用场景:适用于数据变化快且数据库大小可遇见(适合内存容量)的应用程序。 例如:微博、数据分析、实时数据搜集、实时通讯等。 原文发布时间为:2018-10-11 本文作者:yuer 本文来自云栖社区合作伙伴“终端研发部”,了解相关信息可以关注“终端研发部”。
文章
Java  ·  数据库  ·  NoSQL  ·  MongoDB  ·  存储  ·  分布式数据库  ·  Redis  ·  Hbase  ·  分布式计算  ·  Hadoop
2018-10-11
MongoDB、Hbase、Redis等NoSQL优劣势、应用场景
NoSQL的四大种类 NoSQL数据库在整个数据库领域的江湖地位已经不言而喻。在大数据时代,虽然RDBMS很优秀,但是面对快速增长的数据规模和日渐复杂的数据模型,RDBMS渐渐力不从心,无法应对很多数据库处理任务,这时NoSQL凭借易扩展、大数据量和高性能以及灵活的数据模型成功的在数据库领域站稳了脚跟。 目前大家基本认同将NoSQL数据库分为四大类:键值存储数据库,文档型数据库,列存储数据库和图形数据库,其中每一种类型的数据库都能够解决关系型数据不能解决的问题。在实际应用中,NoSQL数据库的分类界限其实没有那么明显,往往会是多种类型的组合体。 主流nosql的详解:MongoDB、Hbase、Redis MongoDB MongoDB 是一个高性能,开源,无模式的文档型数据库,开发语言是C++。它在许多场景下可用于替代统的关系型数据库或键/值存储方式。 1.MongoDB特点 所用语言:C++ 特点:保留了SQL一些友好的特性(查询,索引)。 使用许可: AGPL(发起者: Apache) 协议: Custom, binary( BSON) Master/slave复制(支持自动错误恢复,使用 sets 复制) 内建分片机制 支持 javascript表达式查询 可在服务器端执行任意的 javascript函数 update-in-place支持比CouchDB更好 在数据存储时采用内存到文件映射 对性能的关注超过对功能的要求 建议最好打开日志功能(参数 --journal) 在32位操作系统上,数据库大小限制在约2.5Gb 空数据库大约占 192Mb 采用 GridFS存储大数据或元数据(不是真正的文件系统) 2.MongoDB优点: 1)更高的写负载,MongoDB拥有更高的插入速度。 2)处理很大的规模的单表,当数据表太大的时候可以很容易的分割表。 3)高可用性,设置M-S不仅方便而且很快,MongoDB还可以快速、安全及自动化的实现节点 (数据中心)故障转移。 4)快速的查询,MongoDB支持二维空间索引,比如管道,因此可以快速及精确的从指定位置 获取数据。MongoDB在启动后会将数据库中的数据以文件映射的方式加载到内存中。如果内 存资源相当丰富的话,这将极大地提高数据库的查询速度。 5)非结构化数据的爆发增长,增加列在有些情况下可能锁定整个数据库,或者增加负载从而 导致性能下降,由于MongoDB的弱数据结构模式,添加1个新字段不会对旧表格有任何影响, 整个过程会非常快速。 3.MongoDB缺点: 1)不支持事务。 2)MongoDB占用空间过大 。 3)MongoDB没有成熟的维护工具。 4.MongoDB应用场景 1.)适用于实时的插入、更新与查询的需求,并具备应用程序实时数据存储所需的复制及高度伸缩性; 2) 非常适合文档化格式的存储及查询; 3.)高伸缩性的场景:MongoDB 非常适合由数十或者数百台服务器组成的数据库。 4.)对性能的关注超过对功能的要求。 HBase HBase 是 Apache Hadoop 中的一个子项目,属于 bigtable 的开源版本,所实现的语言为Java(故依赖 Java SDK)。HBase 依托于 Hadoop 的 HDFS(分布式文件系统)作为最基本存储基础单元。 1.HBase 特点: 所用语言: Java 特点:支持数十亿行X上百万列 使用许可: Apache 协议:HTTP/REST (支持 Thrift,见编注4) 在 BigTable之后建模 采用分布式架构 Map/reduce 对实时查询进行优化 高性能 Thrift网关 通过在server端扫描及过滤实现对查询操作预判 支持 XML, Protobuf, 和binary的HTTP Cascading, hive, and pig source and sink modules 基于 Jruby( JIRB)的shell 对配置改变和较小的升级都会重新回滚 不会出现单点故障 堪比MySQL的随机访问性能 3. HBase 优点 1) 存储容量大,一个表可以容纳上亿行,上百万列; 2.)可通过版本进行检索,能搜到所需的历史版本数据; 3.)负载高时,可通过简单的添加机器来实现水平切分扩展,跟Hadoop的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性能(MapReduce); 4.)在第3点的基础上可有效避免单点故障的发生。 4.HBase 缺点 1. 基于Java语言实现及Hadoop架构意味着其API更适用于Java项目; 2. node开发环境下所需依赖项较多、配置麻烦(或不知如何配置,如持久化配置),缺乏文档; 3. 占用内存很大,且鉴于建立在为批量分析而优化的HDFS上,导致读取性能不高; 4. API相比其它 NoSql 的相对笨拙。 5.HBase 适用场景 1)bigtable类型的数据存储; 2)对数据有版本查询需求; 3)应对超大数据量要求扩展简单的需求。 Redis Redis 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。目前由VMware主持开发工作。 1.Redis 特点: 所用语言:C/C++ 特点:运行异常快 使用许可: BSD 协议:类 Telnet 有硬盘存储支持的内存数据库, 但自2.0版本以后可以将数据交换到硬盘(注意, 2.4以后版本不支持该特性!) Master-slave复制(见编注3) 虽然采用简单数据或以键值索引的哈希表,但也支持复杂操作,例如 ZREVRANGEBYSCORE。 INCR & co (适合计算极限值或统计数据) 支持 sets(同时也支持 union/diff/inter) 支持列表(同时也支持队列;阻塞式 pop操作) 支持哈希表(带有多个域的对象) 支持排序 sets(高得分表,适用于范围查询) Redis支持事务 支持将数据设置成过期数据(类似快速缓冲区设计) Pub/Sub允许用户实现消息机制 2. Redis 优势 1)非常丰富的数据结构; 2.)Redis提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断; 3.)数据存在内存中,读写非常的高速,可以达到10w/s的频率。 3.Redis 缺点 1) Redis3.0后才出来官方的集群方案,但仍存在一些架构上的问题; 2.)持久化功能体验不佳——通过快照方法实现的话,需要每隔一段时间将整个数据库的数据写到磁盘上,代价非常高;而aof方法只追踪变化的数据,类似于mysql的binlog方法,但追加log可能过大,同时所有操作均要重新执行一遍,恢复速度慢; 3)由于是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然redis本身有key过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。 4.Redis 应用场景: 最佳应用场景:适用于数据变化快且数据库大小可遇见(适合内存容量)的应用程序。 例如:微博、数据分析、实时数据搜集、实时通讯等。 欢迎工作一到五年的Java工程师朋友们加入Java架构开发:860113481 群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!
文章
NoSQL  ·  Java  ·  数据库  ·  MongoDB  ·  Redis  ·  存储  ·  分布式数据库  ·  Hbase  ·  分布式计算  ·  Hadoop
2018-10-15
优化总结:有哪些APP启动提速方法?
作者 | 戴铭来源 | 阿里技术公众号一 通过 Universal Links 和 App Links 优化唤端启动体验App 都会存在拉新和导流的诉求,如何提高这些场景下的用户体验呢?这里会用到唤端技术。包含选择什么样的换端协议,我们先看看唤端路径,如下:唤端的协议分为自定义协议和平台标准协议,自定义协议在 iOS 端会有系统提示弹框,在 Android 端 chrome 25 后自定义协议失效,需用 Intent 协议包装才能打开 App。如果希望提高体验最好使用平台标准协议。平台标准协议在 iOS 平台叫 Universal Links,在 iOS 9 开始引入的,所以 iOS 9 及以上系统都支持,如果用户安装了要跳的 App 就会直接跳到 App,不会有系统弹框提示。相对应的 Android 平台标准协议叫 App Links,Android 6 以上都支持。这里需要注意的是 iOS 的 Universal Links 不支持自动唤端,也就是页面加载后自动执行唤端是不行的,需要用户主动点击进行唤端。对于自定义协议和平台标准协议在有些 App 里是遇到屏蔽或者那些 App 自定义弹窗提示,这就只能通过沟通加白来解决了。另外对于启动时展示 H5 启动页,或唤端跳转特定功能页,可以将拦截判断置前,判断出启动去往功能页,优先加载功能页的任务,主图相关任务项延后再加载,以提升启动到特定页面的速度。二 H5启动页现在 App 启动会在有活动时先弹出活动运营 H5 页面提高活动曝光率。但如果 H5 加载慢势必非常影响启动的体验。iOS 的话可以使用 ODR(On-Demand Resources) 在安装后先下载下来,点击启动前实际上就可以直接加载本地的了。ODR 安装后立刻下载的模式,下载资源会被清除,所以需要将下载内容移动到自定义的地方,同时还需要做自己兜底的下载来保证在 On-Demand Resources 下载失败时,还能够再从自己兜底服务器上拉下资源。On-Demand Resources 还能够放很多资源,甚至包括脚本代码的预加载,可以减少包体积。由于使用的是苹果服务器,还能够减少 CDN 产生的峰值成本。如果不使用 On-Demand Resources 也可以对 WKWebView 进行预加载,虽然安装后第一次还是需要从服务器上加载一次,不过后面就可以从本地快速读取了。iOS 有三套方案,一套是通过 WKBrowsingContextController 注册 scheme,使用 URLProtocol 进行网络拦截。第二套是基于 WKURLSchemeHandler 自定义 scheme 拦截请求。第三套是在本地搭建 local server,拦截网络请求重定向到本地 server。第三套搭建本地 server 成本高,启动 server 比较耗时。第二套 WKURLSchemeHandler 使用自定义 scheme,对于 H5 适配成本很高,而且需要 iOS 11 以上系统支持。第一套方案是使用了 WKBrowsingContextController 的 registerSchemeForCustomProtocol: 这个方法,这个方法的参数设置为 http 或 https 然后执行,后面这类 scheme 就能够被 NSURLProtocol 处理了,具体实现可以在这里[1]看到。Android 通过系统提供的资源拦截Api即可实现加载拦截,拦截后根据请求的url识别资源类型,命中后设置对应的mimeType、encoding、fileStream即可。三 下载速度App 安装前的下载速度也直接影响到了用户从选择你的 App 到使用的体验,如果下载大小过大,用户没有耐心等待,可能就放弃了你的 App,4G5G 环境下超 200MB 会弹窗提示是否继续下载,严重影响转化率。因此还对下载大小做了优化,将 __TEXT 字段迁移到自定义段,使得 iPhone X 以前机器的下载大小减少了50M,几乎少了1/3的大小,这招之所以对 iPhone X 以前机器管用的原因是因为先前机器是按照先加密再压缩,压缩率低,而之后机器改变了策略因此下载大小就会大幅减少。Michael Eisel 这篇博客《One Quick Way to Drastically Reduce your iOS App’s Download Size》[2] 提出了这套方案,此方案已经线上验证,你可以立刻应用到自己应用中,提高老机器下载速度。Michael Eisel 还用 Swift 包装了 simdjson[3] 写了个库 ZippyJSONDecoder[4] 比系统自带 JSONDecoder 快三倍。人类对速度的追求是没有止境的,最近 YY 大神 ibireme 也在写 JSON 库 YYJSON[5] 速度比 simdjson 还快。Michael 还写个了提速构建的自制链接器 zld[6],项目说明还描述了如何开发定制自己的链接器。还有主线程阻塞(ANR)检测的 swift 类 ANRChecker[7],还有通过 hook 方式记录系统错误日志的例子[8]展示如何通过截获自动布局错误,函数是 UIViewAlertForUnsatisfiableConstraints ,malloc 问题替换函数为 malloc_error_break 即可。Michael 的这些性能问题处理手段非常实用,真是个宝藏男孩。通过每月新增激活量、浏览到新增激活转换率、下载到激活转换率、转换率受体积因素影响占比、每个用户获取成本,使用公式计算能够得到每月成本收益,把你们公司对应具体参数数值套到公式中,算出来后你会发现如果降低了50多MB,每月就会有非常大的收益。对于 Android 来说,很多功能是可以放在云端按需下载使用,后面的方向是重云轻端,云端一体,打通云端链路。下载和安装完成后,就要分析 App 开始启动时如何做优化了,我接下来跟你说说 Android 启动 so 库加载如何做监控和优化。四 Android so 库加载优化1 编译阶段 - 静态分析优化依托自动化构建平台,通过构建配置实现对源码模块的灵活配置,进行定制化编译。-ffunction-sections -fdata-sections // 实现按需加载 -fvisibility=hidden -fvisibility-inlines-hidden // 实现符号隐藏这样可以避免无用模块的引入,效果如下图:2 运行阶段 - hook分析优化Android Linker 调用流程如下:注意,find_library 加载成功后返回 soinfo 对象指针,然后调用其 call_constructors 来调用 so 的 init_array。call_constructors 调用 call_array,其内部循环调用 call_funtion 来访问 init_array 数组的调用。高德 Android 小伙伴们基于 frida-gum[9] 的 hook 引擎开发了线下性能监控工具,可以 hook c++ 库,支持 macos、android、ios,针对 so 的全局构造时间和链接时间进行 hook,对关键 so 加载的关键节点耗时进行分析。dlopen 相关 hook 监控点如下:static target_func_t android_funcs_22[] = { {"__dl_dlopen", 0, (void *)my_dlopen}, {"__dl_ZL12find_libraryPKciPK12android_dlextinfo", 0, (void *)my_find_library}, {"__dl_ZN6soinfo16CallConstructorsEv", 0, (void *)my_soinfo_CallConstructors}, {"__dl_ZN6soinfo9CallArrayEPKcPPFvvEjb", 0, (void *)my_soinfo_CallArray} }; static target_func_t android_funcs_28[] = { {"__dl_Z9do_dlopenPKciPK17android_dlextinfoPKv", 0, (void *)my_do_dlopen_28}, {"__dl_Z14find_librariesP19android_namespace_tP6soinfoPKPKcjPS2_PNSt3__16vectorIS2_NS8_9a"}, {"__dl_ZN6soinfo17call_constructorsEv", 0, (void *)my_soinfo_CallConstructors}, {"__dl_ZL10call_arrayIPFviPPcS1_EEvPKcPT_jbS5_", 0, (void *)my_call_array_28<constructor_func>}, {"__dl_ZN6soinfo10link_imageERK10LinkListIS_19SoinfoListAllocatorES4_PK17android_dlextin"}, {"__dl_g_argc", 0, 0}, {"__dl_g_argv", 0, 0}, {"__dl_g_envp", 0, 0} };Android 版本不同对应 hook 方法有所不同,要注意当 so 有其他外部链接依赖时,针对 dlopen 的监控数据,不只包括自身部分,也包括依赖的 so 部分。在这种情况下,so 加载顺序也会产生很大的影响。JNI_OnLoad 的 hook 监控代码如下:#ifdef ABTOR_ANDROID jint my_JNI_ONLoad(JavaVM* vm, void* reserved) { asl::HookEngine::HoolContext *ctx = asl::HookEngine::getHookContext(); uint64_t start = PerfUtils::getTickTime(); jint res = asl::CastFuncPtr(my_JNI_OnLoad, ctx->org_func)(vm, reserved); int duration = (int)(PerfUtils::getTickTime() - start); LibLoaderMonitorImpl *monitor = (LibLoaderMonitorImpl*)LibLoaderMonitor::getInstance(); monitor->addOnloadInfo(ctx->user_data, duration); return res; } #endif如上代码所示,linker 的 dlopen 完成加载,然后调用 dlsym 来调用目标 so 的 JNI_OnLoad,完成 JNI 涉及的初始化操作。加载 so 需要注意并行出现 loadLibrary0 锁的问题,这样会让多线程发生等锁现象。可以减少并发加载,但不能简单把整个加载过程放到串行任务里,这样耗时可能会更长,并且没法充分利用资源。比较好的做法是,将耗时少的串行起来同时并行耗时长的 so 加载。至此完成了 so 的初始化和链接的监控。说完 Android,那么 iOS 的加载是怎样的,如何优化呢?我接着跟你说。五 App 加载dyld_start 之前做了什么,dyld_start 是谁调用的,通过查看 xnu 的源码[10]可以理出,当 App 点击后会通过_mac_execve 函数 fork 进程,加载解析 Mach-O 文件,调用 exec_activate_image() 开始激活 image 的过程。先根据 image 类型来选择 imgact,开始 load_machfile,这个过程会先解析 Mach-O,解析后依据其中的 LoadCommand 启动 dyld。最后使用 activate_exec_state() 处理结构信息,thread_setentrypoint() 设置 entry_point App的入口点。_dyld_start 之后要少些动态库,因为链接耗时;少些 +load、C 的 constructor 函数和 C++ 静态对象,因为这些会在启动阶段执行,多了就会影响启动时间。因此,没有用的代码就需要定期清理和线上监控。通过元类中flag的方式进行监控然后定期清理。六 iOS 主线程方法调用时长检测+load 方法时间统计,使用运行时 swizzling 的方式,将统计代码放到链接顺序的最前面即可。静态初始化函数在 DATA 的 mod_init_func 区,先把里面原始函数地址保存,前后加上自定义函数记录时间。在 Linux上 有 strace 工具,还有库跟踪工具 ltrace,OSX 有包装了 dtrace 的 instruments 和 dtruss 工具,不过在某些场景需求下不好用。objc_msgSend 实际上会通过在类对象中查找选择器到函数的映射来重定向执行到实现函数。一旦它找到了目标函数,它就会简单地跳转到那里,而不必重新调整参数寄存器。这就是为什么我把它称为路由机制,而不是消息传递。Objective-C 的一个方法被调用时,堆栈和寄存器是为 objc_msgSend 调用配置的,objc_msgSend 路由执行。objc_msgSend 会在类对象中查找函数表对应定向到的函数,找到目标函数就跳转,参数寄存器不会重新调整。因此可以在这里 hook 住做统一处理。hook objc_msgSend 还可以获取启动方法列表,用于二进制重排方案中所需要的 AppOrderFiles,不过 AppOrderFiles 还可以通过 Clang SanitizerCoverage 获得,具体可以看 Michael Eisel 这个宝藏男孩这篇博客《Improving App Performance with Order Files》[11] 的介绍。objc_msgSend 可以通过 fishhook 指定到你定义的 hook 方法中,也可以使用创建跳转 page 的方式来 hook。做法是先用 mmap 分配一个跳转的 page,这个内存后面会用来执行原函数,使用特殊指令集将CPU重定向到内存的任意位置。创建一个内联汇编函数用来放置跳转的地址,利用 C 编译器自动复制跳转 page 的结构,指向 hook 的函数,之前把指令复制到跳转 page 中。ARM64 是一个 RISC 架构,需要根据指令种类检查分支指令。可以在 _objc_msgSend[12] 里找到 b 指令的检查。相关代码如下:ENTRY _objc_msgSend MESSENGER_START cmp x0, #0 // nil check and tagged pointer check b.le LNilOrTagged // (MSB tagged pointer looks negative) ldr x13, [x0] // x13 = isa and x9, x13, #ISA_MASK // x9 = class检查通过就可以用这个指针读取偏移量,并修改指向跳转地址,跳转page完成,hook 函数就可以被调用了。接下来看下 hook _objc_msgSend 的函数,这个我在以前博客《深入剖析 iOS 性能优化》[13] 写过,不过多赘述,只做点补充说明。从这里的源码[14]可以看实现,其中的attribute((naked)) 表示无参数准备和栈初始化, asm 表示其后面是汇编代码,volatile 是让后面的指令避免被编译优化到缓存寄存器中和改变指令顺序,volatile 使其修饰变量被访问时都会在共享内存里重新读取,变量值变化时也能写到共享内存中,这样不同线程看到的变量都是一个值。如果你发现不加 volatile 也没有问题,你可以把编译优化选项调到更优试试。stp表示操作两个寄存器,中括号部分表示压栈存入sp偏移地址,!符号表合并了压栈指令。save() 的作用是把传递参数寄存器入栈保存,call(b, value)用来跳到指定函数地址,call(blr, &before_objc_msgSend) 是调用原 _objc_msgSend 之前指定执行函数,call(blr, orig_objc_msgSend) 是调用 objc_msgSend 函数,call(blr, &after_objc_msgSend) 是调用原 _objc_msgSend 之后指定执行函数。before_objc_msgSend 和 after_objc_msgSend 分别记录时间,差值就是方法调用执行的时长。调用之间通过 save() 保存参数,通过 load() 来读取参数。call 的第一个参数是blr,blr 是指跳转到寄存器地址后会返回,由于 blr 会改变 lr 寄存器X30的值,影响 ret 跳到原方法调用方地址,崩溃堆栈找方法调研栈也依赖 lr 在栈上记录的地址,所以需要在 call() 之前对 lr 进行保存,call() 都调用完后再进行恢复。跳转到hook函数,hook函数可以执行我们自定义的事情,完成后恢复CPU状态。七 进入主图后的优化进入主图后,用户就可以点击按钮进入不同功能了,是否能够快速响应按钮点击操作也是启动体验感知很重要的事情。按钮点击的两个事件 didTouchUp 和 didTouchDown 之间也会有延时,因此可以在 didTouchDown 时在主线程先 async 初始化下一个 VC,把初始化提前完成,这样做可以提高50ms-100ms的速度,甚至更多,具体收益依赖当前主线程繁忙情况和下一个页面 viewDidLoad 等初始化方法里的耗时,启动阶段主线程一定不会闲,即使点击后主线程阻塞,使用 async 也能保证下一个页面的初始化不会停。八 线程调度和任务编排1 整体思路对于任务编排有种打法,就是先把所有任务滞后,然后再看哪个是启动开始必须要加载的。效果立竿见影,很快就能看到最好的结果,后面就是反复斟酌,严格把关谁才是必要的启动任务了。启动阶段的任务,先理出相关依赖关系,在框架中进行配置,有依赖的任务有序执行,无依赖独立任务可以在非密集任务执行期串行分组,组内并发执行。这里需要注意的是Android 的 SharedPreferences 文件加载导致的 ContextImpl 锁竞争,一种解法是合并文件,不过后期维护成本会高,另一种是使用串行任务加载。你可能会疑惑,我没怎么用锁,那是不是就不会有锁等待的问题了。其实不然,比如在 iOS中,dispatch_once 里有 dispatch_atomic_barrier 方法,此方法就有锁的作用,因此锁其实存在各个 API 之下,如不用工具去做检查,有时还真不容易发现这些问题。有 IO 操作的任务除了锁等待问题,还有效率方面也需要特别注意,比如 iOS 的 Fundation 库使用的是 NSData writeToFile:atomically: 方法,此方法会调用系统提供的 fsync 函数将文件描述符 fd 里修改的数据强写到磁盘里,fsync 相比较与 fcntl 效率高但写入物理磁盘会有等待,可能会在系统异常时出现写入顺序错乱的情况。系统提供的 write() 和 mmap() 函数都会用到内核页缓存,是否写入磁盘不由调用返回是否成功决定,另外 c 的标准库的读写 API fread 和 fwrite 还会在系统内核页缓存同步对应由保存了缓冲区基地址的 FILE 结构体的内部缓冲区。因此启动阶段 IO 操作方法需要综合做效率、准确和重要性三方面因素的权衡考虑,再进行有 IO 操作的任务编排。针对初始化耗时的库,比如埋点库,可以延后初始化,先将所需要的数据存储到内存中,待到埋点库初始化时再进行记录。对一些主图上业务网络可以延后请求,比如闪屏、消息盒子、主图天气、限行控件数据请求、开放图层数据、Wi-Fi信息上报请求等。2 多线程共享数据的问题并发任务编排缺少一个统一的异步编程模型,并发通信共享数据方式的手段,比如代理和通知会让处理到处飞,闭包这种匿名函数排查问题不方便,而且回调中套回调前期设计后期维护和理解很困难,调试、性能测试也乱。这些通过回调来处理异步,不光复杂难控,还有静态条件、依赖关系、执行顺序这样的额外复杂度,为了解决这些额外复杂度,还需要使用更多的复杂机制来保证线程安全,比如使用低效的 mutex、超高复杂度的读写锁、双重检查锁定、底层原子操作或信号量的方式来保护数据,需要保证数据是正确锁住的,不然会有内存问题,锁粒度要定还要注意避免死锁。并发线程通信一般都会使用 libdispatch(GCD)这样的共享数据方式来处理,也就异步再回调的方式。libdispatch 的 async 策略是把任务的 block 放到队列链表,使用时会在底层的线程池里找可用线程,有就直接用,没有就新建一个线程(参看 libdispatch[15] 源码,监控线程池 workqueue.c,队列调度 queue.c),使用这样的策略来减少线程创建。当并发任务多时,比如启动期间,即使线程没爆,但 CPU 在各个线程切换处理任务时也是会有时间开销的,每次切换线程,CPU 都需要执行调度程序增加调度成本和增加 CPU 使用率,并且还容易出现多线程竞争问题。单次线程切换看起来不长,但整个启动,切换频率高的话,整体时间就会增大。多线程的问题以及处理方式,带来了开发和排查问题的复杂性,以及出现问题机率的提高,资源和功能云化也有类似的问题,云化和本地的耦合依赖、云化之间的关系处理、版本兼容问题会带来更复杂的开发以及测试挑战,还有问题排查的复杂度。这些都需要去做权衡,对基础建设方案提出了更高的要求,对容错回滚的响应速度也有更高的要求。这里有个 book[16] 专门来说并行编程难的,并告诉你该怎么做。这里有篇文章[17]列出了苹果公司 libdispatch 的维护者 Pierre Habouzit 关于 libdispatch 的讨论邮件。说了一堆共享数据方式的问题,没有体感,下面我说个最近碰到的多线程问题,你也看看排查有多费劲。3 一个具体多线程问题排查思路问题是工程引入一个系统库,暂叫 A 库,出现的问题现象是 CoreMotion 不回调,网络请求无法执行,除了全局并发队列会 pending block 外主线程和其它队列工作正常。第一阶段,排查思路看是否跟我们工程相关,首先看是不是各个系统都有此问题,发现 iOS14 和 iOS13 都有问题。然后把A库放到一个纯净 Demo 工程中,发现没有出问题了。基于上面两种情况,推测只有将A库引入我们工程才会出现问题。在纯净 Demo 工程中,A库使用时 CPU 会占用60%-80%,集成到我们工程后涨到100%,所以下个阶段排查方向就是性能。第二阶段的打法是看是否是由性能引起的问题。先在纯净工程中创建大量线程,直到线程打满,然后进行大量浮点运算使 CPU 到100%,但是没法复现,任务通过 libdispatch 到全局并发队列能正常工作。怎么在 Demo 里看到出线程已爆满了呢?libdispatch 可以使用线程数是有上限的,在 libdispatch 的源码[18]里可以看到 libdispatch 的队列初始化时使用 pthread 线程池相关代码:#if DISPATCH_USE_PTHREAD_POOL static inline void _dispatch_root_queue_init_pthread_pool(dispatch_queue_global_t dq, int pool_size, dispatch_priority_t pri) { dispatch_pthread_root_queue_context_t pqc = dq->do_ctxt; int thread_pool_size = DISPATCH_WORKQ_MAX_PTHREAD_COUNT; if (!(pri & DISPATCH_PRIORITY_FLAG_OVERCOMMIT)) { thread_pool_size = (int32_t)dispatch_hw_config(active_cpus); } if (pool_size && pool_size < thread_pool_size) thread_pool_size = pool_size; ... // 省略不相关代码 }如上面代码所示,dispatch_hw_config 会用 dispatch_source 来监控逻辑 CPU、物理 CPU、激活 CPU 的情况计算出线程池最大线程数量,如果当前状态是 DISPATCH_PRIORITY_FLAG_OVERCOMMIT,也就是会出现 overcommit 队列时,线程池最大线程数就按照 DISPATCH_WORKQ_MAX_PTHREAD_COUNT 这个宏定义的数量来,这个宏对应的值是255。因此通过查看是否出现 overcommit 队列可以看出线程池是否已满。什么时候 libdispatch 会创建一个新线程?当 libdispatch 要执行队列里 block 时会去检查是否有可用的线程,发现有可用线程时,在可用线程去执行 block,如果没有,通过 pthread_create 新建一个线程,在上面执行,函数关键代码如下:static void _dispatch_root_queue_poke_slow(dispatch_queue_global_t dq, int n, int floor) { ... // 如果状态是overcommit,那么就继续添加到pending bool overcommit = dq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT; if (overcommit) { os_atomic_add2o(dq, dgq_pending, remaining, relaxed); } else { if (!os_atomic_cmpxchg2o(dq, dgq_pending, 0, remaining, relaxed)) { _dispatch_root_queue_debug("worker thread request still pending for " "global queue: %p", dq); return; } } ... t_count = os_atomic_load2o(dq, dgq_thread_pool_size, ordered); do { can_request = t_count < floor ? 0 : t_count - floor; // 是否有可用 if (remaining > can_request) { _dispatch_root_queue_debug("pthread pool reducing request from %d to %d", remaining, can_request); os_atomic_sub2o(dq, dgq_pending, remaining - can_request, relaxed); remaining = can_request; } // 线程满 if (remaining == 0) { _dispatch_root_queue_debug("pthread pool is full for root queue: " "%p", dq); return; } } while (!os_atomic_cmpxchgvw2o(dq, dgq_thread_pool_size, t_count, t_count - remaining, &t_count, acquire)); ... do { _dispatch_retain(dq); // 在 _dispatch_worker_thread 里取任务并执行 while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) { if (r != EAGAIN) { (void)dispatch_assume_zero(r); } _dispatch_temporary_resource_shortage(); } } while (--remaining); ... }如上面代码所示,can_request 表示可用线程数,通过当前最大可用线程数减去已用线程数获得,赋给 remaining后,用来判断线程是否满和控制线程创建。dispatch_worker_thread 会取任务并执行。当 libdispatch 使用的线程池中线程过多,并且有 pending 标记,当等待超时,也就是 libdispatch 里 DISPATCH_CONTENTION_USLEEP_MAX 宏定义的时间后,也会触发创建一个新的待处理线程。libdispatch 对应函数关键代码如下:static bool __DISPATCH_ROOT_QUEUE_CONTENDED_WAIT__(dispatch_queue_global_t dq, int (*predicate)(dispatch_queue_global_t dq)) { ... bool pending = false; do { ... if (!pending) { // 添加pending标记 (void)os_atomic_inc2o(dq, dgq_pending, relaxed); pending = true; } _dispatch_contention_usleep(sleep_time); ... sleep_time *= 2; } while (sleep_time < DISPATCH_CONTENTION_USLEEP_MAX); ... if (pending) { (void)os_atomic_dec2o(dq, dgq_pending, relaxed); } if (status == DISPATCH_ROOT_QUEUE_DRAIN_WAIT) { _dispatch_root_queue_poke(dq, 1, 0); // 创建新线程 } return status == DISPATCH_ROOT_QUEUE_DRAIN_READY; }如上所示,在创建新的待处理线程后,会退出当前线程,负载没了就会去用新建的线程。接下来使用 Instruments 进行分析 Trace 文件,发现启动阶段立刻开始使用A库的话,CPU 会突然上升,如果使用 A 库稍晚些,CPU 使用率就是稳定正常的。这说明在第一个阶段性能相关结论只是偶现情况才会出现,出问题时,并没有出现系统资源紧张的情况,可以得出并不是性能问题的结论。那么下一个阶段只能从A库的使用和排查我们工程其它功能的问题。第三个阶段的思路是使用功能二分排查法,先排出 A 库使用问题,做法是在使用最简单的 A 库初始化一个页面在首屏也会复现问题。我们的功能主要分为渲染、引擎、网络库、基础功能、业务几个部分。将渲染、引擎、网络库拉出来建个Demo,发现这个 Demo 不会出现问题。那么有问题的就可能在基础功能、业务上。先去掉的功能模块有 CoreMotion、网络、日志模块、定时任务(埋点上传),依然复现。接下来去掉队列里的 libdispatch 任务,队列里的任务主要是由 Operation 和 libdispatch 两种方式放入。其中 Operation 最后是使用 libdispatch 将任务 block 放入队列,期间会做优先级和并发数的判断。对于 libdispatch 可以 Hook 住可以把任务 block 放到队列的 libdispatch 方法,有 dispatch_async、dispatch_after、dispatch_barrier_async、dispatch_apply 这些方法。任务直接返回,还是有问题。推测验证基础能力和业务对出现问题队列有影响,instruments 只能分析线程,无法分析队列,因此需要写工具分析队列情况。接下来进入第四个阶段。先 hook 时截获任务 block 使用的 libdispatch 方法、执行队列名、优先级、做唯一标识的入队时间、当前队列的任务数、还有执行堆栈的信息。通过截获的内容按照时间线看,当出现全局并发队列 pending block 数量堆积时,新的使用 libdispatch 加入的部分任务可以得到执行,也有没执行的,都执行了也会有问题。然后去掉 Operation 的任务:通过日志还能发现 Operation 调用 libdispatch 的任务直接 hook libdispatch 的方法是获取不到的,可能是 Operation 调用方法有变化。另外在无法执行任务的线程上新建的 libdispatch 任务也无法执行,无法执行的 Operation 任务达到所设置的 maxConcurrentOperationCount,对应的 OperationQueue 就会在 Operation 的队列里 pending。由此可以推断出,在局并发队列 pending 的 block 包含了直接使用 libdispatch 的和 Operation 的任务,pending 的任务。因此还需要 hook 住 Operation,过滤掉所有添加到 Operation Queue 的任务,但结果还是复现问题。此时很崩溃,本来做好了一个一个下掉功能的准备(成本高),这时,有同学发现前阶段两个不对的结论。这个阶段定为第五阶段。第一个不对的结论是经 QA 同学长时间多轮测试,只在14.2及以上系统版本有问题,由于只有这个版本才开始有此问题,推断可能是系统 bug;第二个不对的是只有渲染、引擎、网络库的 Demo 再次检查,可复现问题,因此可以针对这个 Demo 进行进一步二分排查。于是,咱们针对两个先前错误结论,再次出发,同步进行验证。对 Demo 排除了网络库依然复现,后排除引擎还是复现,同时使用了自己的示例工程在iOS14.2上复现了问题,和第一阶段纯净Demo的区别是往全局并发队列里方式,官方 Demo 是 Operation,我们的是 libdispatch。因此得出结论是苹果系统升级问题,原因可能在 OperationQueue,问题重现后,不再运行其中的 operation。14.3beta 版还没有解决。五个阶段总结如下图所示:那么看下 Operation 实现,分析下系统 bug 原因。ApportableFoundation[19] 里有Operation 的开源实现 NSOperation.m[20],相比较 GNUstep[21] 和 Cocotron[22] 更完善,可以看到 Operation 如何在 _schedulerRun 函数里通过 libdispatch 的 async 方法将 operation 的任务放到队列执行。Swift 源码[23]里的fundation也有实现 Operation[24],我们看看 _schedule 函数的关键代码:internal func _schedule() { ... // 按优先级顺序执行 for prio in Operation.QueuePriority.priorities { ... while let operation = op?.takeUnretainedValue() { ... let next = operation.__nextPriorityOperation ... if Operation.__NSOperationState.enqueued == operation._state && operation._fetchCachedIsReady(&retest) { if let previous = prev?.takeUnretainedValue() { previous.__nextPriorityOperation = next } else { _setFirstPriorityOperation(prio, next) } ... if __mainQ { queue = DispatchQueue.main } else { queue = __dispatch_queue ?? _synthesizeBackingQueue() } if let schedule = operation.__schedule { if operation is _BarrierOperation { queue.async(flags: .barrier, execute: { schedule.perform() }) } else { queue.async(execute: schedule) } } op = next } else { ... // 添加 } } } ... }上述代码可见,可以看到 _schedule 函数根据 Operation.QueuePriority.priorities 优先级数组顺序,从最高 barrier 开始到 veryHigh、high、normal、low 到最低的 veryLow,根据 operation 属性设置决定 libdispatch 的 queue 是什么类型的,最后通过 async 函数分配到对应的队列上执行。查看 operation 代码更新情况,最新 operation 提交修复了一个问题,commit 在这[25],根据修复问题的描述来看,和 A 库引入导致队列不可添加 OperationQueue 的情况非常类似。修复的地方可以看下图:如图所示,在先前 _schedule 函数里使用 nextOperation 而不用 nextPriorityOperation 会导致主操作列表里的不同优先级操作列表交叉连接,可能会在执行后面操作时被挂起,而 A 库里的 OperationQueue 都是高优的,如果有其它优先级的 OperationQueue 加进来就会出现挂起的问题。从提交记录看,19年6月12日的那次提交变更了很多代码逻辑,描述上看是为了更接近 objc 的实现,changePriority 函数就是那个时候加进去的。提交的 commit 如下图所示:怀疑(只是怀疑,苹果官方并没有说)可能是在 iOS14 引入 swift 版的 Operation,因此这个 Operation 针对 objc 调用做了适配。之所以14.2之前 Operation 重构后的 bug 没有引起问题,可能是当时 A 库的 Queue 优先级还没调高,14.2版本A库的 Queue 优先级开始调高了,所以出现了优先级交叉挂起的情况。从这次排查可以发现,目前对于并发的监测还是非常复杂的。那么并发问题在 iOS 的将来会得到解决吗?4 多线程并行计算模型既然共享数据方式问题多,那还有其它选择吗?实际上在服务端大量使用着 Actor 这样的并行计算模型,在并行世界里,一切都是 actor,actor 就像一个容器,会有自己的状态、行为、串行队列的消息邮箱。actor 之间使用消息来通信,会把消息发到接受消息 actor 的消息邮箱里,消息盒子可并行接受消息,消息的处理是依次进行,当前处理完才处理下一个,消息邮箱这套机制就好像 actor 们的大管家,让 actor 之间的沟通井然有序。有谁是在使用 actor 模型呢?actor 历史悠久,Erlang[26](Elang设计论文),Akka[27](Scala[28] 编写的 Akka actor[29] 系统,Akka 使用多,相对成熟)、Go(使用的 goroutine,基于 CSP[30] 构建)都是基于 actor 模型实现数据隔离。Swift 并发路线图[31]也预示着 Swift 要加入 actor,Chris Lattner 也希望 Swift 能够在多核机器,还有大型服务集群能够得到方便的使用,分布式硬件的发展趋势必定是多核,去共享内存的硬件的,因为共享内存的编程不光复杂而且原子性访问比非原子性要慢近百倍。提案中设计到 actor 的设计是把 actor 设计成一种特殊类,让这个类有引用语义,能形成 map,可以 weak 或 unowned 引用。actor 类中包含一些只有 actor 才有的方法,这些方法提供 actor 编程模型所需安全性。但 actor 类不能继承自非 actor 类,因为这样 actor 状态可能会有机会以不安全的方式泄露。actor 和它的函数和属性之间是静态关系,这样可以通过编译方式避免数据竞争,对数据隔离,如果不是安全访问 actor 属性的上下文,编译器可以处理切换到那个上下文中。对于 actor 隔离会借鉴强制执行对内存的独占访问[32]提案的思想,比如局部变量、inout参数、结构体属性编译器可以分析变量的所有访问,有冲突就可以报错,类属性和全局变量要在运行时可以跟踪在进行的访问,有冲突报错。而全局内存还是没法避免数据竞争,这个需要增加一个全局 actor 保护。按 actor 模型对任务之间通讯重新调整,不用回调代理等手段,将发送消息放到消息邮箱里进行类似 RxSwift 那样 next 的方式一个一个串行传递。说到 RxSwift,那 RxSwift 和 Combine 这样的框架能替代 actor 吗?对这些响应式框架来说解决线程通信只是其中很小的一部分,其还是会面临闭包、调试和维护复杂的问题,而且还要使用响应式编程范式,显然还是有些重了,除非你已经习惯了响应式编程。任务都按 actor 模型方式来写,还能够做到功能之间的解耦,如果是服务器应用,actor 可以布到不同的进程甚至是不同机器上。actor 中消息邮件在同一时间只能处理一个消息,这样等待返回一个值的方式,需要暂停,内部有返回再继续执行,这要怎么实现呢?答案是使用 Coroutine。在 Swift 并发路线提案里还提到了基于 coroutine 的 async/await 语法,这种语法风格已经被广泛采纳,比如Python、Dart、JavaScript 都有实现,这样能够写出简洁好维护的并发代码。上述只是提案,最快也需要两个版本的等待,那么语言上的支持还没有来,怎么能提前享用 coroutine 呢?处理暂停恢复操作,可以使用 context 处理函数 setjmp 和 longjmp,但 setjmp 和 longjmp 较难实现临时切换到不同的执行路径,然后恢复到停止执行的地方,所以服务器用一般都会使用 ucontext 来实现,gnu 的举的例子 GNU C Library: Complete Context Control[33],这个例子在于创建 context 堆栈,swapcontext 来保存 context,这样可以在其它地方能执行回到原来的地方。创建 context 堆栈代码如下:uc[1].uc_link = &uc[0]; uc[1].uc_stack.ss_sp = st1; uc[1].uc_stack.ss_size = sizeof st1; makecontext (&uc[1], (void (*) (void)) f, 1, 1);上面代码中 uc_link 表示的是主 context。保存 context 的代码如下:swapcontext (&uc[n], &uc[3 - n]);但是在 Xcode 里一试,出现错误提示如下:implicit declaration of function 'swapcontext' is invalid in c99原来最新的 POSXI 标准已经没有这个函数了,IEEE Std 1003.1-2001 / Cor 2-2004,应用了项目XBD/TC2/D6/28,标注 getcontext()、makecontext()、setcontext()和swapcontext() 函数过时了。在 POSIX 2004第743页说明了原因,大概意思就是建议使用 pthread 这种系统编程上,后来的 Rust 和 Swift coroutine 的提案里都是使用的系统编程来实现 coroutine,长期看系统编程实现 coroutine 肯定是趋势。那么在 swift 升级之前还有办法在 iOS 用 ucontext 这种轻量级的 coroutine 吗?其实也是有的,可以考虑临时过渡一下。具体可以看看 ucontext 的汇编实现,重新在自己工程里实现出来就可以了。getcontext[34]、setcontext[35]、makecontext[36]、swapcontext[37] 的在 linux 系统代码里能看到。ucontext_t 结构体里的 uc_stack 会记录 context 使用的栈。getcontext() 是把各个寄存器保存到内存结构体里,setcontext() 是把来自 makecontext() 和 getcontext() 的各寄存器恢复到当前 context 的寄存器里。switchcontext() 合并了 getcontext() 和 setcontext()。ucontext_t 的结构体设计如下:如上图所示,ucontext_t 还包含了一个更高层次的 context 封装 uc_mcontext,uc_mcontext 会保存调用线程的寄存器。上图中 eax 是函数入参地址,寄存器值入栈操作代码如下:movl $0, oEAX(%eax) movl %ecx, oECX(%eax) movl %edx, oEDX(%eax) movl %edi, oEDI(%eax) movl %esi, oESI(%eax) movl %ebp, oEBP(%eax)以上代码中 oECX、oEDX 等表示相应寄存器在内存结构体里的位置。esp 指向返回地址值,由 eip 字段记录,代码如下:movl (%esp), %ecx movl %ecx, oEIP(%eax)edx 是 getcontext() 的栈寄存器会记录 ucontext_t.uc_stack.ss_sp 栈顶的值,oSS_SIZE 是栈大小,通过指令addl 可以找到栈底。makecontext() 会根据 ecx 里的参数去设置栈,setcontext() 是 getcontext 的逆操作,设置当前 context,栈顶在 esp 寄存器。轻量级的 coroutine 实现了,下面咱们可以通过 Swift async/await提案[38](已加了编号0296,表示核心团队已经认可,上线可期)看下系统编程的 coroutine 是怎么实现的。Swift async/await 提案中的思路是让开发者编写异步操作逻辑,编译器用来转换和生成所需的隐式操作闭包。可以看作是个语法糖,并像其它实现那样会改变完成处理程序被调用的队列。工作原理类似 try,也不需要捕获 self 的转义闭包。挂起会中断原子性,比如一个串行队列中任务要挂起,让其它任务在一个串行队列中交错运行,因此异步函数最好是不阻塞线程。将异步函数当作一般函数调用,这样的调用会暂时离开线程,等待当前线程任务完成再从它离开的地方恢复执行这个函数,并保证是在先前的actor里执行完成。九 启动性能分析工具1 iOS 官方工具Instruments 中 Time Profiles 中的 Profile 可以方便的分析模块中每个方法的耗时。Time Profiles 中的 Samples 分析将更加准确的显示出 App 启动后每一个 CPU 核心在一个时间片内所执行的代码。如果在模块开发中有以下的需求,可以考虑使用 Samples 分析:希望更精确的分析某个方法具体执行代码的耗时。想知道一个方法到另一个方法的耗时情况(跨方法耗时分析)。MetricKit 2.0 开始加强了诊断特性,通过收集调用栈信息能够方便我们来进行问题的诊断,通过 didReceive 回调 MXMetricPayload 性能数据,可包含 MXSignpostMetric 自定义采集数据,甚至是你捕获不到的崩溃信号的系统强杀崩溃信息传到自己服务器进行分析和报警。2 如何在 iOS 真机和模拟器上实现自动化性能分析苹果有个 usbmux 协议会给自己 macOS 程序和设备进行通信,场景有备份 iPhone 还有真机调试。macOS 对应的是/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/Resources/ 下的 usbmuxd 程序,usbmuxd 是 IPC socket 和 TCP socket 用来进行进程间通信,这里[39]有他的一个开源实现。对于在手机端是 lockdown 来起服务。因此利用 usbmuxd 的协议,就可以自建和设备通信的应用比如 lookin,实现方式可以参考这个 demo[40]。使用 usbmux 协议的 libimobiledevice[41](相当于 Android 的 adb)提供了更多能力,可以获取设备的信息、搭载 ifuse[42] 访问设备文件系统(没越狱可访问照片媒体、沙盒、日志)、与调试服务器连接远程调试。无侵入的库还有 gamebench[43] 也用到了 libimobiledevice。instruments 可以导出 .trace 文件,以前只能用 instruments 打开,Xcode12 提供了 xctrace 命令行工具可以导出可分析的数据。Xcode12 之前的时候是能使用 TraceUtility 这个库,TraceUtility 的做法是链上 Xcode 里 instruments 用的那些库,比如 DVTFoundation 和 InstrumentsKit 等,调用对应的方法去获取.trace文件。使用 libimobiledevice 能构造操作 instruments 的应用,将 instruments 的能力自动化。perfdog 就是使用了libimobiledevice调用了instruments的接口(见接口研究,实现代码)来实现instruments的一些功能,并进行了扩展定制,无侵入的构建本地性能监控并集成到自动测试中出数据,减少人工成本。无侵入的另一个好处就是可以方便用同一套标准看到其他APP的表现情况。要到具体场景去跑 case 还需要流程自动化。Appium 使用的是 Facebook 开发的一套基于 W3C 标准交互协议 WebDriver[44] 的库 WebDriverAgent[45],python 版可以看这个,不过后来 Facebook 开发了新的一套命令行工具idb(iOS Development Bridge[46]),归档了 WebDriverAgent。idb 可以对 iOS 模拟器和设备跑自动化测试,idb 主要有两个基于 macOS 系统库 CoreSimulator.framework、MobileDevice.framework,包装的 FBSimulatorControl 和 FBDeviceControl 库。FBSimulatorControl 包含了 iOS 模拟器的所有功能,Xcode 和 simctl 都是用的 CoreSimulator,自动化中输入事件是逆向了 iOS 模拟器 Indigo 服务的协议,Indigo 是模拟器通过 mach IPC 通道 mach_msg_send 接受触摸等输入事件的协议。破解后就可以模拟输入事件了。MobileDevice.framework 也是 macOS 的私有库,macOS 上的 Finder、Xcode、Photos 这些会使用 iOS 设备的应用都是用了 MobileDevice,文件读写用的是包装了 AMDServiceConnection 协议的 AFC 文件操作 API,idb 的 instruments 相关功能是在这里[47]实现了 DTXConnectionServices 服务协议。libmobiledevice 可以看作是重新实现了 MobileDevice.framework。pymobiledevice、MobileDevice、C 编写的 SDMMobileDevice,还有Objective-C 编写的 MobileDeviceAccess,这些库也是用的 MobileDevice.framework。总结如下图所示:3 Android ProfilerAndroid Profiler 是 Android 中常用的耗时分析工具,以各种图表的形式展示函数执行时间,帮助开发者分析耗时问题。启动优化着实是牵一发动全身的事情,手段既琐碎又复杂。如何能够将监控体系建设起来,并融入到整个研发到上线流程中,是个庞大的工程。下面给你介绍下我们是如何做的吧。十 管控流程体系保障平台建设APM自动化管控和流程体系保障平台,目标是通过稳定环境更自动化的测试,采集到的性能数据能够通过分析检测,发现问题能够更低成本定位分发告警,同时大盘能够展示趋势和详情。平台设计如下图:如图所示,开发过程会 daily 出迭代报告,开发完成后,会有集成卡口,提前卡住迭代性能问题。集成后,在集成构建平台能够构建正式包和线下性能包,进行线下测试和线上性能数据采集,线下支持录制回放、Monkey 等自动化测试手段,测试期间会有生成版本报告,发布上线前也会有发布卡口,及时处理版本问题。发布后,通过云控进行指标配置、阈值配置还有采集比例等。性能数据上传服务经异常检测发现问题会触发报警,自动在 Bug 平台创建工单进行跟踪,以便及时修复问题减少用户体验损失。服务还会做统计、分级、基线对比、版本关联以及过滤等数据分析操作,这些分析后的性能数据最终会通过版本、迭代趋势等统计报表方式在大盘上展示,还能展示详情,包括对比展示、问题详情、场景分类、条件查询等。相关链接[1]https://github.com/Yeatse/NSURLProtocol-WebKitSupport[2]https://eisel.me/app-size[3]https://github.com/simdjson/simdjson[4]https://github.com/michaeleisel/ZippyJSON[5]https://github.com/ibireme/yyjson?spm=ata.13261165.0.0.5fb070c0J56sGV[6]https://github.com/michaeleisel/zld[7]https://gist.github.com/michaeleisel/77b8efc9bedab1444dbb71a5915dbd15[8]https://gist.github.com/michaeleisel/8eddd0082b4fd7f2bd118d97e79bf12e[9]https://github.com/frida/frida-gum[10]https://opensource.apple.com/tarballs/xnu/[11]https://eisel.me/order[12]https://opensource.apple.com/source/objc4/objc4-647/runtime/Messengers.subproj/objc-msg-arm64.s[13]https://ming1016.github.io/2017/06/20/deeply-ios-performance-optimization/[14https://github.com/ming1016/GCDFetchFeed/blob/master/GCDFetchFeed/GCDFetchFeed/Lib/SMLagMonitor/SMCallTraceCore.c[15]https://github.com/apple/swift-corelibs-libdispatch[16]https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html[17]https://gist.github.com/tclementdev/6af616354912b0347cdf6db159c37057[18]https://opensource.apple.com/tarballs/libdispatch/[19]https://github.com/apportable/Foundation[20]https://github.com/apportable/Foundation/blob/master/System/Foundation/src/NSOperation.m[21]http://www.gnustep.org/[22]http://www.cocotron.org/[23]https://github.com/apple/swift-corelibs-foundation/[24]https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/Foundation/Operation.swift[25]https://github.com/apple/swift-corelibs-foundation/commit/9f44ed353c77a438d6f4ba879b2b388210e2107f[26]https://www.erlang.org/[27]http://akka.io/[28]https://www.scala-lang.org/[29]http://doc.akka.io/docs/akka/current/scala/actors.html[30]https://en.wikipedia.org/wiki/Communicating_sequential_processes[31]https://forums.swift.org/t/swift-concurrency-roadmap/41611[32]https://github.com/apple/swift-evolution/blob/main/proposals/0176-enforce-exclusive-access-to-memory.md[33]https://www.gnu.org/software/libc/manual/html_mono/libc.html[34]https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/x86_64/getcontext.S.html[35]https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/x86_64/setcontext.S.html[36]https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/x86_64/makecontext.c.html[37]https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/x86_64/swapcontext.S.html[38]https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md[39]https://github.com/libimobiledevice/usbmuxd[40]https://github.com/rsms/peertalk[41]https://github.com/libimobiledevice/libimobiledevice[42]https://github.com/libimobiledevice/ifuse[43]https://www.gamebench.net/[44]https://w3c.github.io/webdriver/[45]https://github.com/facebookarchive/WebDriverAgent[46]https://github.com/facebook/idb/[47]https://github.com/facebook/idb/blob/master/FBDeviceControl/Management/FBInstrumentsClient.h
文章
Web App开发  ·  移动开发  ·  监控  ·  Java  ·  Linux  ·  Android开发  ·  Swift  ·  C语言  ·  iOS开发  ·  MacOS
2021-01-07
今日榜首|10年高级技术专家用7000字带你详解响应式技术框架
响应式技术框架目前在后端Web编程和微服务编程领域,存在多种响应式编程技术框架。本篇我们从响应式编程规范开始介绍,进一步加深对响应式编程的理解。响应式编程规范对于响应式编程来说,响应式流是一种非阻塞、响应式、异步流处理、支持背压的技术标准,包括运行时环境(JVM和JavaScript)及网络协议。JDK 9发布的Flow API(java.util.concurrent.Flow)和响应式流规范呼应,成为响应式编程事实上的标准。响应式流规范提供了一组最小化的接口、方法和协议来描述必要的操作和实体对象。● Publisher:消息发布者。发布者只有一种方法,用来接受订阅者进行订阅(Subscribe)。T代表发布者和订阅者之间传输的数据类型,接口声明如下:● Subscriber:消息订阅者。当接收到Publisher的数据时,会调用响应的回调方法。注册完成时,首先会调用onSubscribe方法,参数Subscriptions包含了注册信息。订阅者有四种事件方法,分别在开启订阅、接收数据、发生错误和数据传输结束时被调用,接口声明如下:● Subscription:连接Publisher和Subscriber的消息交互的操作对象。Subscriber可以请求数据(request),或者取消订阅(cancel)。当请求数据时,参数“long n”表示希望接收的数据量,防止Publisher发送过多的数据。一旦开始请求,数据就会在流中传输。每接收一个,就会调用onNext(Tt);当发生错误时,onError(Throwable t)被调用;在传输完成后,onComplete()被调用。接口声明如下:● Processor:同时充当Subscriber和Publisher的组件。可以看出,Processor接口继承了Subscriber和Publisher,是流的中间环节,接口声明如下:响应式流中的数据从Publisher开始,经过若干Processor,最终到达Subscriber,即完整的数据管道(Pipeline)。背压(Back Pressure)在响应式编程规范中,响应式编程采用异步的发布-订阅模式。数据由Publisher推送消息给Subscriber。这种模式容易产生的问题是,当Publisher即生产者产生的数据速度远远大于Subscriber即消费者的消费速度时,消费者会承受巨大的资源压力(Pressure)而可能崩溃。为了解决以上问题,数据流的速度需要被控制,即流量控制(Flow Control),以防止快速的数据流压垮目标。因此需要反压,即背压(Back Pressure),生产者和消费者之间需要通过一种背压机制来相互操作。这种背压机制要求是异步非阻塞的,如果是同步阻塞的,则消费者在处理数据时,生产者必须等待,会产生性能问题。Java Flow API从Java 9开始,增加了java.util.concurrent.Flow API,实现了响应式流规范(Reactive Stream Specification),并且把响应式流标准的接口集成到了JDK中。它和响应式流标准接口定义完全一致,之前需要通过Maven引用的API,Java 9之后可以直接使用了。响应式流的标准Maven依赖如下:Java9通过java.util.concurrent.Flow和java.util.concurrent.SubmissionPublisher实现了响应式流Flow类中定义的四个嵌套的静态接口,用于建立流量控制的组件,Publisher在其中生成一个或多个数据项供Subscriber使用。下面是Java 9 FlowAPI的核心组件。● java.util.concurrent.Flow:这是Flow API的主要类,该类封装了Flow API的所有重要接口。需要说明的是,这个类声明为final类型,所以我们无法扩展它。●java.util.concurrent.Flow.Publisher:每个发布者都需要实现此接口,每个发布者都必须实现它的subscribe方法,并添加相关的订阅者以接收消息。●java.util.concurrent.Flow.Subscriber:每个订阅者都必须实现此接口,订阅者按照严格的顺序调用方法,此接口有下面四种方法。○ onSubscribe:这是订阅者订阅了发布者后接收消息时调用的第一个方法。通常我们调用subscription.request就开始从处理器(Processor)接收项目。○ onNext:当发布者收到项目时调用此方法,这是我们实现业务逻辑来处理流并向发布者请求更多数据的方法。○ onError:当发生不可恢复的错误时调用此方法,我们可以在此方法中执行清理操作,例如关闭数据库连接。○ onComplete:这就像finally方法,在发布者没有发布其他项目或者发布者关闭时调用。可以用来发送流成功处理的通知。●java.util.concurrent.Flow.Subscription:用于在发布者和订 阅 者 之 间 创 建 异 步 非 阻 塞 连 接 。 订 阅 者 调 用 请 求(request)方法来向发布者请求项目。它还有取消订阅(cancel)的方法,即关闭发布者和订阅者之间的连接。●java.util.concurrent.Flow.Processor:此接口同时扩展了Publisher和Subscriber接口,用于在发布者和订阅者之间转换消息。●java.util.concurrent.SubmissionPublisher : 这 个 类 是 对Publisher接口的实现,它将提交的项目异步发送给当前订阅者,直到它关闭。它使用Executor框架,我们将在响应式流示例中使用该类来添加订阅者,然后向其提交项目。Java 9 Flow API接入实例下面使用Java 9 Flow API实现一个简单的发布消息订阅的例子。1.创建一个Item类,作为创建从发布者到订阅者之间的流消息的对象2.实现一个帮助类,创建一个Item列表3.实现消息的订阅在步骤3中,Subscription变量保持消费者对生产者的引用,通过onNext ( ) 方 法 进 行 请 求 处 理 ;Count 变 量 记 录 请 求 个 数 ; 在onSubscribe方法中调用订阅请求来开始处理;在onError方法和onComplete方法中调用发生错误和完成时执行的业务逻辑。4.使用主程序测试完成逻辑在步骤4中,首先使用SubmissionPublisher、TestSubscriber创建发布者和订阅者。通过publisher.subscribe(subs)建立发布者与订阅者之间的关联关系;然后发布者通过submit方法发送消息给订阅者,这个过程是异步执行的;在主线程的while循环中判断Item的size和消费累计的size;当Item全部消费完成时,退出主线程的While循环;最后关闭发布者以免任何内存泄漏。下面是程序的输出结果:RxJava响应式框架RxJava基于ReactiveX(Reactive Extensions的缩写)库和框架,使用观察者模式、迭代器模式及函数式编程,提供了异步数据流处理、非阻塞背压等特性。Reactive Extensions这个概念最早出现在微软的.NET社区中,目前越来越多语言实现了自己的响应式扩展,如Java、Javascript、Ruby等。Reactive Extensions是响应式编程的一种实现,是解决异步事件流的一种方案。通俗地讲,就是利用它可以很好地控制事件流的异步操作,将事件的发生和对事件的响应解耦,让开发者不再关心复杂的线程处理、锁等并发相关问题。RxJava的接入实例RxJava 2.x实现了响应式流规范。它是Netflix开发的一个响应式编程框架。下面是RxJava的典型开发代码:ObservableObservable可以理解为数据的发射器,对应Java Flow的发布者(Publisher)组件,通过create方法生成Observer对象。它会执行相关 业 务 逻 辑 并 通 过 emit 方 法 发 射 数 据 , 传 入 的 参 数 是ObservableOnSubscribe对象,使用泛型T作为操作对象的类型。你可以重写subscribe方法,里面是具体的数据源计划,前面的例子中是发射三个数字:1、2、3。ObservableEmitter是发射器的意思,有三种发 射 数 据 的 方 法 : void onNext( T value ) 、 voidonError(Throwable error)、void onComplete()。onNext方法可以 无 限 调 用 , 观 察 者 ( Observer ) 可 以 接 收 到 所 有 发 布 者(Publisher)发布的数据库,onError和onComplete是互斥的。ObserverObserver 是 数 据 的 观 察 者 , 对 应 Java Flow 的 订 阅 者(Subscriber)组件,通过new方法创建并重写内部方法,onNext、onError、onComplete都是与被观察者发射的方法一一对应的。在本例中,订阅者的onNext方法处理消费数据逻辑,当收到的数据等于20时,将取消订阅,此时数据的发布者就不再向观察者推送数据。通过dispose方法可以取消Observer和Observable之前的订阅关系。SchedulerRxJava支持异步通信的特性是通过Schedulers组件实现的,Scheduler的中文意思是调度器。在RxJava中,可以通过Scheduler来控制调度线程,从Scheduler的源码可以发现它本质上是操纵Runnable对象,支持用立即、延时、周期形式来调度工作线程。RxJava 2.x中内置了多种Scheduler实现,适用于不同场景。这些Scheduler可以在代码中直接使用,屏蔽了开发者对线程调用的管理和控制。在前面的例子中我们使用了Schedulers.io()作为线程调度策略,下表总结的是Schedulers不同的线程调度策略。OperatorRxJava在处理事件的流转过程中,提供了丰富的操作符,用来改变事件流中的数据。以Map操作符为例,Map的作用是将发射的事件进行Map函数定义的数据转换,再将转换后的事件发射给Observer。转换过程如下图所示。(1)通过Emitter发射了1、2、3三个数字。(2)中间通过Map进行转换,转换后事件变成10、20、30。(3)最后将转换后的事件发射给Observer。RxJava2-Android-Samples(GitHub开源项目)的Readme.md中总结了RxJava用到的所有操作符,篇幅所限,其他操作符可以从Reactive官方地址获得详解。RxJava的主要操作符如下表所示。Reactor响应式框架Reactor是Pivotal基于Reactive Streams规范实现的响应式框架 。 作 为 Spring 的 兄 弟 项 目 , 它 进 一 步 扩 展 了 基 本 的 ReactiveStreams Publisher及Flux和Mono API等组件,主要使用依赖的组件是Reactor Core模块。Reactor项目已在GitHub中开源(可使用Reactor关键字搜索),主要包含Reactor Core和Reactor Netty两部分。Reactor Core实现了反应式编程的核心功能,Reactor Netty则是Spring WebFlux等技术的基础。Reactor的接入实例1.使用Reactor进行响应式编程,加载对应的Maven依赖2.使用Reactor进行响应式编程的Demo3.执行上述程序得到如下结果在Reactor项目中,主要有与RxJava类似的发布者、订阅者、操作符等关键API和语法概念,下面结合代码实例讲解主要用到的模块。Reactor的核心模块● FluxFlux是Reactor中数据发布者的重要抽象类。从源码中可以发现,Flux实现了Reactive Streams JVM API Publisher。Flux定义了0~N的非阻塞序列,类比非阻塞Stream,在Reactor中充当数据发布者的角色。在上述实例中,Flux通过just方法发布数据流。just方法是Flux常见的创建Stream的方法,此外,还可以通过create、generate、from等方法创建Flux数据流。上面例子中使用最简单的just方法完成了三个数字的构造和声明发布,如下图所示。● MonoMono和Flux类似。从源码中可以发现,Mono同样实现了ReactiveStreams JVM API Publisher,实现了0~1的非阻塞结果,如下图所示。● Subscriber订阅者通过订阅操作,可以处理数据的请求,在订阅方法中需要重写onSubscribe、onNext、onError、onComplete方法来实现数据流的消费。Flux调用subscribe方法后会触发数据的发送,订阅者接收到数据后会触发onSubscribe方法。onSubscribe表示订阅动作的方式,准备发送给真正的消息接收者,然后执行subscription.request方法发送请求数据。代码例子中request(1)表示只发送一条数据,也可以使用subscription.cancel取消上游数据的传输。然后执行onNext方法进行消息的响应处理,在onNext方法中执行request方法可以把数据交给subscription链,循环处理所有数据。● Operator在Reactor项目中,一个Operator会给一个发布者(Publisher)添加某种行为,并返回一个新的Publisher实例。还可以对返回的Publisher再添加Operator连成一个链条。原始数据沿着链条从第一个Publisher开始向下流动,链条中的每个节点都会以某种方式去转换流入的数据。链条的终点是一个订阅者(Subscriber),Subscriber以某种方式消费这些数据,流程图如下图所示。下面是对Reactor项目中Operator的总结分类,大致可以分为如下几类。● 集合Operator:提供集合运算,如map、filter、sort、group、reduce等,和Java 8 Stream的中间操作具有相同的效果。● 异 常 处 理 Operator : 提 供 异 常 处 理 机 制 , 如 retry 、onErrorReturn等。● 回 调 Operator : 提 供 Publisher 状 态 转 换 时 的 回 调 , 如doOnCancel、doOnRequest等。● 行为Operator:修改Publisher的默认行为,为其添加更多功能,如buffer、defaultIfEmpty、onBackpressureXXX等。● 调试Operator:添加调试信息,如log、elapsed等。Vert.X响应式编程Vert.X是基于JVM构建的一个Reactive工具箱。同时,Vert.X和Spring类似,也有一套微服务开发生态。从开发者的角度来看,Vert.X就是一些库包,提供了HTTP客户端和服务器、消息服务、TCP和UDP底层协议等模块。你可以使用这些模块来构建自己的应用,也可以通过向Vert.X Core(Vert.X的基础组件)中增加任意模块来构建自己的系统。Vert.X的主要功能● Web开发,Vert.X封装了Web开发常用的组件,支持路由、Session管理、模板等。● TCP/UDP开发,Vert.X底层基于Netty,提供了丰富的I/O类库,支持多种网络应用开发,不需要处理底层细节(如拆包和粘包),注重业务代码编写。● 提供对WebSocket的支持,可以做网络聊天室、动态推送等。● Event Bus(事件总线)是Vert.X的神经系统,通过Event Bus可以实现分布式消息、远程方法调用等。正是因为Event Bus的存在,Vert.X才可以更加便捷地开发微服务应用。● 支 持 主 流 的 数 据 和 消 息 的 访 问 , 如 Redis 、 MongoDB 、RabbitMQ、Kafka等。● 支持分布式锁、分布式计数器、分布式Map。Vert.X的特性● 异步非阻塞:Vert.X就像是跑在JVM上的Node.js(使用事件驱动、非阻塞式I/O模型的JavaScript运行环境),所以Vert.X的第一个优势就是它实现了一个异步的非阻塞框架。● Vert.X支持多编程语言,在Vert.X上,可以使用JavaScript、Java、Scala、Ruby等语言。● 不依赖中间件:Vert.X的底层依赖Netty,因此在使用Vert.X构建Web项目时,不依赖中间件。像Node一样,可以直接创建一个HttpServer,相对会更灵活一些,安全性也会更高一些。● 完善的生态:Vert.X提供数据库操作、Redis操作、Web客户端操作等丰富的组件功能。Vert.X的接入实例1.加载对应的Maven依赖2.Vert.X提供了一个创建HTTP服务器的简单方法,该服务器会在每次接收到HTTP请求时返回一个“Hello”的response在这个例子里,我们创建了一个requestHandler来接收HTTP请求事件,并且返回响应。在Vert.X中,所有API都不会阻塞调用线程,如果不能立即响应结果,Handler会在事件准备好后处理,通过异步操作回调Handler方法触发执行。这种非阻塞的开发模型,可以使用较少的线程处理高并发场景。下面是Vert.X中EventLoop的工作模型图。Verticle是Vert.X中的重要组件,可以理解成Java中的Servlet、POJO Bean或Akka中的Actor。一个组件可以有多个实例,Verticle实例之间的通信通过Event Bus实现。ProducerVerticle负责监听8080端口,接收前端请求,它可以通过Event Bus发送一个事件,该事件将被传递给多个该事件的订阅者,代码如下。ConsumeVerticle负责消费Event Bus的数据并返回响应,代码如下。MainApp是启动类,在main方法中发布两个Verticle,下面代码是启动主流程的方法。浏览器调用接口http://127.0.0.1:8080/book/1,出现下面结果则表示正确。Verticle具有以下几个特点。● 每个Verticle都占用一个EventLoop线程,且只对应一个EventLoop。● 每个Verticle中创建的HttpServer、EventBus等资源都会在回收Verticle时被同步回收。● 在多个Verticle中创建同样端口的HttpServer,会变成两个EventLoop线程,处理同一个HttpServer的连接,可以利用Verticle的这一特性来提升并发处理性能。Spring Boot 2响应式编程Spring Boot 2.x在Spring Boot 1.x基础上,基于Spring 5实现了响应式编程框架。从Spring MVC注解驱动的时代开始,Spring官方有意识地去Servlet化。不过在Spring MVC时代,Spring仍然摆脱不了对 Servlet 容 器 的 依 赖 , 然 而 借 助 响 应 式 编 程 ( ReactiveProgramming)的势头,Spring加速了这一时代的到来。WebFlux将Servlet容器从必须项变为可选项,并且默认采用Netty Web Server作为HTTP容器的处理引擎,形成Spring全新的技术体系,包括数据存储等技术栈。Spring Boot 2官方提供的基于Reactor与Servlet容器生态和技术栈的对比如下图所示。对比发现,Spring Boot 2.x与Spring Boot 1.x在技术栈上存在巨大差异。Spring Boot 2.x最显著的变化就是采用了响应式的技术体系。底层的Reactive核心组件、响应式WebFlux框架、响应式数据存储、响应式安全、响应式Web服务引擎组成了Spring响应式技术体系。下面列举了Spring Boot 2中支持响应式编程的部分模块。Spring CoreSpring Core 是 Spring 的 核 心 模 块 。 Spring Framework 5 基 于ProjectReactor和RxJava反应式项目及响应式编程规范实现了对响应式编程的支持。在Spring Core中通过引入ReactiveAdapter实现了Object和Publisher<T>的相互转换,代码如下:使用者可以通过继承ReactiveAdapter实现定制化的数据类型转换 。 ReactiveAdapterRegistry 可 以 作 为 对 象 池 来 保 持ReactiveAdapter实例并提供相应的数据访问方式。响应式I/OSpring Core提供了对I/O的响应式编程支持。Spring Core首先引入了一个字节缓存抽象接口DataBuffer,提供了一个DataBufferUtils工具类,可以实现以Reactive方式对I/O进行访问和交互。从下面的示例代码可以看到,DataBufferUtils返回了一个Flux对象,这样就可以使用Reactor相关接口读取test.txt文件,实现背压的响应式特性。同时,Spring Core通过下面接口实现了基于响应式流的编解码实现类,这样可以方便DataBuffer实例与对象的相互转化,代码如下:Spring WebFlux构建响应式Web服务在Web服务方面,Spring 2.x提供了WebFlux框架,基于Flux和Mono对象实现响应式非阻塞Web服务。同时提供了一个响应式的HTTPWebClient,它可以通过函数式的方式异步非阻塞地发起HTTP请求并处理响应。Spring WebFlux也提供了响应式的WebSocketClient。下一节我们会详细讲解Spring的WebFlux框架。数据层支持响应式开发基于响应式流的应用,就像搭建数据流的管道,使异步数据能够顺畅流过每个环节。大多数系统免不了要与数据库交互,所以我们也需要响应式的持久层API和支持异步的数据库驱动。在消息的处理过程中,如果数据管道在任何一个环节发生阻塞,都有可能造成整体吞吐量的下降。各个数据库都开始陆续推出异步驱动的技术支持,目前可以支持响应式数据访问的数据库有MongoDB、Redis、Apache Cassandra和CouchDB。相关生态的响应式支持● Spring 5实现了对Spring Security的响应式支持。● Spring Cloud基于WebFlux框架实现了Spring Cloud Gateway微服务网关。● Spring Test实现了响应式的支持类WebTestClient。● 在监控领域,Sleuth也提供对响应式WebFlux的追踪支持。本文给大家讲解的内容是响应式微服务架构,响应式技术框架觉得文章不错的朋友可以转发此文关注小编;感谢大家的支持!
文章
前端开发  ·  JavaScript  ·  NoSQL  ·  Java  ·  API  ·  调度  ·  数据库  ·  微服务  ·  Spring  ·  容器
2022-09-20
阿里技术
6787 人关注 | 5087 讨论 | 1208 内容
+ 订阅
  • 降本增效创未来——云原生多模数据库Lindorm 2022双十一总结
  • 使用魔搭开发自己的语音AI:从入门到精通
  • 跨端开发浪潮中的变与不变
查看更多 >
开发与运维
5603 人关注 | 131412 讨论 | 300374 内容
+ 订阅
  • 如何检查域名解析是否生效
  • CentOS7 yum方式安装golang程序
  • 数据开发流程及规范
查看更多 >
数据库
252292 人关注 | 50729 讨论 | 94264 内容
+ 订阅
  • 数据开发流程及规范
  • 数仓中指标-标签,维度-度量,自然键-代理键,数据集市等各名词解析及关系
  • 数据仓库建设规范
查看更多 >
大数据
188004 人关注 | 29189 讨论 | 79988 内容
+ 订阅
  • 数据开发流程及规范
  • 数仓中指标-标签,维度-度量,自然键-代理键,数据集市等各名词解析及关系
  • 数据仓库建设规范
查看更多 >
云原生
233606 人关注 | 11310 讨论 | 44939 内容
+ 订阅
  • 开源项目:ferry开源工单系统搭建(更新于2021 8.30)
  • docker-compose安装
  • kubernetes强制删除pod、namespace等资源
查看更多 >