etcd的安装与命令行使用

背景

etcd是CoreOS团队于2013年6月发起的一个开源项目,它是一个优秀的高可用分布式键值对存储数据库。etcd内部采用了Raft协议作为一致性算法,且使用Go实现。

这篇文章主要记录etcd在Linux上的单机版安装与简单的命令行使用。主要是供初学者参考。

etcd的安装

etcd的github路径地址为:https://github.com/etcd-io/etcd

我们可以直接从github中将安装压缩包下载到测试机上,使用命令

wget https://github.com/coreos/etcd/releases/download/v3.3.25/etcd-v3.3.25-linux-amd64.tar.gz

其中3.3.25为etcd的版本号,目前etcd主要有v2和v3两个大版本。

我们也可以将版本号改为更早的版本,可以在地址https://github.com/etcd-io/etcd/releases查看etcd的已发布版本信息。

下载之后,我们使用命令:tar xzvf etcd-v3.3.25-linux-amd64.tar.gz进行解压,

然后使用命令mv etcd-v3.3.25-linux-amd64 /opt/etcd把目录移到opt目录下,/opt目录下一般用于存放下载的第三方软件。

到此,etcd就安装完毕了。

etcd服务端启动

如果我们只是在测试环境学习etcd,那么可以直接进入etcd的安装目录/opt/etc,用./etcd启动etcd。

这时etcd服务端会打印出一系列信息:
在这里插入图片描述

包括了etcd、go、git等版本信息,以及raft协议选举出来的leader节点信息等等。

etcd客户端的使用

这时,我们可以再次开启一个终端,然后配置一下环境变量:

/etc/profile文件底部添加一行:export PATH=${PATH}:/opt/etcd

然后运行命令source /etc/profile生效环境变量。

这时可以直接使用etcd提供的命令行操作工具etcdctl了,我们输入命令etcdctl -h可以看到etcdctl简单的使用方法。
在这里插入图片描述
我们演示几个命令行使用:

etcdctl mkdir v  // 创建目录v
etcdctl mkdir v/sub_v  // 在目录v下创建目录sub_v
etcdctl mk v/sub_v/key1 value1  // 在目录v/sub_v下创建键key1以及对应的值value1
etcdctl ls -r  // 查看所有的目录以及键

结果:

/v
/v/sub_v
/v/sub_v/key1

使用etcdctl get查看某个键值:

etcdctl get v/sub_v/key1

结果:

value1

etcdctl还有许多的命令以及可选项,可以参考help中的介绍练习使用。

当然,etcd还提供了REST API的方式让我们可以通过HTTP来访问etcd的存储,HTTP提供的接口操作基本与命令行操作差不多,我们可以用etcdctl来熟悉相关操作。

Kafka的基本知识梳理

1 背景

近期在学习《深入理解Kafka核心设计与实践原理》这本书。接下来希望将每次学习的收获与心得记录成文,也希望可以帮助到同样初入门的人。

2 思维导图

先分享一下我在学习Kafka的基本知识之后整理的思维导图。
在这里插入图片描述

3 Kakfa介绍

3.1 三大角色

Kafka主要用于扮演三种角色,消息系统、存储系统、流式处理平台。

3.1.1 消息系统

Kafka被用于最多的是消息系统,Kafka和传统的消息系统都具备了系统解耦、冗余存储、流量削峰、缓冲、异步通信、扩展性、可恢复性等功能。同时,Kafka还提供了大多数消息系统没有的消息顺序保障性以及回溯消费的功能。这也是Kafka相比于其他消息系统的优势所在。

3.1.2 存储系统

Kafka会把消息持久化存储至磁盘中,相比于其他基于内存存储的系统而言,有效地降低了数据丢失的风险。

3.1.3 流式处理平台

Kafka不仅为每个流行的流式处理框架提供了可靠的数据来源,它本身还提供了一个完整的流式处理类库,比如窗口、连接、变换和聚合等各类操作。

3.2 体统架构

在这里插入图片描述
Kafka的体系架构主要有4个组成部分:producer、consumer、kafka broker、zookeeper

3.2.1 producer

即是生产者角色,用于产生数据,并将数据写入到kafka中。

3.2.2 consumer

消费者角色,用于从kafka中拉取数据并且消费。

3.2.3 kafka broker

broker即是一个kafka的实例。当一个服务器中仅有一个kafka broker时,我们就可以说,该服务器就是一个kafka实例了。

3.2.4 zookeeper

分布式协作框架,用于管理集群的元数据。

3.3 两个重要概念

kafka中有两个比较核心的概念:Topic(主题)和Partition(分区)。
Topic是一个逻辑上的概念,表示一个业务单元。也就是说,一个Topic下的数据可以归为一类。
Partition是一个实际的概念,表示一个Topic下的分区。一个Topic下可以有多个分区,而一个分区只属于一个Topic。
同一个Topic下的不同分区会具体存储不同的消息队列,每个分区都可以理解成一个可以追加的日志文件,因此,每个分区都会有一个offset用于标识数据的偏移量。
Kafka可以保证分区里数据的顺序性。
如图:
在这里插入图片描述
比如该Topic有4个Partition,那么每个Partition都有自己的写入数据的内容、顺序、以及偏移量。各个分区之间数据互不干涉。
另外,同一个Topic下的不同Partition是可以分布在不同的broker中的。这样非常有利于Topic的扩展,可以以此来提供比单个broker更强大的功能。

在生产环境中修改MySQL库表结构

背景

如果我们需要在生产环境中修改MySQL数据库中某个库表的结构。那么,需要考虑哪些要点,才能确保不会出问题呢?

碰到的问题

这里先描述一下我在生产环境MySQL数据库中修改库表结构时遇到的问题。
在开发过程中,我发现MySQL中某个库表需要添加一个字段,比如库表:

需要给Sname后面添加一个字段:Sheight。那么就使用命令:
alter table practice.Student add column Sheight int(4) not null default 0 comment '"身高"
输入完这个命令后,我就去做别的事情去了。
直到过了一小会,有人反馈说线上的系统有些界面没有数据。这个时候我才意识到,是这个操作出了问题。导致了线上bug。

问题的解决

我立马查看这个操作,发现还没有执行结束。首先kill了这个执行任务,于是线上系统恢复了正常。

导致该问题的原因

当时,我用命令:show processlist查询,看到这个语句的State显示的是:
Waiting for table metadata lock。我们知道,这个状态是说,该表在等待获取表的metadata lock,也就是MDL。
也就是说,由于前面有MDL读锁没有被释放,因此我这个命令也就获取不到MDL写锁。导致后面再过来的各种操作都无法被执行,都在等待MDL读锁。
这里解释下metadata的概念,metadata lock(MDL)也就是元数据锁,它是一种表级锁。
各种对该表的操作,比如增删改查,都会占有MDL的读锁。当修改表结构时,会占用MDL的写锁。
读锁和读锁之间互不冲突,而读锁与写锁、写锁与写锁之间互相冲突。
简单说,就是对一个表增删改查同时进行,MDL锁不会冲突,我们可以用多线程同时执行这些操作,只会导致行锁,而不会锁整个表。
但是,如果在对表增删改查的同时,要对表结构进行修改,那么就会造成锁等待的状态。
如果有一个长事务在对该表进行操作,那么在修改表结构时,就会有状态:Waiting for table metadata lock,也就是锁等待。如果这个时候,另外又有查询操作过来,那么,后面这个操作就也要进行Waiting for table metadata lock,也就是锁等待了。当然,对该表的查询操作就会全部阻塞。
我当时的情况就是这样,有一个事务操作了该表,但是可能由于大意没有关掉该事务,该事务长时间存在。而我同时又进行表结构的更改,于是导致了这次事故。

如何做

首先,我们要了解一下有没有什么事务对该表进行了操作,却长期没有提交。因为,只有对该表操作的事务最终提交了,MDL锁才会被释放。
换句话说,如果某个事务对该表进行了操作,比如读操作,但是最终没有做提交,那么,该事务依然会占用MDL锁的。
查看事务可以用命令:SELECT * FROM information_schema.INNODB_TRX
做完这一步之后,基本上可以避免出现Waiting for table metadata lock的情况了,但还有一点需要注意,就是线上会不会对该表进行频繁的操作,
有些热表可能一直处于有人在查询的状态,这种时候怎么做呢?
我们可以在变更表结构的命令中添加一个超时时间,如果这个命令在该时间段内一直无法执行,那么会自动超时的,起码可以保证不会长时间的影响用户的操作。
该命令为:alter table practice.Student wait 100 add column Sheight int(4) not null default 0 comment "身高"

总结

在生产环境中变更MySQL数据库中库表结构是一件比较有风险的事情,所以一定要三思而后行,避免引起任何可能的线上事故。

Warning: (1265, u”Data truncated for column ‘XXX’ at row 1″)问题解决

背景

在使用MySQL数据库时,有时会遇到Warning: (1265, u”Data truncated for column ‘XXX’ at row 1″)这样的报错信息。
具体可以考虑哪些问题呢?这里简单记录一下问题原因与解决方法。

报错原因

  1. 写入该字段的数据长度大于该字段定义的最大长度,比如定义了字段user_name VARCHAR(10),这个字段定义了最长写入10位字符,但是,如果你写入的数据超过了10位字符,那么就会出现该警告信息。
  2. 传入的数据类型有误。比如定义了字段cost DECIMAL(10,2),这个字段小数点后有两位,但是,如果你写入的数据为超过了两位小数,比如10.1122,则会出现该警告信息。
  3. 插入了非法字符。

解决方法

具体问题具体分析,可以对库表的字段类型做调整,或者检查插入的数据是否不符合预期。

MySQL中常用于检索的字符串字段如何创建索引?

背景

我们在业务场景中经常会碰到通过某个字符串查询对应记录的情况。比如常见的邮箱登录、或是手机号登录。
如果不给它创建索引,则MySQL就会进行全局扫描,非常耗时。那么,类似邮箱地址这样的字符串,我们应该如何给它创建索引呢?这里简单介绍几种方法。

几种方法

比如我们有一张表user_info用于存储用户登录信息,包括自增主键ID、邮箱地址、对应密码。
这里我们经常会用到的SQL操作是select email, password from user_info where email="XXXXXXX"

直接创建索引

第一种方法当然是直接使用email来创建索引。这种方法虽然简单粗暴且会占用大量的存储空间,但是有一个好处,就是回表操作只需一次就行。

使用前缀索引

所谓前缀索引,比如前面的示例,就是将email字段的前缀截取下来,然后作为索引来使用。

添加索引的SQL语句为:alter table user_info add index index_eamil(email(9)),这里就添加了一个email字段前9位字符为前缀索引。

假如email字段的前9位字符就可以唯一的标示一个email地址,比如前9位是不同的数字,后面都是@qq.com。那么这样就做到了完全的区分度。在选择某email时,比如select email, password from user_info where email="123456789@qq.com",MySQL会拿前9位去索引查找,查找得到则会回表到主键树中获取到对应的记录。

使用前缀索引,一定要权衡好前缀选取的长度,因为前缀的长度就意味着区分度。如果前缀索引可以完全区分,那当然是最好的,这样只需要回表一次就可以拿到数据了。区分度越高越好,区分度越高,也就使得回表操作次数越少。

只要前缀长度取的合适,就可以既有满意的区分度,又有满意的存储空间占用。

不过使用前缀索引也有一个劣势,在于使用前缀索引会影响到覆盖索引的使用。

倒序存储

倒序存储即是将对应字段倒序过来再取前缀创建索引。针对于前缀区分度低,但后缀区分度高时使用。

比如我们需要存储一个user_info用户信息表,存储的是自增ID、身份证号、密码。身份证号就属于前缀区分度低(前缀都是一些通用代号),但后缀区分度高的字符串。

在存储以及查询时也需要将身份证号倒过来使用。比如,在查询时,使用语句:select user_id, password from user_info where user_id=reverse(XXXXXX)

hash存储

将对应字段hash之后作为索引存储,hash之后冲突的概率较低,也可大大降低回表的次数。只是由于需要存储字段hash之后的数据,会增加额外存储空间的占用。

总结

前面简单介绍了一下几种不同的对字符串创建索引的方法。
总结如下:

索引方式优势劣势适用场景
直接创建索引回表次数少索引空间占用大,且每次字符串匹配时间较长数据量不大或字符串不长的情况
前缀索引索引空间占用小,且每次字符串匹配时间较短可能增加回表次数选取出的前缀可以有较高的区分度
倒序存储无需添加新字段只能支持单条查询,不支持范围查询前缀区分度低,后缀区分度高
hash存储冲突概率低,得出的索引区分度较高只能支持单条查询,不支持范围查询,且需要增加新的存储字段无需范围查询且普适性较高

MySQL中查询表的总行数该用什么命令?

背景

我们经常会使用到一个SQL语句,就是查询某张表的总行数。常常使用的查询命令有几种,比如:select count(*) from tselect count(id) from t(id为主键)select count(1) from tselect count(某普通字段) from t以及show table status的rows字段。然而却不知道用哪种查询方式最合适。接下来简单介绍一下我们在MySQL中查询表的总行数时该用什么命令。

不同存储引擎,查询效率不同

在分析几种查询方式之间的不同之前,我们先来看下以select count(*) from t为例,不同存储引擎之间查询行数方式的不同。常见的存储引擎有MyISAM和InnoDB。

InnoDB:每次我们需要查询某张表的总行数时,都会遍历整张表计算出结果。

MyISAM:每张表的总行数会记录在磁盘中存储,在我们需要查询某张表的总行数时,命令会直接取出对应字段信息。(不过需要注意,当命令查询的不是表总行数,而是where查询某些行时,也依然需要像InnoDB一样遍历整张表计算出结果)

很显然,在表行数较多时,MyISAM的方式是要比InnoDB更快的。

那么,为什么InnoDB不采用与MyISAM相同的处理方式呢?

本质原因在于InnoDB支持事务,我们可以看个具体的例子就明白了。

假设我们的表t中包含了100行数据,且有如下三个会话在做SQL操作。(会话2与会话3是事务,会话1是普通语句)
在这里插入图片描述
先不管中间是怎样的过程,我们看最后三个会话输出的查询结果。

如果是MyISAM存储引擎,它是不支持事务的,查询出的结果三个会话就都是102。

而如果是InnoDB存储引擎,它是支持事务的,可重复读是默认的隔离级别,所以查询出的结果三个会话各不相同,会话1是101,会话2是100,会话3是102。

那么,为什么InnoDB不采用与MyISAM相同的处理方式呢?这就很明显了。因为在相同时刻,InnoDB由于支持事务,所以可能会读出的结果不同,

因此,InnoDB只能在需要查询表总行数时去遍历计算了。

MyISAM由于不存在这样的问题,当然就可以把总行数存储下来,每次需要时直接读取就行了。

几个常用查询语句的效率不同

说回到开始说的几个常用查询语句,我们到底该用哪个查询语句呢?我们以InnoDB引擎为例进行说明。

首先,我们要了解一下count()函数的作用,count()函数会判断所查询字段是否为NULL,会将非NULL的行累加起来,得到最终的行数值。

接下来分别分析这几个常用查询语句:

select count(字段) from t(字段为非NOT NULL且非索引):服务器会将字段返回给服务器,服务器判断每一个字段内容是否为NULL,如果不为NULL,则会计数加1,累加出来的结果即为语句结果返回。

select count(id) from t(id为主键):InnoDB会将每一行id返回给服务器,服务器判断主键不可能为NULL,累加出来的结果即为语句结果返回。

select count(1) from t:InnoDB会遍历整张表,不过不会取字段,也不会取值。而是服务器放一个数字1进去,并将累加出来的值作为结果返回。

select count(*) from t:优化器针对该语句做了优化,InnoDB不需要取字段给服务器,而是服务器直接计算行总数并返回。

由于 count(*)count(1) 都无需从InnoDB中取值,而是直接计算行总数,则两者运行时间差不多。

count(id) 会取出id值进行判断,虽然主键值都不会为NULL,但运行时间会比前两者长一些。

count(字段)count(id) 判断方式一样,只是其不是索引,所以遍历起来较count(id) 会慢。

总结来说,四种常用查询方式的速度顺序为:count(字段)

注意:select count(id) from t语句,虽然id为主键不可能是NULL值,理论上来说,MySQL是无需一个一个再进行数值判断的。然而目前MySQL并没有对此做优化,应该是在查询总行数时,MySQL就建议使用select count() from t,因此专门对count() 做了优化。其余的查询就都按照需要什么数据则取什么数据这样的原则统一处理。

show table status命令

show table status命令中的Rows字段也会显示出表的总行数,但是该命令是通过采样统计来计算出的表总行数,因此会有很大的误差。作为参考值还可以,如果要使用精确值,就不推荐了。

总结

简单总结一下,我们在查询某张表总行数时,如果需要知道大体行数,则可使用show table status的rows字段查看。如果需要知道精确行数,则需使用select count(*) from t命令来查看。

在Mysql中执行一条SQL,会经历什么?

背景

我们都经常使用Mysql作为数据库来存储与查询较常用的数据。当我们输入一行如SELECT * FROM table_name WHERE id=26这样的语句之后,Mysql如果正确执行的情况下,会输出你想要的信息。

那么,在你输入这行语句之后,一直到它显示出你想要的信息,这中间Mysql都经历了什么呢?这篇文章会简单聊一下这个事情。

Mysql基本架构图

我们先看下Mysql的一个较整体的架构图。

接下来我会以具体的SQL语句为例,详细的叙述从你在客户端输入了这个语句之后,到它返回你想要的信息,这中间具体经历了什么。
enter image description here

客户端

所谓客户端,即是我们登录与操作Mysql所使用的终端。我们都是在客户端对Mysql进行操作的,无论是输入连接数据库的信息,还是输入查询某个表的SQL,或者是收到Mysql返回给我们的查询信息,这些都是在客户端完成的。

连接器

用户信息验证

我们在一个客户端跟前,想要使用Mysql数据库,那么第一步就是要先连接上你要使用的数据库。

我们都知道,我们要输入命令mysql -h$ip -P$port -u$username -p

之后客户端会要求我们输入密码。再之后,如果我们输入的信息都没有问题了,我们就进入Mysql的操作界面了。

如果我们输入的信息有问题,就会收到客户端返回的报错信息。比如我们将密码输入错误了,这时就会收到”Access denied for user”这样的报错信息。

那么,这中间连接器具体做了什么呢?

首先,连接器会拿着我们输入的IP和端口,去做最经典的TCP握手,握手如果都失败了,那就自然没有后续了,直接返回相应的报错信息。

如果握手成功了,此时则会去验证我们输入的用户名和密码,验证失败则同样会返回相应的报错信息。

用户权限获取

如果用户名密码也没有问题,接下来连接器则会取出权限表读取该用户相应的权限数据。用户跟着所做的所有操作,都基于此时读取到的用户权限。

权限表共有4个:user, db, tables_priv, columns_priv

当用户通过权限验证,进行权限分配时,按照user, db, tables_priv, columns_priv的顺序进行分配。即先检查用户的全局权限表user,如果user中对应的权限为Y,则此用户对所有数据库的权限都为Y,将不再检查剩余3个表;如果为N,则到db表中检查此用户对应的具体数据库,并得到db中为Y的权限;如果db中为N,则检查tables_priv中此数据库对应的具体表,取得表中的权限Y;如果为N,则到columns_priv中检查具体的列。

这也就意味着,当我们修改了某个用户的用户权限,只有到下一次该用户登录(创建新的连接)时,才会影响到该用户。

连接与等待超时

我们可以通过show processlist来查看当前所有的用户连接及其行为。
enter image description here
Command中的字段显示该用户目前的状态,此时这个用户是查询状态。

但若Command显示的状态是Sleep,那么说明该用户当前在等待状态。若等待超过了一段时间,则连接器会自动断开。

该超时时间由wait_timeout变量控制,可以通过show global variables like 'wait_timeout'来查看。
enter image description here
mysql默认为28800秒,即8小时。

长连接与短连接

所谓长连接,即用户的持续操作使用的都是同一个连接,连接在一段时间内长时间建立。

所谓短连接,即用户每做几次操作则断开,再下次操作时再进行连接。

长连接的优点是,在持续操作时,可以节省很多建立连接所需要消耗的时间。但是长连接所要存储的临时数据都在连接对象中,长时间积累,会导致系统内存溢出,具体表现
为Mysql异常重启。

短连接的优缺点与长连接相反,虽然不用担心内存溢出的问题,但短连接在持续操作的情况下多次连接,连接消耗很多时间,整体操作效率会很低。

缓存器

连接器连接完成的下一步就是缓存器的缓存查询,如果我们需要对一张静态表(不常更新)经常做查询操作,那么可能会用到缓存器。

缓存器中使用的是key-value的存储形式,key值存储的是查询语句,value值存储的是对应结果。

要注意的是,只要该表做了一次更新操作,那么该表对应的缓存就会全部被清理。因此使用场景并不多。

所以当前缓存器的使用较少。我们可以通过query_cache_type来查看缓存器是否开启。
enter image description here
现在一般都是默认关闭的状态。且Mysql从8.0版本会开始彻底弃用该功能。

分析器

假设我们不使用缓存器,或者通过缓存器没有命中SQL语句。

那么连接器做连接操作之后,接下来我们就输入了一个查询语句,比如:SELECT host FROM mysql.user LIMIT 1

而分析器做的事情就是对你输入的语句做 “词法分析”“语法分析”

所谓 “词法分析” ,就是判断每一个你输入的词,比如分析器首先会判断出你输入的第一个词是“SELECT”,第二个词你输入了“host”,等等。

“语法分析” 则是跟在 “词法分析” 之后,就是依据你输入的这些词来判断你输入的是否符合语法规则。

假如符合语法规则,则会顺利进行下去并返回相应信息。
enter image description here
假如不符合语法规则,则分析器会返回报错信息给客户端。
enter image description here
具体出错的地方,一般都是跟在use near之后,我们看这里就能知道语法错误出在了哪一块。

优化器

在分析器工作结束后,如果语法有问题,那么就会直接返回报错信息,且不继续向下运行。

若语法正确,那么,则会到优化器部分的工作。优化器顾名思义,就是对该语句的执行做优化。

比如,在一个语句查询某个表时,该表可能有多个索引,此时使用哪个索引会使语句的执行效率最高?这就是优化器要做的事情。

再比如,执行语句select * from t1 join t2 on t1.ID=1 and t2.ID=2

该语句执行时,是先从t1表中找到ID=1的行关联到t2表之后,再从t2表中查找ID=2的行。

还是先从t2表中找到ID=2的行关联到t1表之后,再从t1表中查找ID=1的行。

两种执行顺序可能就导致执行效率的不同,怎样选择执行顺序会提高执行效率,这也是优化器要做的事情。

执行器

在上述步骤完成之后,就轮到执行器去执行具体的语句了。

例如语句:select * from mysql.tables_priv

在执行器做具体的语句执行之前,会对该表的操作权限进行验证,验证失败则返回权限错误的报错。如下:
enter image description here
而实际上,权限验证不仅仅在执行器这部分会做,在分析器之后,也就是知道了该语句要“干什么”之后,也会先做一次权限验证。叫做precheck。

而precheck是无法对运行时涉及到的表进行权限验证的,比如使用了触发器的情况。因此在执行器这里也要做一次执行时的权限验证。

如果验证成功,那么则会使用该表对应的存储引擎的接口,继续执行语句。
最后将成功执行的结果返回给客户端。

总结

简单来说,一条SQL语句在Mysql中执行,一共会经历四步(算上连接Mysql),分别是连接、分析、优化与执行。每一步都会精确执行,如果发现有问题就会返回给客户端相应的报错。只有每一步都正确执行,最终才会在客户端得到你想要查询或操作的结果。

mysql报错:2003, “Can’t connect to MySQL server on ‘ ‘ [Errno 99] Cannot assign requested address 解决方法

背景

使用Python脚本高并发的连接Mysql数据库时遇到了此报错。
但是场景不仅限于Python,其它程序在高并发连接Mysql数据库时也可能会遇到此问题。

原因定位

出现该报错信息是因为在高并发连接Mysql数据库时,由于同时连接Mysql数据库的链接过多,且每次连接都是非常短的时间,导致有许多的TIME_WAIT,以致用光了服务器端口,所以新的连接就没有端口可用,导致了该报错产生。

我们可以看下当前端口占用的个数:
netstat -an|wc -l
在这里插入图片描述
也可以看下当前可用端口的范围:
cat /proc/sys/net/ipv4/ip_local_port_range
在这里插入图片描述

解决方法

1

知道了原因,最好的解决方法是减少这种高并发的连接,由于是每次连接的处理时间较短时,不然很容易出现这种问题。

2

修改可用端口的范围
echo 1024 65000 > /proc/sys/net/ipv4/ip_local_port_range

3

查看内核的时间戳支持:
cat /proc/sys/net/ipv4/tcp_timestamps
如果是0,则修改为1。开启对于TCP时间戳的支持。
运行sysctl -w net.ipv4.tcp_timestamps=1

查看是否开启快速回收:
cat /proc/sys/net/ipv4/tcp_tw_recycle
如果是0,则修改为1。开启TCP连接中TIME-WAIT sockets的快速回收。
运行sysctl -w net.ipv4.tcp_tw_recycle=1

修改tcp_max_tw_buckets
运行sudo sh -c "echo '5000'> /proc/sys/net/ipv4/tcp_max_tw_buckets"

Druid安装(单机环境)

背景

本篇文章将简单介绍Druid在单机上的安装与安装过程中可能会遇到的问题。主要目的是供初次接触Druid的同学了解Druid所用。

准备工作

软件

  • Java8(8u92+)
  • Linux或其他类Unix系统

硬件

  • 4CPU/16GB RAM及以上

下载Druid

Apache Druid 0.16.0-incubating下载到机器中。这里我们下载apache-druid-0.16.0-incubating-bin.tar.gz
下载完成后运行如下命令:

tar -xzf apache-druid-0.16.0-incubating-bin.tar.gz
cd apache-druid-0.16.0-incubating

进入apache-druid的安装目录后,我们可以看一下各子目录和文件的功能:

  • bin/*:用于快速入门的脚本。
  • conf/*:单机和集群设置的配置示例。
  • extensions/*:核心Druid扩展。
  • hadoop-dependencies/*:Druid的Hadoop依赖。
  • lib/*:核心Druid的库和依赖。
  • quickstart/*:快速入门的配置文件、样例数据和其他文件。
  • DISCLAIMER、LICENSE和NOTICE文件。

下载Zookeeper

Druid在Zookeeper在分布式协作上有依赖关系。所以我们还需要下载Zookeeper。
在Druid的安装目录根目录下运行如下命令:

curl https://archive.apache.org/dist/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz -o zookeeper-3.4.14.tar.gz
tar -xzf zookeeper-3.4.14.tar.gz
mv zookeeper-3.4.14 zk

Druid入门教程的启动脚本要求Zookeeper安装目录必须在其根目录下,且重命名为zk。

启动Druid服务

apache-druid-0.16.0-incubating(Druid的安装目录)下,运行./bin/start-micro-quickstart命令。这个命令用于启动Druid和Zookeeper。且是单机版的。
我这里碰到了问题,报错说是服务未启动,查看Druid的日志,发现是这样的报错信息:
在这里插入图片描述
这是说未识别出JVM的ExitOnOutOfMemoryError这个选项。引起该报错的原因是Java的版本不符合要求。建议从Oracle官网下载Java,不要从yum来安装Java,就不会出现该问题了。
Druid和Zookeeper正常启动后如下所示:
在这里插入图片描述
如果是安装在本机中的话,在浏览器中输入:localhost:8888即可访问Druid了
在这里插入图片描述

Golang的值接收者与指针接收者

背景

Go语言中有着面向对象的思想,当我们创建了一个类型之后,可以给这个类型添加不同的方法,给类型添加方法的方式类似于创建一个函数,只是在func和函数名中添加一个(类型名 类型)。这个东西就是所谓的接收者,也就是类型作为接收者接收该函数为自己的方法。有时候我们希望可以更改类型中成员变量的值,而有时候我们不希望类型中成员变量的值被改变。这就是Golang中使用值接收者与指针接收者的区别。

一个例子

语言叙述比较繁琐,直接看这个简单的例子:

package main

import "fmt"

type user struct {
  name string
  email string
}

func (u user) changeEmail0(NewEmail string) {
  u.email = NewEmail
  fmt.Println("changeEmail0: userName is ",u.name," userEmail is ",u.email)
}

func (u *user) changeEmail1(NewEmail string) {
  u.email = NewEmail
  fmt.Println("changeEmail1: userName is ",u.name," userEmail is ",u.email)
}

func main() {
  usr0 := &user{"pf", "xxxxxxxxx@qq.com"}
  usr0.changeEmail0("xxxxxxxxx@163.com")
  fmt.Println("main: userName is ",usr0.name," userEmail is ",usr0.email)

  usr1 := &user{"pf", "xxxxxxxxx@qq.com"}
  usr1.changeEmail1("xxxxxxxxx@163.com")
  fmt.Println("main: userName is ",usr1.name," userEmail is ",usr1.email)
}

这个例子中第10行的func (u user) changeEmail0(NewEmail string)就是将user类型作为值接收者,在使用值接收者时,方法中使用的类型的值只是一个副本,也就是说,无论怎么更改类型中成员变量的值,都对原值不会有任何影响,仅仅是在方法内部有所改变而已。
15行的func (u *user) changeEmail1(NewEmail string)是将user类型作为指针接收者,方法中使用的是该类型的原值,也就是说,在方法中改变该值,那么原值也会有所改变。
该代码的结果

changeEmail0: userName is pf userEmail is xxxxxxxxx@163.com
main: userName is pf userEmail is xxxxxxxxx@qq.com
changeEmail1: userName is pf userEmail is xxxxxxxxx@163.com
main: userName is pf userEmail is xxxxxxxxx@163.com

可以看出值接收者与指针接收者的区别。

总结

使用值接收者还是指针接收者,不应该由该方法是否修改了接收到的值来决定。这个决策应该基于该类型的本质。简单来说,如果给这个类型增加或者删除某个值,是需要创建一个新值,则选择值接收者。如果要修改当前的值,则选择指针接收者。