邱永臣叫西门追雪

喜剧演员,兼开发工程师(github.com/qiuyongchen)


  • Home

  • Archives

  • Search

局域网穿透综合解决方案(拓扑)

Posted on 2018-08-18 | Edited on 2019-03-19 | In 运维
Symbols count in article: 270 | Reading time ≈ 1 mins.

一、局域网
自从装上服务器之后,吃瓜群众我便是在其中跑了许多服务和应用,现今局域网如下:

从外网访问局域网,采用frp内网穿透,谷歌云是代理服务端;
服务器虚拟化技术采用kvm,管理UI是WebVirtMgr;

Read more »

JVM技术内幕总结

Posted on 2018-07-08 | Edited on 2019-02-19 | In jvm
Symbols count in article: 143 | Reading time ≈ 1 mins.

零、前言

大概在2年前开始写关于JVM的学习笔记,具体可以翻一下16年的笔记。

曾多次翻阅《深入理解JVM》等书和资料,对IDEA做过JVM优化,脑海中渐渐觉得对JVM的理解等级上升了不少,应该建立一个知识框架,所以有了这篇脑图笔记。

一、脑图笔记

以图片形式发布笔记,图很大,慎入。

Read more »

IDEA的JVM参数调优

Posted on 2018-03-01 | Edited on 2019-02-19 | In jvm
Symbols count in article: 1.5k | Reading time ≈ 2 mins.

一、前言

吃瓜群众陈达,有一台2核心8G内存的13寸Macbook Pro,工作中长时间使用IDEA,略感卡顿,所以稍微对IDEA底下的JVM做了些优化。

效果:
a) 『Reimport All Maven Projects』不再长时间卡死
b) 日常运行时,不再偶尔出现『神秘卡死』的状况。

Read more »

Java并发编程总结

Posted on 2018-01-07 | Edited on 2019-04-14 | In JAVA
Symbols count in article: 61 | Reading time ≈ 1 mins.

零、前言

吃瓜群众陈达,对高并发还是蛮感兴趣的,故也稍微深入地学习实践了一番并发相关的知识。

一、脑图笔记

(大图慎入)

Read more »

Java多线程深入总结

Posted on 2017-12-19 | Edited on 2019-04-14 | In JAVA多线程
Symbols count in article: 96 | Reading time ≈ 1 mins.

零、前言

多线程是一块比较基础的知识,先前呢,吃瓜群众对它的接触仅限于使用,底层原理略知一二,无奈无法拿出来给其它吃瓜群众吹水,所以这次深入研究一番,得此笔记图。

一、脑图笔记

(大图慎入)

Read more »

Java核心技术(卷一)

Posted on 2017-12-17 | Edited on 2019-05-07 | In JAVA
Symbols count in article: 66 | Reading time ≈ 1 mins.

零、前言

身为奋斗在一线的软件工作者,java基础知识是一定要熟练掌握的。

一、脑图笔记

本笔记为脑图型笔记,图片极长极大,慎入。

Read more »

用Flow.CI搭建CI/CD

Posted on 2017-12-06 | Edited on 2019-02-19 | In 运维
Symbols count in article: 4.7k | Reading time ≈ 8 mins.

一、前言

在上一篇文章里,我使用Travis来做自动部署,链接在此:Travis构建Google App Engine的CI/CD)。Travis使用起来挺方便,只不过它仅支持github。

不希望某些关键代码被人看到,而且,大部分代码托管在bitbucket上,挪移代码仓库不方便,考虑下来,只能找一个支持自定义代码仓库的CI平台了。
调研下来,有CodeShip/JenKins/Flow.Ci等几个工具。

CodeShip支持Bitbucket,只不过每个月只能构建100次。
JenKins是开源的,可以自己部署到服务器上,公司内部用的就是它,不过我不太喜欢JenKins,因为它的icon不甚好看…
Flow.Ci是国产开源的工具,现在(2018-02-13)还是Beta测试版本,类似Jenkins,自己部署,我选的就是它。

Read more »

Travis构建Google App Engine的CI/CD

Posted on 2017-12-04 | Edited on 2019-02-19 | In 运维
Symbols count in article: 1.6k | Reading time ≈ 3 mins.

一、前言

这篇文档的目的,是记录我使用Travis构建持续集成与自动部署的过程中踩到的坑,当做学习总结。

二、Travis

Travis是个CI/CD的平台。

简单地理解,你可以认为它给你提供了一台服务器。你往服务器上放一个CI/CD相关的脚本,脚本监控Github代码变更,调用Maven编译,并部署进你购买的服务器。

(注:我购买的服务器是谷歌云,它拥有App Engine,对外提供接口,使得Travis能调用接口进行部署。)

Read more »

Http的POST请求

Posted on 2017-12-01 | Edited on 2019-04-23
Symbols count in article: 940 | Reading time ≈ 2 mins.

项目系统经常需要调用外部系统的接口,使用http的post请求方式来获得数据,陈达经常搞混post的请求方式,在这里记一下。

Post

post请求,类似于get请求,拥有状态行、请求头header和请求体body。

状态行

状态行比较简单,一般都长下面的模样:

1
<methon> <url> <version>

比如:

1
POST www.iloveqyc.com HTTP/1.1

请求头

请求头则是用来说明解释本请求的上下文,比如请求头里可以指定cookie、UA等信息。在这里主要关注Content-Type这个信息,它指定了请求体body里面数据的编码方式。

编码方式是什么意思呢?陈达要给远方的服务器发消息,消息内容塞在body里面,服务器收到消息后怎么知道消息体里面是什么东西呢?把消息体当做key-value?当做json串?当做xml?服务器做不到啊~

所以在使用Post的时候,需要在Content-Type里指明消息体body的编码方式。

消息体

消息体主要就是用来承载该post请求要发送的内容,其编码方式可以用消息头header里的Content-Type指定。

Content-Type

application/x-www-form-urlencoded

这种方式很常见,一般页面上的表格都是用x-www-form-urlencoded的方式post,body里的提交值按照key1=value1&key2=value2进行排列,且进行url转码(比如转成unicode)。

这种方式也是目前项目用的post方式。

multipart/form-data

这种方式主要是用来上传文件,在body里面指定文件类型,和x-www-form-urlencoded一样,是2种最常用的方式。

application/json

使用这种方式,你可以在body里直接贴json字符串,而不用写键值对。(有的人不直接用application/json,而是使用x-www-form-urlencoded,在键值对的value里面写json,这倒也是一种方法)

text/xml

顾名思义,这种方式提交的是xml,不过xml太臃肿,用的比较少

利用AQS实现同步组件

Posted on 2017-11-23 | Edited on 2017-12-03
Symbols count in article: 4.2k | Reading time ≈ 7 mins.

AQS(AbstractQueueSynchronizer),抽象队列同步器,是jdk提供的一种用来自定义实现同步组件的工具,java并发中的ReentrantLock/CountDownLatch/Semaphore也都是继承了它而实现。

现在陈达想要用它来实现一个自己的同步器,功能是限制同一时间内只有2个线程才能获取锁。

总的思路是同步组件QycLock实现Lock接口,实现加锁lock与解锁unlock。QycLock内部集成了一个继承自AQS的自定义同步器QycSync,主要的同步逻辑均在QynSync上,QycLcok仅是个包装。

QycSync继承自AQS,便是拥有了同步状态和同步队列,把初始状态值设为2,一个线程获取同步状态后,用CAS的方式让状态值-1,释放同步状态后,用CAS的方式让状态值+1。

QycSync代码如下:

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
package com.iloveqyc.service.test.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
* Project: rcs-web
* PACKAGE_NAME: com.iloveqyc.service.test.lock
* Date: 2017/12/3
* Time: 下午2:20
*
* @author qiuyongchen David
* <p>
* Usage: 同步器
*/
public class QycLock implements Lock {

private QycSync qycSync = new QycSync(2);

@Override
public void lock() {
qycSync.acquireShared(1);
}

@Override
public void lockInterruptibly() throws InterruptedException {

}

@Override
public boolean tryLock() {
return false;
}

@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}

@Override
public void unlock() {
qycSync.releaseShared(1);
}

@Override
public Condition newCondition() {
return null;
}

static class QycSync extends AbstractQueuedSynchronizer {

QycSync(int count) {
if (count <= 0) {
throw new RuntimeException();
}
setState(count);
}

@Override
protected int tryAcquireShared(int arg) {
for (;;) {
int currentState = getState();
int newState = currentState - 1;
if (newState < 0) {
System.out.println(Thread.currentThread().getName() + "到达时同步状态已经小于0,将会在同步队列中调用sun.misc.Unsafe.park,进入等待状态");
return newState;
}
boolean isSuccess = compareAndSetState(currentState, newState);
if (!isSuccess) {
System.out.println(Thread.currentThread().getName() + "尝试获取锁失败, 同步状态currentState预期为" + currentState + ", 实际不是");
continue;
}
return newState;
}
}

@Override
protected boolean tryReleaseShared(int arg) {
int currentState = getState();
int newState = currentState + 1;
return compareAndSetState(currentState, newState);
}
}
}

QycThread代码如下:

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
package com.iloveqyc.service.test.lock;

/**
* Project: rcs-web
* PACKAGE_NAME: com.iloveqyc.service.test.lock
* Date: 2017/12/3
* Time: 下午2:47
*
* @author qiuyongchen David
* <p>
* Usage: xxx
*/
public class QycThread implements Runnable {

private QycLock qycLock;

public QycThread(QycLock qycLock) {
this.qycLock = qycLock;
}

@Override
public void run() {
qycLock.lock();
System.out.println(Thread.currentThread().getName() + "成功获取了锁!即将带着锁睡眠60s");
try {
Thread.sleep(60000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "睡眠60s结束,即将释放锁");
qycLock.unlock();
}

}

main方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.iloveqyc.service.test.lock;

/**
* Project: rcs-web
* PACKAGE_NAME: com.iloveqyc.service.test.lock
* Date: 2017/12/3
* Time: 上午9:52
*
* @author qiuyongchen David
* <p>
* Usage: xxx
*/
public class QycTest {
public static void main(String[] args) {
QycLock lock = new QycLock();
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new QycThread(lock));
t.setName(String.valueOf(i));
t.start();
}
}
}

日期分页算法

Posted on 2017-10-25 | Edited on 2018-06-18
Symbols count in article: 2.5k | Reading time ≈ 4 mins.

吃瓜群众

作为吃瓜群众的陈达,一向觉得分页算法不难,瞧不起人家,直到最近才发现,分页算法也需要细致的考虑,做得不好是会出现问题滴。

Read more »

用户用浏览器访问一个网站的时候背后的过程与步骤

Posted on 2017-10-01 | Edited on 2017-10-09
Symbols count in article: 1.1k | Reading time ≈ 2 mins.

『注:这是我一年前在知乎的回答,高赞,不知为何被知乎删了,故搬移至此』

假设你用一个全新的浏览器(第一次启动的那种),访问百度(http://www.baidu.com/),在你敲入网址并按下回车之后,将会发生以下神奇的事情:

浏览器先尝试从Host文件中获取www.baidu.com对应的IP地址,如果能取到当然万事大吉大家都能嗨,如果不能,就使用DNS协议来获取IP咯。

在DNS协议中,PC会向你的本地DNS服务器求助(一般是路由器),希望从本地DNS服务器那里得到百度的IP,得到就好,得不到还得向更高层次的DNS服务器求助,最终总能得到百度的IP。

得到百度的IP,下一步是使用TCP协议,建立TCP连接。

在TCP协议中,建立TCP需要与百度服务器握手三次,你先告诉服务器你要给服务器发东西(SYN),服务器应答你并告诉你它也要给你发东西(SYN、ACK),然后你应答服务器(ACK),总共来回了3次,称为3次握手。

不过,建立TCP连接有个前提(或者说给服务器发消息有个前提):你必须能成功地把消息发到服务器上。虽然已经知道IP,但并无啥用(比如说,你在广东,你知道北京的地理坐标经纬度就能到北京了?你得知道有哪些路通往北京吧你得准备盘缠吧你得花时间吧)。

为了将消息从你的PC上传到服务器上,需要用到IP协议、ARP协议和OSPF协议。

我们都知道,你的PC和百度服务器之间一般会有许多路由器之类的东西,IP协议指定了出发地(你的PC)和目的地(服务器);你的数据会经过一个又一个路由器,OSPF决定了会经过那些路由器(用一种叫路由算法的玩意,找出最佳路径);从一个路由器怎么传给下一个路由器?这是ARP协议的JOB,
ARP负责求下一个节点的地址(我们不止是要目的地,还要中间节点的地址)。IP协议使用的是IP地址,整个发送过程中只涉及出发地和目的地2个IP地址,
而ARP协议使用的是MAC地址,整个发送过程中涉及到每一个节点的MAP地址

现在,我们能和服务器通信,还建立了TCP连接,下一步干嘛,当然是用HTTP协议请求网页内容咯。

你发个HTTP请求报文给服务器,如果服务器禁止你访问它就给你回个”Forbidden”,如果它暂时挂掉了就给你回个“内部服务错误”,如果它正常才给你回个“OK“并将你要的数据传给你;如果你还需要其它的东西再去跟它要(它一般还会给你的-_-)。

你收到了服务器的回复,是一坨HTML形式的文本。浏览器必须要能够理解文本的内容,并快速地渲染到屏幕上(浏览器一般用有限自动机来理解文本内容,渲染的话就各看本事了,之所以微软IE卡成狗而谷歌浏览器很6,就是它们的渲染速度不同…)

渲染出来后,你就看到百度的首页了

弹性的定义

Posted on 2017-07-19 | Edited on 2019-02-20
Symbols count in article: 439 | Reading time ≈ 1 mins.

可用性定义

可用性 = MTTF / ( MTTF + MTTR )
MTTF:Mean Time To Failure,平均失效时间,指的是系统的正常运转时间
MTTR:Mean Time To Recovery,平均修复时间,指的是系统从出现故障到恢复的时长

Read more »

log4j日志保存服务器

Posted on 2017-06-12 | Edited on 2017-07-12
Symbols count in article: 5.1k | Reading time ≈ 8 mins.

在之前的『从0到1理解Java Web』的『日志框架』部分,我们知道了如何让项目打印日志在控制台上,方便我们调试观察代码的运行,这篇文章主要是介绍如何把日志打印输出到服务器文件上保存起来,上线后借助日志文件找到Bug。

这里分3部分,一个是流的概念,一个是log4j配置,另一个是打印spring自身的日志。

流

在linux中有流的概念,从一个地方流入(比如键盘,比如扫描仪,比如文件),流出到另一个地方(比如控制台,比如文件,比如屏幕),上一次我们实现了『从日志系统流出到控制台』,这次实现『从日志系统流出到文件』即可。

把日志的去向从控制台改到文件,需要改动哪里?这牵扯到第二部分,也就是log4j的配置。

log4j配置

log4j有3个组件,loggers/appenders/layouts,它们一起协同工作,指定了日志内容的格式,根据日志的类型与级别,将日志输出到指定的位置。

loggers

指定日志输出的最低级别,把级别信息传给appenders。
日志级别由低到高分别是:Trace < Debug < Info < Warn < Error < Fatal,只记录大于等于当前级别的信息。

appenders

指定日志的输出方式与保存位置。
主要有以下几类appender:
1.ConsoleAppender :将日志输出至控制台
2.DailyRollingFileAppender :将日志输出至文件,日志文件每天切割
3.FileAppender :将日志输出至文件
4.RollingFileAppender :将日志输出至文件,日志文件按指定的大小进行切割
5.WriterAppender :将日志以信息流的格式发送到指定位置

layouts

指定日志内容的格式,比如输出为Html等。

这3个组件的具体配置都在log4j.xml中。把lo4j4.xml修改为以下的内容:

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
<?xml version="1.0" encoding="UTF-8"?>

<Configuration status="info" packages="com.dianping">
<Properties>
<!--日志输入路径-->
<Property name="log-path">/data/applogs/${sys:app.name}/logs</Property>
<!--日志输出到cat格式-->
<Property name="pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [${sys:app.name} ${hostName}}] [%-5level] [%t] [%c] - %msg%xEx%n</Property>
<!--日志进入存储中心的格式-->
<Property name="dw-pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} ${hostName} ${sys:app.name} %p %t %c %msg%xEx%n</Property>
<Property name="scribeCategory">${sys:app.name}</Property> <!--日志需要和日志采集的一致-->
</Properties>

<Appenders>
<!-- 记录除用户行为日志外的所有信息 -->
<RollingFile name="appAppender" fileName="${log-path}/app.log" filePattern="${log-path}/app.log.%d{yyyy-MM-dd}">
<PatternLayout pattern="${pattern}"/>
<Policies>
<!-- 这里的interval由filePattern中的最小时间单位来决定,如果filePattern 中是天,这1就是1天-->
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
</RollingFile>
<!-- 独立的 error appender 便于排查问题 -->
<RollingFile name="errorAppender" fileName="${log-path}/error.log"
filePattern="${log-path}/error.log.%d{yyyy-MM-dd}">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${pattern}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
</RollingFile>
<!--打印日志到控制台,方便调试-->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level [%c] - %msg%n"/>
</Console>
</Appenders>

<Loggers>
<Root level="INFO">
<AppenderRef ref="appAppender"/>
<AppenderRef ref="errorAppender"/>
<AppenderRef ref="Console"/>
</Root>
</Loggers>

</Configuration>

另外建立classpath:META-INF下建立文件app.properties(即mai/resources/METa-INF下),在里面指定项目的名称,内容如下:

1
app.name=my-project-name

随后启动项目,进入/data/applogs/my-project-name/logs目录,即可看到日志文件。

到这里,我们已经成功地将代码里的日志输出到文件里。每天保存,可是,如果你仔细点观察,会发现日志文件里并没有spring和其它第三方库的日志,这可是大问题,很多时候Bug是spring的bean注入失败或找不到对应的类引起的,如果没有spring的日志,我们很难追查到真正原因。

监听web运行中第三方库日志(如spring)

监听web运行时的日志,那么我们就能监听到所有日志,包括spring和其它技术栈的日志。

在web项目配置文件web.xml中加入对应的监听器,内容如下:

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
<context-param>
<param-name>log4jContextName</param-name>
<param-value>poseidon-settlement-service</param-value>
</context-param>

<!--由Spring载入的Log4j配置文件位置 -->
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>classpath:log4j2.xml</param-value>
</context-param>

<listener>
<listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>

<filter>
<filter-name>log4jServletFilter</filter-name>
<filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>log4jServletFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>

<!--Spring默认刷新Log4j配置文件的间隔,单位为毫秒 -->
<context-param>
<param-name>log4jRefreshInterval</param-name>
<param-value>60000</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

同时引入log4j-web包,启动项目,即可。

Quartz源码分析

Posted on 2017-05-10 | Edited on 2019-02-18
Symbols count in article: 32k | Reading time ≈ 54 mins.

零、简要

按照上一篇的介绍,Quartz是个作业调度系统,核心组件有Job/Trigger/Scheduler等,如何使用Quartz我们已经知晓,此次细心看看其内部的代码逻辑。

Read more »
123…7

邱永臣

一个吃瓜群众,有时跑龙套,有时码字.

100 posts
13 categories
33 tags
GitHub E-Mail
© 2015 – 2021 邱永臣 | 480k | 13:20
备案号 粤ICP备19015297号
|