1.理论

当两个域具有相同的协议(如http), 相同的端口(如80),相同的host(如www.example.org),那么我们就可以认为它们是相同的域。比如http://www.example.org/index.htmlhttp://www.example.org/sub/index.html 是同域,而 http://www.example.org , https://www.example.org , http://www.example.org:8080 , http://sub.example.org 中的任何两个都将构成跨域。

同源策略的限制之一就是不能通过ajax的方法去请求不同源中的资源,不管是静态页面、动态网页还是web服务。第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的,不同的框架之间是可以获取window对象的,但却无法获取相应的属性和方法。

2.常用的几种跨域解决方案

2.1 window.name

window 对象的name属性是一个很特别的属性,当该window的location变化,然后重新加载,它的name属性可以依然保持不变(窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限)。

window.name实现的跨域数据传输的实现

2.2 window.postMessage

window.postMessage是HTML5定义的一个很新的方法,这个方法可以很方便地跨window通信。由于它是一个很新的方法,所以在比较旧的浏览器中都无法使用,如:FireFox4.0、IE6。

window.postMessage实现跨域通信实现

2.3 CORS

CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。服务端会在HTTP请求头中增加一系列HTTP请求参数(例如Access-Control-Allow-Origin等),来限制哪些域的请求和哪些请求类型可以接受,而客户端在发起请求时必须声明自己的源(Orgin),否则服务器将不予处理,如果客户端不作声明,请求甚至会被浏览器直接拦截都到不了服务端。服务端收到HTTP请求后会进行域的比较,只有同域的请求才会处理。注意的是旧版本的浏览器也是不支持的

Java之旅–跨域(CORS)

2.4 document.domain

document.domain的方法只适用于不同子域的框架间的交互,当我们把它们document的domain属性都修改为a.com,浏览器就会认为它们处于同一个域下,那么我们就可以互相调用对方的method来通信了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同

1.在页面 http://www.example.com/a.html 中设置document.domain:

<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
    document.domain = 'example.com';//设置成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象
    }
</script>

2.在页面 http://example.com/b.html 中也设置document.domain:

<script type="text/javascript">
    document.domain = 'example.com';//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>

2.5 JONP

在同源策略下,在某个服务器下的页面是无法获取到该服务器以外的数据的,但img、iframe、script等标签是个例外,这些标签可以通过src属性请求到其他服务器上的数据。而JSONP就是通过script节点src属性调用跨域的请求。

JSONP的优点:易于实现,是在受信任的双方传递数据,JSONP是非常合适的选择。

JSONP的缺点:存在一些安全隐患,如果第三方的脚本随意地执行,那么它就可以篡改页面内容,截获敏感数据;另外jsonp支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

//-------------------------javascript代码:
var url = 跨域的dns + "/fzyw/xzfy/smcl/autoUpdateByWS.action";
var data = {
  "writid": writid,
  "reportName": reportname
};

$.ajax({
    contentType: "application/x-www-form-urlencoded;charset=UTF-8",
    type : "GET",
    url : url,
    data : data,
    cache : false, //默认值true
    dataType : "jsonp",
    jsonp: "callback", // 必须,返回的响应需要以此为前缀
    //使用了jsonp 方式,则此方法不被触发.原因可能是dataType如果指定为jsonp的话,就已经不是ajax事件了
    beforeSend: function(){

    },
    success : function(json){
        if (json.result != "success") {
           alert("自动更新扫描材料失败!");
        }
    },
    complete: function(XMLHttpRequest, textStatus){
        $.unblockUI({ fadeOut: 10 });
    },
    //使用了jsonp方式,则此方法不被触发.原因可能是dataType如果指定为jsonp的话,就已经不是ajax事件了
    error: function(xhr){ //请求出错处理
        alert("请求出错(请检查相关度网络状况.)");
    }
});
//---------------------------java后台代码:
@RequestMapping("/autoUpdateByWS")
public @ResponseBody
String autoUpdateByWS(Model model, String callback, String writid,
     String reportName) {
  /**
  *业务代码
  */
  .....

  String response = "${callback}({\"result\":\"success\"})"
       .replaceFirst("\\$\\{callback\\}", callback);
  return response;
}

2.6 webservice跨域问题

服务端直接的访问是不受跨域影响的,所以由js调用后端,后端通过ws方式访问远程服务

//----------------js代码:
$(function(){
$("#btn2").click(function(){
                var name = document.getElementById("name").value;
                $.post(
                      "HttpURLConnectionServlet",
                      "name="+name,
                      function(msg) {
                           //alert(msg);
                           var $Result = $(msg);
                           var value = $Result.find("return").text();
                           alert(value);
                      },
                      "xml"
                );
           });
}
//-----------------java代码:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 使用HttpURLConnection发送webservice请求
 */
public class HttpURLConnectionServlet extends HttpServlet {
      private static final long serialVersionUID = 1L;
      protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
           String name = request.getParameter("name");
           String data = "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
<soap:Body><ns2:sayHello xmlns:ns2='http://ws.day01_ws.atguigu.com/'><arg0>"+name+"</arg0>
</ns2:sayHello></soap:Body></soap:Envelope>";//需要发送的soap消息
           URL url = new URL("http://192.168.10.165:8888/day01_ws/datatypews");
           HttpURLConnection connection = (HttpURLConnection) url.openConnection();
           connection.setRequestMethod("POST");//请求方式
           connection.setDoOutput(true);//是否要输出数据到服务器端
           connection.setDoInput(true); //是否接受数据的输入
           connection.setRequestProperty("Content-Type", "text/xml;charset=utf-8");//请求的参数
           OutputStream os = connection.getOutputStream();
           os.write(data.getBytes("utf-8"));
           int responseCode = connection.getResponseCode();
           if(responseCode==200) {//成功返回时,
                InputStream is = connection.getInputStream();//String xml
                System.out.println("return "+is.available());
                response.setContentType("text/xml;charset=utf-8");
                ServletOutputStream outputStream = response.getOutputStream();
                byte[] buffer = new byte[1024];
                int len = 0;
                while((len=is.read(buffer))>0) {
                      outputStream.write(buffer, 0, len);
                }
                outputStream.flush();
           }
      }
}

3.参考资料

  1. window.name实现的跨域数据传输的实现

  2. window.postMessage实现跨域通信实现

  3. Java之旅–跨域(CORS)

原文链接:我必须得告诉大家的MySQL优化原理

mysql处理请求sql的逻辑结构图

mysql处理请求sql的逻辑结构图

MySQL整个查询执行过程,总的来说分为6个步骤:

  1. 客户端向MySQL服务器发送一条查询请求

  2. 服务器首先检查查询缓存,如果命中缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段

  3. 服务器进行SQL解析、预处理、再由优化器生成对应的执行计划

  4. MySQL根据执行计划,调用存储引擎的API来执行查询

  5. 将结果返回给客户端,同时缓存查询结果

性能优化

  • 通常来说把可为 NULL 的列改为 NOT NULL 不会对性能提升有多少帮助,只是如果计划在列上创建索引,就应该将该列设置为 NOT NULL

  • 对整数类型指定宽度,比如 INT(11) ,没有任何卵用。 INT 使用16为存储空间,那么它的表示范围已经确定,所以 INT(1)INT(20) 对于存储和计算是相同的。

  • UNSIGNED 表示不允许负值,大致可以使正数的上限提高一倍。比如 TINYINT 存储范围是-128 ~ 127,而 UNSIGNED TINYINT 存储的范围却是0 – 255。

  • 通常来讲,没有太大的必要使用 DECIMAL 数据类型。即使是在需要存储财务数据时,仍然可以使用 BIGINT 。比如需要精确到万分之一,那么可以将数据乘以一百万然后使用 BIGINT 存储。这样可以避免浮点数计算不准确和 DECIMAL 精确计算代价高的问题。

  • TIMESTAMP 使用4个字节存储空间, DATETIME 使用8个字节存储空间。因而, TIMESTAMP 只能表示1970 – 2038年,比 DATETIME 表示的范围小得多,而且 TIMESTAMP 的值因时区不同而不同。

  • 大多数情况下没有使用枚举类型的必要,其中一个缺点是枚举的字符串列表是固定的,添加和删除字符串(枚举选项)必须使用 ALTER TABLE (如果只只是在列表末尾追加元素,不需要重建表)。

  • schema的列不要太多。原因是存储引擎的API工作时需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容解码成各个列,这个转换过程的代价是非常高的。如果列太多而实际使用的列又很少的话,有可能会导致CPU占用过高。

  • 大表 ALTER TABLE 非常耗时,MySQL执行大部分修改表结果操作的方法是用新的结构创建一个张空表,从旧表中查出所有的数据插入新表,然后再删除旧表。尤其当内存不足而表又很大,而且还有很大索引的情况下,耗时更久。当然有一些奇淫技巧可以解决这个问题,有兴趣可自行查阅。

  • 如果要统计行数,直接使用 COUNT(*) ,意义清晰,且性能更好。 COUNT(1) 则用于统计某个列值非NULL的数量。

  • 在大数据场景下,表与表之间通过一个冗余字段来关联,要比直接使用 JOIN 有更好的性能。

SELECT A.xx,B.yy
FROM A INNER JOIN B USING(c)
WHERE A.xx IN (5,6)

确保 ON 和 USING 字句中的列上有索引。在创建索引的时候就要考虑到关联的顺序。当表A和表B用列c关联的时候,如果优化器关联的顺序是A、B,那么就不需要在A表的对应列上创建索引。没有用到的索引会带来额外的负担,一般来说,除非有其他理由,只需要在关联顺序中的第二张表的相应列上创建索引(具体原因下文分析)。

确保任何的 GROUP BY 和 ORDER BY 中的表达式只涉及到一个表中的列,这样MySQL才有可能使用索引来优化。

  • 优化LIMIT分页

有时候如果可以使用书签记录上次取数据的位置,那么下次就可以直接从该书签记录的位置开始扫描,这样就可以避免使用 OFFSET ,比如下面的查询:

SELECT id FROM t LIMIT 10000, 10;
改为:
SELECT id FROM t WHERE id > 10000 LIMIT 10;

其他优化的办法还包括使用预先计算的汇总表,或者关联到一个冗余表,冗余表中只包含主键列和需要做排序的列。

  • 优化UNION

MySQL处理 UNION 的策略是先创建临时表,然后再把各个查询结果插入到临时表中,最后再来做查询。因此很多优化策略在 UNION 查询中都没有办法很好的时候。经常需要手动将 WHERE 、 LIMIT 、 ORDER BY 等字句“下推”到各个子查询中,以便优化器可以充分利用这些条件先优化。

  • 索引的优化使用

1.MySQL不会使用索引的情况:非独立的列

select * from where id + 1 = 5

我们很容易看出其等价于 id = 4,但是MySQL无法自动解析这个表达式,使用函数是同样的道理。

2.前缀索引

如果列很长,通常可以索引开始的部分字符,这样可以有效节约索引空间,从而提高索引效率。

3.多列索引和索引顺序

不同版本的mysql对于多列索引的支持并不相同,所以多列索引有时候并不是好的选择,但如果需要使用多列索引,那么索引的顺序对于查询是至关重要的,很明显应该把选择性更高的字段放到索引的前面,这样通过第一个字段就可以过滤掉大多数不符合条件的数据。

索引选择性是指不重复的索引值和数据表的总记录数的比值,选择性越高查询效率越高,因为选择性越高的索引可以让MySQL在查询时过滤掉更多的行。唯一索引的选择性是1,这时最好的索引选择性,性能也是最好的。

以下sql是应该创建 (staff_id,customer_id) 的索引还是应该颠倒一下顺序?执行下面的查询,哪个字段的选择性更接近1就把哪个字段索引前面就好。

select count(distinct staff_id)/count(*) as staff_id_selectivity,
    count(distinct customer_id)/count(*) as customer_id_selectivity,
    count(*) from payment

4.避免多个范围条件

select user.* from user where login_time > '2017-04-01' and age between 18 and 30;

这个查询有一个问题:它有两个范围条件,login_time列和age列,MySQL可以使用login_time列的索引或者age列的索引,但无法同时使用它们。

5.覆盖索引

如果一个索引包含或者说覆盖所有需要查询的字段的值,那么就没有必要再回表查询,这就称为覆盖索引。

SELECT id, title, content FROM article
INNER JOIN (
SELECT id FROM article ORDER BY created DESC LIMIT 10000, 10
) AS page USING(id)
--索引建在created字段(其中id是主键)

6.使用索引扫描来排序

当索引的列顺序和 ORDER BY 子句的顺序完全一致,并且所有列的排序方向也一样时,才能够使用索引来对结果做排序。如果查询需要关联多张表,则只有 ORDER BY 子句引用的字段全部为第一张表时,才能使用索引做排序。 ORDER BY 子句和查询的限制是一样的,都要满足最左前缀的要求(有一种情况例外,就是最左的列被指定为常数,下面是一个简单的示例),其他情况下都需要执行排序操作,而无法利用索引排序。

---索引:(date,staff_id,customer_id),且最左列date为常数用作查询条件
select staff_id,customer_id from demo where date = '2015-06-01' order by staff_id,customer_id

7.冗余和重复索引

比如有一个索引 (A,B) ,再创建索引 (A) 就是冗余索引,应当尽量避免这种索引,发现后立即删除。大多数情况下都应该尽量扩展已有的索引而不是创建新索引。但有极少情况下出现性能方面的考虑需要冗余索引,比如扩展已有索引而导致其变得过大,从而影响到其他使用该索引的查询。

8.删除长期未使用的索引

定期删除一些长时间未使用过的索引是一个非常好的习惯。

英文原文:Does code need to be perfect?

译文出处: 代码真的有必要写到完美吗?

过去几个月,我总是在问自己类似的问题:为什么我们总在苛求完美的代码?因为内部项目需要,重新捡起编码任务之后,我发觉我们组内(也可能是大多数软件开发世界中的大多数人)花费了大量时间在规整编码规范、模式和测试代码,但这真的有必要么?

作为软件开发机构,我们需要持续地进行预算、时间和特性的平衡。这种平衡的结果是,许多特性需要修改,或者干脆不做了,可能原因是耗时过长或者成本太高。从另一个方面来说,工程师通常感到项目特别赶,出来的代码通常都不完美。我相信对于任何软件研发机构来说,这个现状都是很明显的。

上个月,我跟我们的一位客户(CEO)谈话,他们的 CTO 和主程要求我们帮助他们重构一部分代码。在不作出重大修改的前提下添加新功能几乎不可能,而且没有人对整体代码实现很了解。尽管目前运行一切良好,这部分项目初始代码从技术角度来看就是一团乱。这位客户(CEO)问我为什么需要重构,从他的角度来看,代码目前没有任何问题,只是需要发布新功能可以再快一点。

我想这种情况下,双方都很有道理。开发者们希望用最新的技术写出完美的代码,写完善的文档,每个人都可以了解到具体实现,从而可以方便测试和后续的维护升级。而另一方面,其它人却只是希望快速经济地完成功能,从而他们可以推出新功能或者推销给更多客户。

那我们该怎么平衡这两种诉求呢?

忘掉未来,为现在而编码

大多数产品公司经历了几个阶段。每个阶段都需要对“完美”的意思有不同的看法。我们可以长时间地讨论哪些阶段是存在的,但为了本文,我将仅仅(just)区分为:概念验证代码、 MVP 代码和长期维护代码。并分别举例说明。

在为产品制定新的想法时,花费任何时间编写可扩展的、全面测试的并符合最新编码标准的代码是没有意义的。目标是提供一个概念原型,例如连接几个 API 或尝试一个新的接口想法。当实现目标之后,任何人都不太可能再次深入这个代码。

大多数人在构建最小可行的产品时,都高估了对优质代码的需求。每个创业公司的最重要的事情是发布在一个漂亮的、功能完善的产品。该产品的后台工作原理并不重要。直到你的 MVP 真正得到关注,你可以着手处理劣质代码,甚至手工做些事情来证明你拥有一个适合的产品/市场。只有在你确定使用它,并且客户开始流入时,你应该开始关心代码,如果没到这一步,你其实仅仅(just)写了一次性的代码而已。

一旦这些辛苦积攒的客户开始流入,你有可能产生一些收入或吸引外部的融资。 现在是开始思考整洁、长期维护的代码的正确时机。这是在介绍中的示例上我们的客户所处的场景。鉴于你受众有可能增长,你需要开始考虑性能、稳定性和可用性。 你的工程团队也将扩大规模。这将迫使你实施编码标准、文档标准和一系列其他流程和实践。你开始需要完美的代码了。

可以看到,在每个阶段示例中我们的代码目标都有所不同,对于“完美”的定义,自然也有所不同。

并不存在完美的代码

我们都知道,开发软件涉及到多个不同的阶段。所以其实很难断定,到底有什么所谓完美的代码,完全适用于所有的开发阶段。

客户的需求,五花八门。可写代码时用的库其实却更甚。有些库是我们自己写的,也有一些是第三方的。有时候看一个项目的代码,还确实可能会发现它混杂了不同人的代码;比如说,自己的团队先写点代码给项目开个头,之后交给客户的团队写一会。最后呢,却又由我们自己来收尾。

由此可见,每个项目的代码风格,以及用到的技术、实现方法等都可以很不一样。你的项目,或许在发布时堪称完美。但是,经过上面所说的这种把项目丢来丢去的过程之后,我猜最后肯定经常会有人嫌其他团队写的代码有问题,那这种项目显然就不完美了啊。

现实就是如此,想达成某件事,不可能会有什么完美的方法。至于编程,虽然我这么说可能会感觉有点奇怪,但它压根就不是一门严谨的学科。你想编程实现某个需求,往往会有很多方法。到最后你或许会发现,这些方法,其实都行得通。

处理不完美的代码

不完美并不等于劣质。去网上搜一下 Pareto principleSufficient Design 就知道为啥了。

让一个人去写项目,如果这人发现项目里用了一堆过时了的代码,或者是用了 MVP 架构,又或者是项目写了很久很久,那这人肯定很想把整个项目给重写了,这样才感觉整个项目尽在掌握,如鱼得水,而不是看着就头疼。不过呢,重写大项目一直都不是啥好事,整天流于形式写框架,却白白浪费了写业务逻辑的时间,这很没必要的。有些事情可以不用管它,别太纠结。但是呢,如果你重写的代码符合我下面说的这些标准,那你重写也不是啥坏事的说:(节录自这篇文章

  1. 重写的代码真能实现需求么?

  2. 代码真的正确无误,而且效率还不错么?

  3. 遇到并处理错误时可以做到不崩溃,或者安全地结束执行么?

  4. 试起来容易不?

  5. 如果要改动代码,能尽量又简单又安全不?

这最后一条标准大概是最难做到的,毕竟要做到模块分离和抽象化,还要写测试代码来确保符合预期效果;而且代码若还有改动,只要修改相应的一部分测试代码就行,这样才可以更加轻松地调试和改动代码。

从零开始写项目时,一定要花点心思。无论是写新项目,还是重写旧项目,都要规范地写代码。比如说,代码风格要清爽、要有可读性、要遵从一定的代码规范。但是但是,一定要小心,不要过早优化你写的代码。写的时候只管想下一个要实现的需求是什么,而不是边写边纠结怎么缓存资源、怎么弄个复杂的数据结构来储存数据之类的事情,还有别动不动就担心执行效率。你代码越简单,其他那些要接手你代码的人就越感谢你。刚开始写项目时,这些很重要;以后给客户写项目时也一样重要,毕竟说不定哪天客户就要你把项目交给他们来继续写呢。

把这些带入实践中

每个星期我都会和有好想法的人交谈,但他们希望用很小的预算来实现他们的想法。当他们问我实现他们的想法需要花费多少时,我的回答是在 10k 至几十亿之间,所以基本上是把这个问题抛回给对方,问他们希望花费多少。根据他们的回答,我会试图清楚地向他们解释他们可以期待什么:概念证明、MVP(Minimum Viable Product – 最简化可实行产品)或拥有长期可用代码的产品。

作为程序员,我们应该尝试不那么完美主义,并且牢记保持这一目标。提供价值比我们的代码整洁更重要。只有当你为了长期目标,去追求完美才有意义。

作为首席执行官(CEO),你应该问自己,预算是否适合你的产品所在阶段,并且要牢记预算所提供的限制和机会。有时需要重构。

我相信,只要我们在内部或为客户开始一个新项目时,我们都需要询问代码的完美程度。所以我们可以根据短期和长期的期望来交付产品。