JoyLau's Blog

JoyLau 的技术学习与思考

Redis-Master&Slave

配置

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
################################# REPLICATION #################################

# Master-Slave replication. Use slaveof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
#
# 1) Redis replication is asynchronous, but you can configure a master to
# stop accepting writes if it appears to be not connected with at least
# a given number of slaves.
# 2) Redis slaves are able to perform a partial resynchronization with the
# master if the replication link is lost for a relatively small amount of
# time. You may want to configure the replication backlog size (see the next
# sections of this file) with a sensible value depending on your needs.
# 3) Replication is automatic and does not need user intervention. After a
# network partition slaves automatically try to reconnect to masters
# and resynchronize with them.
#
slaveof xx.xx.xx.xx 6379

# If the master is password protected (using the "requirepass" configuration
# directive below) it is possible to tell the slave to authenticate before
# starting the replication synchronization process, otherwise the master will
# refuse the slave request.
#
masterauth xx

# When a slave loses its connection with the master, or when the replication
# is still in progress, the slave can act in two different ways:
#
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
# still reply to client requests, possibly with out of date data, or the
# data set may just be empty if this is the first synchronization.
#
# 2) if slave-serve-stale-data is set to 'no' the slave will reply with
# an error "SYNC with master in progress" to all the kind of commands
# but to INFO and SLAVEOF.
#
slave-serve-stale-data yes

# You can configure a slave instance to accept writes or not. Writing against
# a slave instance may be useful to store some ephemeral data (because data
# written on a slave will be easily deleted after resync with the master) but
# may also cause problems if clients are writing to it because of a
# misconfiguration.
#
# Since Redis 2.6 by default slaves are read-only.
#
# Note: read only slaves are not designed to be exposed to untrusted clients
# on the internet. It's just a protection layer against misuse of the instance.
# Still a read only slave exports by default all the administrative commands
# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
# security of read only slaves using 'rename-command' to shadow all the
# administrative / dangerous commands.
slave-read-only no

参数解释

  • slaveof : Slave库配置Master的ip地址和端口号
  • masterauth :如果Master配置了密码,那么这里设置密码
  • slave-serve-stale-data : 如果Master宕机了,Salve是否继续提供服务
  • slave-read-only : Slave 是否是只读模式,默认为是

部分配置项解释

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
daemonize yes #是否以后台进程运行,默认为no 
pidfile /var/run/redis.pid #如以后台进程运行,则需指定一个pid,默认为/var/run/redis.pid
bind 127.0.0.1 #绑定主机IP,默认值为127.0.0.1(注释)
port 6379 #监听端口,默认为6379
timeout 300 #超时时间,默认为300(秒)
loglevel notice #日志记slave-serve-stale-data yes:在master服务器挂掉或者同步失败时,从服务器是否继续提供服务。录等级,有4个可选值,debug,verbose(默认值),notice,warning
logfile /var/log/redis.log #日志记录方式,默认值为stdout
databases 16 #可用数据库数,默认值为16,默认数据库为0
save 900 1 #900秒(15分钟)内至少有1个key被改变
save 300 10 #300秒(5分钟)内至少有300个key被改变
save 60 10000 #60秒内至少有10000个key被改变
rdbcompression yes #存储至本地数据库时是否压缩数据,默认为yes
dbfilename dump.rdb #本地数据库文件名,默认值为dump.rdb
dir ./ #本地数据库存放路径,默认值为 ./

slaveof 10.0.0.12 6379 #当本机为从服务时,设置主服务的IP及端口(注释)
masterauth elain #当本机为从服务时,设置主服务的连接密码(注释)
slave-serve-stale-data yes #在master服务器挂掉或者同步失败时,从服务器是否继续提供服务。
requirepass elain #连接密码(注释)

maxclients 128 #最大客户端连接数,默认不限制(注释)
maxmemory #设置最大内存,达到最大内存设置后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理后,任到达最大内存设置,将无法再进行写入操作。(注释)
appendonly no #是否在每次更新操作后进行日志记录,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认值为no
appendfilename appendonly.aof #更新日志文件名,默认值为appendonly.aof(注释)
appendfsync everysec #更新日志条件,共有3个可选值。no表示等操作系统进行数据缓存同步到磁盘,always表示每次更新操作后手动调用fsync()将数据写到磁盘,everysec表示每秒同步一次(默认值)。

really-use-vm yes
vm-enabled yes #是否使用虚拟内存,默认值为no
vm-swap-file /tmp/redis.swap #虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-max-memory 0 #vm大小限制。0:不限制,建议60-80% 可用内存大小。
vm-page-size 32 #根据缓存内容大小调整,默认32字节。
vm-pages 134217728 #page数。每 8 page,会占用1字节内存。
vm-page-size #vm-pages 等于 swap 文件大小
vm-max-threads 4 #vm 最大io线程数。注意: 0 标志禁止使用vm
hash-max-zipmap-entries 512
hash-max-zipmap-value 64

list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
activerehashing yes

原理

  • 如果设置了一个Slave,无论是第一次连接还是重连到Master,它都会发出一个SYNC命令;
  • 当Master收到SYNC命令之后,会做两件事:
    a) Master执行BGSAVE,即在后台保存数据到磁盘(rdb快照文件);
    b) Master同时将新收到的写入和修改数据集的命令存入缓冲区(非查询类);
  • 当Master在后台把数据保存到快照文件完成之后,Master会把这个快照文件传送给Slave,而Slave则把内存清空后,加载该文件到内存中;
  • 而Master也会把此前收集到缓冲区中的命令,通过Reids命令协议形式转发给Slave,Slave执行这些命令,实现和Master的同步;
  • Master/Slave此后会不断通过异步方式进行命令的同步,达到最终数据的同步一致;
  • 需要注意的是Master和Slave之间一旦发生重连都会引发全量同步操作。但在2.8之后版本,也可能是部分同步操作。

部分复制

  • 2.8开始,当Master和Slave之间的连接断开之后,他们之间可以采用持续复制处理方式代替采用全量同步。
    Master端为复制流维护一个内存缓冲区(in-memory backlog),记录最近发送的复制流命令;同时,Master和Slave之间都维护一个复制偏移量(replication offset)和当前Master服务器ID(Master run id)。当网络断开,Slave尝试重连时:
    a. 如果MasterID相同(即仍是断网前的Master服务器),并且从断开时到当前时刻的历史命令依然在Master的内存缓冲区中存在,则Master会将缺失的这段时间的所有命令发送给Slave执行,然后复制工作就可以继续执行了;
    b. 否则,依然需要全量复制操作;
  • Redis 2.8 的这个部分重同步特性会用到一个新增的 PSYNC 内部命令, 而 Redis 2.8 以前的旧版本只有 SYNC 命令, 不过, 只要从服务器是 Redis 2.8 或以上的版本, 它就会根据主服务器的版本来决定到底是使用 PSYNC 还是 SYNC :
    如果主服务器是 Redis 2.8 或以上版本,那么从服务器使用 PSYNC 命令来进行同步。
    如果主服务器是 Redis 2.8 之前的版本,那么从服务器使用 SYNC 命令来进行同步。

同步机制

全量同步

Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:
  1)从服务器连接主服务器,发送SYNC命令;
  2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
  3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
  4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

增量同步

Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

Redis主从同步策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

最后

emoji

来个简单的小例子

2个项目先来测试一下:

  • eureka-server
  • eureka-service

eureka-server

pom 配置

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>cn.joylau.cloud.eureka.server</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>eureka-server</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

application.properties

1
2
3
4
5
server.port=8080
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

EurekaServerApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.joylau.cloud.eureka.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

eureka-service

pom 配置

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>cn.joylau.cloud.eureka.service</groupId>
<artifactId>eureka-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>eureka-service</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

application.properties

1
2
3
4
spring.application.name=eureka-service
server.port=8888
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka/

EurekaServerApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.joylau.cloud.eureka.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class EurekaServiceApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication.class, args);
}
}

ComputeController

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
package cn.joylau.cloud.eureka.service;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* Created by JoyLau on 4/14/2017.
* cn.joylau.cloud.eureka.service
*/
@RestController
public class ComputeController {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
private DiscoveryClient client;
@RequestMapping(value = "/add" ,method = RequestMethod.GET)
public Integer add(@RequestParam Integer a, @RequestParam Integer b) {
ServiceInstance instance = client.getLocalServiceInstance();
Integer r = a + b;
logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
return r;
}
}

配置说明

  • @EnableEurekaServer : 开启服务发现
  • eureka是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳(因此可以在内存中完成),在默认情况下eureka server也是一个eureka client ,必须要指定一个 server
  • 当client向server注册时,它会提供一些元数据,例如主机和端口,URL,主页等。Eureka server 从每个client实例接收心跳消息。 如果心跳超时,则通常将该实例从注册server中删除
  • @EnableDiscoveryClient : 注册一个微服务
  • spring.application.name :应用名

关于joylau-mybatis的说明

  • 该项目来源自 https://github.com/abel533/Mapper 详细信息和源代码可fork查看
  • 我封装之后项目地址 https://github.com/JoyLau/joylau-mybatis
  • 我自己整合通用Mapper,分页,以及排序功能,使用起来无缝结合,丝般顺滑
  • 我对其封装了所有的通用mapper,并整合本项目添加了自己的方法,详细请查看下文或者在线查看api文档: http://api.joylau.cn/
  • 文档你主要需要查看function的类注释
  • 下面我来逐一介绍:

BaseController

继承FunctionController,目前有2个抽象方法,getSession()和getContextPath(),一看就知道是干嘛的,不多说。想要扩展很简单,继续写自己的方法即可

BaseMapper

  • 集成了MySQL所使用的绝大部分通用Mapper,包括BaseMapper,ExampleMapper,RowBoundsMapper,MySqlMapper,IdsMapper…等等,详细可查看API文档,或者下载源码查看
  • 所有的单表及简单的多表操作都在这里面啦,基本上你是不需要扩展啦,好不好用,敲起mapper再点一下你就知道了

BaseService

  • 得益于Spring项目的强大支持,在Spring4.x后,支持泛型注入,这使得我们封装的更加简单了
  • 现在,不必再调用到Mapper层,现在在Service层就可以完美使用,封装了3个插入方法,4个更新方法,5个删除方法,13个查询方法
  • 内容涵盖了单条记录CRUD;根据ID或者属性或者条件CRUD;批量删除,插入;分页查询
  • 说下分页查询怎么使用:调用selectPage可以进行单表分页查询,调用selectPageByExample可以进行条件分页查询

BaseServiceImpl

  • 继承的FunctionServiceImpl已经实现了上述所有的通用CURD方法
  • 在继承的FunctionServiceImpl类里我提供了获取mapper的方法,由此方法,可以进行很方便的扩展,你懂得~~

BaseModel

  • 添加每个实体都会用到的id属性
  • 添加了createTime和updateTime属性,虽然在业务上可能没有什么用处,但是对于开发和运维的作用相当大,谁用谁知道

我的接口解释

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
180
181
182
183
184

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 保存一个实体,null的属性也会保存,不会使用数据库默认值
*/
int insert(T model);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 保存一个实体,null的属性不会保存,会使用数据库默认值
*/
int insertSelective(T model);


/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 批量插入,支持批量插入的数据库可以使用,另外该接口限制实体包含`id`属性并且必须为自增列
*/
int insertList(List<T> list);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据主键更新实体全部字段,null值会被更新
*/
int updateByPrimaryKey(T model);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据主键更新属性不为null的值
*/
int updateByPrimaryKeySelective(T model);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据Example条件更新实体`model`包含的全部属性,null值会被更新
*/
int updateByExample(T model, Object example);


/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据Example条件更新实体`model`包含的不是null的属性值
*/
int updateByExampleSelective(T model, Object example);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据实体属性作为条件进行删除,查询条件使用等号
*/
int delete(T model);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据实体id删除
*/
int deleteById(int id);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据Example条件删除数据
*/
int deleteByExample(Object example);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据主键字符串进行删除,类中只有存在一个带有@Id注解的字段
*
* @param ids 如 "1,2,3,4"
*/
int deleteByIds(String ids);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据主键字段进行删除,方法参数必须包含完整的主键属性
*/
int deleteByPrimaryKey(Object key);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据实体中的属性值进行查询,查询条件使用等号
*/
List<T> select(T model);


/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据实体中的id查询实体
*/
T selectById(int id);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 查询全部结果
*/
List<T> selectAll();

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据Example条件进行查询
*/
List<T> selectByExample(Object example);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据example条件和RowBounds进行分页查询
*/
List<T> selectByExampleAndRowBounds(Object example, RowBounds rowBounds);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据主键字符串进行查询,类中只有存在一个带有@Id注解的字段
*
* @param ids 如 "1,2,3,4"
*/
List<T> selectByIds(String ids);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
*/
T selectByPrimaryKey(Object key);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据实体中的属性查询总数,查询条件使用等号
*/
int selectCount(T model);


/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据Example条件进行查询总数
*/
int selectCountByExample(Object example);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号
*/
T selectOne(T model);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据实体属性和RowBounds进行分页查询
*/
List<T> selectByRowBounds(T model, RowBounds rowBounds);


/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 单表分页查询
*/
PageInfo selectPage(int pageNum, int pageSize, T model);

/**
* Created by JoyLau on 4/6/2017.
* 2587038142.liu@gmail.com
* 根据Example条件进行分页查询
*/
PageInfo selectPageByExample(int pageNum, int pageSize, Object example);

怎么使用?

很简单

  • 你的Mapper继承BaseMapper
  • 你的Service继承BaseService
  • 你的ServiceImpl实现你的Service借口,再继承BaseServiceImpl
  • 你的Model继承BaseModel

来试一下

  • 在你的ServiceImpl里点一下方法试试? 是不是很棒???
  • 在你的Mapper里再点一下方法试试?? 6666…

最后

  • 能想到的我都写了,BaseMapper和BaseServiceImpl基本上不需要扩展了,有不明白的可以联系我
  • 欢迎指正,共同学习

Redis-Benchmark

说明

  • redis默认提供了性能测试的工具
  • 在linux下文件是redis-benchmark
  • 在windows下文件是redis-benchmark.exe

参数查看

  • redis-benchmark -h
    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
    Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-
    k <boolean>]

    -h <hostname> Server hostname (default 127.0.0.1)
    -p <port> Server port (default 6379)
    -s <socket> Server socket (overrides host and port)
    -a <password> Password for Redis Auth
    -c <clients> Number of parallel connections (default 50)
    -n <requests> Total number of requests (default 100000)
    -d <size> Data size of SET/GET value in bytes (default 2)
    -dbnum <db> SELECT the specified db number (default 0)
    -k <boolean> 1=keep alive 0=reconnect (default 1)
    -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD
    Using this option the benchmark will expand the string __rand_int__
    inside an argument with a 12 digits number in the specified range
    from 0 to keyspacelen-1. The substitution changes every time a command
    is executed. Default tests use this to hit random keys in the
    specified range.
    -P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline).
    -q Quiet. Just show query/sec values
    --csv Output in CSV format
    -l Loop. Run the tests forever
    -t <tests> Only run the comma separated list of tests. The test
    names are the same as the ones produced as output.
    -I Idle mode. Just open N idle connections and wait.

    Examples:

    Run the benchmark with the default configuration against 127.0.0.1:6379:
    $ redis-benchmark

    Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:
    $ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20

    Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:
    $ redis-benchmark -t set -n 1000000 -r 100000000

    Benchmark 127.0.0.1:6379 for a few commands producing CSV output:
    $ redis-benchmark -t ping,set,get -n 100000 --csv

    Benchmark a specific command line:
    $ redis-benchmark -r 10000 -n 10000 eval 'return redis.call("ping")' 0

    Fill a list with 10000 random elements:
    $ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__

    On user specified command lines __rand_int__ is replaced with a random integer
    with a range of values selected by the -r option.

开始测试

  • 搞清了参数的含义,可以进行测试了
  • 本次配置为Redis的默认配置,默认的配置项已经有足够好的性能表现了,不需要调优
  • redis-benchmark -h joylau.cn -p 6379 -a XXX -t get,set -n 1000 -c 400 -q
    我是模仿了我自己现在公司的业务需求,测试了我直接服务器上的Redis,向redis服务器发送1000个请求,每个请求附带400个并发客户端,以静默显示
    redis-joylau-test-q
    可以看到,set操作每秒处理17241次,get操作每秒处理17543次
  • redis-benchmark -h joylau.cn -p 6379 -a XXX -t get,set -n 1000 -c 400
    同上,以标准格式显示
    redis-joylau-test
    可以看到,set操作每秒处理17857次,get操作每秒处理18518次
  • 我自己也开了本地的服务器做测试,每秒操作次数可达100000次

一些参数说明

  • -t : 可以选择你需要运行的测试用例
  • -r : 设置随机数来SET/GET/INCR
  • -P : 一次性执行多条命令,记得在多条命令需要处理时候使用 pipelining。

陷阱和错误的认识

第一点是显而易见的:基准测试的黄金准则是使用相同的标准。 用相同的任务量测试不同版本的 Redis,或者用相同的参数测试测试不同版本 Redis。 如果把 Redis 和其他工具测试,那就需要小心功能细节差异。

  • Redis 是一个服务器:所有的命令都包含网络或 IPC 消耗。这意味着和它和 SQLite, Berkeley DB, Tokyo/Kyoto Cabinet 等比较起来无意义, 因为大部分的消耗都在网络协议上面。
  • Redis 的大部分常用命令都有确认返回。有些数据存储系统则没有(比如 MongoDB 的写操作没有返回确认)。把 Redis 和其他单向调用命令存储系统比较意义不大。
    简单的循环操作 Redis 其实不是对 Redis 进行基准测试,而是测试你的网络(或者 IPC)延迟。想要真正测试 Redis,需要使用多个连接(比如 redis-benchmark), 或者使用 pipelining 来聚合多个命令,另外还可以采用多线程或多进程。
  • Redis 是一个内存数据库,同时提供一些可选的持久化功能。 如果你想和一个持久化服务器(MySQL, PostgreSQL 等等) 对比的话, 那你需要考虑启用 AOF 和适当的 fsync 策略。
  • Redis 是单线程服务。它并没有设计为多 CPU 进行优化。如果想要从多核获取好处, 那就考虑启用多个实例吧。将单实例 Redis 和多线程数据库对比是不公平的。

影响 Redis 性能的因素

有几个因素直接决定 Redis 的性能。它们能够改变基准测试的结果, 所以我们必须注意到它们。一般情况下,Redis 默认参数已经可以提供足够的性能, 不需要调优。

  • 网络带宽和延迟通常是最大短板。建议在基准测试之前使用 ping 来检查服务端到客户端的延迟。根据带宽,可以计算出最大吞吐量。 比如将 4 KB 的字符串塞入 Redis,吞吐量是 100000 q/s,那么实际需要 3.2 Gbits/s 的带宽,所以需要 10 GBits/s 网络连接, 1 Gbits/s 是不够的。 在很多线上服务中,Redis 吞吐会先被网络带宽限制住,而不是 CPU。 为了达到高吞吐量突破 TCP/IP 限制,最后采用 10 Gbits/s 的网卡, 或者多个 1 Gbits/s 网卡。
  • CPU 是另外一个重要的影响因素,由于是单线程模型,Redis 更喜欢大缓存快速 CPU, 而不是多核。这种场景下面,比较推荐 Intel CPU。AMD CPU 可能只有 Intel CPU 的一半性能(通过对 Nehalem EP/Westmere EP/Sandy 平台的对比)。 当其他条件相当时候,CPU 就成了 redis-benchmark 的限制因素。
  • 在小对象存取时候,内存速度和带宽看上去不是很重要,但是对大对象(> 10 KB), 它就变得重要起来。不过通常情况下面,倒不至于为了优化 Redis 而购买更高性能的内存模块。
  • Redis 在 VM 上会变慢。虚拟化对普通操作会有额外的消耗,Redis 对系统调用和网络终端不会有太多的 overhead。建议把 Redis 运行在物理机器上, 特别是当你很在意延迟时候。在最先进的虚拟化设备(VMWare)上面,redis-benchmark 的测试结果比物理机器上慢了一倍,很多 CPU 时间被消费在系统调用和中断上面。
  • 如果服务器和客户端都运行在同一个机器上面,那么 TCP/IP loopback 和 unix domain sockets 都可以使用。对 Linux 来说,使用 unix socket 可以比 TCP/IP loopback 快 50%。 默认 redis-benchmark 是使用 TCP/IP loopback。 当大量使用 pipelining 时候,unix domain sockets 的优势就不那么明显了。
  • 当大量使用 pipelining 时候,unix domain sockets 的优势就不那么明显了。
  • 当使用网络连接时,并且以太网网数据包在 1500 bytes 以下时, 将多条命令包装成 pipelining 可以大大提高效率。事实上,处理 10 bytes,100 bytes, 1000 bytes 的请求时候,吞吐量是差不多的

我想说

  • 我分别测试我之前在腾讯云上的Redis服务器 (Windows Server 2008R2) 和现在在阿里云上的服务器 (Linux CentOS 7.2) 及 局域网下同事的Redis服务器,和本机Redis的服务器
    速度最快的本机服务器,其次是同事的服务器,再次是阿里云上的服务器,最后是腾讯云上的服务器
  • 测试差异之大除了在硬件上的差别外,最客观的因素在网络带宽上,我自己2个位于云上的服务器都是1M的带宽,如此测试,正如上面所说,远远不达不到数据传输所需要的带宽值

最后

Docker

  • 安装: yum install docker
  • 卸载: yum remove docker
  • 启动: systemctl start docker
  • 开机自启: systemctl enable docker

Dockerfile

1
2
3
4
5
FROM java:8
MAINTAINER joylau
ADD joyalu-0.0.1-SNAPSHOT.jar joylau.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/joylau.jar"]

镜像

  • 编译镜像: docker build –t joylau/docker .
  • 查看镜像: docker images
  • 删除镜像: docker rmi name/id

容器

  • 运行: docker run –d --name joylau –p 8080:8080 joylau/docker
  • 停止容器: docker stop id/name
  • 查看运行中的容器 : docker ps
  • 查看所有容器: docker ps -a
  • 删除容器: docker rm id/name

2018-07-05 16:05:00 更新

拉取docker镜像

docker pull image_name
查看宿主机上的镜像,Docker镜像保存在/var/lib/docker目录下:

docker images

删除镜像

docker rmi docker.io/tomcat:7.0.77-jre7 或者 docker rmi b39c68b7af30
查看当前有哪些容器正在运行

docker ps
查看所有容器

docker ps -a
启动、停止、重启容器命令:

docker start container_name/container_id
docker stop container_name/container_id
docker restart container_name/container_id
后台启动一个容器后,如果想进入到这个容器,可以使用attach命令:

docker attach container_name/container_id
删除容器的命令:

docker rm container_name/container_id
删除所有停止的容器:

docker rm $(docker ps -a -q)
查看当前系统Docker信息

docker info
从Docker hub上下载某个镜像:

docker pull centos:latest
docker pull centos:latest
查找Docker Hub上的nginx镜像

docker search nginx
执行docker pull centos会将Centos这个仓库下面的所有镜像下载到本地repository。

2018-07-09 14:02:25 更新

docker search xxx : 在docker仓库查找镜像
docker images | grep xxx : 在本地仓库查找镜像

2018-07-12 09:44:40 更新

进入容器: docker exec -it 容器的ID或者NAME /bin/bash

2020-04-12 12:30:25 更新

查找自定义属性

1
docker ps -a --format "table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Status}}"

SpringBatch

组成部分

  • JobRepository: 用来注册Job的容器
  • JobLauncher: 用来启动Job的接口
  • Job : 我要实际执行的任务,包含一个或多个Step
  • Step : Step-步骤包含ItemReaderItemProcessorItemWrite
  • ItemReader : 用来读取数据的接口
  • ItemProcessor : 用来处理数据的接口
  • ItemWrite : 用来输出数据的接口

整合

SpringBoot 整合 SpringBatch 只需要引入依赖并注册成Spring 的 Bean 即可,若是想开启批处理的支持还需要在该配置类上添加 @EnableBatchProcessing

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
<!-- SpringBatch会自动加载hsqldb,我们去除即可 -->
<exclusions>
<exclusion>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</exclusion>
</exclusions>
</dependency>

来看代码 :

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
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Bean
public JobRepository jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager)
throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("oracle");
return jobRepositoryFactoryBean.getObject();
}

@Bean
public SimpleJobLauncher jobLauncher(DataSource dataSource, PlatformTransactionManager transactionManager)
throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository(dataSource, transactionManager));
return jobLauncher;
}

@Bean
public Job importJob(JobBuilderFactory jobs, Step s1) {
return jobs.get("importJob")
.incrementer(new RunIdIncrementer())
.flow(s1)
.end()
.listener(csvJobListener())
.build();
}

@Bean
public Step step1(StepBuilderFactory stepBuilderFactory, ItemReader<Person> reader, ItemWriter<Person> writer,
ItemProcessor<Person,Person> processor) {
return stepBuilderFactory
.get("step1")
.<Person, Person>chunk(65000) //1
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}



//接口分别实现

@Bean
public ItemReader<Person> reader() throws Exception {
//
return reader;
}

@Bean
public ItemProcessor<Person, Person> processor() {
//
return processor;
}



@Bean
public ItemWriter<Person> writer(DataSource dataSource) {//1
//
return writer;
}
}

貌似就这么简单的完成了……

扩展

  • 监听Job的执行情况,自定义类实现JobExecutionListener
  • 执行计划任务,在普通的计划任务方法中执行JobLauncher的run方法即可

emoji

准备

  • MySQL5.5.3+
  • mysql-connector-java5.1.13+

有异常

1
2
3
4
5
6
7
8
9
java.sql.SQLException: Incorrect string value: '\xF0\x9F\x92\x94' for colum n 'name' at row 1 
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1073)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3593)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3525)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1986)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2140)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2620)
at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1662)
at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1581)

配置项

1
2
3
4
5
6
7
8
9
10
11
    
[client]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
  • 数据库字符集:utf8mb4 -- UTF-8 Unicode
  • 排序规则:utf8mb4_general_ci

多样的浏览器兼容

1
2
3
4
5
6
7
8
9
<link href="http://cdn.staticfile.org/emoji/0.2.2/emoji.css" rel="stylesheet" type="text/css" />
<script src="http://cdn.staticfile.org/jquery/2.1.0/jquery.min.js"></script>
<script src="http://cdn.staticfile.org/emoji/0.2.2/emoji.js"></script>



var $text = $('.emojstext');
var html = $text.html().trim().replace(/\n/g, '<br/>');
$text.html(jEmoji.unifiedToHTML(html));

Sonatype

说明

  • 个人感觉第一次发布的步骤非常复杂,我在第一次操作的时候来来回回发布了7,8个版本,最后都是校验失败,导致构件不能关闭(因为我遇到了个大坑)
  • 第一次发布成功之后后面的更新和添加新的构件都相对来说要容易一些(groupid不变的情况下)

开始

账户注册

创建并提交工单

创建工单

  • Project和issue Type的填写如上图所示,不能填写错了
  • 创建完成之后就等待网站工作人员的审核就可以了,不知道为什么,我等待的时间非常短,2分钟都不到,工作人员就回复我了,可能是我的运气比较好吧,但是上个星期买房摇号我却没摇到伤心欲绝
  • 当issue 的 state 变为 RESOLVED时就可继续操作了,同时下面的活动区会给你发消息
    Comment

gpg生成密钥对

  • 下载安装:https://www.gpg4win.org/download.html
    安装时注意的是,只安装主体组件和加密解密窗口的组件就可以了,其他的不需要~~~~
  • 查看是否安装成功:gpg --version
    version
  • 生成密钥对:gpg --gen-key
    gpg --gen-key
    gpg --gen-key
  • 之后往下,会让你输入用户名和邮箱,还有一个Passphase,相当于密钥库密码,不要忘记。
  • 查看公钥:gpg --list-keys
  • 将公钥发布到 PGP 密钥服务器
    1
    2
    3
    4
    5
    gpg --keyserver hkp://pool.sks-keyservers.net --send-keys C990D076
    //可能由于网络问题,有点慢,多重试几次

    //查看发布是否成功
    gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys C990D076

配置setting.xml文件和pom.xml文件

  • setting.xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    <servers>
    <server>
    //记住id,需要和pom文件里的id一致
    <id>oss</id>
    <username>username</username>
    <password>password</password>
    </server>
    </servers>
  • pom.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
    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
    <!--
    ~ The MIT License (MIT)
    ~
    ~ Copyright (c) 2017 2587038142@qq.com
    ~
    ~ Permission is hereby granted, free of charge, to any person obtaining a copy
    ~ of this software and associated documentation files (the "Software"), to deal
    ~ in the Software without restriction, including without limitation the rights
    ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    ~ copies of the Software, and to permit persons to whom the Software is
    ~ furnished to do so, subject to the following conditions:
    ~
    ~ The above copyright notice and this permission notice shall be included in
    ~ all copies or substantial portions of the Software.
    ~
    ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    ~ THE SOFTWARE.
    -->

    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.joylau.code</groupId>
    <artifactId>joylau-echarts</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <name>joylau-echarts</name>
    <description>Configure the most attribute for ECharts3.0+ by Gson</description>
    <url>http://code.joylau.cn</url>

    <parent>
    <groupId>org.sonatype.oss</groupId>
    <artifactId>oss-parent</artifactId>
    <version>7</version>
    </parent>

    <licenses>
    <license>
    <name>The Apache Software License, Version 2.0</name>
    <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
    </license>
    </licenses>

    <developers>
    <developer>
    <name>JoyLau</name>
    <email>2587038142@qq.com</email>
    <url>http://joylau.cn</url>
    </developer>
    </developers>

    <scm>
    <connection>scm:git:git@github.com:JoyLau/joylau-echarts.git</connection>
    <developerConnection>scm:git:git@github.com:JoyLau/joylau-echarts.git</developerConnection>
    <url>git@github.com:JoyLau/joylau-echarts</url>
    </scm>
    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
    <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.5</version>
    <scope>compile</scope>
    <optional>true</optional>
    </dependency>
    </dependencies>

    <build>
    <testResources>
    <testResource>
    <directory>src/test/resources</directory>
    </testResource>
    <testResource>
    <directory>src/test/java</directory>
    </testResource>
    </testResources>
    </build>

    <profiles>
    <profile>
    <id>release</id>
    <build>
    <plugins>
    <!--Compiler-->
    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
    <source>1.7</source>
    <target>1.7</target>
    </configuration>
    </plugin>
    <!-- Source -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <version>2.2.1</version>
    <executions>
    <execution>
    <phase>package</phase>
    <goals>
    <goal>jar-no-fork</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    <!-- Javadoc -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>2.9.1</version>
    <executions>
    <execution>
    <phase>package</phase>
    <goals>
    <goal>jar</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    <!-- GPG -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-gpg-plugin</artifactId>
    <version>1.6</version>
    <executions>
    <execution>
    <phase>verify</phase>
    <goals>
    <goal>sign</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>
    <distributionManagement>
    <snapshotRepository>
    <id>oss</id>
    <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
    </snapshotRepository>
    <repository>
    <id>oss</id>
    <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
    </repository>
    </distributionManagement>
    </profile>
    </profiles>
    </project>

上传构件到 OSS 中

1
2
3
mvn clean deploy -P release
<--! jdk1.8后再生成javadoc时语法较为严格,这时去除javadoc即可 !-->
mvn clean deploy -P release -Dmaven.javadoc.skip=true

在上传之前会自动弹出一个对话框,需要输入上面提到的 Passphase,它就是刚才设置的 GPG 密钥库的密码。
随后会看到大量的 upload 信息,因为在国内网络的缘故,时间有点久,耐心等待吧。

在 OSS 中发布构件

  • 在 OSS 中,使用自己的 Sonatype 账号登录后,可在 Staging Repositories 中查看刚才已上传的构件,这些构件目前是放在 Staging 仓库中,可进行模糊查询,快速定位到自己的构件。
  • 此时,该构件的状态为 Open,需要勾选它,然后点击 Close 按钮。系统会自动验证该构件是否满足指定要求,当验证完毕后,状态会变为 Closed。
    1
  • 最后,点击 Release 按钮来发布该构件,这一步没有截图,将就看吧知道就行:
    2

等待构件审批通过

这个,又只能等待了,当然他们晚上上班,还是第二天看。当审批通过后,将会收到邮件通知。

从中央仓库中搜索构件

这时,就可以在maven的中央仓库中搜索到自己发布的构件了,以后可以直接在pom.xml中使用了!

中央仓库搜索网站:http://search.maven.org/

第一次成功发布之后,以后就不用这么麻烦了,可以直接使用Group Id发布任何的构件,当然前提是Group Id没有变。

以后的发布流程:

a)构件完成后直接使用maven在命令行上传构建;

b)在https://oss.sonatype.org/ close并release构件;

c)等待同步好(大约2小时多)之后,就可以使用了

遇坑记录

  • 安装GPG时候,没有安装弹框组件,导致gpg密码框弹不出来
  • 一开始所有的命令行都在git下操作,每次部署的时候都是提示没有私钥错误,后来发现git生成的gpg密钥对在user更目录下,切换到CMD操作,是生成在AppData下。经查看有私钥,问题解决

引用

0%