HA 高可用软件系统一保险健指南

检测

我们先从检查和测试先河。

3

在实际的下载中,我们要足够利用scrapy的互联网下载框架,那天性格好容错性高,而且能够排错。下面的10060荒谬,小编估计放在http中大概便是503(服务器不恐怕抵达)。
眼下的法子都在单线程中运营,就算后边有多进度版的下载代码,由于没有scrapy稳定所以本身设想用多个爬虫来完毕。倘诺开发银行八个scrapy爬虫,三个担负爬页面,多个担当下载,那样的频率应该会高不少。即便近来的笔记中有涉嫌有关代码,使用redis来促成分布式。当然在单机上称不上分布式,可是使用redis作为进度间通信手段确实极好的,不管是多进程依然分布式都能可怜急忙的干活。github上有基于redis版本的scrapy,那里本人的想法是首先个爬虫负责爬页面包车型大巴属于一般爬虫(使用原版的scrapy),而第三个爬虫使用基于redis的爬虫。

  • 1 scrapy-redis安装
    pip install scrapy-redis
    安装格局倒是不会细小略,不过那么些代码相比旧了,版本是0.6.3,那些版本在python3.5上行事不健康(出错跟转码有关str,具体景况不懂),处理的点子便是把0.6.7的代码下载下来间接覆盖就足以了(反正小编也看不懂代码,覆盖了能工作)。
  • 2 配置
    scrapy-redis的安顿或然在settings中,参照文书档案
    文书档案中有多少个必须配备的参数:
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    末端仍是可以安插redis服务器端口号,还有redis服务器地址。
    REDIS_START_URLS_BATCH_SIZE
    下边包车型大巴参数对代表每一趟redis从redis服务器中赢得的链接数量,那么些调高恐怕会增多属性。
  • 3 页面爬虫

class BtSpiderEx(scrapy.Spider):
    name = 'btspiderex'
    start_urls = ['http://taohuabbs.info/forum-181-1.html']
    def parse(self,response):
        urls = response.xpath('//a[@onclick="atarget(this)"]/@href').extract()
        for url in urls:
            yield scrapy.Request(response.urljoin(url),callback=self.parsedetail)

        page_urls = response.xpath('//div[@class="pg"]/a/@href').extract()
        for url in page_urls:
            yield scrapy.Request(response.urljoin(url),callback=self.parse)

    def parsedetail(self,response):
        hrefs = response.xpath('//p[@class="attnm"]/a/@href').extract()
        for h in hrefs:
            yield scrapy.Request(response.urljoin(h),callback=self.parsedown)

    def parsedown(self,response):
        '''
        其实每次只能分析出一个bt链接
        '''
        bt_urls = response.xpath('//div[@style="padding-left:10px;"]/a/@href').extract()
        yield {'bt_urls':bt_urls}

页面爬虫代码其实相对于前方的贯彻,变得更为简约,那里把将下载链接推送到redis服务器的职务交给pipeline。

class DownloadBittorrent(object):
    def __init__(self, dir_path):
        self.dir_path = dir_path
    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            dir_path =crawler.settings.get('DIR_PATH'),
        )
    def open_spider(self, spider):
        if not os.path.exists(self.dir_path):
            os.makedirs(self.dir_path)
        self.conn = redis.Redis(port=6666)
    def close_spider(self,spdier):
        pass
    def process_item(self, item, spider):

        bt_urls = item['bt_urls']
        for url in bt_urls:
            self.conn.lpush('redisspider:start_urls',url)
        return item

open_spider在爬虫运营的时候运行,那里就足以打开redis和建立下载文件夹。redisspider:start_urls以此是redis队列名,缺省景观下scrapy-redis爬虫的行列正是爬虫名+start_urls。

  • 4 下载爬虫
    下载爬虫只承担从redis获取链接然后下载。

from scrapy_redis.spiders import RedisSpider
import re
class DistributeSpider(RedisSpider):
    name = 'redisspider'
    def parse(self,response):
        DIR_PATH = "D:/bt"
        if 'Content-Disposition' in response.headers:
            attachment = response.headers['Content-Disposition']
            pattern = re.compile(r'filename="(.+)"')
            filename = pattern.findall(attachment.decode('utf-8'))[0]
            filepath = '%s/%s' % (DIR_PATH,filename)#DIR_PATH = "D:/bt"
            with open(filepath, 'wb') as handle:
                handle.write(response.body)

settings.py配置,只列出了主要参数,那里修改了暗中同意端口号:

SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
REDIS_PORT = 6666

告警

监测数据收集上来后,怎么着去分析、预先警告那是3个乍一看不难实际很不简单的事情。

当小车没油了就会亮多个灯提醒您加油,轮胎压力不足了再亮其余三个灯提醒你加气,不问可见小车的养生手册上画了一大堆指示灯提醒或警示你差异的注意事项,简单直接明了。但大家面前说了软件系统更像1位,每年笔者去诊所体格检查,一共几十项大小检查,总有那么几项指标数字不正规,医师有时也迫于简单遵照一两项目的尤其就能开出不错的诊断处方。

脚下的通用监察和控制预先警告系统一般只好依据采集的各项系统目标,设定3个靠边范围,若偏离合理范围则发出报告警方。此类一一映射式的报告警方,仅仅完结了早期阶段的任务,提示研发去及时响应。那中间存在的难点不怕,当在1个广泛分布式应用系统中,若有多少个焦点系统出现难点,很恐怕引发相关反应,导致告警沙暴产生。在如此的狂飙中,研究开发有时也是抓瞎,随地都在喊着火,人人手上都有三个灭火器,却不是掌握该往哪个地方喷。那种状态一边只可以自个儿做好系统防火隔断带,另一方面即便进步报告警方分析诊断。

在应激式报告警方的底子上,扩张分析和确诊逻辑,形成针对使用系统特有的独家诊断式告警。那种报告警方是相似通用监察和控制预先警告系统做不了的,而须要动用系统和谐在通用数据搜集和报告警方的根基上来做。可惜的是那眼下还只是三个设想,但方向自个儿深感是没错的。

2

天性难点的确是个大标题,在满足能爬取的动静下,速度要优化。这几天抓取一个AV网站,没错AV网站的种子文件。先抓取文章列表,再抓取各类详细页面,访问种子下载页面,最终下载里面包车型大巴种子文件。

  • 方法一
    其一代码很简短利用的是requests来下载文件,里面包车型地铁下载功用代码就是从requests教程中拷贝出来的。

def process_item(self, item, spider):
        try:
            bt_urls = item['bt_urls']
            if not os.path.exists(self.dir_path):
                os.makedirs(self.dir_path)
            '''
            检查文件夹是否存在这段代码应该放到open_spider中去才是合适的,启动检查一下后面就不管了
            '''
            for url in bt_urls:
                response = requests.get(url,stream=True)
                attachment = response.headers['Content-Disposition']
                pattern = re.compile(r'filename="(.+)"')
                filename = pattern.findall(attachment)[0]
                filepath = '%s/%s' % (self.dir_path,filename)
                with open(filepath, 'wb') as handle:
                    #response = requests.get(image_url, stream=True)
                    for block in response.iter_content(1024):
                        if not block:
                            break
                        handle.write(block)
                '''
                整个代码肯定会严重影响爬虫的运行速度,是否考虑多进程方法
                '''
        except Exception as e:
            print(e)
        return item

bt_url种子的链接就放在bt_urls,由于是从下载页面重返的item,实际中最七只有3个链接。那么些代码运维起来没什么难点,但是速度一点也不慢。scrapy使用的是异步互联网框架,可是requests是逼真的2头方法,单线程的事态下自然影响到全类别统的实施。必须求突破这一个瓶颈,实际中要先考虑代码能正确运营再考虑其余地点。

  • 方法二
    既是在本线程中一贯下载会导致线程阻塞,那开启3个新的进度如何。

class DownloadBittorrent2(object):

    def __init__(self, dir_path):
        self.dir_path = dir_path
        # self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            dir_path =crawler.settings.get('DIR_PATH'),
        )

    def open_spider(self, spider):
        if not os.path.exists(self.dir_path):
            os.makedirs(self.dir_path)

    def close_spider(self, spider):
        pass

    def downloadprocess(self,url):
        try:
            response = requests.get(url,stream=True)
            attachment = response.headers['Content-Disposition']
            pattern = re.compile(r'filename="(.+)"')
            filename = pattern.findall(str(attachment))[0]#这里attachment是bytes必须要转化
            filepath = '%s/%s' % (self.dir_path,filename)
            with open(filepath, 'wb') as handle:
                #response = requests.get(image_url, stream=True)
                for block in response.iter_content(1024):
                    if not block:
                        break
                    handle.write(block)
        except Exception as e:
            print(e)
    def process_item(self, item, spider):

        bt_urls = item['bt_urls']
        if len(bt_urls)>0:#最多只有一个url
            p = Process(target=self.downloadprocess,args=(bt_urls[0],))
            p.start()

        return item

那几个代码也能平常工作,可是报错,直接促成服务器挂了。
HTTPConnectionPool(host='taohuabbs.info', port=80): Max retries exceeded with url: /forum.php?mod=attachment&aid=MjAxNzk 0fDA0OTkxZjM0fDE0ODU4NjY0OTZ8MHwxMzMzNDA= (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConne ction object at 0x00000000041F9048>: Failed to establish a new connection: [WinError 10060]由于连接方在一段时间后没有科学回答或再三再四的主机没有反应,连接尝试战败。’,))
以此只怕跟设置的推移有涉及(俺就没有安装延迟),反正便是把服务器弄死了。还有正是requests在那种非凡意况下容错能力有标题。

  • 方法三
    既然scrapy自带了三个Filespipeline,那么是或不是能够设想用那一个来下载呢!能够试行!

class DownloadBittorrent3(FilesPipeline):
    def get_media_requests(self, item, info):
        for file_url in item['bt_urls']:
            yield scrapy.Request(file_url)

代码报错了,原因是文件名打不开。那么些就事关到什么样命名下载文件名的题材。假使链接中带*.jpg那样好像的名字,程序不会非常,若是或不是会如何,链接中恐怕出现操作系统不容许在文书名中冒出的字符,那就会报错。笔者对系统自带的这一个pipeline通晓吗少,就从未继续探究。
还有某个自己期望文件名源于于服务器的反馈,对于下载文件服务器或许会把文件名发过来,那个就在headers的Content-Disposition字段中。也正是是说作者不可能不要先访问网络之后才能明确文件名。

  • 方法四
    前方大家都应用了pipeline来拍卖,实际上大家一齐能够不要pipeline而直接在spider中处理。

    def download(self,response):
        '''
        在爬取过程中发现有可能返回不是torrent文件,这时候要考虑容错性问题,虽然爬虫并不会挂掉
        '''
        attachment = response.headers['Content-Disposition']
        pattern = re.compile(r'filename="(.+)"')
        filename = pattern.findall(attachment.decode('utf-8'))[0]
        filepath = '%s/%s' % (self.settings['DIR_PATH'],filename)
        with open(filepath, 'wb') as handle:
            handle.write(response.body)

那种艺术质量不错,比较前边50/min速度,那几个能够达到100/min。其实大家能够更快。

组合这一次 618
备战准备,考虑系统的共性和个性,笔者想尝尝看看能否抽象出二个对准此类买卖在线应用所需的高可用系统爱护指南,按此对系统做1个完美地检查和测试后获得对系统运营的八个全体性认识,扶助更好的诊断系统大概潜藏的题材,以便做出及时的优化立异。

1.

过年也没啥事干,继续捣鼓爬虫。开头是准备爬豆瓣电影的,豆瓣存在部分反爬机制,爬一会就爬不动了。当然前面是突破了那几个范围,使用随机bid,设置cookie。听新闻说相会世验证码,小编爬了几万部电影也一贯不出现这几个题材。初期的想法是使用代理ip,网络上的免费代理ip大都不可信,懒得捣鼓。
在豆瓣电影那一个爬虫中,笔者实际是应用七个步骤来进行。第③部分是比照年标签查找电影,从一九〇〇到二零一七年,将各种电影链接存入到mongodb中,然后进行去重(手动)。

class DoubanMovies(scrapy.Spider):
    name = "douban"
    prefix = 'https://movie.douban.com/tag/'
    start_urls = []
    for i in range(1900,2018):
        start_urls.append(prefix+str(i))

    def parse(self,response):
        names = response.xpath('//div[@class="pl2"]/a/text()').extract()
        names = [name.strip('\n/ ') for name in names]
        names = [name for name in names if len(name)>0] #去掉空名字
        movie_urls = response.xpath('//div[@class="pl2"]/a/@href').extract()
        hrefs = response.xpath('//div[@class="paginator"]/a/@href').extract()#获取分页链接
        for href in hrefs:
           yield scrapy.Request(href, callback=self.parse)
        for i in range(len(names)):
            yield {'name':names[i],'url':movie_urls[i]}

有关mongodb去重的难点,作者使用的暂时表。首即便自己对mongodb确实不熟谙,而且笔者对JavaScript那样的语法着实不脑瓜疼。上面包车型地铁代码很简单,各样电影链接就是https://movie.douban.com/subject/26685451/
,笔者那边特别把那中档的数字提取出来,然后举办对照,那几个一定是绝无仅有的。distinct会把收获的多少再一次的展开合并,那在sql中也有那意义。

nums = movies.distinct("number")#我把链接中的数字提取了出来
m = db.movies1
for num in mums:
  movie = movies.find_one({"number":num})
  m.insert_one(movie)
moives.drop()#删除原来的数据表
m.rename('movie')#把新表命名

还有个难点正是针对性douban的时间范围,要求使用DOWNLOAD_DELAY安装时间距离,当然笔者使用bid突破了那些范围。
下边是第3个爬虫的代码,那么些代码就是造访每一种电影页面提取相关数据,然后存到mongodb中。数据提取没什么难度,为了急速判断xpath有效,最好开一个scrapy
shell实行测试。

class DoubanMovies(scrapy.Spider):
    name = "doubansubject"
    def start_requests(self):
        MONGO_URI = 'mongodb://localhost/'
        client = MongoClient(MONGO_URI)
        db = client.douban
        movie = db.movie
        cursor = movie.find({})
        urls = [c['url'] for c in cursor]
        for url in urls:
            bid = "".join(random.sample(string.ascii_letters + string.digits, 11))
            yield scrapy.Request(url,callback=self.parse,cookies={"bid":bid})

def parse(self,response):

    title = response.xpath('//span[@property="v:itemreviewed"]/text()').extract_first()
    year = response.xpath('//span[@class="year"]/text()').extract_first()#(2016)
    pattern_y = re.compile(r'[0-9]+')
    year = pattern_y.findall(year)
    if len(year)>0:
        year = year[0]
    else:
        year = ""

    directors = response.xpath('//a[@rel="v:directedBy"]/text()').extract()#导演?有没有可能有多个导演呢
    '''
    评分人数
    '''
    votes= response.xpath('//span[@property="v:votes"]/text()').extract_first()#评分人数
    '''
    分数
    '''
    score = response.xpath('//strong[@property="v:average"]/text()').extract_first()#抓取分数
    #编剧不好找等会弄
    '''
    演员
    '''
    actors = response.xpath('//a[@rel="v:starring"]/text()').extract()#演员
    genres = response.xpath('//span[@property="v:genre"]/text()').extract()#电影类型
    html = response.body.decode('utf-8')
    pattern_zp = re.compile(r'制片国家/地区:(.*)<br/>')
    nations = pattern_zp.findall(html)
    if len(nations)>0 :
        nations = nations[0]
        nations = nations.split('/')
        nations = [n.strip() for n in nations]
    '''
    多个国家之间以/分开,前后可能出现空格也要删除
    '''
    pattern_bj = re.compile(r"编剧: (.*)<br/>")
    bj_as = pattern_bj.findall(html)
    '''
    bj_as 内容是
    [<a>编剧</a>,<a></a>,<a></a>,<a></a>,]
    需要进一步提取
    '''
    p = re.compile(r'>(.*)<')
    bj = [p.findall(bj) for bj in bj_as]
    '''
    p.findall也会产生数组,需要去掉括号,只有有数据才能去掉
    '''
    bj = [b[0].strip() for b in bj if len(b)>0]#编剧的最终结果

    '''
    语言
    语言: 英语 / 捷克语 / 乌克兰语 / 法语<br/>
    '''
    pattern_lang = re.compile(r'语言:(.*)<br/>')
    langs = pattern_lang.findall(html)
    if len(langs)>0:
        langs = langs[0]
        langs = langs.split('/')
        langs = [l.strip() for l in langs]
    runtime = response.xpath('//span[@property="v:runtime"]/@content').extract_first()
    '''
    上映日期也有多个
    '''
    releasedates = response.xpath('//span[@property="v:initialReleaseDate"]/text()').extract()
    '''
    标签
    '''
    tags = response.xpath('//div[@class="tags-body"]/a/text()').extract()

    ##这里不能用return
    yield {"title":title,"year":year,"directors":directors,"score":score,"votes":votes,
    "actors":actors,"genres":genres,"nations":nations,"bj":bj,"langs":langs,"runtime":runtime,
    "releasedates":releasedates,"url":response.url
    }

上边的代码确实能健康干活,可是有个毛病正是太慢,不到伍万个页面就要多少个钟头,显明瓶颈在条分缕析这一块。质量难题会在下边三个例证中探究。

  • 轮胎
  • 刹车
  • 灯光
  • 电瓶
  • 油液
  • 雨刷
  • 底盘
  • 电路
  • 滤清器
  • 随车工具

依赖

接纳系统运营除了依托的环境,还会有对其余应用或数据库、缓存、音信队列等那么些基础服务的依赖性。各类正视都亟待单独去分析依赖的强弱、可替代性,并提供其可用率、质量等核心监察和控制指标,为确诊提供基于。

强信赖的高可用平时使用主、备方案,而弱依赖除了主、备还足以在一定情景下通过解除注重实现工作降级,那有点像壁虎断尾求生的气象。

收集

面前从资源、应用、重视四个大类来周详检查和测试评估系统,但检查和测试是急需多少搜集匡助的。而上述三类检查和测试种类的多少来自都不平等,在一个特大型的分布式环境下就需求将其构成汇集提供面向更高层次的空洞视图。

采集的点子无外乎二种:

  • Agent 采集上报
  • 使用积极性报告

对此系统财富和一些用到的开源软件,一般都以 Agent
采集上报到骨干服务器,而自行研制的采纳多应用主动报告格局的,最终在主旨监督平台上提供抽象视图突显。如下图,3个对准
Redis
集群的数额收集整合视图,视图最高按集群提供整机数据监视,若有13分可下钻到实际集群中某一台机械上。

图片 1

又过了一年
618,二月是商店一年一度的大促月,一般提前四个月各系统就会缩减须求和作用的费用,转而越来越多去关爱系统可用性、稳定性和管理控制性等方面包车型客车非效用供给。大促前的准备干活一般叫作「备战」,能够把线上运维系统想象成一辆车,大促正是它即将面临的三回严厉驾驶考验。

历次去长途自驾旅行时,笔者会把车送去对车辆情形做二个两全的检查和测试。小车工业的历史有一百多年了,而车的构造组成都部队件又相对固化,已经形成了标准且周密的自笔者批评事项,笔者在爱护检查手册上观察的检讨项目包涵:

图片 2

地点简单列了每三个检查大项,而其间又包罗部分细节的小项。当技师按这几个检查项目列表执行一次后尚未发现难点,就是近水楼台先得月车况不错的定论。然则软件系统的组成都部队件并不像小车那么固定,差异的软件系统恐怕差别,这下面有点像「人」的性子,种种人是见仁见智的,但又是有共性的,所以经济学才能为人建立共同的检查和测试正式,但又必要考虑差别化并对准个人建立例行档案,那样才能依照检查和测试结果作出相对准确的确诊。

资源

系统选拔运营总是须求依托于硬件物理能源,操作系统提供了一些主导的财富选撤消耗意况,蕴涵:

  • CPU
  • 内存
  • 磁盘
  • 网络

操作系统提供的仅仅是单机的财富利用状态,而在二个分布式系统中大家普通须要更高维度的能源使用报告,按集群,按使用等,所以那亟需我们团结去做在单机粒度上的成团和可视化展现。

图片 3

CPU 除了机器整体选择状态,最好能监测到进度级的利用,若一个经过内的 CPU
消耗分明不健康,要求有捕捉到进度内线程 CPU 使用的形式。内存以 Java
应用为例,会更加多关怀 JVM 内部的内存使用和 GC 景况;而类似 Redis
那样的内部存款和储蓄器数据库则更多关注其内部存款和储蓄器的增加趋势。磁盘 I/O
是储存类应用(SQL/NoSQL
数据库)关怀的最主要,而对于超越二分之一服务类应用一般只会打打日志,只关心磁盘存款和储蓄体积的费用。网络,站在运用的角度首要关注可信赖性(丢包率、延时)、带宽和连接数。

预案

预案正是若是某意外交事务件发生那么大家就执行有些措施,将意外导致的损失减至最低,赶快复原系统运营。那是起家在能急速诊断的根基上。前边告警一节说了,若没有针对性使用特有的独家诊断式告警,后续的解析、决策是很耗时的,很难达到急速苏醒系统的料想指标。

把针对使用普通营业的科学普及难题归类并做到告警、分析、决策和预案执行程序化后,才有可能真的真正满意4 个 9 或上述的系统可用性。

最后总计下,一份高可用系统的的保健指南包括上边多少个地点:

  • 检测
  • 收集
  • 告警
  • 预案

最后要做的正是把那四件事都做成程序化、系统化和自动化的,在那之中唯一供给人工加入的,作者觉着唯有代码分析一项,那也是程序员的最大价值所在。经历了此次
618 后,我们还才成功了百分之五十多点,只是半自动化,路漫漫其修远兮。

先前忙于工作支出,每到大促都以甘休或舒缓业务须要来还当真的技能负债,记得好像哪个人说过那样一句话:

研究开发水平的呈未来于工具的营造和选择。

背后,作者想应该须要后续做下来的便是连连打磨工具,让工具得以无人值班守护的每一天为系统做好保驾保护航行。


写点程序世间的文字,画点生活瞬间的画儿。
微信公众号「立时之间」,遇见了不妨就关怀看看。
图片 4

应用

鉴于采取的款式千差万别,大家先看共性的上边。共有的上边根本归纳:

  • 劳动 Performance 品质指标。
    例如 API 的每秒调用量(TPS),处理延时(TP99,TP=Time
    Percentage),可用率(系统成功施行次数占比)
  • 服务 SLA 满足率。
    SLA 是 Service Level
    Agreement(服务等级协议)的缩写,通过静态评估获得要承载预期量的用户数时,各应用服务要求保险的并
  • 服务 HA 可用率。
    服务是不是工作强制必要?可用率须要有多高,须求景况下是或不是可降级?
  • 服务 Isolation 隔离性。
    轻、重处理业务流程如何隔开?同、异步业务流程怎样隔绝?首要、次要的事务间怎么着隔断?
  • 服务 Extension 扩展性。
    无状态服务理论上得以极其横向扩充,但实际上海高校部分无状态服务只有是把状态外移到类似缓存和数据库中,横向的扩充瓶颈点就更换来了缓存或数据库的横向扩充能力上。

图片 5
图片 6

地方属于在应用层能抽象出的共性点,但对于现实的作业逻辑则属于特性的地点,那就需求实际难题具体分析。比如,若兑现采取了看似像异步内部存款和储蓄器队列的艺术,是不是能够显性化监测?但若想经过代码巡检来发现这么的特性化场景,投入产出比低,也不太现实。所以,二零一九年618
大家接纳了针对首要业务流程的梳理问答格局,首要用以重新考虑代码完结流程,发现部分潜在逻辑炸弹。所谓逻辑炸弹,便是在常规时整个能够,但蒙受一些界限条件大概导致系统质量小幅降低甚至宕机,在当年的备战中的确发现了两枚那样的逻辑炸弹,幸甚。

发表评论

电子邮件地址不会被公开。 必填项已用*标注