Mysql调优

优化的维度

  • 表结构与索引
  • SQL语句
  • Mysql参数
  • 硬件和系统配置

数据库的水平和垂直扩展能力,提前规则未来读写和数据量的增长,分库分表方案。按UID维度散列,分为4个库,每个库32张表,保证单表数据量控制在千万级别。

字段选择合适的最小空间的数据类型,如年龄使用tinyint

多字段表拆分为多个表,必要时使用中间表关联。

范式与反范式、反范式即保持一定的冗余。

分析慢查询日志,寻找需要优化的语句

利用分析工具,如explain、profile。

查询语句避免使用select *,要指定需要的列。可以避免查询列字段的元信息。

1.使用内置缓存
大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次,这些查询结果会被缓存。
对于像 NOW() 、RAND()、CURDATE()等SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以需要用一个变量来代替MySQL的函数,从而开启缓存。

2.使用 EXPLAIN 执行SELECT查询
使用 EXPLAIN 关键字可以打印出MySQL是如何处理SQL语句的,可以帮助分析查询语句或者表结构的性能瓶颈。
EXPLAIN 的查询结果还会告诉索引主键是被如何利用的、数据表是如何被搜索和排序的等等。
在SELECT语句(推荐挑选那个最复杂的,有多表联接的),把关键字EXPLAIN加到前面,可以使用phpmyadmin来执行。然后会看到一张表格。

3. 追加 LIMIT 1
查询表有时已经知道结果只会有一条结果,此时加上 LIMIT 1 可以增加性能。MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。
但是在用explain查询分析,显示limit 1加与不加,性能相同。是因为可能是使用了缓存吗?

4. 搜索字段建索引
索引并不一定是给主键或是唯一的字段,如果表中有某个字段经常用来做搜索,那么请为其建立索引。
有些搜索是不能使用正常的索引的。例如需要在一篇大的文章中搜索一个词时,如: “WHERE post_content LIKE ‘%apple%’”,索引是没有意义的。这可能需要使用MySQL全文索引或是自己做一个索引(比如搜索关键词、标签)。
查询语句中使用use index可以指定索引。

5.使用 ENUM 代替 VARCHAR
ENUM 类型是非常快和紧凑的,实际上其保存的是 TINYINT,但其外表上显示为字符串。用这个字段类型来做选项列表变得相当的完美。比如“性别”,“国家”,“民族”,“状态”或“部门”字段,这些字段的取值是有限而且固定的,那么应该使用 ENUM 而不是 VARCHAR。

6.PROCEDURE ANALYSE() 结构建设
PROCEDURE ANALYSE() 会让 MySQL 帮助分析字段和其实际的数据,给出一些有用的建议。只有表中有实际的数据这些建议才会变得有用,因为要做一些大的决定是需要有数据作为基础的。
例如,如果你创建了一个 INT 字段作为你的主键,然而并没有太多的数据,那么,PROCEDURE ANALYSE()会建议你把这个字段的类型改成 MEDIUMINT 。或是你使用了一个 VARCHAR 字段,因为数据不多,你可能会得到一个让你把它改成 ENUM 的建议。但这些建议可能因为数据不够多而决策做得就不够准确。
在phpmyadmin里,你可以在查看表结构时,点击 “Propose table structure” 或“规则表结构”来查看这些建议,建议结果呈现在Optimal_fieldtype字段中。

7.字段尽量设置默认值
除非有特殊原因使用 NULL 值,否则应该总是让字段保持 NOT NULL。NULL需要额外的空间,并且在进行比较的时候,程序会更复杂,难以查询优化。
下面摘自MySQL自己的文档:
“NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.”

MySQL 性能优化…经验分享
https://www.cnblogs.com/pengyunjing/p/6591660.html

mysql中不要用blob等大字段

在开发规范中,我们一般都是要求字段不要设置成blob这种大字段。原因是查询的时候效率问题。另外一个就是在插入量大的时候,生成的binlog太多,容易导致一些问题。线上就遇到了,1k多的插入,binlog狂写,磁盘干满了。

1.多表先约束再连接
由全表连接,变为先约束为小表再连接。

2.索引
随着集合的增长,需要针对查询条件中数据量大的排序字段做索引。
如果没有对索引的键排序,数据库需要将所有数据提取到内存并排序。因此在做无索引排序时,如果数据量过大以致无法在内存中进行排序,数据库将会报错。初步可以对出现查询慢的表建立索引、组合索引。
在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在索引组合的最左边。
mysql 组合索引“最左前缀”规则,简单的理解就是只从最左面的开始组合。查询条件中没有使用组合索引最左列列的语句,不会使用到索引。
MySQL只对<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE才会使用索引。在以通配符%和_开头作查询时,MySQL不会使用索引。
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。
对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。
explain sql1 可以分析sql语句的执行情况,进而对sql语句进行优化。
组合索引-最左优先原则(最左原则)

MySQL有一个复合索引:INDEX(`a`, `b`, `c`),以下查询能用上索引。
A、select * from users where a = 1 and b = 2
B、select * from users where b = 2 and a = 1
C、select * from users where a = 2 and c = 1
第二项会由Mysql查询器优化。

https://blog.csdn.net/u014590757/article/details/79590561

3.数据库拆分:分库、分表、分区

单表的数据量限制,当单表数据量到一定条数之后数据库性能会显著下降。分库减少单台数据库的压力。有些数据表适合通过主键进行散列分库分表。

数据库的垂直切分和水平切分
数据切分可以是物理上的,对数据通过一系列的切分规则将数据分布到不同的DB服务器上,通过路由规则访问特定的数据库,降低单台机器的负载压力。

数据切分也可以是数据库内的,对数据通过一系列的切分规则,将数据分布到一个数据库的不同表中,比如将article表分为article_001,article_002等子表,若干个子表水平拼合有组成了逻辑上一个完整的article表。比如article表中有5000w条数据,在这个表中增加(insert)一条新的数据,insert后数据库会针对这张表重新建立索引,5000w行数据建立索引的系统开销还是不容忽视的。如果将这个表分成100 个子表,每个子表里边就只有50万行数据,向一张只有50w行数据的table中insert数据后建立索引的时间就会呈数量级的下降,提高了DB的并发量。分表的好处还有诸如写操作的锁操作等。

分库降低了单点机器的负载;分表提高了数据操作的效率,尤其是Write操作的效率。

垂直水平切分

分库分表后id主键处理的方法:
A、单库生成自增 id
B、设置数据库 sequence 或者表自增字段步长
C、UUID
D、最佳方法是snowflake 算法

snowflake 算法是 twitter 开源的分布式id生成算法,采用 Scala 语言实现,是把一个 64 位的 long 型的 id,1 个 bit 是不用的,用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。
1 bit 不用,是因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。 41 bit:表示的是时间戳,单位是毫秒。41 bit 可以表示的数字多达 2^41 – 1,也就是可以标识 2^41 – 1 个毫秒值,换算成年就是表示69年的时间。 10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10台机器上哪,也就是1024台机器。但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2^5个机房(32个机房),每个机房里可以代表 2^5 个机器(32台机器)。 12 bit:这个是用来记录同一个毫秒内产生的不同 id,12 bit 可以代表的最大正整数是 2^12 – 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id。

4.读写分离
读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。

例如用户信息,每个用户都有系统内部的一个userid,与userid对应的还有用户名。那么如果分库分表的时候单纯通过userid进行散列分库,那么根据登录名来获取用户的信息,就无法知道该用户处于哪个数据库中。或许可以维护一个username—-userid的映射关系,先查询到userid再根据userid的分库分表规则到对应库的对应表来获取用户的记录信息。但这个映射关系的条数本身也是个瓶颈,原则上是没有减少单表内数据的条数,算是一个单点。并且也要维护这个映射关系和用户信息的一致性。最大一个原因,其实用户信息实际是一个读大于写的库,所以对用户信息拆分还是有一定局限性的。

对于这类读大于写并且数据量增加不明显的数据库,推荐采用读写分离+缓存的模式,用户注册、修改信息、记录登录时间、登录IP、修改密码,这些是写操作,但是这些操作次数都是很小的,所以整个数据库的写压力是很小的。读写分离首要解决的就是将经常变化的数据的拆分,比如:用户登录时间、记录用户登录IP,这类信息可以单独独立出来,记录在持久化类的缓存中(可靠性要求并不高,登陆时间、IP丢了就丢了,下次来了就又来了)。

主库负责写数据、读数据。读库仅负责读数据。每次有写库操作,同步更新cache,每次读取先读cache再读DB。写库就一个,读库可以有多个,例如Oracle采用dataguard来负责主库和多个读库的数据同步。

参考
https://www.cnblogs.com/panxuejun/p/5887118.html
https://www.jianshu.com/p/65e8a31fbab8
https://www.cnblogs.com/alvin_xp/p/4162249.html

MySQL数据库则可以利用MySQL主从配置,实现读写分离,既起到备份作用又可以减轻数据库的读写的压力。读写分离的基本原理是让“主”数据库处理事务性增、改、删操作,“从”数据库处理查询操作。利用数据库复制把事务性操作导致的变更从主数据库同步到从数据库。写库有一个,读库可以有多个,采用日志同步的方式实现主库和多个读库的数据同步。

Mysql 的 Replication 是一个异步的复制过程,从一个MySQL节点(称为Master)复制到另一个MySQL节点(称Slave)。在 Master 与 Slave 之间的实现整个复制过程主要由三个线程来完成,其中两个线程(SQL 线程和 I/O 线程)在 Slave 端,另外一个线程(I/O 线程)在 Master 端。
要实现 MySQL 的 Replication ,首先要打开 Master 端的 Binary Log,整个复制过程实际上就是 Slave 从 Master 端获取日志,然后在自己身上完全顺序的执行日志中所记录的各种操作。
MySQL的Replication原理有以下结论和推论:
* 每个从仅可以设置一个主。
* 主在执行sql之后,记录二进制log文件(bin-log)。
* 从连接主,并从主获取bin-log,存于本地relay-log,并从上次记住的位置起执行sql,一旦遇到错误则停止同步。
* 主从间的数据库不是实时同步。
* 如果主从的网络断开,从会在网络正常后,批量同步。
* 如果对从进行修改数据,那么很可能从在执行主的bin-log时出现错误而停止同步,这个是很危险的操作。所以一般情况下,非常小心的修改从上的数据。
* 一个衍生的配置是双主,互为主从配置,只要双方的修改不冲突,可以工作良好。
* 如果需要多主的话,可以用环形配置,这样任意一个节点的修改都可以同步到所有节点。

4.数据缓存
大型网站为了应对大量的并发访问,除了在业务端实现分布式负载均衡,还要减少数据库的连接,例如采用优秀的代码框架进行代码优化,采用优秀的数据缓存技术如memcached,如果资金丰厚还可以架设服务器群来分担主数据库的压力。

建表原则

1、定长与变长分离
如id int占4个字节,char(4)占4个字节,即每一个单元值占的字节都是固定的。而varchar,text,blob 变长字段适合单放另一张表,在其中使用主键与核心表关联。
2、常用字段和不常用字段向分离
结合具体的业务分析字段的查询场景,把查询频度低的字段单拆出来。
3、把1对多等需要关联统计的数值添加冗余字段。
例如论坛中每个栏目显示今日发帖数目存储下来,不需要每次查询都再统计。

sql语句优化

计算机存储空间分为:寄存器、高速缓存、内存、交换区(外部存储虚拟化)、硬盘以及其他的外部存储。从寄存器开始到硬盘读写速度是从快到慢依次递减。对于批量记录如果一条一条的连接数据库进行操作,耗费的时间非常恐怖。调优的环节之一,则是减少与数据库交互的次数,将多条查询、插入、更新合并为交互一次,也就是批操作。多次处理的操作交给主程序在内存中进行处理,内存中处理的速度要快上很多。

批量插入语句
将插入语句进行拼接,多条插入语句拼接成一条插入语句,与数据库交互一次执行一次。

//如果字符串太长,则需要配置下MYSQL,在mysql 命令行中运行 :
set global max_allowed_packet=2*1024*1024*10
//批量插入语句
insert into 表名(字段名1,字段名2)values(值a1,值b1), (值a2,值b2)

批量更新语句

UPDATE categories SET 
display_order = CASE id 
        WHEN 1 THEN 3 
        WHEN 2 THEN 4 
        WHEN 3 THEN 5 
END, 
title = CASE id 
        WHEN 1 THEN 'New Title 1'
        WHEN 2 THEN 'New Title 2'
        WHEN 3 THEN 'New Title 3'
END
WHERE id IN (1,2,3);

批量查询和删除

select * from tableName where id in (1,2,3,4) order by id
delete from tableName where id in(1,2,3,4,5,6)

sql语句取3000条记录

#Oracle :
select * from table where rownum between 1 and 3000
select * from table rownum<3001
#MySql:
select * from table limit 1 to 3000

插入记录隐藏列名时注意表结构中字段顺序

例如以下插入,是按字段顺序对应,不是按字段名对应。
insert into b_subuser_deleted_table SELECT * from b_subuser_table WHERE id=1

LoopBack

ES6的出品为JS成为企业级语言扫清障碍,与之配套的需要一个真正的企业级框架。Express是一个精巧的微内核不足以支撑起一个大项目。
LoopBack 是建立在 Express 基础上的企业级 Node.js 框架,这个框架有以下特点:

  • 编写少量代码就能创建动态端到端的 REST API
  • 支持主流的数据源,例如 Mongodb、MySQL、SOAP 等和 REST API 的数据。
  • 一致化的模型关系和对 API 访问的权限控制
  • 可使用内置的用于移动应用场景下的地理定位、文件服务以及消息推送
  • 提供 Android、iOS 和 JavaScript 的 客户端SDK

LoopBack 作为一个面向企业级的 Web 框架,提供了更丰富的功能,方便添加模型,权限控制,连接数据源等操作。例如可以通过修改配置增加模型,并指定模型的数据源。它默认提供了一些基础模型,例如 User 这个模型包含了注册登录等逻辑,可以非常方便的继承这些内建模型,实现个性化的定制。它还提供了 Hook 编程的机制,提供了可视化的调试页面,自动生成对应的前端 SDK 。这些功能在开发大型 Web 服务的时候,帮助我们更容易查看和管理项目。

StrongLoop 是 IBM 的一家子公司,StrongLoop 的 LoopBack 框架能够轻松地连接数据并将数据公开为 REST 服务。它能够在图形(或命令行)界面中以可视方式创建数据模型,可以使用这些模型自动生成 REST API – 从而为 REST 服务层生成 CRUD 操作,无需编写任何代码。这很重要?因为它使 API 开发变得更容易,并显著减少了从概念到实现所需的时间。

在 LoopBack 框架中,是通过所谓的“数据连接器”来获取和持久化数据。

Cloudant 是一个 DBaaS 服务,是一个衍生的 Apache CouchDB,所以将 LoopBack 连接器连接到 Cloudant 很简单,只需在应用程序中使用现有的社区驱动的 CouchDB LoopBack Connector。

StrongLoop 是生成 LoopBack 框架的工具程序,有的认为Strongloop 相当于Loopback + Angularjs。

相关文章

node 框架 LoopBack 教程
https://cnodejs.org/topic/57e5b2859c495dce044f397c
StrongLoop学习笔记(一)
https://blog.csdn.net/sanpo/article/details/45082089
使用Loopback3.0构建应用程序(一)
https://www.jianshu.com/p/763b1a847d2c
IBM developerWorks,可以其中搜索Strongloop
https://www.ibm.com/developerworks/cn/
Loopback 2.0
http://loopback.io/doc/en/lb2/Remote-methods.html#overview
Loopback 3.8.0
http://apidocs.strongloop.com/loopback/#model-remotemethod
http://apidocs.strongloop.com/loopback/#persistedmodel-create
https://loopback.io/doc/en/lb3/Remote-methods.html
find方法中Filter的where中的语法格式:
http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries

使用loopback做邮箱验证,LoopBack用户模型提供了注册新用户并确认其电子邮件地址的方法。您还可以使用loopback-component-passport模块将登录信息与Facebook,Google和其他第三方提供商集成。
https://www.jianshu.com/p/21b4d84fb7af

生成lb-services.js

Loopback生成lb-services.js,使用Loopback CLI工具的lb-ng命令

$ lb-ng server.js lbservices/lb-services.js

代码示例

//自定义方法
Unit.remoteMethod(
        'deleteRecursive',
        {
            description:'',
            accessType: 'WRITE',
            accepts: {arg: 'id', type: 'string',description: '机组 id', http: {source: 'query'}},
            returns: [{arg: 'status', type: 'string'}],
            http: [
                {verb: 'delete', path: '/deleteRecursive'}
            ]
        }
    );
#source的选择有 body,path,query
//实现包装find的功能
    Project.findProjects=function(filter,callback) {
        console.log("findProjects",filter);
        var json=JSON.parse(filter);
        Project.find(json,function (err,data) {
            callback(null,data);
        });
    }
    Project.remoteMethod('findProjects', {
            description:'查询工程列表',
            accessType: 'READ',
            accepts: {arg: 'filter', type: 'string',description: '查询条件', http: {source: 'query'}},
            returns: {type: 'array', root: true},
            http: [
                {verb: 'get', path: '/findProjects'}
            ]
        }
    );

ES6

ECMAScript 6.0(简称 ES6)是 JavaScript 语言的下一代标准,在 2015 年 6 月正式发布。它的目标是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ECMAScript 和 JavaScript 的关系,大体上前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 Jscript 和 ActionScript)。在日常场合这两个词是可以互换的。

从软件工程角度来看,ES6就相当于当年的Java5,是历史性的发展,从此可以用js做大型项目了。事实上各大主流浏览器现在已经支持大部分新特性了,后端的Node.js更是可以直接使用ES6的绝大多数语法。

Babel

Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在现有环境执行。这意味着可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。

// 转码前,箭头函数
input.map(item => item + 1);

// 转码后,普通函数
input.map(function (item) {
  return item + 1;
});

变量声明方式 let/const

ES6中全面使用let/const替换var。使用let声明一个值会被改变的变量,使用const声明一个值不会被改变的变量,即常量。

与var不同,新的变量声明方式带来了一些不一样的特性,其中最重要的两个特性就是提供了块级作用域与不再具备变量提升。let或const变量一定要在声明后面使用,否则报错is not defined。在实际使用中,尽量避免使用变量提升的特性带来的负面影响。只有在面试题中,才会对变量提升不停的滥用。
var声明的变量仍旧是函数级作用域。

var和let在for循环中的应用:
下面的代码如果使用var,最后输出的是10。

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10
上面代码中最后一行输出的结果是10。变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。

如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。
var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。

既然使用let使的每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
结果:
// abc
// abc
// abc

箭头函数

当函数直接被return时,可以省略函数体的括号。

// es5
var fn = function(a, b) {
    return a + b;
}
// es6 6
const fn = (a, b) => a + b;

箭头函数可以替换函数表达式,但是不能替换函数声明。
箭头函数中没有this。在ES6中,默认采用严格模式,this不会自动指向window对象了,this就只能是undefined。

模板字符串

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。在其中使用 ${} 来包裹一个变量或者一个表达式。

const a = 20;
const b = 30;
const str = `${a}+${b}=${a+b}`;

解构赋值

获取对象属性值都可以使用解析结构来减少代码量。本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果解构不成功,变量的值就等于undefined。

const props = {
    className: 'tiger-button',
    loading: false,
    clicked: true
}
//取对象中的值到变量
const { loading, clicked } = props;

//数组以序列号一一对应,这是一个有序的对应关系。
const arr = [1, 2, 3];
const [a, b, c] = arr;
let [a, b, c] = [1, 2, 3];

如果变量名与属性名不一致,必须使用完整写法。也就是说,对象的解构赋值的内部机制是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。

遍历 Map 结构:

任何部署了 Iterator 接口的对象,都可以用for…of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。

const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

// 只获取键名
for (let [key] of map) {
  // ...
}
// 只获取键值
for (let [,value] of map) {
  // ...
}

默认值

变量默认值

const { loading = false, clicked } = props;

函数参数默认值
在实际开发中给参数添加适当的默认值,可以让我们对函数的参数类型有一个直观的认知。

function add(x = 20, y = 30) {
    return x + y;
}

ES6 内部使用严格相等运算符(===),判断只有当参数严格等于undefined,对会取值为默认值。

展开运算符

在ES6中用…来表示展开运算符。展开运算符可以将数组方法或者对象进行展开。

const arr1 = [1, 2, 3];
const arr2 = [...arr1, 10, 20, 30];//arr2 结果变成了[1, 2, 3, 10, 20, 30];
const obj1 = {
  a: 1,
  b: 2, 
  c: 3
}
const obj2 = {
  ...obj1,
  d: 4,
  e: 5,
  f: 6
}// 结果类似于 const obj2 = Object.assign({}, obj1, {d: 4})

展开运算符还可以声明为变量并接收数据

const props = {
  size: 1,
  src: 'xxxx',
  mode: 'si'
}
const { size, ...others } = props;

展开运算符还用在函数的参数中表示函数的不定参。只有放在最后才能作为函数的不定参,否则会报错。

const add = (a, b, ...more) => {
    return more.reduce((m, n) => m + n) + a + b
}
console.log(add(1, 23, 1, 2, 3, 4, 5)) // 39

对象字面量写法

ES6针对对象字面量做了许多简化语法的处理。
当属性名与值的变量名相同时,在对象结构中可以只写属性名。对象中的方法也可以使用字面量写法。

const name = 'Jane';
const age = 20
// es5
var person = {
  name: name,
  age: age,
  getName: function getName() {
    return this.name;
  }
};
// es6
const person = {
  name,
  age,
  getName() { // 只要不使用箭头函数,this就还是我们熟悉的this
    return this.name
  }
}

还可以使用变量作为对象的键。

const field = 'rock';
const heat = '50%';
const music = {
  [field]: heat
}
console.log(music); // Object {rock: "50%"}

class

ES6为创建对象提供了新的语法糖,就是Class语法。

// ES5
// 构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// 原型方法
Person.prototype.getName = function() {
  return this.name
}

// ES6
class Person {
  constructor(name, age) {  // 构造函数
    this.name = name;
    this.age = age;
  }
  getName() {  // 原型方法
    return this.name
  }
}

babel会将ES6的写法编译成利用Object.defineProperty实现的方式,这个方法的具体用处大家可以在《JavaScript高级编程3》中学习了解,包括get,set,等都有详细的说明

除此之外,我们还需要特别注意在实际使用中的几种写法方式的不同,在下面的例子注释中,我说明了他们分别对应的ES5中的含义。

class Person {
constructor(name, age) { // 构造函数
this.name = name;
this.age = age;
}

getName() { // 这种写法表示将方法添加到原型中
return this.name
}

static a = 20; // 等同于 Person.a = 20

c = 20; // 表示在构造函数中添加属性 在构造函数中等同于 this.c = 20

// 箭头函数的写法表示在构造函数中添加方法,在构造函数中等同于this.getAge = function() {}
getAge = () => this.age

}
箭头函数需要注意的仍然是this的指向问题,因为箭头函数this指向不能被改变的特性,因此在react组件中常常利用这个特性来在不同的组件进行传值会更加方便。

继承 extends

只需要一个extends关键字,可以实现继承了,不用像ES5那样去担心构造函数继承和原型继承。

//es6
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  getName() {
    return this.name
  }
}

// Student类继承Person类
class Student extends Person {
  constructor(name, age, gender, classes) {
    super(name, age);
    this.gender = gender;
    this.classes = classes;
  }
  getGender() {
    return this.gender;
  }
}

还需要关注super方法。在继承的构造函数中必须调用一次super方法,它表示构造函数的继承,与ES5中利用call/apply继承构造函数是一样的功能。

// 构造函数中
// es6 ,super还可以直接调用父级的原型方法,如super.getName。
super(name, age);

// es5
Person.call(this);

Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
http://www.jianshu.com/p/fe5f173276bd

模块 Modules

1.引入模块

// src/index.js
import test1 from './test'

上面示例中,import表示引入一个模块,test 暂时理解为引入模块的名字。
引入这个动作执行时,test.js中的代码也执行了。如果在test.js中没有对外暴露任何接口,那么test1为一个空对象。
2.对外提供接口
ES6 modules使用export关键字对外提供接口。
export default对包暴露了一个对象。import 后跟的对象就默认是export default暴露的对象。

export default {
    num,
    arr,
    obj,
    foo
}

还可以通过export暴露更多方法与属性。

export const bar = () => {}
export const zcar = 12345;

可以一次引入获得模块暴露的所有接口。

import * as test1 from './test';

上面示例中的 * 表示所有,这是比较常用的通配符,as表示别名,* as test1的意思是将test.js暴露的所有接口组成的对象,命名为test1。
在引入中使用解析结构,下面示例中test1仍然表示为export default暴露的对象,而 { bar, zcar }则表示从整个返回对象中去取得对应的接口。

import test1, { bar, zcar } from './test';

Symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

let s = Symbol();

typeof s
// "symbol"

上面代码中,变量s就是一个独一无二的值。typeof运算符的结果,表明变量s是 Symbol 数据类型,而不是字符串之类的其他类型。
注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。

Set和Map

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
  console.log(i);
}
// 2 3 5 4

WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。

首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,是一种更完善的 Hash 结构实现。
Map 遍历有一个forEach方法,与数组的forEach方法类似。

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
});

WeakMap结构与Map结构类似,也是用于生成键值对的集合。

Generator 函数

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

前端基础进阶系列
https://www.jianshu.com/p/cd3fee40ef59
ECMAScript 6 入门
http://es6.ruanyifeng.com/

JS基础

字符串和整数混合运算

‘1’+1 结果 ’11’
‘1’-1 结果 0
1+1+’1′ 结果 ’21’
‘1’+1+1 结果 ‘111’

类型转换

1.转Number
数字:转换后还是数字
字符串:如果可以被解析为数值,则为相应的数值,如果不能,则是 NaN,如果是空字符串那就是0
布尔值:true为1,false为0
undefined:NaN
null:0
object:先执行valueOf,看是否能转换,如果不可以再执行toString,看是否可以转换,如果不可以报错
2.转String
数字:转换成对应的字符串
字符串:还是对应的字符串
布尔值:true为’true’,false为’false’
undefined:undefined
null:null
object:先执行toString,看是否能转换,如果不可以再执行valueOf,看是否可以转换,如果不可以报错
3.转Boolean
下面这几个是false,其他都是true
NaN
null
undefined
0
“”
false

4.隐式类型转换
四则运算、判断语句中在隐式类型转换。

逻辑判断

=== 会先判断类型是否相同,再判断内容是否相同
== 只判断内容是否相同
1==’1′ 结果true

逻辑判断

给字符串添加属性并赋值,再打印此属性,值为undefined。这说明并不能给字符串增加属性。

JS基础知识(覆盖JS基础面试题)
https://yq.aliyun.com/articles/608880

Node应用

蓝牙

有经典蓝牙(classic)和低功耗蓝牙(Bluetooth Low Energy BLE).

BLE关键术语:每个蓝牙4.0的设备都是通过服务和特征来展示自己的。
service : 一个设备必然包含一个服务以上(至少包含一个服务,有可能包含更多的服务)。
characteristic : 包含在服务下, 描述该服务下特定的功能的, 一般拥有多个characteristic, 而且characteristic有类型, 读(read), 写(write), 通知(notify)等。
UUID(Universally Unique Identifier) : 设备的每一个service有相应的UUID, 同时每一个characteristic也有相应的UUID。

  • Service:服务,服务是一个集合的特点。例如有一个名为“心率监视器”的服务,其中包括诸如“心率测量”的特征。可以在bluetooth.org上找到现有基于GATT的个人资料和服务的列表。
  • GATT:通用属性简档,用于通过BLE链路发送和接收称为“属性”的短数据块的一般规范。所有当前的低能量应用配置文件都基于GATT。
    蓝牙SIG为低能量设备定义了许多配置文件 。 配置文件是设备在特定应用程序中的工作方式的规范。 请注意,设备可以实现多个配置文件。 例如,设备可以包含心率监视器和电池水平检测器。
  • ATT:属性协议,GATT建立在ATT之上,也称为GATT/ATT。ATT经过优化,可在BLE设备上运行,为此它使用尽可能少的字节。每个属性由通用唯一标识符(UUID)唯一标识,UUID是用于唯一标识信息的字符串ID的标准化128位格式。由ATT传送的属性被格式化为特征和服务。
  • 特征:特征含描述特征值的单个值和0-n个描述符。即每个特征值包含一个value 和多个 descriptor,在某个特征值的value中包含数据信息。
  • Descriptor:描述符,描述符是描述特征值的定义属性。

每个service包含一个或多个characteristic(特征值)

蓝牙常用开发步骤
https://www.jianshu.com/p/2809ead73d2a

蓝牙协议4.0、4.1、4.2的比较

SIG在2010年发布了4.0的specification,2013年发布了4.1的specification,2014年又发布了4.2的specification。从4.0版本起,革命性的加入了BLE协议部分,同时将2.1+EDR和3.0+HS全都包含在内。4.1和4.2在4.0的基础上做了改进,主要包括连接速度,传输效率等等,可以看出是向着适用于物联网的方向做的改进。
首先,相比4.0,4.1和4.2最明显的特点是增加了一个volume 7:Wireless Coexistence volume。主要介绍手机的无线共存测试。移动通讯采用4G-LTE标准后会占用2.4GHz频段,4.1和4.2的specification对此做出了测试。
4.1和4.2在4.0的基础上添加了IPv6和6LowPAN,搭载蓝牙芯片的设备可以取得在互联网上的唯一标记,与其他的联网设备进行通讯。
4.1和4.2提高了4.0的传输速率,4.0的协议栈规定了每包承载有效数据不大于20字节,4.2把这个数值扩大了10倍,最终将BLE的传输速率提高了2.5倍,不过据说要等硬件升级才能感受到这一低功耗高速率的传输方式。
4.1和4.2实现了主从一体,比如智能手环作为主和防丢器连接的时候,智能手环同时也可以作为从和智能手机相连。
4.1和4.2再有一个很重要的方面是改进了蓝牙连接的安全性。
物联网构成无线连接一定是组网灵活、低功耗、带宽适用和安全的,蓝牙在向着这样的方向发展。与wifi和zigbee相比,蓝牙无疑是最有竞争力成为物联网协议的无线连接规范。

NODE REPL

REPL(Read Eval Print Loop:交互式解释器)

Node 自带了交互式解释器,可以很好的调试 Javascript 代码,可以执行以下任务:

读取 – 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。
执行 – 执行输入的数据结构
打印 – 输出结果
循环 – 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。

http://www.runoob.com/nodejs/nodejs-repl.html

安卓手机向Mac通过蓝牙发送文件

mac系统偏好设置>共享 勾蓝牙共享,选接受并存储。

小米手环
http://www.cnblogs.com/wobeinianqing/p/5883135.html
http://www.jianshu.com/p/1dfba61e2a9d

其它
https://developer.android.com/guide/topics/connectivity/bluetooth-le.html
https://developer.android.com/guide/topics/connectivity/bluetooth.html?hl=zh-cn#TheBasics
https://developer.android.com/guide/topics/connectivity/bluetooth-le.html?hl=zh-cn
https://github.com/akexorcist/Android-BluetoothSPPLibrary
https://github.com/Jasonchenlijian/FastBle/issues
http://blog.csdn.net/dingjikerbo/article/details/49254573
https://github.com/dingjikerbo/BluetoothKit
http://blog.csdn.net/woshasanguo/article/details/41082395
https://my.oschina.net/tingzi/blog/215008
http://www.jianshu.com/p/2268cfedc051
http://www.cnblogs.com/cxk1995/p/5693979.html
http://blog.csdn.net/fangqiangqi/article/details/51510115
https://books.google.ca/books?id=vw9JDwAAQBAJ&pg=PT164&lpg=PT164&dq=%E8%93%9D%E7%89%99%E4%B8%BB%E4%BB%8E%E6%A8%A1%E5%BC%8F+%E4%B8%80%E5%AF%B9%E5%A4%9A&source=bl&ots=qIFm7_6O47&sig=gd0F710X2JwmVK5905XUh9xI0mk&hl=zh-CN&sa=X&redir_esc=y#v=onepage&q=%E8%93%9D%E7%89%99%E4%B8%BB%E4%BB%8E%E6%A8%A1%E5%BC%8F%20%E4%B8%80%E5%AF%B9%E5%A4%9A&f=false

蓝牙通讯过程

经典蓝牙和BLE的流程是不同的。
经典模式: 1.扫描设备. 2.建立设备连接. 3.建立socket连接. 4.发送和接收数据. 5.通讯完毕关闭连接清理缓存.
BLE 比经典模式复杂很多。
作为一个中心角色要实现完整的通讯,一般要经过这样几个步骤:
建立中心角色 -> 扫描外设(discover)—> 连接外设(connect) —> 扫描外设中的服务和特征(discover) —> 与外设做数据交互(explore and interact) —> 断开连接(disconnect)。

一个设备里的服务和特征往往比较多,大部分情况下我们只会在发现服务和特征的回调里去匹配关心其中几个。
数据的读分为两种,一种是直接读(reading directly),另外一种是订阅(subscribe)。具体用哪一种要看具体的应用场景以及特征本身的属性。特征有个properties字段(characteristic.properties),它是一个整型值,可能以下一些定义:

enum {  
     CBCharacteristicPropertyBroadcast = 0x01,  
     CBCharacteristicPropertyRead = 0x02,  
     CBCharacteristicPropertyWriteWithoutResponse = 0x04,  
     CBCharacteristicPropertyWrite = 0x08,  
     CBCharacteristicPropertyNotify = 0x10,  
     CBCharacteristicPropertyIndicate = 0x20,  
     CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,  
     CBCharacteristicPropertyExtendedProperties = 0x80,  
     };

如果要交互的特征的properties的值是0x10,表示只能用订阅的方式来接收数据。

RSSI

为什么无线信号(RSSI)是负值
答:其实归根到底为什么接收的无线信号是负值,这样子是不是容易理解多了。因为无线信号多为mW级别,所以对它进行了极化,转化为dBm而已,不表示信号是负的。1mW就是0dBm,小于1mW就是负数的dBm数。

弄清信号强度的定义就行了:
RSSI:接收信号强度 Received Signal Strength Indicator
Rss=10logP, 只需将接受到的信号功率P代入就是接收信号强度(灵敏度)。
RSSI的单位是dbm,假设发射功率取最大值为1mw,那么RSSI的值为0,也就是说距离蓝牙最近时在理想状态下所获取的RSSI的值为0,但在实际中基本不会存在这个理想状态,因此RSSI的值基本都为负数。而在蓝牙中,当距离很近时,所收到的RSSI的信号值大约在-50dbm。

UUID

每个蓝牙4.0的设备都是通过服务和特征来展示自己。一个设备必然包含一个或多个服务,每个服务下面又包含若干个特征。特征是与外界交互的最小单位。例如一台蓝牙4.0设备,用特征A来描述自己的出厂信息,用特征B来与收发数据。
服务和特征都是用UUID来唯一标识的,国际蓝牙组织为一些很典型的设备(比如测量心跳和血压的设备)规定了标准的service UUID(特征的UUID比较多,这里就不列举了),例如:

#define     BLE_UUID_ALERT_NOTIFICATION_SERVICE   0x1811  
 #define     BLE_UUID_BATTERY_SERVICE   0x180F  
 #define     BLE_UUID_BLOOD_PRESSURE_SERVICE   0x1810  
 #define     BLE_UUID_CURRENT_TIME_SERVICE   0x1805  
 #define     BLE_UUID_CYCLING_SPEED_AND_CADENCE   0x1816  
 #define     BLE_UUID_DEVICE_INFORMATION_SERVICE   0x180A  
 #define     BLE_UUID_GLUCOSE_SERVICE   0x1808  
 #define     BLE_UUID_HEALTH_THERMOMETER_SERVICE   0x1809  
 #define     BLE_UUID_HEART_RATE_SERVICE   0x180D  
 #define     BLE_UUID_HUMAN_INTERFACE_DEVICE_SERVICE   0x1812  
 #define     BLE_UUID_IMMEDIATE_ALERT_SERVICE   0x1802  
 #define     BLE_UUID_LINK_LOSS_SERVICE   0x1803  
 #define     BLE_UUID_NEXT_DST_CHANGE_SERVICE   0x1807  
 #define     BLE_UUID_PHONE_ALERT_STATUS_SERVICE   0x180E  
 #define     BLE_UUID_REFERENCE_TIME_UPDATE_SERVICE   0x1806  
 #define     BLE_UUID_RUNNING_SPEED_AND_CADENCE   0x1814  
 #define     BLE_UUID_SCAN_PARAMETERS_SERVICE   0x1813  
 #define     BLE_UUID_TX_POWER_SERVICE   0x1804  
 #define     BLE_UUID_CGM_SERVICE   0x181A

但还有很多设备并不在这个标准列表里,例如体脂秤血压计。蓝牙设备硬件厂商通常都会提供他们的设备里面各个服务(service)和特征(characteristics)的功能。比如哪些是用来交互(读写),哪些可获取模块信息(只读)等。

集成测试

持续集成

持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

Jenkins是基于Java开发的一种持续集成工具,用于监控持续重复的工作,功能包括:
1、持续的软件版本发布/测试项目。
2、监控外部调用执行的工作。

Jenkins使用极低的代价能部署一个软件,自动化进行项目构建,项目测试,静态扫描,上传构建,远程备份,发布。很多时候只需要点击立即构建,就进行了项目的部署操作。
它还提供权限管理,可以让不同的人员负责各自的项目建构。多项目构建,可以一次性完成多个项目的构建。

官方地址:https://jenkins.io/
docker:https://hub.docker.com/r/jenkins/jenkins/

Jenkins的原理,如果在新建的时候指定了Maven项目和代码的Git地址,Jenkins首先会通过Git将代码clone到本地,然后执行在Build中指定的pom.xml文件和指定的命令。

https://blog.csdn.net/bntX2jSQfEHy7/article/details/78743664

Jenkins 启动一直显示 Jenkins正在启动,请稍后…,深度修改配置中的访问的网站地址。
https://blog.csdn.net/heatdeath/article/details/79733611

对于自动安装失败的插件,可以在错误中找到hpi文件,通过手动下载然后在高级页面中上传插件的方式安装。

配置Publish Over SSH

在“系统设置”中配置“Publish Over SSH”。ssh的配置可使用密码,此时Passphrase中要填写应用服务器root等账号的密码。ssh的配置也可以使用密钥,使jenkins服务器可免密码直接登陆应用服务器。在配置密钥之前要先配置好jenkins服务器和应用服务器的密钥认证。在jenkins服务器上使用ssh-keygen -t rsa命令生成密钥对。在docker部署的jenkins/jenkins:lts中成功生成信息如下:

Your identification has been saved in /var/jenkins_home/.ssh/id_rsa.
Your public key has been saved in /var/jenkins_home/.ssh/id_rsa.pub.

将jenkins服务器的公钥id_rsa.pub中的内容复制到应用服务器/root/.ssh/authorized_keys文件中,在应用服务器上重启ssh服务service sshd restart。
解释两个不大明确的配置项:
Passphrase:生成rsa密钥时的密码。
Path to key:jenkins服务器上私钥文件的路径。
配置完成后可点击“Test Configuration”测试到目标主机的连接,出现”success“则成功连接。
https://www.cnblogs.com/hanxiaohui/p/8796025.html
https://www.jianshu.com/p/0d805ed204e6

压力测试

JMeter在Mac下的安装
https://www.jianshu.com/p/bce9077d883c

全文检索

全文检索是将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。
这部分从非结构化数据中提取出然后重新组织的信息,我们称之索引。这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。

Lucene是apache下的一个开源的全文检索引擎工具包(类库)。它的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。Lucene提供了完整的查询引擎和索引引擎,部分文本分析引擎。

更新依据的字段只能是字符串类型,StringField。

Document doc=new Document();
doc.add(new StringField("id","abcd",Field.Store.YES));
indexWriter.updateDocument(new Term("id","abcd"),doc);

Field.Store.YES或者NO是用来决定分词前的原内容是否存储。

lucene一个field多个条件查询
https://blog.csdn.net/guiyecheng/article/details/56484049

用lucene实现在一个(或者多个)字段中查找多个关键字
https://www.cnblogs.com/xudong-bupt/archive/2013/05/08/3065297.html

Elastic Search 概述(一)
https://blog.csdn.net/yezonggang/article/details/80064394

Elasticsearch的不足
http://dockone.io/article/3655

Docker Compose

docker-compose

docker-compose是Docker官方编排工具,用来管理和运行Docker容器,负责实现对Docker容器集群的快速编排。使用一个 Dockerfile 模板文件,可以很方便的定义一个单独的应用容器,而Compose则可以方便的实现需要多个容器相互配合来完成某项任务的情况。
Docker Compose将所管理的容器分为三层,工程(project),服务(service)以及容器(contaienr)。Docker Compose运行的目录下的所有文件(docker-compose.yml、extends文件或环境变量文件等)组成一个工程,默认名称为当前目录名。一个工程当中可包含多个服务,一个服务当中可包括多个容器实例。Docker Compose并没有解决负载均衡的问题,需要借助其他工具实现服务发现及负载均衡。
Compose 通过一个单独的 docker-compose.yml 模板文件(YAML格式)来定义一组相关联的应用容器为一个项目 (project)。
Compose 中有两个重要的概念:
服务(service):一个应用的容器,实际上可以包括若干运行相同镜像的容器 实例。
项目(project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。
Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

Docker Compose是一个部署多个容器的简单必要的工具,使用一条简单的命令部署多个容器,简化了多容器的部署过程,使多容器移植变得简单可控。从其Roadmap可以看出,Docker Compose的目标是做一个生产环境可用的工具,包括服务回滚,多环境支持(dev/test/staging/prod),支持在线服务部署升级,防止服务中断并且监控服务使其始终运行在正确的状态。Roadmap中的另一个目标是更好的与Docker Swarm集成,目前版本存在的主要问题是无法保证处于多个主机的容器间正常通信因为目前不支持跨主机间容器通信。另一个问题是在Docker Compose中定义构建的镜像只存在在一台Docker Swarm主机上,无法做到多主机共享,因此目前需要手动构建镜像并上传到一个镜像仓库使多个Docker Swarm主机可以访问并下载镜像。相信随着Docker Compose的完善,其必将取代docker run成为开发者启动docker容器的首选。

https://blog.csdn.net/chengqiuming/article/details/80957521

Docker服务器端用Nginx作为反向代理并添加SSL证书(阿里云免费DV证书)
https://www.cnblogs.com/wushangjue/p/7810339.html
http://www.mamicode.com/info-detail-1703824.html
http://blog.51cto.com/11962757/2086760
http://www.lixiangfeng.com/blog/article/content/8595503
https://www.jianshu.com/p/2217cfed29d7
可能冒号后需要加个空格,不然在执行 docker-compose up -d后报错ERROR: yaml.scanner.ScannerError: mapping values are not allowed here。
创建 docker-compose.yml 文件,然后在当前目录下执行命令 docker-compose up -d 来启动各个服务。

version: "2"
services:
  mysql1:
    image: mysql:5.7.9
    container_name: mysql1
    restart: always
    ports:
      - 3306:3306
    volumes:
      #- /alidata/docker_work/mysql-cogrowth/conf.d:/etc/mysql/conf.d
      - /data/docker_work/cogrowth/mysql/data:/var/lib/mysql
    environment:
      #- TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=123456
  php-fpm1:
    image: bitnami/php-fpm
    container_name: php-fpm1
    restart: always
    links:
      - mysql1:mysql-alias
    volumes:
      - /alidata/docker_work/www:/host/www
  nginx2:
    image: nginx:1.15.2
    container_name: nginx2
    restart: always
    ports:
      - 82:80
    volumes:
      - /alidata/docker_work/www/:/usr/share/nginx/html
  nginx1:
    image: nginx
    container_name: nginx1
    restart: always
    ports:
      - 81:80
      - 444:443
    volumes:
      - /alidata/docker_work/nginx/conf:/etc/nginx/conf.d
      - /alidata/docker_work:/host
    links:
      - php-fpm1:php-fpm-alias
  phpMyAdmin1:
    image: phpmyadmin/phpmyadmin:4.8
    container_name: phpMyAdmin1
    restart: always
    ports:
      - 8085:80
    environment:
      - PMA_HOSTS=172.19.0.10,172.19.0.9
      - PMA_PORTS=3306,3306
      - PMA_VERBOSES=master,slave
  hksmart-inout:
    image: node:6.15
    container_name: hksmart-inout
    restart: always
    command:
      - /bin/sh
      - -c
      - |
        cd /hksmart/hksmart-inout
        npm install
        node inout_server.js
    volumes:
      - /alidata/docker_work/node/hksmart/hksmart_inout:/hksmart/hksmart-inout

version: "3"
services:
  wtsr-mysql:
    image: mysql:5.7.9
    container_name: wtsr-mysql
    restart: always
    ports:
      - 3306:3306
    volumes:
      #- /alidata/docker_work/mysql-cogrowth/conf.d:/etc/mysql/conf.d
      - ${MYSQL_DIR}/data:/var/lib/mysql
    environment:
      #- TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
  wtsr-redis:
    image: redis:5.0.5
    container_name: wtsr-redis
    restart: always
    ports:
      - 6379:6379
    volumes:
      - ${REDIS_DIR}/conf:/usr/local/etc/redis
      #- ${REDIS_DIR}/data/appendonly.aof:/data/appendonly.aof
      - ${REDIS_DIR}/data:/data
    #privileged: true
    command:
      redis-server /usr/local/etc/redis/redis.conf --requirepass ${REDIS_PASSWORD}
  wtsr-vsftpd:
    image: fauria/vsftpd
    restart: always
    container_name: wtsr-vsftpd
    ports:
      #- 20:20
      - "2123:21"
      #- "2124-2223:2124-2223"
      #- "2124-2134:21100-21110"
      - "2124-2134:2124-2134"
    environment:
      - FTP_USER=zhongxunlocator
      - FTP_PASS=Wtsr.0x7B.0x3123
      #- PASV_ADDRESS=127.0.0.1
      - PASV_ADDRESS=39.106.35.86
      - PASV_MIN_PORT=2124
      - PASV_MAX_PORT=2134
    volumes:
      - /data/docker_work/vsftpd/data:/home/vsftpd

.env文件

# .env文件内容
# redis
REDIS_DIR=/data/docker_work/redis
REDIS_PASSWORD=123456

# mysql
MYSQL_DIR=/data/docker_work/mysql
MYSQL_ROOT_PASSWORD=123456
 

注意在此环境下的Php连接mysql,需要使用mysqli_connect。
phpmyadmin的PMA_PORTS中使用的是docker容器内部的端口。

docker-machine

VirtualBox和Docker的区别

VirtualBox和Docker的区别,一句话总结就是VirtualBox虚拟化硬件,Docker虚拟化操作系统。
VirtualBox,是创建硬件虚拟化的软件。通常情况下,一个操作系统运行在硬件上,其中硬件和操作系统之间的通信是通过移动数据到内存地址,然后发出指令来通知可使用该数据的硬件(或者是数据在被读取时)。 在VirtualBox(或其它虚拟机)设置的环境中,那些内存地址实际上是虚拟机软件自身的内存区域,并且那些指令是由虚拟机而不是直接由底层的CPU解释的。实际结果是,你在VirtualBox中运行一个操作系统,对于这个操作系统来说,VirtualBox程序看起来像一台完整计算机,硬件以及所有配件都有。实际上它不知道自己是在另一个程序中运行的。

Docker,则是不进行硬件的虚拟化。相反,它的作用是创建一个文件系统,使其看起来像一个普通的Linux文件系统,并且运行应用程序在一个所有文件和资源都在文件系统内的锁定环境中。事实上,该应用程序的容器并不模仿任何硬件,应用程序仍然在硬件上运行,它只是隔离了应用程序并允许您可以运行该应用程序跟特定的并且完全不是主机操作系统的软件和第三方库合作。这意味着,在启动或停止Docker应用程序时几乎没有开销,因为它们不需要预先分配的内存和磁盘空间等等。因此Docker容器很容易设置或者拆除。此外,容器在假装需要系统中各种硬件组件上运行软件的时候并不浪费任何开销 – 它是直接使用硬件的。

yum安装VirtualBox
配置yum源:
vim /etc/yum.repos.d/virtualbox.repo
输入

[virtualbox]
name=Oracle Linux / RHEL / CentOS-$releasever / $basearch - VirtualBox
baseurl=http://download.virtualbox.org/virtualbox/rpm/el/$releasever/$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://www.virtualbox.org/download/oracle_vbox.asc

然后用yum直接安装:
yum install VirtualBox-5.2

CentOS7下minikube start后VirtualBox执行报错
https://blog.csdn.net/jiekou0376/article/details/80454808

[root@openshift ~]# docker-machine create –driver virtualbox virtualbox1
Running pre-create checks…
Error with pre-create check: “This computer doesn’t have VT-X/AMD-v enabled. Enabling it in the BIOS is mandatory”

Docker Swarm

Docker Swarm 和 Docker Compose 都是 Docker 官方容器编排项目,不同的是,Docker Compose 是一个在单个服务器或主机上创建多个容器的工具,而 Docker Swarm 则可以在多个服务器或主机上创建容器集群服务,对于微服务的部署 Docker Swarm 更加适合。

【Docker】 Swarm简单介绍
https://www.cnblogs.com/franknihao/p/8490416.html

Swarm就是一组运行docker的机器,并联合成为一个集群。当启动Swarm集群之后,docker命令会通过Swarm manager执行在整个集群之上。Swarm集群的机器可以是物理机,也可以是虚拟机,当加入Swarm集群之后,被称为nodes。
Swarm managers有两种不同的方式运行container:第一种emptiest node,尽量使用少的机器部署容器;第二种global,确保每一台机器上都会运行一个容器的实例。可以在docker-compose.yml中指定运行的模式。
Swarm managers是集群的核心控制节点,它负责执行命令,授权新机器加入集群。Worker节点只负责提供资源。
docker可以很方便的切换为 swarm mode,切换的命令是docker swarm init,一旦切换为 swarm mode后,当前的机器角色就变为Swarm managers。

docker-compose 命令

#一次重启多个服务
docker-compose restart service1 service2

docker-compose常用命令
docker-compose up [options] [SERVICE…]
该命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。
链接的服务都将会被自动启动,除非已经处于运行状态。
docker-compose down
此命令将会停止 up 命令所启动的容器,并移除网络
docker-compose restart [options] [SERVICE…]
重启项目中的服务。

安装 Docker Compose

https://docs.docker.com/compose/install/