爬虫找工作之面试题(2)

1.你写爬虫的时候都遇到过什么反爬虫措施,你是怎么解决的?

通过headers反爬虫:解决策略,伪造headers

基于用户行为反爬虫:动态变化去爬取数据,模拟普通用户的行为

通过动态更改代理ip来反爬虫

基于动态页面的反爬虫:跟踪服务器发送的ajax请求,模拟ajax请求,selnium 和phtamjs

2.用的什么框架,为什么选择这个框架(我用的是scrapy框架,所以下面的问题也是针对scrapy)

scrapy

基于twisted异步io框架,是纯python实现的爬虫框架,性能是最大的优势
可以加入request和beautifulsoup
方便扩展,提供了很多内置功能
内置的cssselector和xpath非常方便
默认深度优先
pyspider: 爬虫框架,基于PyQuery实现的

优势: 1. 可以实现高并发的爬取数据, 注意使用代理;

2. 提供了一个爬虫任务管理界面, 可以实现爬虫的停止,启动,调试,支持定时爬取任务;

3. 代码简洁

劣势: 1.可扩展性不强;

2.整体上来说: 一些结构性很强的, 定制性不高, 不需要太多自定义功能时用pyspider即可, 一些定制性高的,需要自定义一 些 功能时则使用Scrapy

二.框架问题(scrapy)可能会根据你说的框架问不同的问题,但是scrapy还是比较多的

1.scrapy的基本结构(五个部分都是什么,请求发出去的整个流程)

流程
1.引擎打开一个域名,蜘蛛处理这个域名,并让蜘蛛获取第一个爬取的URL。

2.引擎从蜘蛛那获取第一个需要爬取的URL,然后作为请求在调度中进行调度。

3.引擎从调度那获取接下来进行爬取的页面。

4.调度将下一个爬取的URL返回给引擎,引擎将他们通过下载中间件发送到下载器。

5.当网页被下载器下载完成以后,响应内容通过下载中间件被发送到引擎。

6.引擎收到下载器的响应并将它通过蜘蛛中间件发送到蜘蛛进行处理。

7.蜘蛛处理响应并返回爬取到的项目,然后给引擎发送新的请求。

8.引擎将抓取到的项目项目管道,并向调度发送请求。

系统重复第二步后面的操作,直到调度中没有请求,然后断开引擎与域之间的联系

2.scrapy的去重原理 (指纹去重到底是什么原理)

需要将dont_filter设置为False开启去重,默认是False;
对于每一个url的请求,调度器都会根据请求的相关信息加密得到一个指纹信息,并且将指纹信息和set()集合中得指纹信息进行比对,如果set()集合中已经存在这个数据,就不在将这个Request放入队列中。如果set()集合中没有,就将这个Request对象放入队列中,等待被调度。
3.scrapy中间件有几种类,你用过那些中间件,

scrapy的中间件理论上有三种(Schduler Middleware,Spider Middleware,Downloader Middleware),在应用上一般有以下两种

1.爬虫中间件Spider Middleware

主要功能是在爬虫运行过程中进行一些处理.

  2.下载器中间件Downloader Middleware

主要功能在请求到网页后,页面被下载时进行一些处理.

4.scrapy中间件再哪里起的作用(面向切面编程)

三.代理问题

1.为什么会用到代理

一些网站会有相应的反爬虫措施,例如很多网站会检测某一段时间某个IP的访问次数,如果访问频率太快以至于看起来不像正常访客,它可能就会会禁止这个IP的访问。所以我们需要设置一些代理服务器,每隔一段时间换一个代理,就算IP被禁止,依然可以换个IP继续爬取。

2.代理怎么使用(具体代码,请求在什么时候添加的代理)

3.代理失效了怎么处理

四.验证码处理

1.登陆验证码处理

2.爬取速度过快出现的验证码处理

3.如何用机器识别验证码

五.模拟登陆问题

1.模拟登陆流程

2.cookie如何处理

3.如何处理网站传参加密的情况

六.分布式

1.什么是分布式

需要计算的数据量大,任务多,一台机器搞不定或者效率极低,需要多台机器共同协作(而不是孤立地各做各的,所以需要通信),最后所有机器完成的任务汇总在一起,完成大量任务.

将一个项目拷贝到多台电脑上,同时爬取数据

分布式爬虫则是将多台主机组合起来,共同完成一个爬取任务,这将大大提高爬取的效率。

记住爬虫的本质是网络请求和数据处理,如何稳定地访问网页拿到数据,如何精准地提取出高质量的数据才是核心问题。

2.分布式原理

3.分布式如何判断爬虫已经停止了

4.分布式去重原理

对于每一个url的请求,调度器都会根据请求得相关信息加密得到一个指纹信息,并且将指纹信息和set()集合中的指纹信息进行比对,如果set()集合中已经存在这个数据,就不在将这个Request放入队列中。如果set()集合中没有存在这个加密后的数据,就将这个Request对象放入队列中,等待被调度。

七.数据存储和数据库问题

1.关系型数据库和非关系型数据库的区别

2.爬下来数据你会选择什么存储方式,为什么

3.各种数据库支持的数据类型,和特点,比如:redis如何实现持久化,mongodb

是否支持事物等。。

八.python基础问题

# 基础问题非常多,但是因为爬虫性质,还是有些问的比较多的,下面是总结

1.python2和python3的区别,如何实现python2代码迁移到python3环境

2.python2和python3的编码方式有什么差别(工作中发现编码问题还是挺让人不爽的)

3.迭代器,生成器,装饰器

4.python的数据类型

Number(数字) 包括int,long,float,complex
String(字符串) 例如:hello,"hello",hello
List(列表) 例如:[1,2,3],[1,2,3,[1,2,3],4]
Dictionary(字典) 例如:{1:"nihao",2:"hello"}
Tuple(元组) 例如:(1,2,3,abc)
Bool(布尔) 包括True、False
九.协议问题

# 爬虫从网页上拿数据肯定需要模拟网络通信的协议

1.http协议,请求由什么组成,每个字段分别有什么用,https和http有什么差距

2.证书问题

3.TCP,UDP各种相关问题

十.数据提取问题

1.主要使用什么样的结构化数据提取方式,可能会写一两个例子

2.正则的使用

3.动态加载的数据如何提取

爬取动态页面目前来说有两种方法

分析请求页面
通过Selenium模拟浏览器获取(不推荐这种,太慢)
分析很简单,我们只需要打开了浏览器F12开发者模式,获取它的js请求文件(除JS选项卡还有可能在XHR选项卡中,当然 也可以通过其它抓包工具)

我们打开第一财经网看看,发现无法获取元素的内容

打开Network,看下它的请求,这里我们只看它的 j s 请求就够了, 找到json接口

将它的url放到浏览器看下,发现是我们要的数据,就可以获取了

一些网站所有的接口都进行了加密操作,我们无法解析js,就必须采用selenium+phantomjs进行获取

4.json数据如何提取

使用eval解析
json.loads() 是把 Json格式字符串解码转换成Python对象,如果在json.loads的时候出错,要注意被解码的Json字符的编码
json.dumps() 是把json_obj 转换为json_str
10.动态加载又对及时性要求很高怎么处理?
Selenium+Phantomjs
尽量不使用 sleep 而使用 WebDriverWait
11.HTTPS有什么优点和缺点?
优点:相比于http,https可以提供更加优质保密的信息,保证了用户数据的安全性,此外https同时也一定程度上保护了服务端,使用恶意攻击和伪装数据的成本大大提高。
缺点:缺点也同样很明显,第一https的技术门槛较高,多数个人或者私人网站难以支撑,CA机构颁发的证书都是需要年费的,此外对接Https协议也需要额外的技术支持;其二,目前来说大多数网站并不关心数据的安全性和保密性,其https最大的优点对它来说并不适用;其三,https加重了服务端的负担,相比于http其需要更多的资源来支撑,同时也降低了用户的访问速度;第四,目前来说Http网站仍然大规模使用,在浏览器侧也没有特别大的差别,很多用户不关心的话根本不感知。
12.HTTPS是如何实现安全传输数据的?
首先https的服务端必须要拥有一个CA认证合法授权的证书,没有这个证书,客户端在访问该服务器时就会提醒用户这个网站是不受信任的。只有通过CA认证的服务器才是可靠的,这保证了用户在访问服务器的安全性。浏览器会保持一个信任的CA机构列表,通过这些机构出查询所访问的服务器提供的证书是否合法。
如果此时发现证书是合法OK的,那么就从这个服务器端的证书中获取到了加密秘钥,这个加密秘钥会沟通商议出一个随机的对称秘钥,服务端在传输信息使用该秘钥进行加密。而客户端在收到这部分信息后,在浏览器侧通过之前得到的对称秘钥进行解密,相反如果客户端想要向服务端发送消息时也是如此。
13.谈一谈你对Selenium和PhantomJS了解
selenium
Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等主流浏览器。这个工具的主要功能包括:测试与浏览器的兼容性——测试你的应用程序看是否能够很好得工作在不同浏览器和操作系统之上。

它的功能有:

框架底层使用JavaScript模拟真实用户对浏览器进行操作。测试脚本执行时,浏览器自动按照脚本代码做出点击,输入,打开,验证等操作,就像真实用户所做的一样,从终端用户的角度测试应用程序。
使浏览器兼容性测试自动化成为可能,尽管在不同的浏览器上依然有细微的差别。
使用简单,可使用Java,Python等多种语言编写用例脚本
也就是说,它可以根据指令,做出像真实的人在访问浏览器一样的动作,比如打开网页,截图等功能。

phantomjs
(新版本的selenium已经开始弃用phantomjs, 不过有时候我们可以单独用它做一些事情)

是一个基于Webkit的无界面浏览器,可以把网站内容加载到内存中并执行页面上的各种脚本(比如js)。

14.平常怎么使用代理的 ?
proxies = {'http':'http://10.10.10.10:8765','https':'https://10.10.10.10:8765'}

resp = requests.get(url,proxies = proxies)

注:免费的代理IP可以在这个网站上获取:http://www.xicidaili.com/nn/

15.怎么监控爬虫的状态?
使用 python 的 STMP 包将爬虫的状态信心发送到指定的邮箱
Scrapyd、pyspider
引入日志
集成日志处理平台来进行监控,如 elk
16.描述下scrapy框架运行的机制?
从start_urls里获取第一批url并发送请求,请求由引擎交给调度器入请求队列,获取完毕后,调度器将请求队列里的请求交给下载器去获取请求对应的响应资源,并将响应交给自己编写的解析方法做提取处理:1. 如果提取出需要的数据,则交给管道文件处理;2. 如果提取出url,则继续执行之前的步骤(发送url请求,并由引擎将请求交给调度器入队列…),直到请求队列里没有请求,程序结束。

17.谈谈你对Scrapy的理解?
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,我们只需要实现少量代码,就能够快速的抓取的数据内容。Scrapy使用了Twisted一部网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。

scrapy框架的工作流程:

首先Spider(爬虫)将需要发送请求的url(requests)经ScrapyEngine(引擎)交给Scheduer(调度器)。
Scheduler(排序,入队)处理后,经ScrapyEngine(引擎),DownloaderMiddlewares(下载器中间件,可选,主要有User_Agent,Proxy代理)交给Downloader(下载器)。
Downloader(下载器)向互联网发送请求,并接受下载响应(response)。将响应(response)经ScrapyEngine(引擎),SpiderMiddlewares(爬虫中间件,可选)交给Spiders(爬虫)。
Spiders(爬虫)处理response(响应界面),提取数据并将数据经ScrapyEngineScrapyEngine(引擎)交给ItemPipeline保存(可以是本地,也可以是数据库)。提取url重新经ScrapyEngineScrapyEngine(引擎)交给Scheduler(调度器)进入下一个循环。直到无Url请求程序停止结束。
优点:
scrapy是异步的;
采取可读性更强的xpath代替正则;
强大的统计和log系统;
支持shell方式,方便独立调试;
写middleware,方便写一些统一的过滤器;
通过管道的方式存入数据库。
缺点:
基于python的爬虫框架,扩展性比较差;
基于 twisted 框架,运行中的 exception 是不会干掉 reactor(反应器),并且异步框架出错后是不会停掉其他任务的,数据出错后难以察觉
18.怎么样让 scrapy 框架发送一个 post 请求(具体写出来)
19.怎么判断网站是否更新?
1、304页面http状态码

当第二次请求页面访问的时候,该页面如果未更新,则会反馈一个304代码,而搜索引擎也会利用这个304http状态码来进行判断页面是否更新。

首先第一次肯定是要爬取网页的,假设是A.html,这个网页存储在磁盘上,相应地有个修改时间(也即是更新这个文件的时间)。

那么第二次爬取的时候,如果发现这个网页本地已经有了,例如A.html,这个时候,你只需要向服务器发送一个If-Modified-Since的请求,把A.html的修改时间带上去。

如果这段时间内,A.html更新了,也就是A.html过期了,服务器就会HTTP状态码200,并且把新的文件发送过来,这时候只要更新A.html即可。

如果这段时间内,A.html的内容没有变,服务器就会返返回HTTP状态码304(不返回文件内容),这个时候就不需要更新文件。

2、Last-Modified文件最后修改时间

这是http头部信息中的一个属性,主要是记录页面最后一次的修改时间,往往我们会发现,一些权重很高的网站,及时页面内容不更新,但是快照却还是能够每日更新,这其中就有Last-Modified的作用。通产情况下,下载网页我们使用HTTP协议,向服务器发送HEAD请求,可以得到页面的最后修改时间LastModifed,或者标签ETag。将这两个变量和上次下载记录的值的比较就可以知道一个网页是否跟新。这个策略对于静态网页是有效的。是对于绝大多数动态网页如ASP,JSP来说,LastModifed就是服务器发送Response的时间,并非网页的最后跟新时间,而Etag通常为空值。所以对于动态网页使用LastModifed和Etag来判断是不合适的,因此Last-Modified只是蜘蛛判断页面是否更新的一个参考值,而不是条件。

20.图片、视频爬取怎么绕过防盗连接
URL url = new URL("");
// 获得连接
URLConnection connection = url.openConnection();
connection.setRequestProperty("Referer", "http://www.xxx.com");
因为一些网站在解决盗链问题时是根据Referer的值来判断的,所以在请求头上添加Referer属性就好(可以填爬取网站的地址)。
另外Referer携带的数据 是用来告诉服务器当前请求是从哪个页面请求过来的。

21.你爬出来的数据量大概有多大?大概多长时间爬一次?
22.用什么数据库存爬下来的数据?部署是你做的吗?怎么部署?
分布式爬虫的部署
1.下载scrapy_redis模块包
2.打开自己的爬虫项目,找到settings文件,配置scrapy项目使用的调度器及过滤器
3.修改自己的爬虫文件
4.如果连接的有远程服务,例如MySQL,Redis等,需要将远程服务连接开启,保证在其他主机上能够成功连接
5.配置远程连接的MySQL及redis地址
6.上面的工作做完以后,开启我们的redis服务器
进入到redis文件下打开我们的cmd窗口:输入:redis-server redis.windows.conf
如果出现错误:# Creating Server TCP listening socket 127.0.0.1:6379: bind: No error
解决方法:在命令行中运行
redis-cli
127.0.0.1:6379>shutdown
not connected>exit
然后重新运行redis-server redis.windows.conf,启动成功!
7.修改redis.windows.conf配置文件,修改内容如下:

# 配置远程IP地址,供其他的电脑进行连接redis
bind: (当前电脑IP) (192.168.40.217)

# 关闭redis保护模式
protected-mode: no
8.所有爬虫都启动之后,部署redis-server服务的电脑再打开一个命令窗口,输入redis-cli.exe -h 127.0.0.1(如果是自己的ip改成自己的IP地址) -p 6379连上服务端

9.连上之后会有127.0.0.1:6379>这样的字样提示,然后输入如下命令

10.lpush 爬虫文件里面自己定义的爬虫名字:start_urls 爬虫的网址

12.数据写不进去数据库里面:
修改MySQL的my.ini文件,以MySQL8为例
路径在C:ProgramDataMySQLMySQL Server 8.0
找到sql-mode="STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"这一行
把里面的STRICT_TRANS_TABLES,删除,逗号也删除,保存文件
修改过之后需要重启mysql服务
在windows命令窗口中使用net stop mysql80先停止服务,再使用net start mysql80启动服务
如果my.ini文件不修改,爬虫的数据写入不了数据库

23.增量爬取
①缓存
通过开启缓存,将每个请求缓存至本地,下次爬取时,scrapy会优先从本地缓存中获得response,这种模式下,再次请求已爬取的网页不用从网络中获得响应,所以不受带宽影响,对服务器也不会造成额外的压力,但是无法获取网页变化的内容,速度也没有第二种方式快,而且缓存的文件会占用比较大的内存,在setting.py的以下注释用于设置缓存。这种方式比较适合内存比较大的主机使用。

#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
②对item实现去重
#处理书信息
def process_BookItem(self,item):
bookItemDick = dict(item)
try:
self.bookColl.insert(bookItemDick)
print("插入小说《%s》的所有信息"%item["novel_Name"])
except Exception:
print("小说《%s》已经存在"%item["novel_Name"])
#处理每个章节
def process_ChapterItem(self,item):
try:
self.contentColl.insert(dict(item))
print('插入小说《%s》的章节"%s"'%(item['novel_Name'],item['chapter_Name']))
except Exception:
print("%s存在了,跳过"%item["chapter_Name"])
def process_item(self, item, spider):
'''
if isinstance(item,ChaptersItem):
self.process_ChaptersItem(item)
'''
if isinstance(item,BookItem):
self.process_BookItem(item)
if isinstance(item,ChapterItem):
self.process_ChapterItem(item)
return item
两种方法判断mongodb中是否存在已有的数据,一是先查询后插入,二是先设置唯一索引或者主键再直接插入,由于mongodb的特点是插入块,查询慢,所以这里直接插入,需要将唯一信息设置为”_id”列,或者设置为唯一索引,在mongodb中设置方法如下

db.集合名.ensureIndex({"要设置索引的列名":1},{"unique":1})
需要用什么信息实现去重,就将什么信息设置为唯一索引即可(小说章节信息由于数据量比较大,用于查询的列最好设置索引,要不然会非常慢),这种方法对于服务器的压力太大,而且速度比较慢,我用的是第二种方法,即对已爬取的url进行去重

③对url实现去重
class UrlFilter(object):
#初始化过滤器(使用mongodb过滤)
def __init__(self):
self.settings = get_project_settings()
self.client = pymongo.MongoClient(
host = self.settings['MONGO_HOST'],
port = self.settings['MONGO_PORT'])
self.db = self.client[self.settings['MONGO_DB']]
self.bookColl = self.db[self.settings['MONGO_BOOK_COLL']]
#self.chapterColl = self.db[self.settings['MONGO_CHAPTER_COLL']]
self.contentColl = self.db[self.settings['MONGO_CONTENT_COLL']]
def process_request(self,request,spider):
if (self.bookColl.count({"novel_Url":request.url}) > 0) or (self.contentColl.count({"chapter_Url":request.url}) > 0):
return http.Response(url=request.url,body=None)
但是又会有一个问题,就是有可能下次开启时,种子url已经被爬取过了,爬虫会直接关闭,后来想到一个笨方法解决了这个问题,即在pipeline.py里的open_spider方法中再爬虫开启时删除对种子url的缓存

def open_spider(self,spider):

self.bookColl.remove({"novel_Url":"http://www.23us.so/xiaoshuo/414.html"})
24.爬取下来的数据如何去重,说一下scrapy的具体的算法依据。
通过 MD5 生成电子指纹来判断页面是否改变

nutch 去重。nutch 中 digest 是对采集的每一个网页内容的 32 位哈希值,如果两个网页内容完 全一样,它们的 digest 值肯定会一样。

数据量不大时,可以直接放在内存里面进行去重,python 可以使用 set()进行去重。当去重数据 需要持久化时可以使用 redis 的 set 数据结构。

当数据量再大一点时,可以用不同的加密算法先将长字符串压缩成 16/32/40 个字符,再使用 上面两种方法去重。

当数据量达到亿(甚至十亿、百亿)数量级时,内存有限,必须用“位”来去重,才能够满足需 求。Bloomfilter 就是将去重对象映射到几个内存“位”,通过几个位的 0/1 值来判断一个对象是 否已经存在。

然而 Bloomfilter 运行在一台机器的内存上,不方便持久化(机器 down 掉就什么都没啦),也不 方便分布式爬虫的统一去重。如果可以在 Redis 上申请内存进行
Bloomfilter,以上两个问题就都能解 决了。

simhash 最牛逼的一点就是将一个文档,最后转换成一个 64 位的字节,暂且称之为特征字,然后 判断重复只需要判断他们的特征字的距离是不是小于n(根据经验这个 n 一般取值为 3),就可以判断两个 文档是否相似。

可见 scrapy_redis 是利用 set 数据结构来去重的,去重的对象是 request 的 fingerprint(其实 就是用 hashlib.sha1()对 request 对象的某些字段信息进行压缩)。其实 fp 就是 request 对象加密 压缩后的一个字符串(40 个字符,0~f)。

# 去重源码分析
# from scrapy.core.scheduler import Scheduler
# Scheduler下:def enqueue_request(self, request)方法判断是否去重
if not request.dont_filter and self.df.request_seen(request):
Requests对象,RFPDupeFilter对象
# 如果要自己写一个去重类
-写一个类,继承BaseDupeFilter类
-重写def request_seen(self, request):
-在setting中配置:DUPEFILTER_CLASS = '项目名.dup.UrlFilter'


-增量爬取(100链接,150个链接)
-已经爬过的,放到某个位置(mysql,redis中:集合)
-如果用默认的,爬过的地址,放在内存中,只要项目一重启,就没了,它也不知道我爬过那个了,所以要自己重写去重方案
-你写的去重方案,占得内存空间更小
-bitmap方案
-BloomFilter布隆过滤器


from scrapy.http import Request
from scrapy.utils.request import request_fingerprint

# 这种网址是一个
requests1=Request(url='https://www.baidu.com?name=lqz&age=19')
requests2=Request(url='https://www.baidu.com?age=18&name=lqz')

ret1=request_fingerprint(requests1)
ret2=request_fingerprint(requests2)
print(ret1)
print(ret2)

# bitmap去重 一个小格表示一个连接地址 32个连接,一个比特位来存一个地址
# https://www.baidu.com?age=18&name=lqz ---》44
# https://www.baidu.com?age=19&name=lqz ---》89
# c2c73dfccf73bf175b903c82b06a31bc7831b545假设它占4个bytes,4*8=32个比特位
# 存一个地址,占32个比特位
# 10个地址,占320个比特位
#计算机计量单位
# 比特位:只能存0和1


def request_seen(self, request):
# 把request对象传入request_fingerprint得到一个值:aefasdfeasd
# 把request对象,唯一生成一个字符串
fp = self.request_fingerprint(request)
#判断fp,是否在集合中,在集合中,表示已经爬过,return True,他就不会再爬了
if fp in self.fingerprints:
return True
# 如果不在集合中,放到集合中
self.fingerprints.add(fp)
if self.file:
self.file.write(fp + os.linesep)
29.什么是分布式存储?
传统定义:分布式存储系统是大量 PC 服务器通过 Internet 互联,对外提供一个整体的服务。

分布式存储系统具有以下的几个特性:
可扩展 :分布式存储系统可以扩展到几百台甚至几千台这样的一个集群规模,系统的 整体性能线性增长。

低成本 :分布式存储系统的自动容错、自动负载均衡的特性,允许分布式存储系统可 以构建在低成本的服务器上。另外,线性的扩展能力也使得增加、减少服务器的成本低, 实现分布式存储系统的自动运维。

高性能 :无论是针对单台服务器,还是针对整个分布式的存储集群,都要求分布式存 储系统具备高性能。

易用 :分布式存储系统需要对外提供方便易用的接口,另外,也需要具备完善的监 控、运维工具,并且可以方便的与其他的系统进行集成。

布式存储系统的挑战主要在于数据和状态信息的持久化,要求在自动迁移、自动容 错和并发读写的过程中,保证数据的一致性。

容错:如何可以快速检测到服务器故障,并自动的将在故障服务器上的数据进行迁移

负载均衡:新增的服务器如何在集群中保障负载均衡?数据迁移过程中如何保障不影 响现有的服务。

事务与并发控制:如何实现分布式事务。

易用性:如何设计对外接口,使得设计的系统易于使用。

原文地址:https://www.cnblogs.com/ngngng/p/13771062.html