Jason Lee Essay

Only dead fish go with the flow!

Jason Lee Essay

Mysql配置主从的监控,主要的监控指标有三个:

  • Slave_io_running 负责与主机的io通信
  • Slave_sql_running 负责自己的slave mysql进程
  • Seconds_behind_master

其中Seconds_behind_master表示从库与主库的同步时延,即主库的变更需要多久才能同步到从库上。计量单位是timestamp的差值,ms。

什么时候sbm会是Null?

  • SBM在slave关闭的时为null
  • Slave_sql_running不是Yes的时候
  • Slave_io_running是no的时候。

解决sbm是null的问题?

首先检测问题

1
2
3
4
5
show slave status ; 

Slave_io_running : yes
Slave_sql_running : no
Seconds_behind_master : null

可以通过设置SQL_SLAVE_SKIP_COUNTER来重新开始sql同步。

1
2
3
4
5
6
7
8
stop slave;
SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
START SLAVE;
show slave status ;

Slave_io_running : yes
Slave_sql_running : yes
Seconds_behind_master : 7928

解决了后发现sbm还是很大。

sbm不停增长?

未找到解决方案。

HashMap是Java集合类中,使用比较多的一个集合类。类似request的参数集合、jdbcTemplate的数据库返回啊,这些不定长度的key-value型数据集合,大量的使用到了HashMap。那到底HashMap的api有啥神秘呢?put的过程到底有什么问题呢?

PUT的实现

HashMap的Put过程基本可以分为两部分,确认插入位置和更新数据。

两个过程其实奥秘就是两个基础API:hashCode和equals。

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
47
48
49
//先计算key的hash值,调用hashCode方法,再进行位移计算。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//实现放入值的核心过程。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//读取当前数组的长度,如果为空,初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 按照hash读取数组指定位置的元素,为空直接插入
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

Java创建线程有两种方式:

  1. 继承Thread类
  2. 实现Runnable接口,实际Thread类也实现了Runnable接口。

看下Thread的构造方法列表:

  • public Thread()
  • public Thread(Runnable target)
  • public Thread(Runnable target, String name)
  • public Thread(ThreadGroup group, Runnable target, String name)
  • public Thread(ThreadGroup group, Runnable target, String name, long stackSize)
  • public Thread(ThreadGroup group, String name)

可以看到Runnable接口的实例是Thread的target。run犯法的实现方式是怎么样子的呢?

1
2
3
4
5
6
@Override
public void run() {
if (target != null) {
target.run();
}
}

这个调用的形式就是通过一个线程实例(创建了一个Thread),调用了另一个线程的run方法。

所以new Thread()即创建了一个线程。new Thread(Runnable)用一个线程作为容器,共享target的线程变量。为什么呢?

线程私有变量和线程间共有变量。通过new Thread()的方式创建线程即为线程修改私有变量。new Thread(Runnable)的线程私有变量是target,所以需要共享target的变量。

当然还有Java的单继承的原因,实际使用中使用Runnable的时候更多。

还有一点,如果手动调用Runnable.run方法是什么效果呢?不会创建一个新线程,跟普通的同步方法执行一样。

购买理由

程序员一枚,多年办公电脑非公司台式机就是笔记本,最贵的是一台港版Thinkpad T440P。从未搞过一台稍微像点样子的主机犒赏自己,不巧,Thinkpad再一次罢工黑屏,所以趁机….

总共话费不到7000元。

配件展示

所有配件图,请忽略帮忙的小王爷所有配件图,请忽略帮忙的小王爷

配机器的目的以办公为主,游戏需求不高,能运行魔兽争霸就可以,撑死玩个Dota2。所以CPU,这块对线程数要求一定要多。原本很钟情于8700K,6核12线程,无奈价格贵,渠道少;搭配主板也不好组合,B360觉得低,Z370又没有合适的,上Z390又觉得不值当。I5的只有6核6线程。回头看下AMD,核数够了,主频不够高,但也够用,果断AMD了。

配件详单:

微星(MSI)B450M MORTAR TITANIUM 迫击炮钛金版主板+AMD 锐龙 5(r5) 2600X CPU 板U套装/主板CPU套装2110元

AMD 2600X 和微星B450M 迫击炮钛金版,便宜好用

威刚(ADATA) XPG-威龙系列 Z1 DDR4 2666频 8GB 台式机内存(红色)389元

2条2666内存,手感不错,马甲摸着很结实,从盒子取出来的时候卡的太紧了

索泰(ZOTAC)GeForce GTX1060 X-GAMING OC吃鸡显卡/游戏电竞台式机独立显卡 6GD5/1569-1784/8008MHz1719元

惠普(HP) EX900系列 250G M.2 NVMe SSD固态硬盘299元

主硬盘

东芝(TOSHIBA) 2TB 7200转64M SATA3 台式机硬盘(DT01ACA200)389元

数据盘,容量和速度还好

Tt(Thermaltake)启航者F1 黑色静音版 Mini机箱(支持MATX主板/独立电源仓/支持背线/侧透/钢板0.6mm/U3)179元

比想象的要大,以为M-ATX机箱挺小,这个一点也不小。

乔思伯(JONSBO)12020 12CM机箱风扇(20MM薄型厚度/主板3PIN接口+电源D型口接口/静音)19.9元

乳白色,风量还可以。

戴尔(DELL)P2419H 23.8英寸微边框全面屏旋转升降广视角IPS屏护眼不闪滤蓝光电脑显示器17系列升级款1129元

显示器一直用dell,ips屏幕习惯了。

使用感受

装了Win10,开机不到10-15秒,感觉还可以接受。本人不喜欢各种测试数字,跟使用感受没太大关系,放弃掉。

安装了Steam,¥65元买了个2K19,挺流畅的。还用上了吃了两年灰的北通 阿修罗TE 2185的手柄,用来打2k19,很顺手。

小程序选定了mpvue作为开发框架,搭建开发环境和构建环境。自从用了Travis和Jenkins之后,再也回不到手工构建的时代了。

目的-自动构建

web项目中,自从前后台分离的结构形成,就形成了一个要求,前后台的连接URL需要根据部署环境进行切换,线上的URL和测试的URL肯定不同;这点类似于java应用连接数据库的连接切换。
后台URL的定义要能够多环境构建是自动构建的目标。

构建简介

mpvue的框架基于vue.js构建,利用webpack的扩展工具将vue源码转换为小程序的源码。vue的源码是基于node构建的,理论将node构建生态的env模式也能带入mpvue构建过程。
process.env是nodejs提供的官方api。官方定义是:process.env属性返回一个包含用户环境信息的对象。

process.env

process.env.NODE_ENV是默认的全局定义的全局变量.process.env是一个定义对象,可以自定义扩展。
比如:

1
2
3
4
process.env = {
NODE_ENV : 'dev',
api : 'http://example.com'
}
这样子就实现了自定义的过程,将定义分别放到env.dev.js,env.prod.js,env.test.js即可实现多环境实践。

mpvue中使用

mpvue的quickStart提供的构建脚手架,env的定义在config目录中,通过prod.env.js和dev.env.js实现对env的定义。

prod.env.js
1
2
3
4
module.exports = {
NODE_ENV: '"production"',
api: '"example.com"'
}

如何使用呢?
因为process是一个node的全局变量,使用Process对象在vue源码中应该是任意使用的。测试下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// App.vue
<script>
export default {
created () {
// 调用API从本地缓存中获取数据
const logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
console.log('app created and cache logs by setStorageSync')
}
}
</script>
//pages/index/index.vue
<script>
export default {
methods: {
clickHandle (msg, ev) {
console.log(process.env)
console.log('clickHandle:', msg, ev)
}
}
}
</script>

启动构建工具,打开微信开发工具,可以正确输出定义结果,目标达成。

最近工作和个人都有想法搞一个小程序出来,捡起多年不写的javascript大法研究下。

注册帐号

想开发公众号或者小程序,首先需要一个微信公众帐号,公众号帐号和小程序帐号并不互通,需要单独注册。注册过程不详细介绍了。十分钟注册了一个主体是个人的小程序帐号。

环境

有了帐号,还得知道如何进行开发,用什么工具开发,什么工具调试?先查看了下官方的说明,需要专门的小程序开发工具,很不幸,只提供了windows和mac版本(linux用户表示很气愤)。难道linux党只能转用windows搞定吗?(mac买不起)还好有无敌的github。查到了一个神器,wechat_web_devtools。开源,支持linux。试用了下,基本可行,除了腾讯的代码库打不开之外,未发现其他问题。五星好评。

语法

有了帐号,有了开发环境,够了吗?还不够,还得研究下语法,鹅厂的官方文档翻了一遍,基本确定了js,wxml,wcss,json四个文件为一体的构建结构。但是要做大的项目,这个组织形式有点难受了。一个页面加载4个文件搞定,但是如何集成node的构建环境是个问题,总不至于真的一个页面写4个文件去搞吧。感觉写起来好累的感觉。

框架

相信开源的力量,通过http://www.cnblogs.com/zxj95121/p/9224163.html索引帖,查到了相关的资源,初步目标定在了wepy上面,后面有发现了mpvue。前者是亲儿子,腾讯背书,算是自创的一套语法;后面是美团的亲儿子,基于vue搞出来的。

其实对于没有vue基础的我来说,选择哪一个似乎都可以。但是mpvue和vue靠的近啊,有vue资源可以参考;而且mpvue有可能会有多端的迁移方案,比wepy复用性好了。初步选定了mpvue来搞。

mpvue

使用 mpvue 开发小程序,你将在小程序技术体系的基础上获取到这样一些能力:

  • 彻底的组件化开发能力:提高代码复用性
  • 完整的 Vue.js 开发体验
  • 方便的 Vuex 数据管理方案:方便构建复杂应用
  • 快捷的 webpack 构建机制:自定义构建策略、开发阶段 hotReload
  • 支持使用 npm 外部依赖
  • 使用 Vue.js 命令行工具 vue-cli 快速初始化项目
  • H5 代码转换编译成小程序目标代码的能力

将页面的三元素合并到一起的做法看着很jsp的感觉,对老java程序员有莫名的好感。

个人感觉的问题:

  • 每个目录下一个页面,每个页面需要三个文件定义:main.js,index.vue,main.json(可选),其实并没有降低太多的目录复杂性。
  • quickstart构建流程没有有点晦涩,可能vue的构建环境就是这个样子吧。
  • vue的一些语法还是需要读懂的,关于数据的控制逻辑和生命周期等。

这两天在测试一个Spring RMI接口的时候,出现了个奇怪的问题。Server端返回的数据,到了客户端出现了属性丢失的情况。
类继承体系类图。客户端里面定义在ClassA中的属性全部为null。

分析问题:

RMI过程中的数据有一个序列化和反序列化的过程,分析可能是由于序列化和反序列化的原因导致数据丢失。

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws IOException, ClassNotFoundException {
ClassB b = new ClassB();
b.setName("abc");
b.setId(1L);
logger.info(b.toString());
FileOutputStream fs = new FileOutputStream("foo.ser");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(b);

ObjectInputStream is = new ObjectInputStream(new FileInputStream("foo.ser"));
ClassB b2 = (ClassB) is.readObject();
logger.info(b2.toString());
}

反序列化的数据丢失了父类ClassA中的属性。

解决

ClassA也实现 Serializable 接口。更新后的类图:类图

1
2
16:24:37.769 [main] INFO  ser.Main - ClassB{id='1'name='abc'}
16:24:37.879 [main] INFO ser.Main - ClassB{id='1'name='abc'}

解读

序列化的使用场景:

  1. 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
  2. 当你想用套接字在网络上传送对象的时候;
  3. 当你想通过RMI传输对象的时候;

小结:

序列化的范围是 Serializable 接口的子类,不包含该子类的不包含。

现状

先有5台服务器,其中1台HP微型服务器、2台HP刀片、2台HP塔式服务器。
配置情况如下:

序号 型号 IP CPU 内存 存储 RAID
内网(hp DL388 Gen9 ) 192.168.1.184 六核 E5-2609v4 64G 4T
内网(hp DL388 Gen9 ) 192.168.1.160 六核 E5-2609v4 128G 6T
内网(hp ML350 Gen9) 192.168.1.80 六核 E5-2609v3 64G 2T
内网(hp ML350 Gen9) 192.168.1.200 六核 E5-2620v3 16G 2T
内网(HP Z220 SFF Workstation ) 192.168.1.199 四核 E3-1225 V2 20G 4T

以上是所有的IT硬件资源,是用的方式是,4、5两台做为数据备份主机。1号作为主力数据库机器。2、3号主机虚拟了多台主机,部署了公共服务如:git、nexus、jenkins等服务;还准备了各个项目的开发、测试、上线同步环境。3个客户共10个虚拟环境,产品开发、测试环境2台虚拟主机。

以上的分配基本耗尽了现有的资源。

规划

IT资源分层建设,统一规划、统一部署。
分为3层:
1,宿主机层,所有物理主机统一操作系统Centos7.0。
2, 虚拟机层,类似IAAS架构,采用CloudStack云计算框架搭建虚拟主机层。
3,PASS层,虚拟机之上搭建3类应用服务,大数据集群、K8s集群、传统虚拟主机。
后续再规划SASS层,现有的服务达不到SASS标准。

你想输入的替代文字

宿主机层面

操作系统统一为Centos7,安装cloudstack软件,需要搭配出NFS和ntp服务,同步时钟。

Echarts是百度发布的一套优秀的浏览器端图表控件,Echarts是基于html5的cavens绘图实现。而使用server端生成图片无法借用浏览器端渲染。通用的做法有两种:

  1. 是用headless浏览器渲染,例如Phantomjs
  2. node-cavens渲染,类似node-echarts等方案,虽然Phantomjs也是基于Nodejs。

环境准备:

下载Phantomjs

从 http://phantomjs.org/download.html 下载指定版本,配置好环境变量,当

1
2
$ phantomjs -v
2.1.1

就算完成了。

相关依赖下载

jquery,echarts从相关CDN或者官网下载。

主流程

使用命令:

1
2
3
4
$ phantomjs echarts-convert.js -infile option.js -outfile t.png
Echarts.options has been parsed
The images have been loaded
render complete:t.png
  • echarts-convert.js 类似一个浏览器页面,执行js的入口。
  • infile 注入参数
  • outfile 输出的文件

echarts-convert.js

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179

(function () {
var system = require('system');
var fs = require('fs');
var config = {
// define the location of js files
JQUERY: 'jquery.1.9.1.min.js',
ESL: 'esl.js',
ECHARTS: 'echarts-all.js',
// default container width and height
DEFAULT_WIDTH: '600',
DEFAULT_HEIGHT: '700'
}, parseParams, render, pick, usage;

usage = function () {
console.log("\nUsage: phantomjs echarts-convert.js -options options -outfile filename -width width -height height"
+ "OR"
+ "Usage: phantomjs echarts-convert.js -infile URL -outfile filename -width width -height height\n");
};

pick = function () {
var args = arguments, i, arg, length = args.length;
for (i = 0; i < length; i += 1) {
arg = args[i];
if (arg !== undefined && arg !== null && arg !== 'null' && arg != '0') {
return arg;
}
}
};

parseParams = function () {
var map = {}, i, key;
if (system.args.length < 2) {
usage();
phantom.exit();
}
for (i = 0; i < system.args.length; i += 1) {
if (system.args[i].charAt(0) === '-') {
key = system.args[i].substr(1, i.length);
if (key === 'infile') {
// get string from file
// force translate the key from infile to options.
key = 'options';
try {
map[key] = fs.read(system.args[i + 1]).replace(/^\s+/, '');
} catch (e) {
console.log('Error: cannot find file, ' + system.args[i + 1]);
phantom.exit();
}
} else {
map[key] = system.args[i + 1];
}
}
}
return map;
};

render = function (params) {
var page = require('webpage').create(), createChart;

var bodyMale = config.SVG_MALE;
page.onConsoleMessage = function (msg) {
console.log(msg);
};

page.onAlert = function (msg) {
console.log(msg);
};

createChart = function (inputOption, width, height,config) {
var counter = 0;
function decrementImgCounter() {
counter -= 1;
if (counter < 1) {
console.log(messages.imagesLoaded);
}
}

function loadScript(varStr, codeStr) {
var script = $('<script>').attr('type', 'text/javascript');
script.html('var ' + varStr + ' = ' + codeStr);
document.getElementsByTagName("head")[0].appendChild(script[0]);
if (window[varStr] !== undefined) {
console.log('Echarts.' + varStr + ' has been parsed');
}
}

function loadImages() {
var images = $('image'), i, img;
if (images.length > 0) {
counter = images.length;
for (i = 0; i < images.length; i += 1) {
img = new Image();
img.onload = img.onerror = decrementImgCounter;
img.src = images[i].getAttribute('href');
}
} else {
console.log('The images have been loaded');
}
}
// load opitons
if (inputOption != 'undefined') {
// parse the options
loadScript('options', inputOption);
// disable the animation
options.animation = false;
}

// we render the image, so we need set background to white.
$(document.body).css('backgroundColor', 'white');
var container = $("<div>").appendTo(document.body);
container.attr('id', 'container');
container.css({
width: width,
height: height
});
// render the chart
var myChart = echarts.init(container[0]);
myChart.setOption(options);
// load images
loadImages();
return myChart.getDataURL();
};

// parse the params
page.open("about:blank", function (status) {
// inject the dependency js
page.injectJs(config.ESL);
page.injectJs(config.JQUERY);
page.injectJs(config.ECHARTS);


var width = pick(params.width, config.DEFAULT_WIDTH);
var height = pick(params.height, config.DEFAULT_HEIGHT);

// create the chart
var base64 = page.evaluate(createChart, params.options, width, height,config);
fs.write("base64.txt",base64);
// define the clip-rectangle
page.clipRect = {
top: 0,
left: 0,
width: width,

height: height
};
// render the image
page.render(params.outfile);
console.log('render complete:' + params.outfile);
// exit
phantom.exit();
});
};
// get the args
var params = parseParams();

// validate the params
if (params.options === undefined || params.options.length === 0) {
console.log("ERROR: No options or infile found.");
usage();
phantom.exit();
}
// set the default out file
if (params.outfile === undefined) {
var tmpDir = fs.workingDirectory + '/tmp';
// exists tmpDir and is it writable?
if (!fs.exists(tmpDir)) {
try {
fs.makeDirectory(tmpDir);
} catch (e) {
console.log('ERROR: Cannot make tmp directory');
}
}
params.outfile = tmpDir + "/" + new Date().getTime() + ".png";
}

// render the image
render(params);
}());

option.js

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{
title : {
text: '某地区蒸发量和降水量',
subtext: '纯属虚构'
},
tooltip : {
trigger: 'axis'
},
legend: {
data:['蒸发量','降水量']
},
toolbox: {
show : true,
feature : {
mark : {show: true},
dataView : {show: true, readOnly: false},
magicType : {show: true, type: ['line', 'bar']},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable : true,
xAxis : [
{
type : 'category',
data : ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']
}
],
yAxis : [
{
type : 'value'
}
],
series : [
{
name:'蒸发量',
type:'bar',
data:[2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3],
markPoint : {
data : [
{type : 'max', name: '最大值'},
{type : 'min', name: '最小值'}
]
},
markLine : {
data : [
{type : 'average', name: '平均值'}
]
}
},
{
name:'降水量',
type:'bar',
data:[2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3],
markPoint : {
data : [
{name : '年最高', value : 182.2, xAxis: 7, yAxis: 183, symbolSize:18},
{name : '年最低', value : 2.3, xAxis: 11, yAxis: 3}
]
},
markLine : {
data : [
{type : 'average', name : '平均值'}
]
}
}
]
};

运行结果:

外部资源无法加载

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{
title : {
text: '某地区蒸发量和降水量',
subtext: '纯属虚构'
},
tooltip : {
trigger: 'axis'
},
legend: {
data:['蒸发量','降水量']
},
toolbox: {
show : true,
feature : {
mark : {show: true},
dataView : {show: true, readOnly: false},
magicType : {show: true, type: ['line', 'bar']},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable : true,
xAxis : [
{
type : 'category',
data : ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']
}
],
yAxis : [
{
type : 'value'
}
],
series : [
{
name:'蒸发量',
type:'bar',
data:[2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3],
markPoint : {
data : [
{type : 'max', name: '最大值'},
{type : 'min', name: '最小值'}
]
},
markLine : {
data : [
{type : 'average', name: '平均值'}
]
}
},
{
name:'降水量',
type:'bar',
data:[2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3],
markPoint : {
data : [
{name : '年最高', value : 192.2, xAxis: 7, yAxis: 183, symbolSize:18,symbol:"image://./organ/gall.png"},
{name : '年最低', value : 2.3, xAxis: 11, yAxis: 3}
]
},
markLine : {
data : [
{type : 'average', name : '平均值'}
]
}
}
]
};

执行后,

没有symbol丢失。怀疑是symbol的图片无法外部加载。

docker私服搭建有官方的registry镜像,也有改版后的NexusOss3.x,因为maven的原因搭建了nexus,所以一并将docker私服也搭建到nexus上。

nexus的安装过程就单独说了,如果是2.x系列需要升级到2.14版本再升级到3.y系列,如果3.x到3.y直接升级就可以。

从3.0版本开始,nexus不再只是一个maven仓库,还可以是docker、npm、bower的私有仓库。

配置SSL

docker的仓库链接是基于HTTPS的,故一般情况下需要将nexus的访问方式改为支持https。
配置SSL主要的难点在于证书,证书可以用公网证书,而一般情况下,私服部署在内网,没有域名,用内网IP访问,这种情况下用自签名的证书是最好的选择。证书生成工具用jdk自带的就可以。
配置过程,进入${nexus}/etc/ssl目录下面,执行命令过程中会输入多次密码,记下密码,后面有用处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ keytool -genkeypair -keystore example.jks -storepass password -alias example.com \
> -keyalg RSA -keysize 2048 -validity 5000 -keypass password \
> -dname 'CN=*.example.com, OU=Sonatype, O=Sonatype, L=Unspecified, ST=Unspecified, C=US' \
> -ext 'SAN=DNS:nexus.example.com,DNS:clm.example.com,DNS:repo.example.com,DNS:www.example.com'

Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore example.jks -destkeystore example.jks -deststoretype pkcs12".
$ keytool -exportcert -keystore example.jks -alias example.com -rfc > example.cert
Enter keystore password: password

Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore example.jks -destkeystore example.jks -deststoretype pkcs12".
$ keytool -importkeystore -srckeystore example.jks -destkeystore example.p12 -deststoretype PKCS12
Importing keystore example.jks to example.p12...
Enter destination keystore password:
Re-enter new password:
Enter source keystore password:
Entry for alias example.com successfully imported.
Import command completed: 1 entries successfully imported, 0 entries failed or cancelled
$ openssl pkcs12 -nocerts -nodes -in example.p12 -out example.key
Enter Import Password:
MAC verified OK

修改配置文件:etc/nexus.properties

1
2
3
4
5
原:
nexus-args=${jetty.etc}/jetty.xml,${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-requestlog.xml
修改后:
application-port-ssl=8433
nexus-args=${jetty.etc}/jetty.xml,${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-https.xml,${jetty.etc}/jetty-requestlog.xml

修改配置文件:etc/jetty/jetty-https.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
<Set name="KeyStorePath"><Property name="ssl.etc"/>/keystore.jks</Set> // 文件名和前面证书的一致
<Set name="KeyStorePassword">password</Set> // 密码用前面的
<Set name="KeyManagerPassword">password</Set>
<Set name="TrustStorePath"><Property name="ssl.etc"/>/keystore.jks</Set>
<Set name="TrustStorePassword">password</Set>
<Set name="EndpointIdentificationAlgorithm"></Set>
<Set name="NeedClientAuth"><Property name="jetty.ssl.needClientAuth" default="false"/></Set>
<Set name="WantClientAuth"><Property name="jetty.ssl.wantClientAuth" default="false"/></Set>
<Set name="ExcludeCipherSuites">
<Array type="String">
<Item>SSL_RSA_WITH_DES_CBC_SHA</Item>
<Item>SSL_DHE_RSA_WITH_DES_CBC_SHA</Item>
<Item>SSL_DHE_DSS_WITH_DES_CBC_SHA</Item>
<Item>SSL_RSA_EXPORT_WITH_RC4_40_MD5</Item>
<Item>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
<Item>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</Item>
<Item>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</Item>
</Array>
</Set>
</New>

重启nexus,打开浏览器访问,https://ip:8443/ 会提示出来证书签名有问题,直接略过就行。

创建仓库(私有,代理,组合)

创建三个仓库,如果需要代理多个来源,可以创建多个代理库。
私有仓库:注意将http或者https的端口打开。

代理中央库的注意地址:
remote: https://registry-1.docker.io
docer index : use Docker hub

组合仓库将刚建立的Hosted和Proxy仓库都加入就可以了。

配置docker本地

本地配置最主要的是配置insecure-registries和docker login。

配置daemon.json

/etc/docker/daemon.json

1
2
3
4
{
"registry-mirrors": ["https://IP:18443"],
"insecure-registries":["IP:18443","IP:18444"]
}

为什么需要两个insecure-registry呢?因为group仓库不可以作为push仓库,如果单纯的进行pull,可以只配置一个。

docker login

分别login两个insecure-registry。

1
2
3
4
5
6
7
8
$ docker login IP:18444
Username (admin): admin
Password:
Login Succeeded
$ docker login IP:18443
Username (admin): admin
Password:
Login Succeeded

这样可以愉快的进行push和pull操作了。

push和pull的坑

nexus有一个设定,push只能对Hosted,pull从三种仓库都可以。因为push的操作给proxy的,是无法推送给被代理库的。group的仓库接受push后,无法确定是给proxy还是hosted。
但是nexus可以通过push给hosted的仓库,但是通过group仓库pull。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 3fd9065eaf02 5 months ago 4.15MB
IP:18443/alpine latest 3fd9065eaf02 5 months ago 4.15MB
$ docker tag alpine IP:18444/alpine
$ docker push IP:18444/alpine
The push refers to repository [IP:18444/alpine]
cd7100a72410: Layer already exists
latest: digest: sha256:8c03bb07a531c53ad7d0f6e7041b64d81f99c6e493cb39abba56d956b40eacbc size: 528
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 3fd9065eaf02 5 months ago 4.15MB
IP:18444/alpine latest 3fd9065eaf02 5 months ago 4.15MB
$ docker pull IP:18443/alpine
Using default tag: latest
latest: Pulling from alpine
Digest: sha256:8c03bb07a531c53ad7d0f6e7041b64d81f99c6e493cb39abba56d956b40eacbc
Status: Image is up to date for IP:18443/alpine:latest
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 3fd9065eaf02 5 months ago 4.15MB
IP:18443/alpine latest 3fd9065eaf02 5 months ago 4.15MB
IP:18444/alpine latest 3fd9065eaf02 5 months ago 4.15MB

完成。

0%