原理_原理图 - CSDN
精华内容
参与话题
  • web缓存原理分析

    千次阅读 2018-03-23 11:00:23
    为什么2月份会停更一个月的博客呢? … 过年是一个原因, 其次就是改bug, 改bug的过程感觉很恶心, 没有什么技术上的收获, 只有经验上的收获, 例如多自测几套数据, 多测测极限数据的问题, 而这些又没什么好写的, 也就...

    为什么2月份会停更一个月的博客呢? … 过年是一个原因, 其次就是改bug, 改bug的过程感觉很恶心, 没有什么技术上的收获, 只有经验上的收获, 例如多自测几套数据, 多测测极限数据的问题, 而这些又没什么好写的, 也就一直没有更新博客. 上班真的很辛苦, 每天感觉挺累的, 书也好久没有看了, 今天恰逢没有新需求, 项目在提测之际来写下一遍转载的文章, 主要记录一下在各处搜索到的关于web缓存的一些事情.


    这边文章其实写在3月22日之前, 刚参加完腾讯的面试觉得差距真是非常大, 很多内部实现原理之前根本没想学习, 现在发现这是不行的,
    http://blog.csdn.net/c_kite/article/details/79646035
    这是面试经历


    各种类型的缓存

    缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当 web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。这样带来的好处有:缓解服务器端压力,提升性能(获取资源的耗时更短了)。对于网站来说,缓存是达到高性能的重要组成部分。缓存需要合理配置,因为并不是所有资源都是永久不变的:重要的是对一个资源的缓存应截止到其下一次发生改变(即不能缓存过期的资源)。

    1.数据库数据缓存
    Web应用,特别是社交网络服务类型的应用,往往关系比较复杂,数据库表繁多,如果频繁进行数据库查询,很容易导致数据库不堪重荷。为了提供查询的性能,会将查询后的数据放到内存中进行缓存,下次查询时,直接从内存缓存直接返回,提供响应效率。比如常用的缓存方案有memcached,redis等。

    2.服务器端缓存
    代理服务器缓存
    代理服务器是浏览器和源服务器之间的中间服务器,浏览器先向这个中间服务器发起Web请求,经过处理后(比如权限验证,缓存匹配等),再将请求转发到源服务器。代理服务器缓存的运作原理跟浏览器的运作原理差不多,只是规模更大。可以把它理解为一个共享缓存,不只为一个用户服务,一般为大量用户提供服务,因此在减少相应时间和带宽使用方面很有效,同一个副本会被重用多次。常见代理服务器缓存解决方案有Squid,Nginx,Apache等。

    CDN缓存
    CDN(Content delivery networks)缓存,也叫网关缓存、反向代理缓存。CDN缓存一般是由网站管理员自己部署,为了让他们的网站更容易扩展并获得更好的性能。浏览器先向CDN网关发起Web请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态将请求转发到合适的源服务器上。虽然这种架构负载均衡源服务器之间的缓存没法共享,但却拥有更好的处扩展性。从浏览器角度来看,整个CDN就是一个源服务器,浏览器和服务器之间的缓存机制,在这种架构下同样适用。

    3.浏览器端缓存
    浏览器缓存根据一套与服务器约定的规则进行工作,在同一个会话过程中会检查一次并确定缓存的副本足够新。如果你浏览过程中,比如前进或后退,访问到同一个图片,这些图片可以从浏览器缓存中调出而即时显现。

    4.Web应用层缓存
    应用层缓存指的是从代码层面上,通过代码逻辑和缓存策略,实现对数据,页面,图片等资源的缓存,可以根据实际情况选择将数据存在文件系统或者内存中,减少数据库查询或者读写瓶颈,提高响应效率。

    HTTP头信息控制缓存

    通用首部字段

    我们先来瞅一眼http1.1协议报文首部字段中与缓存相关的字段

    1.通用首部字段

    字段名称 说明
    Cache-Control 控制缓存的行为
    Pragma http1.0的旧社会遗留物, 值为”no-cache”时禁用缓存

    2.请求首部字段

    字段名称 说明
    if-Match 比较ETag是否一致
    if-None-Match 比较ETag是否不一致
    if-Modified-Since 比较资源最后更新的时间是否一致
    if-Unmodified-Since 比较资源最后更新的时间是否不一致

    3.响应首部字段

    字段名称 说明
    ETag 资源的匹配信息

    4. 实体首部字段

    字段名称 说明
    Expires http1.0的遗留物, 实体主体过期的时间
    Last-Modified 资源最后一次修改的时间

    http1.0 时代缓存字段详解

    在 http1.0 时代,给客户端设定缓存方式可通过两个字段PragmaExpires来规范。虽然这两个字段早可抛弃,但http协议做了向下兼容,所以依然可以看到。

    1.Pragma

    Pragma:设置页面是否缓存,为Pragma则缓存,no-cache则不缓存

    当该字段值为no-cache的时候,会知会客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。

    2.Expires

    有了Pragma来禁用缓存,自然也需要有个东西来启用缓存和定义缓存时间,对http1.0而言,Expires就是做这件事的首部字段。 Expires的值对应一个GMT(格林尼治时间),比如Mon, 22 Jul 2002 11:12:01 GMT来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。

    如果Pragma头部和Expires头部同时存在,则起作用的会是Pragma,需要注意的是,响应报文中Expires所定义的缓存时间是相对服务器上的时间而言的,其定义的是资源“失效时刻”,如果客户端上的时间跟服务器上的时间不一致(特别是用户修改了自己电脑的系统时间),那缓存时间可能就没啥意义了。


    http1.1时代缓存字段详解

    1.Cache-Control

    针对上述的Expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,http1.1新增了 Cache-Control 来定义缓存过期时间。注意:若报文中同时出现了 ExpiresCache-Control,则以 Cache-Control 为准。

    也就是说优先级从高到低分别是 Pragma -> Cache-Control -> Expires

    Cache-Control也是一个通用首部字段,这意味着它能分别在请求报文和响应报文中使用。在RFC中规范了 Cache-Control 的格式为:

    Cache-Control: cache-directive

    作为请求首部时,cache-directive 的可选值有:

    字段名称 说明
    no-cache 告知代理服务器不直接使用缓存, 要求向原服务器发起请求
    no-store 所有内容都不会被保存到缓存或Internet临时文件中
    max-age=delta-seconds 告知服务器客户端希望接收一个存在时间(Age)不大于delta-seconds秒的资源
    max-stale[=delta-seconds] 告知(代理)服务器客户端愿意接收一个超过缓存时间的资源, 若有定义delta-seconds则为delta-seconds秒, 若没有则为任意超过的时间
    min-fresh=delta-seconds 告知(代理)服务器客户端希望接收一个在小于delta-seconds秒内被更新过的资源
    no-transform 告知(代理)服务器客户端希望获取实体数据没有被转换(比如压缩)过的资源
    only-if-cached 告知(代理)服务器客户端希望获取缓存的内容(若有),而不用向原服务器发去请求
    cache-extension 自定义扩展值,若服务器不识别该值将被忽略掉

    Cache-Control: no-cache:这个很容易让人产生误解,使人误以为是响应不被缓存。实际上Cache-Control: no-cache是会被缓存的,
    只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。
    Cache-Control: no-store:这个才是响应不被缓存的意思。

    作为相应头部, cache-directive的可选值:

    字段名称 说明
    public 表明任何情况下都得缓存该资源(即使是需要http认证的资源)
    Private[=”field-name”] 表明返回报文中全部或部分(若指定了field-name则为field-name的字段数据)仅开放给某些用户(服务器指定的share-user, 如代理服务器)做缓存使用, 其他用户则不能缓存这些数据
    no-cache 不直接使用缓存, 要求向服务器发起(新鲜度校验)请求
    no-store 所有内容都不会被保存到缓存或Internet临时文件中
    no-transform 告知客户端缓存文件时不得对实体数据做任何改变
    only-if-cached 告知(代理)服务器客户端希望获取缓存的内容(若有), 而不用向原服务器发去请求
    must-revalidate 当前资源一定是向原服务器发去验证请求的, 若请求失败会返回504(而非代理服务器上的缓存
    proxy-revalidate 与must-revalidate类似, 但仅能应用于共享缓存(如代理)
    max-age=delta-seconds 告知客户端该资源在delta-seconds秒内是新鲜的, 无需向服务器发送请求
    s-maxage=delta-seconds 同max-age, 但仅应用于共享缓存(如代理)
    cache-extension 自定义扩展值. 若服务器不识别该值将被忽略掉

    Cache-Control 允许自由组合可选值,例如:

    Cache-Control: max-age=3600, must-revalidate

    它意味着该资源是从原服务器上取得的,且其缓存(新鲜度)的有效时间为一小时,在后续一小时内,用户重新访问该资源则无须发送请求。

    当然这种组合的方式也会有些限制,比如 no-cache 就不能和 max-agemin-freshmax-stale 一起搭配使用。

    2.Last-Modified/If-Modified-Since

    Last-Modified/If-Modified-Since要配合Cache-Control使用。

    (1) Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间。

    (2) If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache

    3.Etag/If-None-Match

    Etag/If-None-Match也要配合Cache-Control使用。

    (1)Etag: web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器觉得)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。

    (2)If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match (Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304

    4.既生Last-Modified何生Etag?

    你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

    (1). Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

    (2). 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存

    (3). 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

    Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-ModifiedETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

    5.不太常用的两个http字段If-Unmodified-Since/If-Match

    (1)If-Unmodified-Since: Last-Modified-value
    告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。

    当遇到下面情况时,If-Unmodified-Since 字段会被忽略:

    1. Last-Modified值对上了(资源在服务端没有新的修改);
    2. 服务端需返回2XX和412之外的状态码;
    3. 传来的指定日期不合法

    (2)If-Match: ETag-value
    告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。


    浏览器缓存流程图

    小结一下, 浏览器第一次请求

    第一次流程图

    浏览器第二次请求

    第二次流程图


    如何配置

    1)通过代码的方式,在web服务器返回的响应中添加Expires和Cache-Control Header;

    比如在JavaWeb里面,我们可以使用类似下面的代码设置强缓存:

    java.util.Date date = new java.util.Date(); 
    response.setDateHeader("Expires",date.getTime()+20000); // Expires:过时期限值 
    response.setHeader("Cache-Control", "public");          // Cache-Control来控制页面的缓存与否,public:浏览器和缓存服务器都可以缓存页面信息;
    response.setHeader("Pragma", "Pragma");                 // Pragma:设置页面是否缓存,为Pragma则缓存,no-cache则不缓存

    还可以通过类似下面的java代码设置不启用强缓存:

    response.setHeader( "Pragma", "no-cache" ); 
    response.setDateHeader("Expires", 0); 
    response.addHeader( "Cache-Control", "no-cache" );//浏览器和缓存服务器都不应该缓存页面信息

    2)通过配置web服务器的方式,让web服务器在响应资源的时候统一添加Expires和Cache-Control Header。

    tomcat提供了一个ExpiresFilter专门用来配置强缓存

    这里写图片描述

    nginx和apache作为专业的web服务器,都有专门的配置文件,可以配置expires和cache-control,

    Nginx服务器的配置方法为:

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
        #过期时间为30天,#图片文件不怎么更新,过期可以设大一点,
        expires 30d;
    }
    location ~ .*\.(js|css)$ {
        #如果频繁更新,则可以设置得小一点。
        expires 1d; 
    
        add_header Cache-Control max-age=86400;
        etag on;
    }

    Apache服务器的配置方法为:

    <Location ~ "\.(js|css|png|jpg|gif|bmp|html)$">
    
         ExpiresActive On
    
         ExpiresDefault "access plus 1 hours"
    
         Header set Cache-Control max-age=3600
    
         Header unset Pragma
    
    </Location>
    
    <Location ~ "\.(do|jsp|aspx|asp|php|json|action|ashx|axd|cgi)$">
    
       Header set Cache-Control no-cache,no-store,max-age=0
       Header unset Expires
       Etag   INode Mtime Size
    
    </Location>

    3.缓存配置的一些注意事项

    1. 只有get请求会被缓存,post请求不会

    2. Etag 在资源分布在多台机器上时,对于同一个资源,不同服务器生成的Etag可能不相同,此时就会导致304协议缓存失效,客户端还是直接从server取资源。可以自己修改服务器端etag的生成方式,根据资源内容生成同样的etag。需要注意的是分布式系统里多台机器间文件的last-modified必须保持一致,以免负载均衡到不同机器导致比对失败,Yahoo建议分布式系统尽量关闭掉Etag(每台机器生成的Etag都会不一样,因为除了 last-modified、文档节点也很难保持一致)


    用户行为与缓存

    行为缓存流程图


    缓存的清除方法

    由于在开发的时候不会专门去配置强缓存,而浏览器又默认会缓存图片,css和js等静态资源,所以开发环境下经常会因为强缓存导致资源没有及时更新而看不到最新的效果,解决这个问题的方法有很多,常用的有以下几种:

    1)直接ctrl+f5,这个办法能解决页面直接引用的资源更新的问题;

    2)使用ctrl+shift+delete清除缓存;

    3)如果用的是chrome,可以F12在network那里把缓存给禁掉(这是个非常有效的方法):

    例图

    4)在开发阶段,给资源加上一个动态的参数,如css/index.css?v=0.0001,由于每次资源的修改都要更新引用的位置,同时修改参数的值,所以操作起来不是很方便,一般使用前端的构建工具来修改这个参数

    4.1) 原生写法

    function addVersion(asset){
      asset.forEach(function(item,index) {
        if(item.indexOf('.js') != -1) {
          document.write('<script src="'+item+'?v='+ (new Date().getTime()) +'"><\/script>');
        }else if(item.indexOf('.css') != -1){
          document.write('<link rel="stylesheet" href="'+item+'?v='+(new Date().getTime())+'">');
        }
      });
    }

    4.2) 采用gulp插件

    4.2.1) gulp-rev-append

    这里写图片描述

    4.2.2) gulp-rev和gulp-rev-collector也能实现同样的功能

    // 修改html和css文件,给静态文件打戳
    gulp.task('stamp', function(){
        gulp.src(['rev/*.json', dest.css + "**/*.css"]).
            pipe(revCollector({
                replaceReved: true
            })).
            // 修改为 ?v=stamp 形式
            pipe(replace(/\-([0-9a-z]{8,})\.(png|jpg|gif|ico)/g, function(a, b, c){
                return '.' + c + '?v=' + b;
            })).
            pipe(gulp.dest(dest.css));
        gulp.src(['rev/*.json', src.html]).
            pipe(revCollector({
                replaceReved: true
            })).
            // 修改为 ?v=stamp 形式
            pipe(replace(/\-([0-9a-z\-]{8,})\.(css|js)/g, function(a, b, c){
    
                return '.' + c + '?v=' + b;
            })).
            pipe(gulp.dest(dest.html));
    });

    4.3)

    如果资源引用的页面,被嵌入到了一个iframe里面,可以在iframe的区域右键单击重新加载该页面,以chrome为例:

    这里写图片描述

    4.4)

    如果缓存问题出现在ajax请求中,最有效的解决办法就是ajax的请求地址追加随机数;

    4.5)

    还有一种情况就是动态设置iframe的src时,有可能也会因为缓存问题,导致看不到最新的效果,这时候在要设置的src后面添加随机数也能解决问题;


    离线缓存

    localstorage和sessionstorage太简单不说了

    设置方法

    1 . 在HTML5的html标签中添加一个 manifest=”XXX.appcache” 属性声明

    <!DOCTYPE html>
    <html manifest="list.appcache">

    2 . XXX.appcache文件中定义需要缓存的文件清单(里面的资源文件的路径是相对于manifest的路径而言的)

    CACHE MANIFEST
    # VERSION 0.3
    # 直接缓存的文件
    CACHE:
    # 需要在线访问的文件
    NETWORK:
    # 替代方案
    FALLBACK:

    CACHE MANIFEST –(必须) 此标题下列出的文件将在首次下载后进行缓存

    ../addDevice.html
    ../static/css/reset.css
    ../static/js/addDevice.js
    ../static/img/ms1.png
    ../static/img/clean-face.jpg

    NETWORK—-(可选)

    (1)通配符’*’表示不在CACHE MANIFEST清单里的文件,每次都要重新请求

    *

    (2)或者指定特定文件,比如login.asp不被离线存储,每次都要重新发起请求

    login.asp

    FALLBACK—-(可选) 断网时访问指定路径时的替换文件

    如断网时访问/html5/ 目录下的所有资源文件,则用 “offline.html” 替代

    /html5/ /offline.html

    更新原理

    更新了manifest文件,浏览器会自动的重新下载新的manifest文件并把manifest缓存列表中的所有文件重新请求一次(第二次刷新替换本地缓存为最新缓存),而不是单独请求某个特定修改过的资源文件,因为manifest是不知道哪个文件被修改过了的。

    对于全局更新不必要担心,因为没有更新过的资源文件,请求依旧是304响应,只有真正更新过的资源文件才是服务器返回的才是200.

    所以控制离线存储的更新,需要2个步骤,一是更新资源文件,二是更新manifest文件,只要修改manifest文件随意一处,浏览器就会感知manifest文件更新,而我们的资源文件名称通常是固定的,需要更新manifest文件怎么操作呢?一个比较好的方式是更新以# 开头的版本号注释,告诉浏览器这个manifest文件被更新过。

    manifest资源是滞后静默更新的

    这里写图片描述

    第二次刷新界面之后,才能看到更新后的效果

    这里写图片描述

    /*code1,简单粗暴的*/
        applicationCache.onupdateready = function(){
            applicationCache.swapCache(); //强制替换缓存
            location.reload();            //重新加载页面
        };
        /*code2,缓存公用方法*/
        // var EventUtil = {
        //     addHandler: function(element, type, handler) {
        //         if (element.addEventListener) {
        //             element.addEventListener(type, handler, false);
        //         } else if (element.attachEvent) {
        //             element.attachEvent("on" + type, handler);
        //         } else {
        //             element["on" + type] = handler;
        //         }
        //     }
        // };
        // EventUtil.addHandler(applicationCache, "updateready", function() {      //缓存更新并已下载,要在下次进入页面生效
        //     applicationCache.update();  //检查缓存manifest文件是否更新,ps:页面加载默认检查一次。
        //     applicationCache.swapCache();  //交换到新的缓存项中,交换了要下次进入页面才生效
        //     location.reload();              //重新载入页面
        // });

    applicationCache 提供了如下的事件:

    Event handler Event handler event type
    onchecking checking
    onerror error
    onnoupdate noupdate
    ondownloading downloading
    onprogress progress
    onupdateready updateready
    oncached cached
    onobsolete obsolete

    提供了如下的API:

    void update();
    // 更新, 但是这个方法适用于一些长期打开的页面,而不会有刷新动作,比如邮件系统,所以这个就比较适合做自动更新下载

    void abort();
    // 取消

    void swapCache();
    // 替换缓存内容 ,对于manifest文件的改变,通常是下一次的刷新才会触发下载更新,第二次刷新才会切换使用新的缓存文件,通过这个方法,可以强制将缓存替换

    注意事项

    站点中的其他页面即使没有设置manifest属性,请求的资源如果在缓存中也从缓存中访问

    系统会自动缓存引用清单文件的 HTML 文件

    如果manifest文件,或者内部列举的某一个文件不能正常下载,整个更新过程将视为失败,浏览器继续全部使用老的缓存

    在manifest中使用的相对路径,相对参照物为manifest文件

    站点离线存储的容量限制是5M

    manifest文件中CACHE则与NETWORK,FALLBACK的位置顺序没有关系,如果是隐式声明需要在最前面

    manifest中必须一一声明文件名,这很令人头痛

    引用manifest的html必须与manifest文件同源,在同一个域下

    除此之外,还增加了两大问题:

    (1)PV UV的计算难题,由于当前页面被强制加入manifest,那么PV 和UV的统计,成了一个难题,因为请求不再是发送到服务器;

    (2)缓存对于某个使用manifest的文件,其带有的参数可能是随机性的统计参数,如sid=123sss, sid=234fff ,尤其是比如商品详情的id字段等,这样每个页面都自动加入到manifest中,将会带来很大的存储开销,而且是毫无意义的;

    所以伴随而来的,是如何在现有的体系架构下进行数据统计的难题,

    对于第一个问题 常规方案是进入离线存储页面后自动发出ajax请求,以告知服务器统计PV UV;

    对于第二个问题,是将GET请求方式改成POST方式。


    转载:
    [1]http://www.zhangxinxu.com/wordpress/2013/05/caching-tutorial-for-web-authors-and-webmasters/

    [2]http://www.codeceo.com/article/http-cache-control.html

    [3]http://www.cnblogs.com/Joans/p/3956490.html

    [4]http://web.jobbole.com/86970/

    [5]http://www.jianshu.com/p/1a9268594deb

    [6]http://blog.techbeta.me/2016/02/http-cache/

    [7]http://hahack.com/wiki/sundries-http-web.html

    [8]http://www.jianshu.com/p/99dc1f8f62bf

    [9]https://www.cnblogs.com/wangpenghui522/p/5498427.html

    展开全文
  • Redis详解(1)--原理和机制

    万次阅读 2019-06-03 18:37:56
    一、性能 1 性能测试 测试环境: RHEL 6.3 / HP Gen8 Server/ 2 * Intel Xeon 2.00GHz(6 core) / 64G DDR3 memory / 300G RAID-1 SATA / 1 master(writ AOF), 1 slave(write AOF & RDB) ...

    一、性能


    1 性能测试

    测试环境: RHEL 6.3 / HP Gen8 Server/ 2 * Intel Xeon 2.00GHz(6 core) / 64G DDR3 memory / 300G RAID-1 SATA / 1 master(writ AOF), 1 slave(write AOF & RDB)

    数据准备: 预加载两千万条数据,占用10G内存。 

    测试工具:自带的redis-benchmark,默认只是基于一个很小的数据集进行测试,调整命令行参数如下,就可以开100条线程(默认50),SET 1千万次(key在0-1千万间随机),key长21字节,value长256字节的数据。
    1
    redis-benchmark -t SET -c 100 -n 10000000 -r 10000000 -d 256
    测试结果(QPS):

    1.SET:4.5万,

    2.GET:6万 ,

    3.INCR:6万,

    4.真实混合场景: 2.5万SET & 3万GET
    单条客户端线程时6千TPS,50与100条客户端线程差别不大,200条时会略多。
    Get/Set操作,经过了LAN,延时也只有1毫秒左右,可以反复放心调用,不用像调用REST接口和访问数据库那样,每多一次外部访问都心痛。
    资源监控:
    1.CPU: 占了一个处理器的100%,总CPU是4%(因为总共有2CPU*6核*超线程 = 24个处理器),可见单线程下单处理器的能力是瓶颈。 AOF rewrite时另一个处理器占用50-70%。
    2.网卡:15-20 MB/s receive, 3Mb/s send(no slave) or 15-20 MB/s send (with slave) 。当把value长度加到4K时,receive 99MB/s,已经到达千兆网卡的瓶颈,TPS降到2万。
    3.硬盘:15MB/s(AOF append), 100MB/s(AOF rewrite/AOF load,普通硬盘的瓶颈),
     

    2 为什么快

    主要是以下几点点


    一)、纯内存操作

     数据存放在内存中,内存的响应时间大约是 100纳秒 ,这是Redis每秒万亿级别访问的重要基础。


    二)、单线程操作,避免了频繁的上下文切换

    虽然是采用单线程,但是单线程避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU;虽然作者认为CPU不是瓶颈,内存与网络带宽才是。但实际测试时并非如此,见上。


    三)、采用了非阻塞I/O多路复用机制

    多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了事件,不在I/O上浪费过多的时间。 

    四)、纯ANSI C编写。
    不依赖第三方类库,没有像memcached那样使用libevent,因为libevent迎合通用性而造成代码庞大,所以作者用libevent中两个文件修改实现了自己的epoll event loop。微软的兼容Windows补丁也因为同样原因被拒了。
    快,原因之一是Redis多样的数据结构,每种结构只做自己爱做的事,当然比数据库只有Table,MongogoDB只有JSON一种结构快了。

     

       

     

     

    二、I/O复用模型和Reactor 设计模式


    详情请看:使用 libevent 和 libev 提高网络应用性能——I/O模型演进变化史 

    Redis内部实现采用epoll+自己实现的简单的事件框架。 epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性, 绝不在io上浪费一点时间:

    1、I/O 多路复用的封装

    I/O 多路复用其实是在单个线程中通过记录跟踪每一个sock(I/O流) 的状态来管理多个I/O流。

    因为 Redis 需要在多个平台上运行,同时为了最大化执行的效率与性能,所以会根据编译平台的不同选择不同的 I/O 多路复用函数作为子模块,提供给上层统一的接口。

    redis的多路复用, 提供了select, epoll, evport, kqueue几种选择,在编译的时候来选择一种。

    select是POSIX提供的, 一般的操作系统都有支撑;
    epoll 是LINUX系统内核提供支持的;
    evport是Solaris系统内核提供支持的;
    kqueue是Mac 系统提供支持的;

    #ifdef HAVE_EVPORT
    #include "ae_evport.c"
    #else
        #ifdef HAVE_EPOLL
        #include "ae_epoll.c"
        #else
            #ifdef HAVE_KQUEUE
            #include "ae_kqueue.c"
            #else
            #include "ae_select.c"
            #endif
        #endif
    #endif

    为了将所有 IO 复用统一,Redis 为所有 IO 复用统一了类型名 aeApiState,对于 epoll 而言,类型成员就是调用 epoll_wait所需要的参数
    接下来就是一些对epoll接口的封装了:包括创建 epoll(epoll_create),注册事件(epoll_ctl),删除事件(epoll_ctl),阻塞监听(epoll_wait)等
    创建 epoll 就是简单的为 aeApiState 申请内存空间,然后将返回的指针保存在事件驱动循环中
    注册事件和删除事件就是对 epoll_ctl 的封装,根据操作不同选择不同的参数
    阻塞监听是对 epoll_wait 的封装,在返回后将激活的事件保存在事件驱动中



     

    Reactor 设计模式:事件驱动循环流程

    Redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符) 

    当 main 函数初始化工作完成后,就需要进行事件驱动循环,而在循环中,会调用 IO 复用函数进行监听
    在初始化完成后,main 函数调用了 aeMain 函数,传入的参数就是服务器的事件驱动

    Redis 对于时间事件是采用链表的形式记录的,这导致每次寻找最早超时的那个事件都需要遍历整个链表,容易造成性能瓶颈。而 libevent 是采用最小堆记录时间事件,寻找最早超时事件只需要 O(1) 的复杂度
     

    如上图,IO多路复用模型使用了Reactor设计模式实现了这一机制。

    通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。

    用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select/epoll函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。由于select/epoll函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。

     

    redis线程模型:

    如图所示:

    简单来说,就是。我们的redis-client在操作的时候,会产生具有不同事件类型的socket。在服务端,有一段I/0多路复用程序,将其置入队列之中。然后,IO事件分派器,依次去队列中取,转发到不同的事件处理器中。

     

    3、数据结构说明


    http://redis.io/topics/data-types

    2.1 Key

    1、key越短越好:Redis是个内存数据库,Key键越短你需要的空间就越少,因此key不能太长,比如1024字节。

    举个例子:在一个32位的Redis服务器上,如果储存一百万个键,每个值的长度是32-character,那么在使用6-character长度键名时,将会消耗大约96MB的空间,但是如果使用12-character长度的键名时,空间消耗则会提升至111MB左右。随着键的增多,15%的额外开销将产生重大的影响。

    2、key命名要表达清楚意思。建议用”:”分隔域划分键名,用”.”作为单词间的连接,如”comment:1234:reply.to”。

    使用合适的命名方法会简化你的数据库管理,当你通过你的应用程序或者服务做键的命名空间时,你就可以在数据迁移、转换或者删除时轻松的识别。

    Redis另一个常见用例是作为热数据项作的第二数据存储,大部分的数据被保存在其他的数据库中,比如PostgreSQL或MongoDB。在这些用例中,当数据从主存储移除时,开发者经常会忘记删除Redis中对应的数据。这种存在跨数据存储的情况下,通常需要做级联删除,这种情况下,可以通过在Redis配置保存特定数据项的所有识别符来实现,从而保证数据在主数据库被删除后,系统会调用一个清理程序来删除所有相关副本和信息。

     

     

    2.2 String

    String是最简单的类型,一个key对应一个value,string类型是二进制安全的。Redis的string可以包含任何数据,比如jpg图片或者序列化的对象(PHP中对象序列化函数serialize)

           内部实现,其本质是一个byte数组,字符串的大小被限制在512M以内

    struct sdshdr {  
          long len; //buf数组的长度  
          long free; //buf数组中剩余可用字节数  
          char buf[]; //存储实际字符串内容  
    }  

     

    所有常用命令的复杂度都是O(1),普通的Get/Set方法,可以用来做Cache,存Session,为了简化架构甚至可以替换掉Memcached。

    Incr/IncrBy/IncrByFloat/Decr/DecrBy,可以用来做计数器,做自增序列。key不存在时会创建并贴心的设原值为0。IncrByFloat专门针对float,没有对应的decrByFloat版本?用负数啊。

    SetNx, 仅当key不存在时才Set。可以用来选举Master或做分布式锁:所有Client不断尝试使用SetNx master myName抢注Master,成功的那位不断使用Expire刷新它的过期时间。如果Master倒掉了key就会失效,剩下的节点又会发生新一轮抢夺。

    2.3 Hash

    Key-HashMap结构,相比String类型将这整个对象持久化成JSON格式,Hash将对象的各个属性存入Map里,可以只读取/更新对象的某些属性。这样有些属性超长就让它一边呆着不动,另外不同的模块可以只更新自己关心的属性而不会互相并发覆盖冲突。

    2.4 List

    List是一个双向链表,支持双向的Pop/Push,江湖规矩一般从左端Push,右端Pop——LPush/RPop,而且还有Blocking的版本BLPop/BRPop,客户端可以阻塞在那直到有消息到来,所有操作都是O(1)的好孩子,可以当Message Queue来用。当多个Client并发阻塞等待,有消息入列时谁先被阻塞谁先被服务。任务队列系统Resque是其典型应用。

    有RPopLPush/ BRPopLPush,弹出来返回给client的同时,把自己又推入另一个list,LLen获取列表的长度。

    2.5 Set

    Set就是Set,可以将重复的元素随便放入而Set会自动去重,底层实现也是hash table。

    2.6 Sorted Set

    有序集,元素放入集合时还要提供该元素的分数。

    Sorted Set的实现是hash table(element->score, 用于实现ZScore及判断element是否在集合内),和skip list(score->element,按score排序)的混合体。 skip list有点像平衡二叉树那样,不同范围的score被分成一层一层,每层是一个按score排序的链表。

    ZAdd/ZRem是O(log(N)),ZRangeByScore/ZRemRangeByScore是O(log(N)+M),N是Set大小,M是结果/操作元素的个数。可见,原本可能很大的N被很关键的Log了一下,1000万大小的Set,复杂度也只是几十不到。当然,如果一次命中很多元素M很大那谁也没办法了。

    2.8 Lua Script

    Redis2.6内置的Lua Script支持,可以在Redis的Server端一次过运行大量逻辑,就像存储过程一样,避免了海量中间数据在网路上的传输。

    Lua自称是在Script语言里关于快的标准,Redis选择了它而不是流行的JavaScript。
    因为Redis的单线程架构,整个Script默认是在一个事务里的。
    Script里涉及的所有Key尽量用变量,从外面传入,使Redis一开始就知道你要改变哪些key。(but why?)
    Eval每次传输一整段Script比较费带宽,可以先用Script Load载入script,返回哈希值。然后用EvalHash执行。因为就是SHA-1,所以任何时候执行返回的哈希值都是一样的。
    内置的Lua库里还很贴心的带了CJSON,可以处理json字符串。
    一段用Redis做Timer的示例代码,下面的script被定期调用,从以触发时间为score的sorted set中取出已到期的Job,放到list中给Client们blocking popup。
     
    — KEYS: [1]job:sleeping, [2]job:ready
    — ARGS: [1]currentTime
    — Comments: result is the  job id
    local jobs=redis.call(‘zrangebyscore’, KEYS[1], ‘-inf’, ARGV[1])
    local count = table.maxn(jobs)
     
    if count>0  then
      — Comments: remove from Sleeping Job sorted set
      redis.call(‘zremrangebyscore’, KEYS[1], ‘-inf’, ARGV[1])
     
      — Comments: add to the Ready Job list
      — Comments: can optimize to use lpush id1,id2,… for better performance
      for i=1,count do
        redis.call(‘lpush’, KEYS[2], jobs[i])
      end
    end

     

    在Redis使用过程中,Lua脚本的支持无疑给开发者提供一个非常友好的开发环境,从而大幅度解放用户的创造力。如果使用得当,Lua脚本可以给性能和资源消耗带来非常大的改善。取代将数据传送给CPU,脚本允许你在最接近数据的地方执行逻辑,从而减少网络延时和数据的冗余传输。

    在Redis中,Lua一个非常经典的用例就是数据过滤或者将数据聚合到应用程序。通过将处理工作流封装到一个脚本中,你只需要调用它就可以在更短的时间内使用很少的资源来获取一个更小的答案。

    提示:Lua确实非常棒,但是同样也存在一些问题,比如很难进行错误报告和处理。一个明智的方法就是使用Redis的Pub/Sub功能,并且让脚本通过专用信道来推送日志消息。然后建立一个订阅者进程,并进行相应的处理。


    2.9使用合适的数据结构

     

    不管是内存使用或者是性能,有的时候数据结构将产生很大的影响,下面是一些可以参考的最佳实践:

    1、使用hash取代将数据存储为数千(或者数百万)独立的字符串。哈希表是非常有效率的,并且可以减少你的内存使用(因为小Hashes会被编码成一个非常小的空间);同时,哈希还更有益于细节抽象和代码可读。

    2、合适时候,使用list代替set。如果你不需要使用set特性,List在使用更少内存的情况下可以提供比set更快的速度。

    3、Sorted sets是最昂贵的数据结构,不管是内存消耗还是基本操作的复杂性。如果你只是需要一个查询记录的途径,并不在意排序这样的属性,那么轻建议使用哈希表。

    4、Redis中一个经常被忽视的功能就是bitmaps或者bitsets(V2.2之后)。Bitsets允许你在Redis值上执行多个bit-level操作,比如一些轻量级的分析。

     

    5、使用bit位级别操作和byte字节级别操作来减少不必要的内存使用

     

     

    4、数据一致性:事务


          用Multi(Start Transaction)、Exec(Commit)、Discard(Rollback)实现。 在事务提交前,不会执行任何指令,只会把它们存到一个队列里,不影响其他客户端的操作。在事务提交时,批量执行所有指令。《Redis设计与实现》中的详述。

    注意,Redis里的事务,与我们平时的事务概念很不一样:

    它仅仅是保证事务里的操作会被连续独占的执行。因为是单线程架构,在执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的。
    它没有隔离级别的概念,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题。
    它不保证原子性——所有指令同时成功或同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半进行回滚的能力。在redis里失败分两种,一种是明显的指令错误,比如指令名拼错,指令参数个数不对,在2.6版中全部指令都不会执行。另一种是隐含的,比如在事务里,第一句是SET foo bar, 第二句是LLEN foo,对第一句产生的String类型的key执行LLEN会失败,但这种错误只有在指令运行后才能发现,这时候第一句成功,第二句失败。还有,如果事务执行到一半redis被KILL,已经执行的指令同样也不会被回滚。
    Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行。

     

    5、配置详解


    1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

        daemonize no

    2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

        pidfile /var/run/redis.pid

    3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

        port 6379

    4. 绑定的主机地址

        bind 127.0.0.1

    5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

        timeout 300

    6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

        loglevel verbose

    7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null

        logfile stdout

    8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

        databases 16

    9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

        save <seconds> <changes>

        Redis默认配置文件中提供了三个条件:

        save 900 1

        save 300 10

        save 60 10000

        分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

     

    10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

        rdbcompression yes

    11. 指定本地数据库文件名,默认值为dump.rdb

        dbfilename dump.rdb

    12. 指定本地数据库存放目录

        dir ./

    13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

        slaveof <masterip> <masterport>

    14. 当master服务设置了密码保护时,slav服务连接master的密码

        masterauth <master-password>

    15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭

        requirepass foobared

    16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

        maxclients 128

    17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区

        maxmemory <bytes>

    18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

        appendonly no

    19. 指定更新日志文件名,默认为appendonly.aof

         appendfilename appendonly.aof

    20. 指定更新日志条件,共有3个可选值: 
        no:表示等操作系统进行数据缓存同步到磁盘(快) 
        always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) 
        everysec:表示每秒同步一次(折衷,默认值)

        appendfsync everysec

     

    21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)

         vm-enabled no

    22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

         vm-swap-file /tmp/redis.swap

    23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

         vm-max-memory 0

    24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值

         vm-page-size 32

    25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

         vm-pages 134217728

    26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

         vm-max-threads 4

    27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

        glueoutputbuf yes

    28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

        hash-max-zipmap-entries 64

        hash-max-zipmap-value 512

    29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)

        activerehashing yes

    30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

        include /path/to/local.conf

    展开全文
  • 掌握 Windows Server 2012 R2 系统中,DHCP,DNS,WINS,IPAM,打印服务,VPN 服务和 DirectAccess 服务的应用场景,原理和配置过程。
  • 网络抓包原理

    万次阅读 多人点赞 2018-06-08 16:27:02
    # 网络抓包原理及常用抓包工具本文以App作为例子,实际应用不限于App范围。## 1. 为什么要抓包1. 定位网络接口问题2. 分析其他App数据接口 3. 学习网络协议,使用抓包工具分析网络数据更直观 大部分场合都可以通过...

    # 网络抓包原理及常用抓包工具

    本文以App作为例子,实际应用不限于App范围。

    ## 1. 为什么要抓包

    1. 定位网络接口问题

    2. 分析其他App数据接口

    3. 学习网络协议,使用抓包工具分析网络数据更直观 

    大部分场合都可以通过程序调试来定位问题,但有些场景使用抓包来定位接口问题更准确、更方便,如以下场景:

    * 你发送数据给后台,但后台没有收到,可以对接口进行抓包分析,看是后台处理有问题,还是App没有将数据发出去,或是App发送数据格式有误

    * 你和后台接口联调测通,但业务数据对不上,你认为是后台问题,后台认为是你发的问题,可以抓包确认问题所在

    * 线上App出现bug需要定位,但你没在公司,没有代码可调试,可直接抓包分析

    * App页面渲染缓慢,抓包看下接口响应时长,是不是后台出现性能问题

    * 需要测试弱网环境下App的体验?抓包工具可设置流量限制,可设置接口堵塞

    * 想改变某接口的响应报文?想多次重发某一请求,但App业务流程有限制?可以试试抓包工具提供的功能

    ## 2. 抓包的原理

    要实现对App的网络数据抓包,需要监控App与服务器交互之间的网络节点,监控其中任意一个网络节点(网卡),获取所有经过网卡中的数据,对这些数据按照网络协议进行解析,这就是抓包的基本原理。

    但是中间网络节点,不受我们控制,所以基本无法实现抓包的,只能在客户端和服务端进行抓包。

    通常我们监控本地网卡数据,如下图:

     

     

    > 手机、本地网络属于客户端侧的抓包,接入设备、服务器属于后台侧的抓包,两者没有什么不同

    `本地网络`指的是WIFI的路由,如果直接抓路由器的包还是比较麻烦的,因此我们会在`手机``本地路由`之间加一层`代理服务`,这样只要抓代理服务的网络数据即可:

     

     

    虽然在`手机`侧也可实现抓包,但和`本地路由`一样,抓包比较麻烦,如果不是没有办法,尽量还是不在手机侧抓包。但是有一种情况必须在手机端抓包,那就是在4G网络情况下:


     4G网络状态下如何抓包,以及它的劣势,我们后面章节再细讲。

    ## 3. 网络协议

    抓包实际上是分析网络协议的一种过程,尽管繁琐的细节劳动都让抓包工具做了,但我们还是需要了解下基础的网络协议,好帮助我们更好的分析问题。

    首先需要了解下经典的OSI七层网络模型,以及每层的作用,其次对TCPHTTP协议简单了解。

     

    > 虚线框中的部分是需要着重了解的。

    #### HTTPSHTTP有什么不同?

    HTTPS是基于HTTP协议的一种改进,在`TCP之上`的会话层增加安全处理。对于应用层来说,HTTPSHTTP没有什么不同,也就是说,HTTPS是保证网络传输的安全性,对业务数据无侵入。

     

    简化理解大概是这个样子:

     

     SSLTLS是保证安全传输的协议,包括证书认证、加解密和数字签名。

    项目中HTTPS的链路:

     

    因此,客户端与后台,编写网络接口时,不需要关心SSLTLS,按照HTTP协议处理即可。

    既然HTTPS在网络传输是经过加密的,那么抓包抓到的数据就是密文,不经过解密是无法看到报文的。针对这个问题,如上图WIFI环境下设置代理的方式可以解决,具体思路是:

     

     

    所以整个网络链路依然是HTTPS在传输,但代理服务自己可以获取到明文数据。

    ## 4. 常用抓包工具

    比较常用的抓包工具大概有4个,主要用作互补,配合使用基本所有平台、所有抓包需求都能满足:

     

     

    * fidder  windows平台最受欢迎抓包工具,免费、易用

    * charles  Mac平台下最佳抓包工具,易于使用,收费软件,可一直试用

    * wireShark  老牌抓包工具,跨平台,功能齐全、强大

    * tcpdump  命令行程序,适用于手机系统和后台系统

    `需要说明的是,tcpdump可将数据保存成文件,直接用wireShark打开分析,针对后台或手机抓包使用起来十分方便。`

    几个工具间的使用关系:

    1. 如果是windows平台,使用fidder

    2. 如果是Mac平台,使用charles

    3. 如果是linux平台(手机或后台),使用tcpdump

    4. 如果抓非HTTP(S)协议的包,如TCP包,则使用wireShark 

    ## 5. 真机抓包 

    为什么要真机抓包?必定是没有办法设置代理服务了,如4G网络情况下直接和移动基站链接,没法设置代理服务。凡是非4G网络状态下,都不建议真机抓包。 

    #### 5.1 iOS真机抓包

    iOS 5后,apple引入了RVI remote virtual interface的特性,它只需要将iOS设备使用USB数据线连接到mac上,然后使用rvictl工具以iOS设备的UDID为参数在Mac中建立一个虚拟网络接口rvi,就可以在mac设备上使用tcpdumpwireshark等工具对创建的接口进行抓包分析。

    具体步骤:

    1. 获得iOS设备的UDID

    2. 使用USB数据线将iOS设备和MAC连接

    3. 创建RVI接口 `rvictl -s <UDID>`

    4. 使用wireShark抓取`rvi0`虚拟接口

    5. 移除RVI接口 `rvictl -x <UDID>`

    > 真机抓包,意味着无法获取HTTPS的明文数据,因此真机抓包的意义不大,只能用户分析网络接口连通性,正确性,无法分析业务报文是否正确。

    #### 5.2 android真机抓包

    androidlinux系统,和后台一样可以使用tcpdump命令来抓包,但是需要root权限,因为一般手机系统不带有抓包命令`tcpdump`,需要自行安装。

    安装tcpdump

    1. 下载android版本的[tcpdump](https://www.androidtcpdump.com)

    2. adb shell

    3. su #切换root用户

    4. chmod 777 /data/local  #修改目标路径权限,这里是/data/local

    5. adb push tcpdump /data/local #tcpdump拷贝到目标路径

    > tcpdump命令不能执行,需要赋予该文件chmod +x tcpdump

    使用tcpdump

    1. adb shell

    2. su

    3. /data/local/tcpdump -p -vv -s 0 -i any -w /data/local/capture.pcapng

    这是就已经进入抓包状态,手机所有的网络请求都会被捕获,并保存到capture文件中。

    导出capture文件:

    1. adb pull /data/local/capture.pcapng .  #导出到当前目录

    2. 使用wireShark打开capture文件,进行浏览分析

    > 若进行pushpull操作不成功,可能是因为不让直接操作/data/local等系统目录,可以先pullsd卡,在将文件从sd卡拷贝到/data/local,反之依然。

     

     

     

     

     

     

     

     

     

    展开全文
  • Git——基本原理

    千次阅读 2018-11-27 11:31:41
    一、哈希   哈希是一个系列的加密算法,各个不同的哈希算法虽然加密强度不同,但是有以下几个共同点:   ①不管输入数据的数据量有多大,使用同一个哈希算法,得到的加密结果长度固定   ②哈希算法确定,输入...

    一、哈希
    在这里插入图片描述
      哈希是一个系列的加密算法,各个不同的哈希算法虽然加密强度不同,但是有以下几个共同点:
      ①不管输入数据的数据量有多大,使用同一个哈希算法,得到的加密结果长度固定
      ②哈希算法确定,输入数据确定,输出结果保证不变
      ③哈希算法确定,输入数据有变化,输出结果一定有变化,而且通常变化很大
      ④哈希算法不可逆
      ⑤哈希算法中不区分英文大小写
      哈希算法有很多种,如:MD5、SHA-1等。Git 底层采用的是 SHA-1 ,因为哈希算法可以被用来验证文件,Git 就是靠这种机制来从根本上保证数据完整性的
    在这里插入图片描述
    二、Git保存版本的机制
     1、集中式版本控制工具(如SVN)的文件管理机制:以文件变更列表的方式存储信息。这类系统将它们保存的信息看作是一组基本文件(对应下图的Version1)和每个文件随时间逐步累积的差异(对应下图的其他Version),在文件管理的时候只保存当前版本相对于上一个版本的差异,这是一种增量式的版本控制
    在这里插入图片描述
     2、Git 的文件管理机制:Git 把数据看作是小型文件系统的一组快照。每次提交更新时 Git 都会对当前的全部文件制作一个快照并保存这个快照的索引。为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。所以 Git 的工作方式可以称之为快照流。下图中Version2及其之后的实线框中的文件表示该版本的该文件相较于上个版本有变动,虚线框中的文件表示该版本的该文件相较于上一版本没有变动(只保存上一个版本的指针,而无需保存文件),这样根据当前版本的文件和指向上一版本的指针就可以找到该版本的所有文件的状态
    在这里插入图片描述
     Git 的提交对象:
    在这里插入图片描述
     提交对象及其父对象形成的链条:
    在这里插入图片描述
    三、Git的分支管理机制(依赖于指针的变化)
     1、分支的创建:创建一个指向HEAD当前指向的分支的当前版本的指针
    在这里插入图片描述
      说明:此时HEAD指向master分支的f30ab版本,那么新创建的testing分支的指针也会指向master分支的f30ab版本,实际上是创建的分支的指针会指向HEAD指针指向的分支所在的版本
     2、分支的切换:仅仅是改变HEAD指针的指向
    在这里插入图片描述
      说明:切换分支时,仅仅是切换一下HEAD指针的指向,从原分支指向想切换的分支
     3、版本的提交
    在这里插入图片描述
      说明:若此时在testing分支上提交了版本,则只会使该分支的指针向后移动,不会影响其他分支,如上图所示,其他分支的指针指向并没有发生变化
     4、冲突的产生
    在这里插入图片描述
      说明:在testing分支修改后,再将HEAD切换至master,然后在master上修改相同的文件,然后再master分支上提交,就会形成下面的局面:这时就有可能产生冲突,这合并版本的时候就需要解决冲突
    在这里插入图片描述

    5、和SVN的比较
      SVN在创建分支的时候是将所有文件复制一份,而git仅仅是创建一个指向当前版本的指针,因此效率很高;Git中分支之间的切换仅仅是HEAD指针的变化,效率也很高;
      综上:Git的操作很依赖于HEAD指针的变化。

    展开全文
  • NAT基本原理及应用

    万次阅读 多人点赞 2017-07-04 00:18:53
    原文出处:http://www.cnblogs.com/derrick/p/4052401.html?utm_source=tuicool&utm_medium=referral#undefined 1 概述 1.1 简介 1.1.1 名词解释 公有IP地址:也叫全局地址,是指合法的IP地址,它是由NIC(网络...
  • 微服务,你得知道这些!(底层原理

    千次阅读 多人点赞 2018-12-11 16:00:33
    目录 一、业务场景介绍 二、Spring Cloud核心组件:Eureka 三、Spring Cloud核心组件:Feign 四、Spring Cloud核心组件:Ribbon 五、Spring Cloud核心组件:Hystrix ...六、Spring Cloud核心组件:Zuul ...
  • JS的底层原理

    千次阅读 2019-07-03 11:19:51
    JS 的底层运行原理 每调用一个函数就会生成一个执行环境(俗称执行上下文),执行上下文环境数量没有限制 每调用一个函数就会生成一个执行环境(俗称执行上下文),执行上下文环境数量没有限制 单线程 同步执行,只有...
  • ZooKeeper原理解析

    千次阅读 2020-02-07 09:48:03
    分布式协调服务组件zookeeper的介绍、特点及原理解析
  • synchronize关键字synchronize的使用synchronize的原理 synchronize的使用 我们知道,当出现race condition的时候,应用就不会同步。 举个例子: package sync; import org.junit.Test; import java.util....
  • hashMap的实现原理

    2020-09-15 19:56:48
    HashMap的实现原理 HashMap的数据结构 在看Hashmap的数据结构之前先来看看数组和链表的特点 数组:寻址容易插入和删除的时候比较困难(数组有下表寻址,但是插入删除的时候下表要移动,扩容的时候也很麻烦) 链表:...
  • Canny算子边缘检测原理及实现

    万次阅读 多人点赞 2019-11-05 17:19:37
    写在前面 Canny边缘检是在在1986年提出来的,到今天已经30多年过去了,但Canny算法仍然是图像边缘检测算法中最经典、先进的算法之一。 相比Sobel、Prewitt等算子,Canny算法更为优异。Sobel、Prewitt等算子有如下...
  • kafka工作原理介绍

    万次阅读 多人点赞 2018-06-27 11:55:56
    两张图读懂kafka应用:Kafka 中的术语 broker:中间的kafka cluster,存储消息,是由多个server组成的集群。 topic:kafka给消息提供的分类方式。broker用来存储不同topic的消息数据。 producer:往broker中某个...
  • 如题,遇见了个原理图,但是并没有它的原理图库,所以想将原理图的元器件导到自己的原理图库中,请问如何操作呢?还有请问哪位高手有比较丰富的原理图库和封装图库,我用的AD,很多东西都没有,比如差模电感,共模...
  • Mybatis工作原理

    万次阅读 多人点赞 2019-12-29 22:37:45
    本片博客针对Mybatis内部工作原理进行阐述。 一、Mybatis工作原理图 mybatis 原理图如下所示: 二、工作原理解析 mybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件(也...
  • HashMap原理深入理解

    万次阅读 多人点赞 2020-06-27 20:03:20
    hashing(散列法或哈希法)的概念 散列法(Hashing)是一种将字符组成的字符串转换为固定长度(一般是更短长度)的数值或索引值的方法,称为散列法,也叫哈希法。由于通过更短的哈希值比用原始值进行数据库搜索更快...
  • 计算机CPU的工作原理动画

    万次阅读 2012-09-17 15:09:24
    一直在找有关CPU相关的工作原理动画,终于找到了。喜欢这个的可以认真看看了 CPU工作原理系列动画下载
  • STM32F103C8T6最小系统原理

    万次阅读 多人点赞 2019-07-03 14:49:05
    原理图下载链接: https://download.csdn.net/download/id_l_j_j_/11165257
  • 4使用AD将原理图导入PCB

    万次阅读 2018-04-24 15:24:18
    建议养成良好的工作习惯,总是先确定封装,再画原理图。器件总是从库里边放置到原理图文件内,而不是复制粘贴得到的。部分器件的封装如果确定库里边是有的,仍然报错"Footprint Not Found XXX",可以尝试...
  • 3 AD检查原理图的方法

    万次阅读 2018-04-24 15:24:37
    原理图设计完成以后可以使用自带的工具进行检查。首先可以目测,原理图中不能出现棕色的波浪线 3.1 编译工程然后根据右下角,system-&gt;message如果有error则必须处理。出于严谨的态度,尽可能把warning也处理...
1 2 3 4 5 ... 20
收藏数 2,645,758
精华内容 1,058,303
关键字:

原理