精华内容
下载资源
问答
  • BLE4.0读写示例(read and write

    热门讨论 2015-04-08 18:06:39
    BLE4.0的读写操作,详见http://blog.csdn.net/luochoudan/article/details/44944213
  • Datax 支持增量 oracle writeMode update

    千次阅读 热门讨论 2020-06-21 10:05:43
    columnHolders, List<String> valueHolders, String writeMode, DataBaseType dataBaseType, boolean forceUseUpdate) { boolean isWriteModeLegal = writeMode.trim().toLowerCase().startsWith("insert") || write...

    datax介绍

    DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL、Oracle、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、DRDS 等各种异构数据源之间高效的数据同步功能。

    支持增量 oracle update

    我们使用datax 希望支持oracle 增量导入数据:地址:https://gitee.com/cecotw/DataX

    链接:https://pan.baidu.com/s/1mbEvLsDZZNWMYrTTTeYkAw 密码:v97c

    修改 OracleWriter.java

    删除限制
    在这里插入图片描述

    修改WriterUtil.java

    添加oracle 数据插入类型转换:
    在这里插入图片描述

        public static String getWriteTemplate(List<String> columnHolders, List<String> valueHolders, String writeMode, DataBaseType dataBaseType, boolean forceUseUpdate) {
            boolean isWriteModeLegal = writeMode.trim().toLowerCase().startsWith("insert")
                    || writeMode.trim().toLowerCase().startsWith("replace")
                    || writeMode.trim().toLowerCase().startsWith("update");
    
            if (!isWriteModeLegal) {
                throw DataXException.asDataXException(DBUtilErrorCode.ILLEGAL_VALUE,
                        String.format("您所配置的 writeMode:%s 错误. 因为DataX 目前仅支持replace,update 或 insert 方式. 请检查您的配置并作出修改.", writeMode));
            }
            // && writeMode.trim().toLowerCase().startsWith("replace")
            String writeDataSqlTemplate;
            if (forceUseUpdate ||
                    ((dataBaseType == DataBaseType.MySql || dataBaseType == DataBaseType.Tddl) && writeMode.trim().toLowerCase().startsWith("update"))
            ) {
                //update只在mysql下使用
    
                writeDataSqlTemplate = new StringBuilder()
                        .append("INSERT INTO %s (").append(StringUtils.join(columnHolders, ","))
                        .append(") VALUES(").append(StringUtils.join(valueHolders, ","))
                        .append(")")
                        .append(onDuplicateKeyUpdateString(columnHolders))
                        .toString();
            } else {
                if (dataBaseType == DataBaseType.Oracle) {
                    writeDataSqlTemplate = new StringBuilder().append(onMergeIntoDoString(writeMode, columnHolders, valueHolders)).append("INSERT (")
                            .append(StringUtils.join(columnHolders, ","))
                            .append(") VALUES(").append(StringUtils.join(valueHolders, ","))
                            .append(")").toString();
                } else {
                    //这里是保护,如果其他错误的使用了update,需要更换为replace
                    if (writeMode.trim().toLowerCase().startsWith("update")) {
                        writeMode = "replace";
                    }
                    writeDataSqlTemplate = new StringBuilder().append(writeMode)
                            .append(" INTO %s (").append(StringUtils.join(columnHolders, ","))
                            .append(") VALUES(").append(StringUtils.join(valueHolders, ","))
                            .append(")").toString();
                }
            }
    
            return writeDataSqlTemplate;
        }
    
    

    增加onMergeIntoDoString方法:
    在这里插入图片描述

        public static String onMergeIntoDoString(String merge, List<String> columnHolders, List<String> valueHolders) {
            String[] sArray = getStrings(merge);
            StringBuilder sb = new StringBuilder();
            sb.append("MERGE INTO %s A USING ( SELECT ");
    
            boolean first = true;
            boolean first1 = true;
            StringBuilder str = new StringBuilder();
            StringBuilder update = new StringBuilder();
            for (String columnHolder : columnHolders) {
                if (Arrays.asList(sArray).contains(columnHolder)) {
                    if (!first) {
                        sb.append(",");
                        str.append(" AND ");
                    } else {
                        first = false;
                    }
                    str.append("TMP.").append(columnHolder);
                    sb.append("?");
                    str.append(" = ");
                    sb.append(" AS ");
                    str.append("A.").append(columnHolder);
                    sb.append(columnHolder);
                }
            }
    
            for (String columnHolder : columnHolders) {
                if (!Arrays.asList(sArray).contains(columnHolder)) {
                    if (!first1) {
                        update.append(",");
                    } else {
                        first1 = false;
                    }
                    update.append(columnHolder);
                    update.append(" = ");
                    update.append("?");
                }
            }
    
            sb.append(" FROM DUAL ) TMP ON (");
            sb.append(str);
            sb.append(" ) WHEN MATCHED THEN UPDATE SET ");
            sb.append(update);
            sb.append(" WHEN NOT MATCHED THEN ");
            return sb.toString();
        }
    

    增加getStrings方法:
    在这里插入图片描述

        public static String[] getStrings(String merge) {
            merge = merge.replace("update", "");
            merge = merge.replace("(", "");
            merge = merge.replace(")", "");
            merge = merge.replace(" ", "");
            return merge.split(",");
        }
    

    修改CommonRdbmsWriter.java

    在这里插入图片描述

            public void startWriteWithConnection(RecordReceiver recordReceiver, TaskPluginCollector taskPluginCollector, Connection connection) {
                this.taskPluginCollector = taskPluginCollector;
                List<String> columns = new ArrayList<>();
                List<String> columnsOne = new ArrayList<>();
                List<String> columnsTwo = new ArrayList<>();
                if (this.dataBaseType == DataBaseType.Oracle) {
                    String merge = this.writeMode;
                    String[] sArray = WriterUtil.getStrings(merge);
                    int size = this.columns.size();
                    int i = 0;
                    for (int j = 0; j < size; j++) {
                        if (Arrays.asList(sArray).contains(this.columns.get(j))) {
                            columnsOne.add(this.columns.get(j));
                        }
                    }
                    for (int j = 0; j < size; j++) {
                        if (!Arrays.asList(sArray).contains(this.columns.get(j))) {
                            columnsTwo.add(this.columns.get(j));
                        }
                    }
                    for (String column : columnsOne) {
                        columns.add(i, column);
                        i++;
                    }
                    for (String column : columnsTwo) {
                        columns.add(i, column);
                        i++;
                    }
                }
                columns.addAll(this.columns);
    
                // 用于写入数据的时候的类型根据目的表字段类型转换
                this.resultSetMetaData = DBUtil.getColumnMetaData(connection,
                        this.table, StringUtils.join(columns, ","));
                // 写数据库的SQL语句
                calcWriteRecordSql();
    
                List<Record> writeBuffer = new ArrayList<Record>(this.batchSize);
                int bufferBytes = 0;
                try {
                    Record record;
                    while ((record = recordReceiver.getFromReader()) != null) {
                        if (record.getColumnNumber() != this.columnNumber && this.dataBaseType != DataBaseType.Oracle) {
                            // 源头读取字段列数与目的表字段写入列数不相等,直接报错
                            throw DataXException
                                    .asDataXException(
                                            DBUtilErrorCode.CONF_ERROR,
                                            String.format(
                                                    "列配置信息有错误. 因为您配置的任务中,源头读取字段数:%s 与 目的表要写入的字段数:%s 不相等. 请检查您的配置并作出修改.",
                                                    record.getColumnNumber(),
                                                    this.columnNumber));
                        }
    
                        writeBuffer.add(record);
                        bufferBytes += record.getMemorySize();
    
                        if (writeBuffer.size() >= batchSize || bufferBytes >= batchByteSize) {
                            doBatchInsert(connection, writeBuffer);
                            writeBuffer.clear();
                            bufferBytes = 0;
                        }
                    }
                    if (!writeBuffer.isEmpty()) {
                        doBatchInsert(connection, writeBuffer);
                        writeBuffer.clear();
                        bufferBytes = 0;
                    }
                } catch (Exception e) {
                    throw DataXException.asDataXException(
                            DBUtilErrorCode.WRITE_DATA_ERROR, e);
                } finally {
                    writeBuffer.clear();
                    bufferBytes = 0;
                    DBUtil.closeDBResources(null, null, connection);
                }
            }
    

    在这里插入图片描述

            protected void doBatchInsert(Connection connection, List<Record> buffer)
                    throws SQLException {
                PreparedStatement preparedStatement = null;
                try {
                    connection.setAutoCommit(false);
                    preparedStatement = connection
                            .prepareStatement(this.writeRecordSql);
    
                    if (this.dataBaseType == DataBaseType.Oracle) {
                        String merge = this.writeMode;
                        String[] sArray = WriterUtil.getStrings(merge);
                        for (Record record : buffer) {
                            List<Column> recordOne = new ArrayList<>();
                            for (int j = 0; j < this.columns.size(); j++) {
                                if (Arrays.asList(sArray).contains(this.columns.get(j))) {
                                    recordOne.add(record.getColumn(j));
                                }
                            }
                            for (int j = 0; j < this.columns.size(); j++) {
                                if (!Arrays.asList(sArray).contains(this.columns.get(j))) {
                                    recordOne.add(record.getColumn(j));
                                }
                            }
                            for (int j = 0; j < this.columns.size(); j++) {
                                recordOne.add(record.getColumn(j));
                            }
                            for (int j = 0; j < recordOne.size(); j++) {
                                record.setColumn(j, recordOne.get(j));
                            }
                            preparedStatement = fillPreparedStatement(
                                    preparedStatement, record);
                            preparedStatement.addBatch();
                        }
                    } else {
                        for (Record record : buffer) {
                            preparedStatement = fillPreparedStatement(
                                    preparedStatement, record);
                            preparedStatement.addBatch();
                        }
                    }
                    preparedStatement.executeBatch();
                    connection.commit();
                } catch (SQLException e) {
                    LOG.warn("回滚此次写入, 采用每次写入一行方式提交. 因为:" + e.getMessage());
                    connection.rollback();
                    doOneInsert(connection, buffer);
                } catch (Exception e) {
                    throw DataXException.asDataXException(
                            DBUtilErrorCode.WRITE_DATA_ERROR, e);
                } finally {
                    DBUtil.closeDBResources(preparedStatement, null);
                }
            }
    
    

    效果

    {
        "job": {
            "setting": {
                "speed": {
                     "byte": 1048576
                },
                    "errorLimit": {
                    "record": 0,
                    "percentage": 0.02
                }
            },
            "content": [
                {
                    "reader": {
                        "name": "postgresqlreader",
                        "parameter": {
                            "username": "postgres",
                            "password": "postgres",
                            "connection": [
                                {
                                                                    "querySql":["SELECT seq,userid,name FROM user"],
                                    "jdbcUrl": [
                                        "jdbc:postgresql://127.0.0.1:5432/postgres"
                                    ]
                                }
                            ]
                        }
                    },
                    "writer": {
                        "name": "oraclewriter",
                        "parameter": {
                            "username": "oracle",
                            "password": "oracle",
                            "column": [
                                "seq",
                                "userid",
                                "name"
                            ],
                            "connection": [
                                {
                                    "jdbcUrl": "jdbc:oracle:thin:@localhost:1521:oracle",
                                    "table": [
                                        "user1"
                                    ]
                                }
                            ],
                            "writeMode": "update (seq,userid)"
                        }
                    }
                }
            ]
        }
    }
    

    源码

    • 关于 DATAX改造后的代码 ,参考 这儿.(https://gitee.com/cecotw/DataX)
    展开全文
  • document write( )解析

    千次阅读 2019-04-25 16:31:21
    document write( )解析 转至:https://www.cnblogs.com/ziyunfei/p/5881426.html 该文主要作为学习笔记进行转载,如有侵权请联系 在传统的浏览器中,同步的 script 标签是会阻塞 HTML 解析器的,无论是内联的还是...

    document write( )解析

    转至:https://www.cnblogs.com/ziyunfei/p/5881426.html

    该文主要作为学习笔记进行转载,如有侵权请联系

    在传统的浏览器中,同步的 script 标签是会阻塞 HTML 解析器的,无论是内联的还是外链的,比如:

    <script src="a.js"></script>
    <script src="b.js"></script>
    <script src="c.js"></script>
    <img src="a.jpg">
    

    在这个例子中,HTML 解析器会先解析到第一个 script 标签,然后暂停解析,转而去下载 a.js,下载完后开始执行,执行完后,才会继续解析、下载、执行后面的两个 script 标签,最后解析那个 img 标签,下载图片,展现图片。假设每个文件的下载时间都是 1 秒,且忽略浏览器的执行耗时,那么你最终会在第 4 秒结束时看到 a.jpg 渲染在了浏览器上。

    如今的浏览器已经不再这么线性的执行了,在遇到第一个 script 标签后,主线程中的解析器暂停解析,但浏览器会开启一个新的线程去于预解析后面的 HTML 源码,同时预加载遇到的CSS、JS、图片等资源文件,也就是说,在现代浏览器中,上面这个例子中的四个资源文件是会被并行下载的,所以不考虑浏览器的执行耗时的话,渲染出最后那张图片只需要 1 秒钟。

    额外小知识:

    但浏览器能做的仅仅是预解析和预加载,脚本的执行和 DOM 树的构建仍然必须是线性的,从而页面的渲染也必须是线性的。脚本必须顺序执行这很好理解,比如 b.js 很可能用到 a.js 里的变量;DOM 树不能提前构建的原因也能想到,a.js 里很可能去查询 DOM 树,在那时执行 querySelectorAll("script").length 必须是 1,img 的话必须是 0。 但还有一个东西也能解释上面两个优化不能做的原因,甚至也能让预解析和预加载这两个已经做了的优化失效的东西,那就是 document.write(),document.write 可以在当前执行的 script 标签之后插入任意的 HTML 源码,如果你插入一个 "<div>foo</div>" 那还好,但如果插入一个未闭合的开标签呢,比如:
    <script>
    document.write("<textarea>") // 还可以是 document.write("<!--") 等
    </script>
    <script src="a.js"></script>
    <script src="b.js"></script>
    <script src="c.js"></script>
    <img src="a.jpg">
    

    当第 1 个 script 标签执行完毕后,浏览器就会发现,因为 document.write 输出了一个未闭合的开标签,所以刚才做的预解析成果得全部扔掉,重新解析一次,第二次解析后 script 标签和 img 标签都成了 textarea 的内容了,因此预加载的 JS 和图片资源都白加载了。但这种情况毕竟是少数,预解析的利远远大于弊,所以浏览器们才做了这个优化,MDN 上有一篇文章列举了一些会让浏览器做的预解析优化失失效的代码

    本文的主角是用 document.write 输出一个 script 标签的情况,比如:

    <script src="a.js"></script>
    <script>
    document.write('<script src="http://thirdparty.com/b.js"><\/script>')
    </script>
    <script src="c.js"></script>
    

    这个例子中,由于 b.js 是通过 JS 代码插入的,HTML 预解析器是看不到的,所以只有当 a.js 下载并执行完毕,且第二个内联的 script 执行完毕后,b.js 才会开始下载,也就是说,b.js 不能和 a.js 及 c.js 并行下载了,从而导致页面展现变慢,同样假设每个文件的下载时间都是 1 秒,那么这三个文件下载执行完就需要两秒,就因为 b.js 不能预加载。在一个外链的 JS 文件比如 a.js 中执行 document.write("<script...) 也是类似的效果。

    Chrome 的工程师们最近发现,因这种包含于 document.write() 中的 script 标签而导致的页面加载变慢的情况非常普遍,同时还发现了个普遍的规律,那就是这些脚本的 URL 如果不是本站的(跨站的),一般都是些广告和统计功能的第三方脚本,是对页面正常展现非必须的,如果是本站的,则更可能是当前页面展现所必须的脚本。

    这些工程师们还在 Chrome for Android 中针对 2G 环境做了采样统计,发现有 7.6% 的页面包含了至少一个这样的 script 标签,而且发现假如禁止加载这些非必要的脚本后,页面本身的展现速度会有显著提升:

    用 document.write 去加载脚本,绝大多数情况下都是错误的做法,是应该被优化的。那该怎么优化呢?改成普通的 script 标签放在 HTML 里面吗?不行也不该,先来说说为什么不行,一般来说,一个脚本之所以要放在 JS 里去加载,而不是直接放在 HTML 里,可能的原因有:

    1. 脚本的 URL 是不能写死的,比如要动态添加一些参数,用户设备的分辨率啊,当前页面 URL 啊,防止缓存的时间戳啊之类的,这些参数只能先用 JS 获取到,再比如国内常见的 CNZZ 的统计代码:

    <script>
    var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");
    document.write(unescape("%3Cspan id='cnzz_stat_icon_30086426'%3E%3C/span%3E%3Cscript src='" + 
                            cnzz_protocol + 
                            "w.cnzz.com/c.php%3Fid%3D30086426' type='text/javascript'%3E%3C/script%3E"))
    </script>
    

    它之所以为用户提供 JS 代码,而不是 HTML 代码,是为了先用 JS 判断出该用 http 还是 https 协议。

    2. 在外链的脚本里加载另外一个脚本,这种情况就没法写在页面的 HTML 里面了,比如百度联盟的这个脚本里就可能用 document.write 去加载另外一个脚本:

    再来说说为什么不该,即便真的有少数的代码可以优化成 HTML 代码,比如上面这个 CNZZ 的就可以改成:

    <span id='cnzz_stat_icon_30086426'></span>
    <script src='//w.cnzz.com/c.php?id=30086426' type='text/javascript'></script>
    

    这样浏览器就可以预加载了,算是进行优化了,但这并不是最佳的优化,因为,当你能明显感觉到你的页面因为第三方脚本的原因导致展现缓慢,通常都不是因为它没有被预加载,而是因为它的加载速度比你自己网站的脚本加载速度慢太多,再拿出这个例子:

    <script src="a.js"></script>
    <script>
    document.write('<script src="http://thirdparty.com/b.js"><\/script>')
    </script>
    <script src="c.js"></script>
    

    thirdparty.com 网站出问题的时候,a.js 和 c.js 1 秒就加载完了,而 b.js 也许需要 10 秒才能加载完,那 c.js 的执行以及后面的 HTML 的渲染就需要等 10 秒钟,极端情况就是 b.js 一直卡在那里直到超时,如果这些脚本是放在 head 里的,那用户永远不会看到你的页面,在国内的人应该早已深有体会,就是那些引用了 Google 统计、广告等同步版脚本的页面,这种情况下只靠预加载是解决不了根本问题的。

    最佳的做法是把它改成异步执行的,异步的 script 根本不会阻塞 HTML 解析器,也就用不到预解析了。通过 HTML 载入的 script 可以用 async 属性将它变成异步的:

    <span id='cnzz_stat_icon_30086426'></span>
    <script async src='//w.cnzz.com/c.php?id=30086426' type='text/javascript'></script>
    

    当然,这个外链的脚本本身也可能需要做相应的调整,比如万一里面还有个 document.write,那整个页面就会被覆盖了。

    上面也说到了,大部分第三方脚本都需要添加动态参数,没法修改成 HTML 的代码,所以更加常见的做法是用 document.createElement("script") 配合 appendChild/insertbefore 插入 script,以这种方式插入的 script 都是异步的,比如:

    <span id='cnzz_stat_icon_30086426'></span>
    <script>
    document.head.appendChild(document.createElement('script')).src = '//w.cnzz.com/c.php?id=30086426'
    </script>
    

    目前国内国外绝大多数的广告、统计服务提供商都有提供异步版本的代码,但也有可能没有,比如 CNZZ 的统计代码, 看这里这里

    本着用户体验至上的原则,Chrome 的工程师们准备进行一个大胆的尝试,那就是屏蔽掉这种脚本,具体的屏蔽规则是,符合下面所有这些条件的 script 标签对应的脚本不会再被 Chrome 执行:

    1. 是用 document.write 写入的

    无法预解析和预加载

    2. 同步加载的,也就是不带有 asyc 或 defer 属性的

    即便写在 document.write 里,异步的 script 标签也不会阻塞后面脚本的执行以及后面 HTML 源码的解析

    3. 外链的

    内联的反正没有网络请求,不影响展现速度,况且谁会去写 <script>document.write("<script>alert('foo')<\/script>")</script> 这样的代码。。

    4. 跨站的

    上面说过了,跨站的脚本影响页面本身的内容展现的可能性更小,跨站和跨域的区别,请看我的这篇文章

    5. 所在页面的此次加载不是通过刷新操作触发的

    虽然说第三方脚本影响页面主体内容和功能的可能性不大,但仍然有这个可能,假如页面主体内容收到影响了,用户必然会点刷新,所以刷新的时候,这个屏蔽逻辑得关掉

    6. 所在页面是顶层的(self === top),而不是 iframe

    因为 iframe 往往是广告之类的小区块,而用户想看的主页面通常是这些 iframe 的父页面,且 iframe 内的脚本并不会阻塞父页面的渲染,所以没必要优化它们

    7. 未被缓存

    如果这个外链脚本已经被缓存了,当然可以直接拿来执行了。

    但这毕竟是个 breaking change,考虑用户体验的同时也不能不考虑网站本身,所以这个改动会循序渐进的一步一步(我总结成了 4 步)执行,给开发者留出修改自己代码的时间,具体计划是:

    1. 警告

    从 Chrome 53,也就是目前的稳定版开始,开发者工具的控制台中会出现下面这样的警告(即便脚本已经被缓存或者页面是通过刷新操作打开的,也会出现这个警告):

    2016.10.6 追加,从 Chrome 55 开始,除了上面的警告,这个被警告的脚本的 HTTP 请求会被添加一个额外的请求头,方便该脚本的维护者提前知道自己的脚本在未来会被屏蔽:

    Intervention:<https://www.chromestatus.com/feature/5718547946799104>; level="warning" 

    比如下面是百度首页一个被警告脚本的 HTTP 请求头截图:

    2. 在 2G 网络下开启屏蔽(issue 640844

    从 Chrome 54(2016 年 10 月中旬发布)开始,在 2G 网络环境下开启屏蔽。需要指出的是,屏蔽一个脚本并不是真的不发起请求,而是会发一个异步的请求,且优先级很低(优先级为 0,Chrome 给每个 http 请求都标有优先级)。这个异步请求的目的不是为了去执行它(上面也说了,把一个同步脚本直接当成异步脚本去执行,是很可能会出问题的),而是为了:

    (1)为了把脚本放到缓存里,也就是说,第一次屏蔽了,第二次翻页等操作后如果还需用到那个脚本,那它很可能已经在缓存里了,这也是为了减少 breaking 的概率。

    (2)为了通知这个脚本所在的服务器,“你的脚本被我屏蔽了”。脚本被屏蔽后异步发起的请求会被 Chrome 添加一个特殊的请求头 Intervention,值是一个对应的 chromestatus 网址:

    如果你是一个第三方服务提供者,比如广告投放系统的负责人,你在你的服务器的访问日志里看到这个请求头,就说明你的脚本已经被屏蔽了,从 Referer 头里也能看到被屏蔽的脚本是在哪个页面里被引用的,然后你需要做的是就是让这个网站把你们提供的代码更新成异步版本的。

    因为是 2G,所以肯定是移动版的 Chrome,也就是 Chrome for Android,Android WebView 不知道不会开启,在 6 月份 Chrome 官方发布的消息中说到还没有定要不要在 WebView 中开启:  

    Will this feature be supported on all six Blink platforms (Windows, Mac, Linux, Chrome OS, Android, and Android WebView)?

    This feature will be enabled on Win, Mac, Linux, ChromeOS and Android, but we are still deciding whether it's appropriate to apply this intervention for WebView. 

    Chrome for IOS 内核不是 blink,不受影响。

    2016.10.20 追加,推迟到了 Chrome 55。

    为了方便调试,在 Chrome PC 版开发者工具中将网络切换成 2G 也能触发这个屏蔽规则(还在实现中)。

    2016.10.6 追加,上面的这个 issue 已经 fixed 了,但我发现开发者工具模拟成 2G 并不能触发真实的屏蔽,可能人家只是为了方便自己写测试代码,开发者工具并没有支持,我在 issue 下面问了,目前还没回。不过我发现另外一个开启真实屏蔽的方法,就是打开 chrome://flags/#disallow-doc-written-script-loads,开启这个选项后,所有网络环境下符合那 7 个条件脚本都会被真实的屏蔽掉,比如百度首页这个脚本:

    这两个请求的 URL 是一模一样的,上面那个是原来的请求,被屏蔽了,会报 ERR_CACHE_MISS 的错误,下面那个是异步发起的请求。

    我自己看到的一个到时候可能受到影响的手机网站:https://sina.cn/

    3. 在网速较差的 3G 和 WiFi 环境下开启屏蔽(issue 640846

    目前还没有决定从哪个版本开始,如果上一个 2G 阶段进行顺利,才可能会进入这个阶段,等有消息的时候我会在这里追加具体开启的版本号,PC 页面在这个阶段才会受到影响。

    我自己看到的两个到时候可能受到影响的网站:https://www.baidu.com/ https://www.taobao.com/

    4. 完全屏蔽

    任何网络环境都开启屏蔽,这完全是我的猜测,还没有看到 Chrome 的人在讨论,但即便最后要这样做了,肯定也需要较长的过度时间。

    有些同学可能会问:“我把它放在页面最底部,总该没事了吧”。别忘了同步的 script 会阻塞 DOMContentLoaded/load 事件,关掉 vpn 运行下面的 demo 试试:

    <script>
    document.addEventListener("DOMContentLoaded", function(){
      alert("执行异步渲染、绑定事件等操作")
    })
    document.write("<script src=http://www.twitter.com><\/script>")
    </script>
    

    用 jQuery 的话,所有 $(function(){}) 里的回调函数都会被卡主,问题依然很严重。

    最后总结一下:“为什么说 document.write("<script...) 不好” - “因为它本来能够写成异步的,却写成了同步且不能预加载的”

    PS:Chrome 还在做另外一个优化的尝试,就是开启一个单独的 V8 线程用来执行那些包含有 document.write("<script...) 字样的内联的 script 标签中的代码从而预加载那个脚本,但就像我上面说的(预加载不能解决阻塞问题),即便这个优化真做成了,意义也不大。  

    PPS:HTML 规范也做了对应的修改,说允许浏览器做这种优化。

    展开全文
  • 再探Linux内核write系统调用操作的原子性

    万次阅读 多人点赞 2017-12-23 13:52:52
    很多人都在问Linux系统的write调用到底是不是原子的。网上能搜出一大堆文章,基本上要么是翻译一些文献,要么就是胡扯,本文中我来结合实例来试着做一个稍微好一点的回答。  先摆出结论吧。结论包含两点,即write...

    很多人都在问Linux系统的write调用到底是不是原子的。网上能搜出一大堆文章,基本上要么是翻译一些文献,要么就是胡扯,本文中我来结合实例来试着做一个稍微好一点的回答。


      先摆出结论吧。结论包含两点,即write调用不能保证什么以及write调用能保证什么

      首先,write调用不能保证你要求的调用是原子的,以下面的调用为例:

    ret = write(fd, buff, 512);

    Linux无法保证将512字节的buff写入文件这件事是原子的,因为:

    1. 即便你写了512字节那也只是最大512字节,buff不一定有512字节这么大;
    2. write操作有可能被信号中途打断,进而使得ret实际上小于512;
    3. 实现根据不同的系统而不同,且几乎都是分层,作为接口无法确保所有层资源预留。磁盘的缓冲区可能空间不足,导致底层操作失败。

    如果不考虑以上这些因素,write调用为什么不设计成直接返回True或者False呢?要么成功写入512字节,要么一点都不写入,这样岂不更好?之所以不这么设计,正是基于上述不可回避的因素来考虑的。

      在系统调用设计的意义上,不信任的价值大于信任,最坏的考虑优先于乐观地盲进

      其次,write调用能保证的是,不管它实际写入了多少数据,比如写入了n字节数据,在写入这n字节数据的时候,在所有共享文件描述符的线程或者进程之间,每一个write调用是原子的,不可打断的。举一个例子,比如线程1写入了3个字符’a’,线程2写入了3个字符’b’,结果一定是‘aaabbb’或者是‘bbbaaa’,不可能是类似‘abaabb’这类交错的情况。

      也许你自然而然会问一个问题,如果两个进程没有共享文件描述符呢?比如进程A和进程B分别独立地打开了一个文件,进程A写入3个字符’a’,进程B写入了3个字符’b’,结果怎样呢?

      答案是,这种情况下没有任何保证,最终的结果可能是‘aaabbb’或者是‘bbbaaa’,也可能是‘abaabb’这种交错的情况。如果你希望不交错,那么怎么办呢?答案也是有的,那就是在所有写进程打开文件的时候,采用O_APPEND方式打开即可。

      作为一个和用户态交互的典型系统调用,write无法保证用户要求的事情是原子的,但它在共享文件的范围内能保证它实际完成的事情是原子的,在非共享文件的情况下,虽然它甚至无法保证它完成的事情是原子的,但是却提供了一种机制可以做到这种保证。可见,write系统调用设计的非常之好,边界十分清晰!

      关于以上的这些保证是如何做到的,下面简要地解释下。我本来是不想解释的,但是看了下面的解释后,对于理解上述的保证很有帮助,所以就不得不追加了。解释归于下图所示:

    这里写图片描述

    总结一下套路:

    1. APPEND模式通过锁inode,保证每次写操作均在inode中获取的文件size后追加数据,写完后释放锁;
    2. 非APPEND模式通过锁file结构体后获取file结构体的pos字段,并将数据追加到pos后,写完更新pos字段后释放锁。

    由此可见,APPEND模式提供了文件层面的全局写安全,而非APPEND模式则提供了针对共享file结构体的进程/线程之间的写安全。

      值得一再重申的是,由于write调用只是在inode或者file层面上保证一次写操作的原子性,但无法保证用户需要写入的数据的一次肯定被写完,所以在多线程多进程文件共享情况下就需要用户态程序自己来应对short write问题,比如设计一个锁保护一个循环,直到写完成或者写出错,不然循环不退出(详见《UNIX网络编程》),锁不释放…

      此外,我们知道,apache,nginx以及另外一些服务器写日志都是通过APPEND来保证独立原子写入的,要知道这些日志对于这类服务器而言是极端重要的。


    本文写到这里貌似应该可以结束了吧。

      如果是这样,我是不会写这篇文章的,要不是发生了点什么事情,我绝不会写这种总结性的文章,这不是我的风格。既然写了这篇,说明下面才是重头戏!

      从一个悲哀的故事说起。

      我自己写了一个分析TCP数据包的程序,通过不断打日志的方式把数据包的信息记录在文件里,程序是个多线程程序,大概10多个线程同时写一个内存文件系统的文件,最后我发现少了一条日志!程序本身不是重点,我可以通过以下的小程序代之解释:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/prctl.h>
    #include <string.h>
    #include <unistd.h>
    
    char a[512];
    char b[16];
    
    int main()
    {
            int fd;
    
            memset(a, 'a', 512);
            memset(b, '-', 16);
    
            fd = open("/usr/src/probe/test.txt", O_RDWR|O_CREAT|O_TRUNC, 0660);
    
            if (fork() == 0) {
                    prctl(PR_SET_NAME, (unsigned long)"child");
                    write(fd, b, 16);
                    exit(0);
            }
            write(fd, a, 512);
            exit(0);
    }

    编译为parent并运行,你猜猜最后test.txt里面是什么内容?

      由于父子进程是共享fd指示的file结构体的,按照上面的解释,最终的文件内容肯定是下面两种中的一种:

    ----------------aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

    或者:

    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa----------------

    可是,事实并不是这样!事实上,在很小的概率下,文件中只有512个字符‘a’,没有看到任何字符‘-‘(当然还会有别的情况)!Why?

      你能理解,当事实和理论分析不符的时候是多么痛苦,标准上明明就是说要保证共享file结构体的进程/线程一次写操作的原子性,然而事实证明有部分内容确实是被覆盖了,这显然并不合理。

      再者说了,系统调用在设计之初就要做出某种级别的保证,比如一次操作的原子性等等,这样的系统API才更友好,我相信标准是对的,所以我就觉得这是代码的BUG所致。是这么个思路吗?

      不!上面的这段话是事后诸葛亮的言辞,本文其实是一篇倒叙,是我先发现了写操作被覆盖,进而去逐步排查,最终才找到本文最开始的那段理论的,而不是反过来。所以,在我看到这个莫名其妙的错误后,我并不知道这是否合理,我只是隐约记得我曾经写过的一篇文章:
    关于O_APPEND模式write的原子性http://blog.csdn.net/dog250/article/details/29185821
    这篇文章的写作背景我早就忘记了,我记得当时也是费了一番功夫,所以我只是依靠信仰觉得这次又是内核的BUG!然而我如何来证明呢?

      首先我要想到一个写操作被覆盖的场景,然后试着去重现这个场景,最终去修复它。首先第一步还是看代码,出问题的内核是3.10社区版内核,于是我找到源码:

    SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
            size_t, count)
    {
        struct fd f = fdget(fd);
        ssize_t ret = -EBADF;
    
        if (f.file) {
            loff_t pos = file_pos_read(f.file);
            ret = vfs_write(f.file, buf, count, &pos);
            file_pos_write(f.file, pos);
            fdput(f);
        }
    
        return ret;
    }

    说实话,这段代码我是分析了足足10分钟才发现一个race的,而且是参考了我之前的那篇文章。简单讲,我把这个系统调用分解为了三部分:

    1. get pos
    2. vfs_write
    3. update pos

    race发生在1和2或者2和3之间。以下图示之:

    这里写图片描述

    既然找到了就容易重现了,方法有两类,一类是拼命那个写啊写,碰运气重现,但这不是我的方式,另一种方法我比较喜欢,即故意放大race的条件!

      对于本文的场景,我使用jprobe机制故意在1和2之间插入了一个schedule。试着加载包含下面代码的模块:

    ssize_t jvfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
    {
            if (!strcmp(current->comm, "parent")) {
                    msleep(2000);
            }
    
            jprobe_return();
            return 0;
    }
    
    static struct jprobe delay_stub = {
            .kp = {
                    .symbol_name    = "vfs_write",
            },
            .entry  = jvfs_write,
    };

    我是HZ1000的机器,上述代码即在1和2之间睡眠2秒钟,这样几乎可以100%重现问题。

      试着跑了一遍,真的就重现了!文件中有512个字符‘a’,没有看到任何字符‘-‘

      看起来这问题在多CPU机器上是如此地容易重现,以至于任何人都会觉得这问题不可能会留到3.10内核还不被修补啊!但是内核源码摆在那里,确实是有问题啊!这个时候,我才想起去看一些文档,看看这到底是一个问题呢还是说这本身是合理的,只是需要用户态程序采用某种手段去规避(比如《UNIX环境高级编程》就特别爱用这种方式)。曲折之路就不多赘述了,直接man 2 write,看BUGS section

    BUGS
           According to POSIX.1-2008/SUSv4 Section XSI 2.9.7 ("Thread Interactions with Regular File Operations"):
    
               All of the following functions shall be atomic with respect to each other in the effects specified in POSIX.1-2008 when they operate on regular files or
               symbolic links: ...
    
           Among the APIs subsequently listed are write() and writev(2).  And among the effects that should be atomic across threads (and processes) are updates of the
           file offset.  However, on Linux before version 3.14, this was not the case: if two processes that share an open file description  (see  open(2))  perform  a
           write()  (or  writev(2)) at the same time, then the I/O operations were not atomic with respect updating the file offset, with the result that the blocks of
           data output by the two processes might (incorrectly) overlap.  This problem was fixed in Linux 3.14.
    

    嗯,说明3.10的内核真的是BUG,3.14以后的内核解决了,非常OK!看了4.14的内核,问题没有了,这问题早就在3.14社区内核中解决:

    SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
            size_t, count)
    {
        struct fd f = fdget_pos(fd);  // 这里会锁file的pos锁
        ssize_t ret = -EBADF;
    
        if (f.file) {
            loff_t pos = file_pos_read(f.file);
            ret = vfs_write(f.file, buf, count, &pos);
            if (ret >= 0)
                file_pos_write(f.file, pos);
            fdput_pos(f);
        }
    
        return ret;
    }

    针对该问题的patch说明在:https://lkml.org/lkml/2014/3/3/533

    From: Linus Torvalds <torvalds@linux-foundation.org>
    Date: Mon, 3 Mar 2014 09:36:58 -0800
    Subject: [PATCH 1/2] vfs: atomic f_pos accesses as per POSIX
    
    Our write() system call has always been atomic in the sense that you get
    the expected thread-safe contiguous write, but we haven't actually
    guaranteed that concurrent writes are serialized wrt f_pos accesses, so
    threads (or processes) that share a file descriptor and use "write()"
    concurrently would quite likely overwrite each others data.
    
    This violates POSIX.1-2008/SUSv4 Section XSI 2.9.7 that says:
    
     "2.9.7 Thread Interactions with Regular File Operations
    
      All of the following functions shall be atomic with respect to each
      other in the effects specified in POSIX.1-2008 when they operate on
      regular files or symbolic links: [...]"
    
    and one of the effects is the file position update.
    
    This unprotected file position behavior is not new behavior, and nobody
    has ever cared.  Until now.  Yongzhi Pan reported unexpected behavior to
    Michael Kerrisk that was due to this.
    
    This resolves the issue with a f_pos-specific lock that is taken by
    read/write/lseek on file descriptors that may be shared across threads
    or processes.

    一波三折的事情貌似结束了,总结一下收获就是,碰到问题直接看文档而不是代码估计可能会更快速解决问题。


    我禁不住把这份收获分享给了温州皮鞋老板和王姐姐,为了防止他们较真儿挑战,我准备整理一下我的环境,然后把重现方法也告诉他们,我重启了我的机器,问题发生了…


    这绝对是本文的最后一部分,如果再发生故事,我保证会放弃!因为这个问题本来就是碰到了顺便拿来玩玩的。

      当我把机器重启到Centos 2.6.32内核(我认为低版本内核更容易重现,更容易说明问题)时,依然载入我那个jprobe内核模块,运行我那个parent程序,然而并没有重现问题,相反地,当parent被那个msleep阻塞后,child同样也被阻塞了,看样子是修复bug后的行为啊。

      第一感觉这可能性不大,毕竟3.10内核都有的问题,2.6.32怎么可能避开?!然而事后仔细一想,不对,3.10的问题内核是社区内核,2.6.32的是Centos内核,后者会拉取很多的上游patch来解决一些显然的问题的,对于衍生自Redhat公司的稳定版内核,这并不稀奇。

      最后,我在以下的地址:
    https://oss.oracle.com/git/gitweb.cgi?p=redpatch.git;a=blob;f=fs/read_write.c;h=2e01b41be52b0a313a10fac1a6ebd7161901434a;hb=rhel-2.6.32-642.13.2.el6
    找到了write的实现:

    SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
                     size_t, count)
    {
            struct file *file;
            ssize_t ret = -EBADF;
            int fput_needed;
    
            file = fget_light_pos(fd, &fput_needed);  // 这里是关键
            if (file) {
                    loff_t pos = file_pos_read(file);
                    ret = vfs_write(file, buf, count, &pos);
                    file_pos_write(file, pos);
                    fput_light_pos(file, fput_needed);
            }
    
            return ret;
    }

    请注意fget_light_pos是一个新的实现:

    struct file *fget_light_pos(unsigned int fd, int *fput_needed)
    {
            struct file *file = fget_light(fd, fput_needed);
    
            if (file && (file->f_mode & FMODE_ATOMIC_POS)) {
                    if (file_count(file) > 1) {
                            *fput_needed |= FDPUT_POS_UNLOCK;
                            // 如果有超过一个进程/线程在操作同一个file,则先lock它!
                            mutex_lock(&file->f_pos_lock);
                    }
            }
            return file;
    }

    事情就是在这里起了变化!Centos早就拉取了修复该问题的patch,解决了问题便无法重现问题。

      所以,社区版内核和发行版内核是完全不同的,侧重点不同吧,社区版内核可能更在意内核本身的子系统以及性能因素,而发行版内核则更看重稳定性以及系统调用,毕竟系统就是用来跑应用的,系统调用作为一个接口,一定要稳定无BUG!


    事情结束!

      以下是一点关于这次问题排查的补遗。

      这问题事后跟温州老板以及王姐姐讨论过,我关注的点在于,write一次写(不管实际上写了多少)到底能不能被打断,并不是写多少不会被打断,对于后者,说实话系统保证不了,比如说万一你要求写100T的数据,写着写着你Ctrl-C了或者机器断电了,你能咋滴,谁来负责?但是系统能保证的是,不管你写多少的数据,在你退出write调用前,都不可能被其它的写操作所打断。这是正确的系统调用行为,至于别的,系统并不保证!

      这就好比你去服务大厅排队办事,业务员完全有理由在受理你的业务期间由于你的疏忽或者她自己的疏忽让你仅仅办了一部分事或者说甚至无功而返,但决不会在正在受理你的业务同时又接待了别人,这样你就可以投诉她了吧。write调用的行为也是完全一模一样。

      有人说当文件类型是PIPE时,系统要求至少原子写入PIPE_BUF字节,对于普通文件,也差不多是这么多。这简直太牵强,之所以说是PIPE_BUF而不是硬写成是4096就是因为写操作的具体实现是系统实现相关的,取决于你拿什么作为载体作为到达磁盘的媒介,一般而言就是页面,一次申请的最小单位就是页面,因此刷入4096字节这么一个页面的大小的块是必须的。然而,如果一个页面是1字节呢?或者是1T呢?所以说,这并不是作为用户接口的系统调用所能承诺写入的理由。

      还是那句话,能写多少,系统决然无法保证(业务员无法阻止你忘记带身份证从而业务只能办理一半),但它能保证在它写的时候,不会被其它的写操作打断!


    标准规定的都是正确的,至少比代码更正确,先有的标准再有的代码。但这并不意味着实现就一定符合标准,实现是可以有bug的,比如Linux 3.14版本前社区版内核实现就有bug。所以写完本文后最大的收获就是先看标准和文档再看代码。其实,我是倾向于能不看代码就不看代码的,代码仅仅是一种实现方式而已,我认识的一些Cisco这种公司的网络技术大牛告诉过我,看手册,看Paper,看标准,跑测试case对于理解和玩转一个技术要比单纯的源码分析有效很多很多

      嗯,我就是那个送煤气罐的人。

    ————— 平安夜补遗 —————

    文档比代码重要吗?

    Linus说过“Talk is cheap. Show me the code”,但Document价值几何呢?Linus并没有说。

      我的意思是说,在排查问题的时候,首先要了解事情应该是什么样子的,而不是事情做成了什么样子coding的过程是充满乐趣的,但是一段有bug的code总是令人乏味的!以冒泡排序为例,如果你发现你的代码并没有完成一次正确的排序,首先你要确保你对冒泡排序真的已经理解了,其次才是去debug那段令人沮丧的代码。

      又扯到TCP了。我想很多人在学习Linux内核网络协议栈的时候都避开了TCP的实现,不光是我们这些凡人,就连基本讲Linux网络的经典的书都不包括TCP的内容。这是为什么呢?

      实话实说,Linux的TCP实现太复杂太庞大了,任何初学者看到Linux的TCP实现几乎都会望而却步,仅仅tcp_ack函数就够你喝一壶的了…我本人曾经看了大半年的这部分代码都没有搞明白TCP是怎么回事。后来我是怎么突然就懂了呢?

      我相信量变会引起质变,当我坚持死磕Linux TCP实现的代码,总有一天会看懂的,但是我觉得时间并不是决定性的因素。在我看了很久都没有看懂的时候,我其实不会死磕,我会放弃,我想很多人都会放弃。是的,我放弃了,我想如果给我时间,我能写出一个TCP实现,并且这将是一个我肯定能看懂的TCP实现。

      转而我开始看TCP相关的各种RFC标准,后面我就不说了,反正结果就是看了半个月RFC(上班路上看,上班看,下班路上看,晚上看,周末看,梦里还看…),然后再看Linux内核TCP实现的代码,就秒懂了,这是真事儿。闲着没事儿的时候,还自己试着写了一个用户态的TCP实现版本,用Tap虚拟网卡来通信,后来发现这个跟uIP,lwIP这些有点重复,就没有继续下去…既然知道了事情应该做成什么样子,那么如何去做就不重要了。

      如果程序符合预期,你很好奇这是怎么做到的,那么代码里面没有秘密。
      如果程序不符合预期,第一要务是搞清楚正确的做法,而不是直接去看有bug的代码。

    关于write原子性的讨论

    昨天把这篇文章发到了朋友圈,有位朋友马上就回复了,以下是回复内容,作为本文的补充:

    A:原子写一般只保证512字节,一个sector的大小。
    B:这个确实是实现相关的,现在很多实现基于Page cache,就成了一个Page的大小。按照物理实现,当前的SSD可能又有不同。。。
    A:固件不支持,内核再怎么变也没用。所以才有mysql的double write buffer,就是因为hdd原子写是一个扇区。

    很不错的视角。

      这里要补充的是,一个写操作从发起到磁盘,经过了太多的层,其中每一个层都有该层的最小操作单位,在VFS到磁盘缓冲的层,Page就是最小单位,再往下到磁盘的时候,扇区就成了最小单位,也许某种新的磁盘操作的并不是扇区,也许最终的文件就是个内存块,也可能是NFS的网络对端…总之,内核是无法对应用程序做出原子保证的,很简单,在系统调用这个层次,太高了,系统在这里对底层并不知情,当然无法获知底层所做出的任何承诺。

      对于某些实现,可以通过ioctl这类带外控制通道的调用获取底层的元数据,这样至少可以让应用程序可以对自己的行为行使更多的策略,拥有更多的选择。

    平安夜礼物

    2002年平安夜,我和几个屌丝在哈尔滨中央大街看橱窗里穿着白色礼服的俄罗斯美女,不用买礼物,带着眼就行,足足在一家婚纱店外面抽了5根烟才离去。
      后面几年跟这个差不多,只不过换了地方换了几个不同的屌丝而已。我记得2004年圣诞节前我是从河南工学院一路滑着冰到郑州火车站的,然后逃票到了新乡去找女朋友(现在成了小小的妈)。
      2007年平安夜,吉林长春,欧亚商都,巴黎春天。我本来是想给女朋友买件大衣的,然而价格均在800块以上,沮丧地离开商场在门外买了一串气球回家,买不起衣服,吃不起东方肉馆…
      后来到了上海,终于能买得起了,女朋友也成了老婆,然而也胖了不再买衣服了,也不再把肉食作为奢侈品了,这是多么的幸福,没钱的时候,买不起,有钱的时候,不用买了。
      然而事情在起变化。
      躲了老婆,躲不了情人。我想送小小一件圣诞节礼物,是的,我想了很久了。然而不知道怎么回事,平安夜就在眼前了…中午的时候,我出去晃悠了一下,想看看能不能找点灵感,然而还是令人痛苦地失败了。我买了一个超大的健达蛋回家,突然感受到了2007年平安夜买那串气球的感觉。
      刚进门,就被小小堵截了,我被迫把礼物给了她,她非常之高兴,我感到很惭愧,一个23块钱的礼物在她眼里有如此昂贵。唉,温州老板说我空手套白狼,我想温州老板是对的。都说女儿是老爸上辈子的情人,上辈子没能给她幸福,这辈子呢?作为父亲的我和女儿其实没有任何关系,一切都是因为缘分,不管是良缘还是孽缘,让她选择了做我的女儿,所以一定要对她好,毕竟她本来可能是有选择父亲的权利的。
      圣诞节是基督教的节日,我也是基督教的信徒,我本该多说点,但还是选择保持缄默,闭上眼睛,心里祷告。眼里有工作,心中有上帝,四海共见美好。

      愿圣诞节的清晨,使我们做为您的孩子,幸福快乐,愿圣诞节的夜晚引领我们来到床前,用感恩的心为你述说,赦免我的过去,赦免我的现在,因着耶稣基督的缘故,赦免我,这样的祷告,是奉我主耶稣基督的名!阿门!

    ———- 2017/12/24 22:13 作文———-

    展开全文
  • MongoDB 批量操作(bulkWrite)

    千次阅读 2020-03-26 21:01:53
    mongodb 3.2 版中的新版本提供了db.collection.bulkWrite() 方法提供了执行批量插入、更新和删除操作的能力。 mongodb 还支持批量插入 db.collection.insertMany()。 1.1、语法 db.collection.bulkWrite( [ <...

    一、概述

    mongodb 3.2 版中的新版本提供了db.collection.bulkWrite() 方法提供了执行批量插入、更新和删除操作的能力。

    mongodb 还支持批量插入 db.collection.insertMany()。

    1.1、语法

    db.collection.bulkWrite(
       [ <operation 1>, <operation 2>, ... ],
       {
          writeConcern : <document>,
          ordered : <boolean>
       }
    )
    
    参数类型描述
    operationsarraybulkWrite() 写操作的数组。支持操作:insertOne、updateOne、updateMany、deleteOne、deleteMany、replaceOne
    writeConcerndocument可选, write concern 文档,省略则使用默认的 write concern。
    orderedboolean可选,表示mongod实例有序还是无序执行操作。默认值true。

    方法返回值:

    • 操作基于 write concern 运行则 acknowledged 值为true,如果禁用 write concern 运行则 acknowledged 值为false。
    • 每一个写操作数。
    • 成功 inserted 或 upserted文档的 _id 的组数。

    行为

    bulkWrite() 接收一个写操作的数组然后执行它们中的每一个。默认是有序的执行。

    二、写操作

    insertOne

    插入单个文档到集合中。

    db.collection.bulkWrite( [
       { insertOne : { "document" : <document> } }
    ] )
    

    updateOne 及 updateMany

    updateOne 更新集合中 filter 匹配的单个文档。如果匹配到多个文档 updateOne 仅更新第一个匹配到的文档。

    db.collection.bulkWrite( [
       { updateOne :
          {
             "filter" : <document>,
             "update" : <document>,
             "upsert" : <boolean>
          }
       }
    ] )
    

    updateMany 更新集合中所有匹配到的文档。

    db.collection.bulkWrite( [
       { updateMany :
          {
             "filter" : <document>,
             "update" : <document>,
             "upsert" : <boolean>
          }
       }
    ] )
    

    对字段的更新操作例如 $ set 、$ unset 、$rename等。

    默认情况 upsert 为 false。

    replaceOne

    replaceOne 替换集合中 filter 匹配到的单个文档。如果匹配到多个文档 replaceOne 只会替换一个匹配到的文档。

    db.collection.bulkWrite([
       { replaceOne :
          {
             "filter" : <document>,
             "replacement" : <document>,
             "upsert" : <boolean>
          }
       }
    ] )
    

    replacement 字段中不能包含 update 操作。

    默认情况 upsert 为 false。

    deleteOne 及 deleteMany

    deleteOne 删除集合中 filter 匹配到的单个文档。如果匹配到多个文档 deleteOne 只会删除一个匹配到的文档。

    db.collection.bulkWrite([
       { deleteOne :  { "filter" : <document> } }
    ] )
    

    deleteMany 删除集合中 filter 匹配到的所有文档。

    db.collection.bulkWrite([
       { deleteMany :  { "filter" : <document> } }
    ] )
    

    三、_id 字段

    如果文档未指定 _id 字段,则mongod会在 insert 或 upsert 文档之前添加 _id 字段并指定唯一的ObjectId。 大多数驱动程序会创建一个ObjectId并插入到 _id 字段,但如果驱动程序或应用程序没有,mongod将创建并填充 _id。

    如果文档包含 _id 字段,则 _id 值在集合中必须是唯一的,以避免重复键错误。

    更新或替换操作不能指定与原始文档不同的 _id 值。

    四、执行操作

    ordered 参数指定 bulkWrite() 是否有序执行,默认情况下是有序执行。

    含有6个操作的 bulkWrite() 代码如下:

    db.collection.bulkWrite(
       [
          { insertOne : <document> },
          { updateOne : <document> },
          { updateMany : <document> },
          { replaceOne : <document> },
          { deleteOne : <document> },
          { deleteMany : <document> }
       ]
    )
    

    默认情况下 ordered : true ,每个操作将会有序的执行,从第一个insertOne 到最后一个deleteMany 顺序执行。

    应用程序不依赖操作执行顺序是,可以设置 ordered 为 false ,此时mongod 会重新排序操作来提高性能。

    含有6个操作无序的 bulkWrite() 代码如下:

    db.collection.bulkWrite(
       [
          { insertOne : <document> },
          { updateOne : <document> },
          { updateMany : <document> },
          { replaceOne : <document> },
          { deleteOne : <document> },
          { deleteMany : <document> }
       ],
       { ordered : false }
    )
    

    对于ordered:false,操作结果可能会有所不同。 例如,deleteOne或deleteMany 删除的文档可能会变多或变少,具体取决于deleteOne或deleteMany 是在insertOne,updateOne,updateMany或replaceOne操作之前或之后的运行。

    每组操作最多可以有1000次操作。 如果一个组超过此限制,MongoDB会将该组划分为1000或更小的组。 例如,如果队列包含2000个操作,MongoDB将创建2个组,每个组具有1000个操作。

    大小和分组机制是内部的执行细节,在将来的版本中可能会有所变化。

    在分片集合上执行有序操作通常比执行无序操作慢,因为对于有序,每个操作必须等待上一个操作完成。

    五、固定集合(Capped Collections)

    bulkWrite() 写操作在固定集合上使用有所限制。

    • updateOne 和 updateMany 更新时增加了被修改文档的大小将会抛出 WriteError

    • replaceOne 操作替换的文档比之前的文档大会抛出 WriteError

    • deleteOne 和 deleteMany 操作在固定集合上会抛出 WriteError

    六、操作处理(Error Handling)

    bulkWrite() 在错误发生时会抛出 BulkWriteError 异常。

    排除Write Concern错误,有序操作在发生错误后停止,及无序操作继续处理队列中的剩余写入操作。

    Write Concern 错误显示在 writeConcernErrors字段中,而所有其他错误都显示在writeErrors字段中。 如果遇到错误,则显示成功写入操作的数量而不是插入的_id值。 有序操作显示遇到的单个错误,而无序操作显示数组中的每个错误。

    七、实例

    7.1、批量写

    characters 集合包含以下文档:

    { "_id" : 1, "char" : "Brisbane", "class" : "monk", "lvl" : 4 },
    { "_id" : 2, "char" : "Eldon", "class" : "alchemist", "lvl" : 3 },
    { "_id" : 3, "char" : "Meldane", "class" : "ranger", "lvl" : 3 }
    

    bulkWrite() 在集合上执行批量操作:

    try {
       db.characters.bulkWrite(
          [
             { insertOne :
                {
                   "document" :
                   {
                      "_id" : 4, "char" : "Dithras", "class" : "barbarian", "lvl" : 4
                   }
                }
             },
             { insertOne :
                {
                   "document" :
                   {
                      "_id" : 5, "char" : "Taeln", "class" : "fighter", "lvl" : 3
                   }
                }
             },
             { updateOne :
                {
                   "filter" : { "char" : "Eldon" },
                   "update" : { $set : { "status" : "Critical Injury" } }
                }
             },
             { deleteOne :
                { "filter" : { "char" : "Brisbane"} }
             },
             { replaceOne :
                {
                   "filter" : { "char" : "Meldane" },
                   "replacement" : { "char" : "Tanys", "class" : "oracle", "lvl" : 4 }
                }
             }
          ]
       );
    }
    catch (e) {
       print(e);
    }
    

    操作结果如下:

    {
       "acknowledged" : true,
       "deletedCount" : 1,
       "insertedCount" : 2,
       "matchedCount" : 2,
       "upsertedCount" : 0,
       "insertedIds" : {
          "0" : 4,
          "1" : 5
       },
       "upsertedIds" : {
     
       }
    }
    

    如果 第二个 insertOne 操作的 _id 是集合中已经存在的,则会抛出以下错误:

    BulkWriteError({
       "writeErrors" : [
          {
             "index" : 0,
             "code" : 11000,
             "errmsg" : "E11000 duplicate key error collection: guidebook.characters index: _id_ dup key: { : 4 }",
             "op" : {
                "_id" : 5,
                "char" : "Taeln"
             }
          }
       ],
       "writeConcernErrors" : [ ],
       "nInserted" : 1,
       "nUpserted" : 0,
       "nMatched" : 0,
       "nModified" : 0,
       "nRemoved" : 0,
       "upserted" : [ ]
    })
    

    默认情况下 ordered 为 true, 顺序执行时遇到错误就停止执行(后续的操作不会被执行)。

    7.2、无序批量写

    characters 集合包含以下文档:

    { "_id" : 1, "char" : "Brisbane", "class" : "monk", "lvl" : 4 },
    { "_id" : 2, "char" : "Eldon", "class" : "alchemist", "lvl" : 3 },
    { "_id" : 3, "char" : "Meldane", "class" : "ranger", "lvl" : 3 }
    

    bulkWrite() 在集合上执行批量操作:

    try {
       db.characters.bulkWrite(
             [
                { insertOne :
                   {
                      "document" :
                      {
                         "_id" : 4, "char" : "Dithras", "class" : "barbarian", "lvl" : 4
                      }
                   }
                },
                { insertOne :
                   {
                      "document" :
                         {
                            "_id" : 4, "char" : "Taeln", "class" : "fighter", "lvl" : 3
                         }
                   }
                },
                { updateOne :
                   {
                      "filter" : { "char" : "Eldon" },
                      "update" : { $set : { "status" : "Critical Injury" } }
                   }
                },
                { deleteOne :
                   { "filter" : { "char" : "Brisbane"} }
                },
                { replaceOne :
                   {
                      "filter" : { "char" : "Meldane" },
                      "replacement" : { "char" : "Tanys", "class" : "oracle", "lvl" : 4 }
                   }
                }
             ],
                { ordered : false }
          );
       }
       catch (e) {
       print(e);
    }
    

    操作结果如下:

    BulkWriteError({
       "writeErrors" : [
          {
             "index" : 0,
             "code" : 11000,
             "errmsg" : "E11000 duplicate key error collection: guidebook.characters index: _id_ dup key: { : 4 }",
             "op" : {
                "_id" : 4,
                "char" : "Taeln"
             }
          }
       ],
       "writeConcernErrors" : [ ],
       "nInserted" : 1,
       "nUpserted" : 0,
       "nMatched" : 2,
       "nModified" : 2,
       "nRemoved" : 1,
       "upserted" : [ ]
    })
    

    无序操作,尽管操作过程中出现错误,剩余的操作也不会就此终止执行。

    7.3、基于 Write Concern 的批量写

    enemies 集合包含以下文档:

    { "_id" : 1, "char" : "goblin", "rating" : 1, "encounter" : 0.24 },
    { "_id" : 2, "char" : "hobgoblin", "rating" : 1.5, "encounter" : 0.30 },
    { "_id" : 3, "char" : "ogre", "rating" : 3, "encounter" : 0.2 },
    { "_id" : 4, "char" : "ogre berserker" , "rating" : 3.5, "encounter" : 0.12}
    

    以下使用 write concern 值为 “majority” 及 timeout 为 100 毫秒来执行批量写操作:

    try {
       db.enemies.bulkWrite(
          [
             { updateMany :
                {
                   "filter" : { "rating" : { $gte : 3} },
                   "update" : { $inc : { "encounter" : 0.1 } }
                },
     
             },
             { updateMany :
                {
                   "filter" : { "rating" : { $lt : 2} },
                   "update" : { $inc : { "encounter" : -0.25 } }
                },
             },
             { deleteMany : { "filter" : { "encounter" { $lt : 0 } } } },
             { insertOne :
                {
                   "document" :
                      {
                         "_id" :5, "char" : "ogrekin" , "rating" : 2, "encounter" : 0.31
                      }
                }
             }
          ],
          { writeConcern : { w : "majority", wtimeout : 100 } }
       );
    }
    catch (e) {
       print(e);
    }
    

    如果副本集中所有必需节点确认写入操作所需的总时间大于wtimeout,则在wtimeout 时间过去时将显示以下writeConcernError。

    BulkWriteError({
       "writeErrors" : [ ],
       "writeConcernErrors" : [
          {
             "code" : 64,
             "errInfo" : {
                "wtimeout" : true
             },
             "errmsg" : "waiting for replication timed out"
          }
       ],
       "nInserted" : 1,
       "nUpserted" : 0,
       "nMatched" : 4,
       "nModified" : 4,
       "nRemoved" : 1,
       "upserted" : [ ]
       })
    

    结果集显示执行的操作,因为writeConcernErrors错误不是任何写入操作失败的标志。

    展开全文
  • Copy On Write机制了解一下

    万次阅读 多人点赞 2019-05-15 17:29:50
    执行BGSAVE命令或者BGREWRITEAOF命令的过程中,Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)来优化子进程的使用效率,所以在子进程存在期间,服务器会提高负载因子的阈值...
  • python write( )函数

    万次阅读 2016-05-07 14:19:50
    python2 write
  • write函数过程解析 __write_nocancel

    千次阅读 2017-07-26 11:08:19
    write函数过程解析 write函数作为用户向终端或者文件进行写数据的重要函数,有着重要的作用。 |------| |---------| |---------| |----------|  | write |----->|sys_write|-------->|vfs_write|------->|ext...
  • write.csv()函数--R语言

    千次阅读 2021-04-12 22:50:35
    write.table prints its required argument x (after converting it to a data frame if it is not one nor a matrix) to a file or connection. 将X输出到文件或者链接 函数语法: write.table(x, file = "", ...
  • document中的write用法

    万次阅读 2018-08-28 20:07:02
    一、document.write()运行原理  首先我们先了解一下这条语句运行的原理:  document.write()是Javascript中对document.open()所开启的文档流操作的API方法。  它能够直接砸文档流中写入字符串,一旦文档流已经...
  • 文章目录 概览 Cache-Aside 读操作 更新操作 1、缓存失效 2、缓存更新 Read-Through Write-Through Write-Behind 总结 参考 概览 缓存是一个有着更快的查询速度的存储技术,这里的更快是指比起从初始的数据源查询...
  • fortran write格式

    千次阅读 2020-07-17 23:14:31
    write(*,"(f10.1)",advance="no")A 格式化输出的控制字符非常的丰富,但常用的并不多,一般说来:" I 、F、E、A、X "是最常使用的几个格式,最好把它们都记下来。 Iw[.m]以w个字符的宽度来输出整数,至少输出m个...
  • fatfs文件系统详解之f_write函数分析

    千次阅读 2020-05-31 18:36:43
    本篇分析f_write()函数,fatfs文件系统对应的不知道文件的读写,也对应了文件的其他的操作,也有文件夹的操作,函数分析确实是一个非常耗时耗精力的事情,此函数分析完之后,就结束函数分析。 分析假设 (1)假设一...
  • 最近用libcurl开源库做了...1,curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HttpPostWriteBack); HttpPostWriteBack是回调函数指针,需要自己实现原型为:unsigned int HttpPostWriteBack(void *contents, siz...
  • Write-Ahead Log(WAL)的工作原理

    万次阅读 2019-06-07 10:12:05
    本文将要阐述的预写式日志Write Ahead Log(WAL),正是对此的优化。 Write Ahead Log概述 Write Ahead Log简称WAL,在分布式存储系统中的元数据更新中应用得十分广泛。WAL的主要意思是说在将元数据的变更操作...
  • 在ARM开发板上跑如下测试程序的时候会概率性出现“Write Failed Bad address”: /* Write test */ #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #...
  • turtle.write方法使用说明

    千次阅读 多人点赞 2021-02-21 10:56:13
    turtle.write方法使用说明 关于turtle可参见 Python新手入门学习讲座系列文章 https://blog.csdn.net/cnds123/article/details/108676296 turtle.write()方法 在当前乌龟位置写入文本。使用格式: write(arg...
  • 总结了Fortran中WRITE和READ语句的格式化使用方法。
  • Android 10+ 适配数据存取权限,WRITE_EXTERNAL_STORAGE no longer provides write access when targeting Android 10+Android 10+ 适配数据存取权限简介方法总结 Android 10+ 适配数据存取权限 你好! 这是你第一次...
  • prometheus的remote write功能

    千次阅读 2020-08-30 17:37:54
    prometheus配置了remote write的目标地址后,它会从WAL读取数据,然后把采样数据写入各分片的内存队列,最后发起向远程目标地址的请求。 数据流的逻辑大致如下: |--> queue (shard_1) --> remote endpoint ...
  • 产生socket write error的原因

    万次阅读 2019-09-12 15:51:31
    最近拿到一个工程,不停的报socket write error,虽然不影响正常使用,但是真的很烦,而且会影响日志的记录.所以决定找到这个问题的答案: excepion的堆栈信息如下: Exception Processing ErrorPage[errorCode=404, ...
  • write(,) x,y 即可实现同行输出 多变量 表示 输出到屏幕上 见底下 write(*,’(f10.5)’) time 输出某一个变量 另外一种形式是输出3个变量 原始参考链接
  • linux多线程下的fwrite和write使用详解

    千次阅读 2020-12-09 15:48:41
    今天,主要研究多线程下的fwrite与write,每个线程都对相同的FILE*或者fd进行写操作,看看结果是否为预期行为。 第一种情况:使用C库的fwrite,其线程的实现如下: 第二种情况:使用系统调用write,其线程的实现...
  • write.table() | write.csv() | write.csv2() of R

    千次阅读 2020-04-15 16:10:02
    write.table(x, file = “”, append = FALSE, quote = TRUE, sep = " ", eol = “\n”, na = “NA”, dec = “.”, row.names = TRUE, col.names = TRUE, qmethod = c(“escape”, “double”), fileEncoding = ...
  • JavaCV异常:avformat_write_header error() error -40: Could not write header to 'null'问题描述解决思路在FFmpegFrameGrabber.start()之前设置FFmpeg日志级别观察控制台打印信息确认视频编码格式解决方法 ...
  • write和read返回值详解

    万次阅读 2019-05-29 10:50:34
    write返回值 1、返回值>0 a、等于给定字节数 b、小于给定字节数,有如下几种可能: 底层物理介质上没有足够的空间 创建的文件指定了RLIMIT_FSIZE,也就是指定了文件允许的最大字节数,不能再往其中添加数据 ...
  • 今天redis服务被报出错误:NOWRITE You can’t write against a non-write redis.] with root cause 详细代码如下: 2019-03-20 11:27:30 [ERROR] [org.apache.juli.logging.DirectJDKLog:182] - Servlet.service...
  • iwrite复制粘贴

    千次阅读 2020-10-21 23:48:32
    1,登录iwrite 登录iwrite 2,打开需要完成的作业 打开作业 3,按<F12>,再按<F1>,打开chrome控制台, 打开chrome控制台 找到Disable JavaScript前面的小方框并选中 关闭JavaScript 不要...
  • Write Back(Write behind) Write back是相较于Write Through而言的一种异步回写策略. 异步写可以减少与物理磁盘存储的交互,也可以进行合并写等优化. 问题 实现比较复杂,可能会丢失数据. 适用场景 用于读少写多的...
  • document.write详解

    千次阅读 2018-01-07 01:22:30
    原文地址:document.write的用处! document.write是JavaScript中对document.open所开启的文档流(document stream操作的API方法,它能够直接在文档流中写入字符串,一旦文档流已经关闭,那document.write就会重新...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,805,071
精华内容 1,122,028
关键字:

write