Docker 操作简记

1
2
3
4
FROM centos:7
RUN yun install -y git
RUN rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
RUN yum install -y nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 拉取镜像
docker pull <image-name>
# 本地提交镜像
docker commit <image-id> <namespace>/<image-name>
docker commit f30 jarvan4dev/centos7
# 启动容器
docker start <container-id>
# 停止所有容器
docker ps -a | awk '{print $1}' | xargs docker rm
# 查看所有容器
docker ps -a
# 进入容器内部
docker attach <container-id>
# 运行某个镜像
docker run -it -v /Users/jarvan4dev/Documents/docker:/ -p 20000:80 centos /bin/bash
# 提交镜像
docker login --username=<阿里云用户名> <阿里云仓库地址>
docker tag <image-id> <阿里云仓库地址>:<自定义版本>
docker push <阿里云仓库地址>:<自定义版本>

reference:
http://welcomeweb.blog.51cto.com/10487763/1735251

Leer más

Spring容器生命周期

如何在Spring应用启动后立即加载数据到缓存?

如何在Spring启动后执行某个任务?当然可以用quartz,但是没有必要

1、@PreConstruct

2、class MissionStarter implement InitializingBean

Leer más

SQL中的连接

Leer más

springMVC中的x-www-form-urlencoded和form-data

写好的接口经常会拿chrome中的插件postman进行测试,在postman中有两个选项,一个是 form-data,另一个是 x-www-form-urlencoded,有什么不同呢?

Leer más

ScheduledExecutorService

突然想起以前写的代码,处理的任务就是去平台查询订单状态,根据订单状态做后续处理。处理过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Order order = orderMapper.getOrder(orderId); // 从数据库查出订单
int status = order.getStatus();
int queryCount = 0;
while(true) {// 初始化状态
status = queryStatusFromPlatform(orderId); // 从平台查询订单状态
order.setStatus(status);
if(status == 0) {
sleep(500);// 线程停500ms
} else {
notify(order);
break;
}
if (queryCount >= 5) {
break;
}
}

// 通知第三方
private void notify(Order order) {
while(true) {
// 这里也是循环,发起通知,正确收到返回则停止,否则重试
sleep(500);
}
}

其实问题没什么大问题,但是当系统中任务比较多的时候,如果线程挂起,线程无法释放,会导致很多资源占用致使系统资源不足,可能会宕机 :smile

下面我们来用ScheduledExecutorService进行下改造!除了性能之外,代码会变得更优雅!做个有洁癖的程序员。

1
2


Leer más

乐观锁在MySQL中的应用

失败重试机制:http://blog.csdn.net/nihao_8/article/details/39525807

http://blog.csdn.net/zhanghongzheng3213/article/details/50819539

Leer más

乐观锁与悲观锁

http://www.tuicool.com/articles/yiyy6bI

http://chenzhou123520.iteye.com/blog/1863407

Leer más

spring-data-redis之秒杀

在现代电商中,秒杀是常用的促销手段,秒杀带来促销的同时,无疑给IT系统带来巨大冲击,今天我们就来模拟下秒杀。

超卖原因

下面我们说说,抢购流程!

购买流程图

在上面的图中,如果购买的人只有一个是没有问题的,但是如果是两个就有问题,可能会出现“超卖现象”。

  1. 有A,B两个线程操作数据库。
  2. 假设此时库存为1,即只允许一人买。
  3. A先查到数据库获取库存,A进行购买,支付成功后库存进行减一操作,但此时由于CPU切换,A被挂起。
  4. B访问数据库,此时A还未更新,B同样进行了购买,B成功进行了减一操作,数据库数量变为0,这个时候A被唤醒,也进行了减一操作,这就导致了库存变为-1,导致超卖。

如何控制?

方式一

加锁!最简单的方式,强制线程同步。同时缺点也很明显,那就是某一个线程操作的时候,其他线程必须等待它执行结束才能获取执行权。synchronized是典型的悲观锁,等待时间非常长,响应慢 。

方式二

使用队列,所有的请求全部进入队列,取其中前n个处理,其他的丢弃。但是并发量高的话,并发量会让队列内存瞬间升高,影响服务器性能。

方式三

使用乐观锁,乐观锁的一种实现是CAS(Compare And Swap),其思想是假设数据一般情况下没有冲突,在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做(在当前的情景下,直接返回秒杀失败即可)。

乐观锁实践

乐观锁,可以用redis的watch实现,也可以用MySQL实现,高并发情况下还是选择redis比较靠谱。其MySQL实现,请参看我的另一篇博客《乐观锁在MySQL中的应用》。

redis为什么能实现乐观锁?

在Redis的事务中,WATCH命令可用于提供CAS功能。假设我们通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Null multi-bulk应答以通知调用者事务执行失败。

具体实践

当商户在后台添加商品的时候,输入商品数,此时将商品的id作为key(key有规范的哦!),数量作为值,存入redis。

Talk is cheap, show me the code!

spring-redis.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
47
48
49
50
51
52
53
54
55
56
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 连接池配置 最大连接数 最大空闲数 最长等待时间 连接是否可用-->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!--最大连接数-->
<property name="maxTotal" value="${redis.maxActive}"/>
<!--最大空闲数-->
<property name="maxIdle" value="${redis.maxIdle}"/>
<!--最大连接数后最长阻塞时间-->
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
<!--当连接给调用者使用时,是否检测其有效性-->
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
<!--归还连接时,是否检测其有效性-->
<property name="testOnReturn" value="${redis.testOnReturn}"/>
<!--达到最大连接数是否阻塞-->
<property name="blockWhenExhausted" value="${redis.blockWhenExhausted}"/>
<!--空闲连接的检测周期-->
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
<!--当连接给调用者使用时,是否检测空间超时-->
<property name="testWhileIdle" value="${redis.testWhileIdle}"/>
</bean>

<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="hostName" value="${redis.host}"/>
<property name="port" value="${redis.port}"/>
<property name="password" value="${redis.password}"/>
<property name="usePool" value="${redis.usePool}"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
</bean>

<!--RedisTemplate:可操作对象,最终会将对象转化为字节(所以对象需支持序列化和反序列化)-->
<!--StringRedisTemplate:操作对象是String类型-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<!--开启事务支持-->
<property name="enableTransactionSupport" value="true"/>
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<!-- 序列化方式 建议key/hashKey采用StringRedisSerializer。 -->
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
</property>
</bean>
</beans>
redis.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#一个pool可分配多少个jedis实例
redis.maxActive=2048
#一个pool最多可有多少个状态为idle(空闲)的jedis实例
redis.maxIdle=300
#当borrow一个jedis实例时,最大的等待时间,如果超过了等待时间,则直接抛出JedisConnectionException
redis.maxWaitMillis=10000
#在borrow一个jedis实例时,是否提前进行validate操作,如果为true,则得到的jedis实例时可用的
redis.testOnBorrow=true
#连接耗尽时是否阻塞,false报异常,true阻塞直到超时,默认为true
redis.testOnReturn=false
redis.blockWhenExhausted=true
redis.testWhileIdle=false
redis.timeBetweenEvictionRunsMillis=60000
redis.host=127.0.0.1
redis.port=6379
redis.password=huweitech
redis.usePool=true
Java Code
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
package com.huweitech.redis.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
* Created by jarvan4dev on 2017/3/11.
*/
@ContextConfiguration(locations = "classpath:spring-context.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class RedisTest extends AbstractJUnit4SpringContextTests{

private static final String myWatchKey = "myWatchKey";

@Autowired
private RedisTemplate<String, String> redisTemplate;

@Test
public void testWatch() throws InterruptedException {
System.out.println(redisTemplate.getDefaultSerializer());
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set(myWatchKey, "10");
ExecutorService executor = Executors.newFixedThreadPool(200);
for (int i=0; i< 10000; i++) {
executor.execute(new SecKillRunnable(redisTemplate));
}
executor.awaitTermination(30, TimeUnit.SECONDS);
}

}
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
package com.huweitech.redis.test;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.List;

/**
* Created by jarvan4dev on 2017/3/16.
*/
public class SecKillRunnable implements Runnable{

private static final String myWatchKey = "myWatchKey";

private RedisTemplate<String, String> redisTemplate;
private ValueOperations<String, String> valueOperations;

public SecKillRunnable(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
this.valueOperations = redisTemplate.opsForValue();
}

@Override
public void run() {
System.out.println("线程执行");
redisTemplate.watch(myWatchKey);
Integer count = Integer.parseInt(valueOperations.get(myWatchKey));
System.out.println("当前剩余数量:" + count);
if (count > 0) {
System.out.println("进入");
redisTemplate.multi();
valueOperations.increment(myWatchKey, -1);
List<Object> list = redisTemplate.exec();
if (list != null) {
System.out.println("秒杀成功");
} else {
System.out.println("秒杀失败");
}
} else {
System.out.println("秒杀结束了");
}
}
}

关于spring-data-redis的RedisSerializer,请参看下面的第二篇参考链接。

参考文章

  1. http://lib.csdn.net/article/redis/18162
  2. http://blog.csdn.net/wangjun5159/article/details/52387782

Leer más

SpringMVC下载文件

在微信开发中
根据微信要求:
将文件MP_verify_bLdKbdjQoQFPia5u.txt(点击下载)
上传至填写域名或路径指向的web服务器(或虚拟主机)的目录(若填写域名,将文件放置在域名根目录下,
例如wx.qq.com/MP_verify_bLdKbdjQoQFPia5u.txt;
若填写路径,将文件放置在路径目录下,例如wx.qq.com/mp/MP_verify_bLdKbdjQoQFPia5u.txt),并确保可以访问。

本来放在Nginx中直接解决了,但是我是本地服务器,当然也可以直接放在工程目录webapp目录下,当时大脑短路,自己弄了个下载服务,后来弄了没什么用,还是留作记录吧,说不定以后用得上。

直接上代码

1
2
3
4
5
6
7
@RequestMapping(value = "/MP_verify_bLdKbdjQoQFPia5u.txt", method = RequestMethod.GET)
public ResponseEntity<byte[]> downloadMpFile() throws IOException {
File file = ResourceUtils.getFile("classpath:MP_verify_bLdKbdjQoQFPiM5u.txt");
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment", new String("MP_verify_bLdKbdjQoQFPiM5u.txt".getBytes("gb2312"), "iso8859-1"));
return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.OK);
}

Leer más

Ngrok---内网穿透程序

在微信开发中,需要将开发者服务器和微信服务器连接起来,但是如果每次修改了代码就要发布到公网,然后再调试,这无疑会大大降低开发速度。所以需要使用Ngrok将本地程序代理到公网,本来一直在使用 sunny-ngrok,一块钱1G流量,而且微信因为是测试开发者账号,所以没有要求域名备案,但是今天申请的服务号下来了,需要备案域名,所以sunny-ngrok就没法使用了。所以还是自己搭建Ngrok服务器咯。

我的配置

说说我的服务器,阿里云ECS,CentOS.64位。

Go

下载

Go下载地址

安装
1
2
3
4
5
tar -C /usr/local -xzf go1.8.linux-amd64.tar.gz
# 将以下内容写到 ~/.bash_profile中
export PATH=$PATH:/usr/local/go/bin
# 别忘了让 .bash_profile生效
source ~/.bash_profile
测试
1
2
[root@iZ94pjjvvshZ src]# go version
输出:go version go1.8 linux/amd64

以上内容在Go的下载页面都有说明。

Ngrok

下载
1
git clone https://github.com/inconshreveable/ngrok.git
生成自签名证书并替换默认证书

ngrok需要一个域名作为base域名,ngrok会为客户端分配base域名的子域名。例如:ngrok的base域名为tunnel.mydomain.com,客户端即可被分配子域名test.tunnel.mydomain.com。使用ngrok官方服务时,base域名是ngrok.com,并且使用默认的SSL证书。现在自建ngrok服务器,所以需要重新为自己的base域名生成证书。

我的base domain是dev-heaven.com,打算分配一个子域名wechat.dev-heaven.com给客户端。

1
2
3
4
5
6
7
cd ngrok
# 为base域名tunnel.mydomain.com生成证书
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=tunnel.mydomain.com" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=tunnel.mydomain.com" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

执行完上述命令,正常情况下,该目录会多出device.crt、device.csr、device.key、rootCA.key、rootCA.pem、rootCA.srl六个文件,用它们来替换默认的证书文件即可。默认的证书文件在“./assets/client/tls”和“./assets/server/tls/”目录中

1
2
3
4
# 替换默认的证书文件
cp rootCA.pem assets/client/tls/ngrokroot.crt
cp device.crt assets/server/tls/snakeoil.crt
cp device.key assets/server/tls/snakeoil.key
编译生成服务器端
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
make release-server release-client
输出:GOOS="" GOARCH="" go get github.com/jteeuwen/go-bindata/go-bindata
bin/go-bindata -nomemcopy -pkg=assets -tags=release \
-debug=false \
-o=src/ngrok/client/assets/assets_release.go \
assets/client/...
bin/go-bindata -nomemcopy -pkg=assets -tags=release \
-debug=false \
-o=src/ngrok/server/assets/assets_release.go \
assets/server/...
go get -tags 'release' -d -v ngrok/...
github.com/inconshreveable/mousetrap (download)
github.com/rcrowley/go-metrics (download)
Fetching https://gopkg.in/inconshreveable/go-update.v0?go-get=1
Parsing meta tags from https://gopkg.in/inconshreveable/go-update.v0?go-get=1 (status code 200)
get "gopkg.in/inconshreveable/go-update.v0": found meta tag main.metaImport{Prefix:"gopkg.in/inconshreveable/go-update.v0", VCS:"git", RepoRoot:"https://gopkg.in/inconshreveable/go-update.v0"} at https://gopkg.in/inconshreveable/go-update.v0?go-get=1
gopkg.in/inconshreveable/go-update.v0 (download)
github.com/kardianos/osext (download)
github.com/kr/binarydist (download)
Fetching https://gopkg.in/inconshreveable/go-update.v0/check?go-get=1
Parsing meta tags from https://gopkg.in/inconshreveable/go-update.v0/check?go-get=1 (status code 200)
get "gopkg.in/inconshreveable/go-update.v0/check": found meta tag main.metaImport{Prefix:"gopkg.in/inconshreveable/go-update.v0", VCS:"git", RepoRoot:"https://gopkg.in/inconshreveable/go-update.v0"} at https://gopkg.in/inconshreveable/go-update.v0/check?go-get=1
get "gopkg.in/inconshreveable/go-update.v0/check": verifying non-authoritative meta tag
Fetching https://gopkg.in/inconshreveable/go-update.v0?go-get=1
Parsing meta tags from https://gopkg.in/inconshreveable/go-update.v0?go-get=1 (status code 200)
Fetching https://gopkg.in/yaml.v1?go-get=1
Parsing meta tags from https://gopkg.in/yaml.v1?go-get=1 (status code 200)
get "gopkg.in/yaml.v1": found meta tag main.metaImport{Prefix:"gopkg.in/yaml.v1", VCS:"git", RepoRoot:"https://gopkg.in/yaml.v1"} at https://gopkg.in/yaml.v1?go-get=1
gopkg.in/yaml.v1 (download)
github.com/inconshreveable/go-vhost (download)
github.com/alecthomas/log4go (download)
github.com/nsf/termbox-go (download)
github.com/mattn/go-runewidth (download)
github.com/gorilla/websocket (download)
go install -tags 'release' ngrok/main/ngrokd

出现以上表示编译成功。

运行服务端ngrok
1
2
./bin/ngrokd -domain="tunnel.mydomain.com" -httpAddr=":8080" -httpsAddr=":4000"
默认的https端口和http端口是44380,但是这两个端口已经被Nginx占了,所以换成90004000

出现以下内容,即为成功

1
2
3
4
[21:30:55 CST 2017/03/09] [INFO] (ngrok/log.(*PrefixLogger).Info:83) [registry] [tun] No affinity cache specified
[21:30:55 CST 2017/03/09] [INFO] (ngrok/log.Info:112) Listening for public http connections on [::]:9000
[21:30:55 CST 2017/03/09] [INFO] (ngrok/log.Info:112) Listening for public https connections on [::]:4000
[21:30:55 CST 2017/03/09] [INFO] (ngrok/log.Info:112) Listening for control and proxy connections on [::]:4443

可以使用nohup后台运行。

1
nohup ./bin/ngrokd -domain="tunnel.mydomain.com" -httpAddr=":8080" -httpsAddr=":4000" &
编译客户端
Mac
1
GOOS=darwin GOARCH=amd64 make release-client
Windows
1
2
GOOS=windows GOARCH=amd64 make release-client
#以上GOARCH=amd64指的是编译为64位版本,如需32位改成GOARCH=386即可

以上编译成功后可以在./bin目录下找到对应的可执行程序,下载即可。

Leer más