优化Elasticsearch的写入

我们这边因为需要对trace系统的数据做一些高级查询,所以会将Span的可能会被用作搜索条件的信息写入elasticsearch中。
由于trace系统的数据量比较大,虽然trace系统本身的设计会有采样率这个东西来降低trace采集的数据量,但是本身还是
比较大的数据量。所以需要对es的写入做一些优化。这篇文章记录一下我们的优化项

分析我们场景的特点:

  • 写请求特别大
  • 读请求很少,实时性要求低
  • trace系统对数据的可靠性要求低,但是要求写入及时(数据的价值会随着时间而降低)

贴一下我们优化以后的template设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"order": 0,
"index_patterns": [
"trace.advanced.query-*"
],
"settings": {
"index": {
"refresh_interval": "120s",
"number_of_shards": "10",
"translog": {
"flush_threshold_size": "1024mb",
"sync_interval": "120s",
"durability": "async"
},
"number_of_replicas": "0"
}
},
"mappings": {},
"aliases": {}
}

主要的优化步骤:

  • 关闭副本,设置number_of_replicas为0 在我们的场景下数据丢了是可以忍受的
  • 调大translog.flush_threshold_size,我们设置的为1024mb
  • 调大translog.sync_interval为120s
  • 我们容许数据丢失,设置async模式

因为我们目前的es集群性能是足够的,所以并没有完全按照参考文章将 ELASTICSEARCH 写入速度优化到极限中的所有项目都优化。推荐大家阅读这个参考文章,比我写的好多了。

参考资料

测试hexo-deployer-git自动部署github私有仓库

因为之前github支持了个人免费的私有仓库,所以就打算把自己的博客的仓库设置为private的,但是因为我有点懒,
担心从库从publish修改为private以后而导致hexo-deployer-git需要额外配置,后来测试以后发现我想多了。一切
都不需要做,完美,感谢github。

单据的设计

本文主要是初步记录一下关于单据零散的想法,看看是否可以形成一套可行的方法论。

单据实体

单据这个实体,其实从抽象的层面来说,它其实描述了一次行为:

谁,在什么时间点,用了什么样的成本,对什么目标产生了一次什么样的行为

如果让我们来对上面这句话进行解析,其实可以发现它可以拆分为下面几块:

  • 时间信息
  • 成本
  • 行为描述
  • 行为目标

下面我们来一次分析这个问题。

一般订单的发起者其实都是【用户】,当然这个用户并不一定是C端用户。而用户的信息如何描述呢?一般的公司都会有【用户中心】,或者【供应商管理系统】等等的来描述【用户】的信息。做的好的公司也会有【用户画像】

时间信息

时间信息一般分为2类:

  • 行为动作的产生时间
    • 比如用户的下单时间
  • 行为动作所带来的影响发生的时间
    • 用户购买的商品的出库时间
    • 用户购买的商品的送货时间
    • 用户购买的商品的预计收货时间
      前者是用户主动产生的,后者其实对用户来说是被动的。

成本信息

用户在下订单的时候往往都会消耗一定的成本,这个成本并不一定是金钱。可能是:

  • 金钱
  • 积分
  • 优惠券

##行为描述

行为描述主要是用来解释行为,比如:

  • 购买行为
  • 退货行为

不同的行为会有不同的属性信息,比如购买行为会有购买的数量,购买服务的持续时间等。

行为目标

行为的目标可以是物理实体,比如商品(一瓶可乐),也可以是虚拟实体,比如一次服务。

  • 商品
  • 服务
  • 流量

而具体的目标也会有一大套自己的模型和属性。比如商品的sku就是一套很经典的模型。

单据本身的信息

单据本身也需要一些自己的属性信息:

  • 单号
  • 单据类型
  • 状态机

关于单据号的设计可以很有讲究,一般会有如下的考虑:

  • 单据号的形式,长度
  • 是否有业务含义
  • 业务含义的具体信息
  • 业务含义占单据号的长度
  • 是否连续(可遍历)
  • 单据创建时间
  • 单据生成的serverIP
  • 路由信息
  • 分库分表的依据
  • 其他路由形式
  • 单据数量增长对单据号的影响
  • 单据号长度的划分
  • 每秒最大单据数量预估
  • 关于路由信息也有一个需要注意的点,就是要尽量做到数据分布平衡。

单据流程

单据的流程一般有正向流程和逆向流程。考虑到解耦和业务复杂度不可控导致的变数,一般情况下建议正向和逆向分开设计和实现。然后通过单号等信息进行关联。

  • 正向流程
    • 订单正常生产到配送的过程
  • 逆向流程
    • 逆向流程则指订单发生取消、退货等情况时引发的订单流程过程。

快速的熟悉陌生的系统

工作和学习过程中经常会遇到陌生的系统需要去熟悉,下面是我总结的一些自己的方法论,希望对你有所帮助。

了解系统Overview

首先我们需要对系统有一个Overview的了解,了解的方式可以是自己摸索,找对应系统负责人,团队leader聊,也可以自己找wiki,文档等方法。我简单的总结了一下步骤:

  • 熟悉系统的定位,明确系统要解决的主要问题
  • 如果系统有对应的UI页面,比如对于web系统,可以申请系统的权限,然后登陆系统看看系统主要有哪些菜单(一般情况下菜单其实会对于系统的功能模块),每个菜单下面都有哪些菜单项,然后浏览一下每个菜单项对应的页面有哪些按钮,哪些表格,哪些表单等。这样会对系统有哪些模块、功能有一个大概的了解。
  • 如果你幸运的话,你可以阅读系统使用手册、设计文档、需求文档等,当然大多数情况下是没有的。这个时候你其实更多的需要上一步骤对系统的大概理解,然后结合自己的工作经验,大致猜猜系统怎么设计实现的,其实对于大多数业务系统来说,看看功能模块其实是可以猜出个七七八八的系统设计和使用的情况的。当然了对于类似中间件那些系统(比如MQ,Config,Schedule)等,其实了解起来更加的迅速,因为大家基本在日常工作过程中都使用过这些系统,所以对于系统需要提供的基本功能和如何使用都比较熟悉,可能就是在系统的设计和实现上有所差异。
  • 了解系统的上下游。上下游都是哪些团队的哪些系统?这些团队的负责人是谁?系统和上下游系统是如何交互的?交互方式是什么,交互的时机等。
  • 了解系统的核心数据流向。清楚一个完成的数据链路是怎么样的。
  • 如果可以的话,自己动手在系统的dev环境或者beta环境来点一点,创建一些数据,然后走走核心数据链路。
  • 了解系统的核心监控指标。了解清楚核心监控指标,其实便于你理解系统的核心功能,并且方便日后对系统修改的时候,看看是否对核心监控指标有影响。
  • 了解系统的部署情况。一般一个系统都会有好几个模块(module),清楚每个模块干啥的,这些模块之间的依赖关系(比如admin模块,Server模块,client模块),模块或者系统系统如何部署的,是部署在KVM上?Kubernetes上?多少个实例?多少台Server?Server啥配置?存储用的啥?MySQL?HBase?DB数据量大致多少?存储多少个G/T? 存储增长量多少?系统的QPS大致多少?
  • 按照上面的步骤操作完以后,相信你一定会对系统有一个明确的了解。这个时候其实你应该可以对系统的存储模型(数据模型)猜出个七七八八了,接下来可以看看系统的存储模型了,来验证我们的猜测。

阅读系统存储模型

此处说的存储模型也可以称为是数据模型。可以是MySQL表设计(有哪些表,表之间的关联关系,表的索引是哪些,唯一索引是哪些),Hbase的存储方式(rowKey是如何设计的,列簇如何弄的,有哪些列),也可以是抽象意义上的数据模型。

阅读系统的源码

基本了解系统功能以后,知道存储模型以后,大致怎么实现其实 应该也可以猜出来了。当然了很多功能其实的实现方式会有很多种,通过阅读系统的源码我们就可以知道系统是采用的哪种方式。如果遇到和我们猜测的实现方式不一样的话,其实可以思考一下为啥不一样?两种实现方式的区别在哪,孰优孰劣。

关于阅读源码,我整理了几个注意点

  • 首先让系统在本地/dev/beta跑起来,最好在本地,这样其实对系统的运行环境可以更加熟悉。
  • 先从从核心数据链路的最开始进行源码阅读,第一次阅读只需要关心核心链路,分支链路第一次阅读的时候请忽略。
  • 对核心逻辑流程进行debug。因为设计的比较好的系统,往往接口和实现是分离的,而且会有多个实现,光看接口其实有时候根本不清楚走的哪个实现,这个时候就需要进行debug了。有时候一些方法实现的可能很不清晰,这个时候通过debug,或者写main方法,单元测试等手段来了解。
  • 开业手动画出系统核心流程的关键类的调用关系
  • 阅读系统非核心逻辑代码
  • 尝试讲给系统的开发人员,确认你理解的是正确的。对一些感觉设计不合理的点,咨询当时那么设计的原因

解决自己的疑惑,确认可优化点

一般情况下,我们在阅读完系统代码之后,会有自己的疑惑,也会发现一些设计不合理的点(虽然可能这个设计在当时的情况下是合理的),我们可以提出自己的优化方案,然后找对应系统的产品、开发、负责人确认。

结语

如果你完全按照上面的方式做了,还是没有对某个系统熟悉的话,那么就只能找对应系统的产品、开发、负责人具体咨询了。

ApacheBench ab压测工具

ab简介

ApacheBench 是 Apache服务器自带的一个web压力测试工具,简称ab。ab又是一个命令行工具,根据ab命令可以创建很多的并发访问线程,模拟多个访问者同时对某一URL地址进行访问,因此可以用来测试目标服务器的负载压力。

安装ab

1
sudo apt-get install apache2-utils

参数列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
-n requests Number of requests to perform //请求链接数
-c concurrency Number of multiple requests to make at a time //表示并发数
-t timelimit Seconds to max. to spend on benchmarking
This implies -n 50000
-s timeout Seconds to max. wait for each response
Default is 30 seconds
-b windowsize Size of TCP send/receive buffer, in bytes
-B address Address to bind to when making outgoing connections
-p postfile File containing data to POST. Remember also to set -T
-u putfile File containing data to PUT. Remember also to set -T
-T content-type Content-type header to use for POST/PUT data, eg.
'application/x-www-form-urlencoded'
Default is 'text/plain'
-v verbosity How much troubleshooting info to print
-w Print out results in HTML tables
-i Use HEAD instead of GET
-x attributes String to insert as table attributes
-y attributes String to insert as tr attributes
-z attributes String to insert as td or th attributes
-C attribute Add cookie, eg. 'Apache=1234'. (repeatable)
-H attribute Add Arbitrary header line, eg. 'Accept-Encoding: gzip'
Inserted after all normal header lines. (repeatable)
-A attribute Add Basic WWW Authentication, the attributes
are a colon separated username and password.
-P attribute Add Basic Proxy Authentication, the attributes
are a colon separated username and password.
-X proxy:port Proxyserver and port number to use
-V Print version number and exit
-k Use HTTP KeepAlive feature
-d Do not show percentiles served table.
-S Do not show confidence estimators and warnings.
-q Do not show progress when doing more than 150 requests
-l Accept variable document length (use this for dynamic pages)
-g filename Output collected data to gnuplot format file.
-e filename Output CSV file with percentages served
-r Don't exit on socket receive errors.
-h Display usage information (this message)
-Z ciphersuite Specify SSL/TLS cipher suite (See openssl ciphers)
-f protocol Specify SSL/TLS protocol
(SSL3, TLS1, TLS1.1, TLS1.2 or ALL)

基本使用

GET 压测

1
ab -n 100 -c 10 http://www.baidu.com/
  • -n表示请求数
  • -c表示并发数

样例输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking www.baidu.com (be patient).....done


Server Software: BWS/1.1
Server Hostname: www.baidu.com
Server Port: 80


Document Path: / #测试页面
Document Length: 112439 bytes #测试页面大小

Concurrency Level: 10 #并发数
Time taken for tests: 1.256 seconds #整个测试话费的时间
Complete requests: 100 #完成请求的总量
Failed requests: 96 #失败的请求次数
(Connect: 0, Receive: 0, Length: 96, Exceptions: 0)
Write errors: 0
Total transferred: 11348660 bytes #传输数据总大小
HTML transferred: 11253726 bytes #传输页面总大小
Requests per second: 79.62 [#/sec] (mean) #平均每秒请求数
Time per request: 125.593 [ms] (mean) #平均每次并发10个请求的处理时间
Time per request: 12.559 [ms] (mean, across all concurrent requests) #平均每个请求处理时间,所有并发的请求加一起
Transfer rate: 8824.29 [Kbytes/sec] received #平均每秒网络流量

Connection Times (ms)
min mean[+/-sd] median max
Connect: 4 20 7.7 18 38
Processing: 18 90 50.5 82 356
Waiting: 4 22 7.9 22 41
Total: 22 111 50.7 101 384
#花费在连接Connect,处理Processing,等待Waiting的时间的最小min,平均值mean,标准差[+/-sd],中值median,最大表max的一个表。

Percentage of the requests served within a certain time (ms)
50% 101 #50%请求的响应时间在101ms内
66% 103 #66%请求的响应时间在103ms内
75% 104 #...以此类推
80% 105
90% 111
95% 267
98% 311
99% 384
100% 384 (longest request)

带自定义header请求:

1
ab -n 100 -H “Cookie: Key1=Value1; Key2=Value2” http://test.com/

POST请求

1
ab -n 1 -c 1 -p 'post.txt' -T 'application/x-www-form-urlencoded'   http://192.168.188.6:8080/distributeLock2
  • -p 用来做post数据的文件,这里此文件保存在ab同级目录下
  • -T 设置content-type值

org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available

问题描述

今天在操作es的过程中出现了异常:

1
2
org.elasticsearch.client.transport.NoNodeAvailableException: None of the
configured nodes are available

而我创建es client的代码也很简单,核心是:

1
2
3
4
Settings settings = Settings.builder()
.put("cluster.name", esClusterInfo.getClusterName())
.put("client.transport.sniff", true) //自动嗅探整个集群的状态,把集群中其他ES节点的ip添加到本地的客户端列表中
.build();

我的排查步骤

记录一下我当时的排查过程:

  • 看异常第一反应是集群有问题,但是排查集群的节点以后,发现集群的节点都是没问题的。
  • 而后开始检查settings中设置的cluster.name的是否正确发现也是正确的
  • google发现很多人是因为将es集群的端口写错,也就是9300错写为9200,但是检查我的数据以后发现也是没问题的。
  • 而后开始怀疑我设置的client.transport.sniff的缘故,因为这个参数的作用是:

使客户端去嗅探整个集群的状态,把集群中其它机器的ip地址加到客户端中。这样做的好处是,一般你不用手动设置集群里所有集群的ip到连接客户端,它会自动帮你添加,并且自动发现新加入集群的机器

但是这个参数有一个问题就是:

当ES服务器监听(publish_address )使用内网服务器IP,而访问(bound_addresses )使用外网IP时,不要设置client.transport.sniff为true。不设置client.transport.sniff时,默认为false(关闭客户端去嗅探整个集群的状态)。因为在自动发现时会使用内网IP进行通信,导致无法连接到ES服务器。因此此时需要直接使用addTransportAddress方法把集群中其它机器的ip地址加到客户端中。

但是检查我的环境,发现我的环境全部是内网,所以设置不设置这个client.transport.sniff是没区别的。

最后持续google许久,也没发现问题,喝杯茶刷了一会手机突然想起印象中es client的版本和es集群的版本不一致也有可能
出问题,于是google了一波es 版本不一致这个关键字,果然是因为版本不一致导致的。

ES client和ES集群版本不一致的问题

我遇到的这个问题是es集群的版本比我使用的es client的版本低,相当于我使用高版本的client去访问低版本的集群,所以出现org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available问题。

因此我们平时需要注意es的版本问题。

使用Let's Encrypt配置nginx证书

Automatically enable HTTPS on your website with EFF’s Certbot, deploying Let’s Encrypt certificates

主要参考:https://certbot.eff.org/lets-encrypt/centosrhel7-nginx

操作起来很简单,主要用到了下面的命令:

1
2
3
4
sudo yum install nginx
sudo certbot --nginx
sudo certbot renew --dry-run
0 0 1 * * certbot renew --post-hook "/usr/sbin/nginx -s reload"

Mac外接4k显示器选1080p字体发虚

MacBook外接4K显示器使用HDMI数据线选择缩放模式,然后选择1080p,会出现外接显示器字体发虚的问题,一般有下面几种的排查方式:

Slow startup Tomcat because of SecureRandom

今天在新机器上启动tomcat应用的时候,发现巨慢,检查日志发现有如下信息:

1
2
Jan 09, 2018 8:44:35 PM org.apache.catalina.util.SessionIdGenerator createSecureRandom
INFO: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [239,939] milliseconds.

这块初始化SecureRandom用了239,939毫秒,之前没遇到这个问题。查了一下发现在官方wiki https://wiki.apache.org/tomcat/HowTo/FasterStartUp#Entropy_Source

Entropy Source
Tomcat 7+ heavily relies on SecureRandom class to provide random values for its session ids and in other places. Depending on your JRE it can cause delays during startup if entropy source that is used to initialize SecureRandom is short of entropy. You will see warning in the logs when this happens, e.g.:
org.apache.catalina.util.SessionIdGenerator createSecureRandom
INFO: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [5172] milliseconds.
There is a way to configure JRE to use a non-blocking entropy source by setting the following system property: -Djava.security.egd=file:/dev/./urandom
Note the “/./“ characters in the value. They are needed to work around known Oracle JRE bug #6202721. See also JDK Enhancement Proposal 123. It is known that implementation of SecureRandom was improved in Java 8 onwards.
Also note that replacing the blocking entropy source (/dev/random) with a non-blocking one actually reduces security because you are getting less-random data. If you have a problem generating entropy on your server (which is common), consider looking into entropy-generating hardware products such as “EntropyKey”.

解决办法

基于官方wiki,解决办法是在tomcat的startenv.sh脚本中增加

1
-Djava.security.egd=file:/dev/./urandom

不过tomcat的wiki中提到,如果使用这个非阻塞的/dev/urandom的话,会有一些安全方面的风险,说实话没看懂,不过写了一篇Myths about /dev/urandom来证明使用/dev/urandom是没问题的,所以就先用着吧:-)

关于 熵源”(entropy source)

这篇文章:JVM上的随机数与熵池策略说的比较清楚,推荐大家阅读

参考资料

Spring RestTemplate parse gzip response

假设http://10.89.xx.xx:8080/_/metrics接口返回的数据格式是gzip格式
他的Response Headers信息如下

1
2
3
4
5
6
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Encoding: gzip
Content-Type: text/plain;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 28 Dec 2017 08:13:53 GMT

如果我们使用Spring RestTemplate想直接拿到String形式的返回,而不是byte[]格式,那么可以使用如下的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.apache.http.impl.client.HttpClientBuilder;
public static void main(String[] args) {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
HttpClientBuilder.create().build());
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://10.89.xx.xxx:8080/_/metrics", String.class);
HttpStatus statusCode = responseEntity.getStatusCode();
System.out.println(responseEntity.getBody());
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×