Sunshow Life

Beyond


  • 首页

  • 归档

  • 标签

缓存使用最佳实践

发表于 2017-08-17 | 分类于 Programming

所谓最佳实践没有标准,只是把个人的一些经验总结一下。

根据使用场景,我把缓存简单分成两个大类:

  1. 非一致性缓存
  2. 一致性缓存

一般规则

为每一个缓存设置过期时间

推荐在任何时候设置缓存时,都设置一定的有效期或者过期时间。

设置过期时间,大体上有以下好处:

  1. 让开发者养成不依赖于缓存存在的开发习惯
  2. 明确区别于持久化存储
  3. 避免消耗过多的内存(缓存空间)

对于第三点,以 Redis 为例,默认 Redis 只会使用内存作为缓存存储空间,如果有持续增加的不会过期的缓存内容生成,最终会导致内存耗尽导致服务崩溃,我曾经吃过这个亏。

允许缓存失效且可被重建

从缓存这个名字的中文字面意思理解,缓存应该是一个临时性的存储,开发者使用时必须要做好面对缓存不存在的准备,同时根据不同的使用场景,决定是否需要重建缓存。

以我司某历史遗留系统功能为例,由于查询过慢,最后使用的解决方案是单独跑一个线程以较短间隔定期去执行查询并设置一个长效缓存,前端业务直接读取缓存结果展现。

这个方案属于很常见的处理方案,并没有什么大的问题,但是一旦设置缓存的线程跑挂了,就会导致缓存失效或者缓存长时间不更新导致用户看不到最新数据等问题。

可选的改进方案之一,在前端业务未请求到缓存数据时触发缓存刷新(何时刷新,刷新策略,都是一个很复杂的命题,需要根据具体使用场景和时效性要求具体分析),同时发送系统内部报警给相关负责人。

对缓存的 Key 进行统一管理

对于 Key-Value 缓存,由于使用方便,如果没有一个好的开发规范对 Key 进行统一管理,不可避免的会造成问题追溯困难、Key 冲突等问题。

好的 Key 管理,应该考虑到以下这些方面:

  1. 支持 namespace ,通常表现为 Key 前缀的形式
  2. Key 生成策略配置和管理
  3. 支持分区/分片,例如 Redis 的不同 DB ,或者分布式系统的水平拆分等

当完成了统一管理之后,就可以简单的通过 Filter 等机制,加入一些例如统计之类的功能。

非一致性缓存

即缓存数据和持久层的数据允许不一致,绝大多数的缓存应用场景都可以是非一致性缓存,例如各种列表查询业务。

用流行的术语来讲,非一致性缓存只追求最终一致性,对于一致性的期望值直接取决于缓存时长的设置。

非一致性缓存通常是短时的缓存,一般不会超过分钟级。

前端系统对所有查询设置缓存

对于直接被触发的前端业务系统,大部分情况下请求数量并不受自己控制,此时推荐对所有查询结果设置缓存。

对于使用 Spring 等流行框架开发的 Web 系统来说,通常都可以很方便的在全局实现查询结果缓存,并根据不同的业务需求对不同的请求接口设置缓存时间,例如可以通过设置全局默认一秒的缓存时间之类的设计来抵抗并发。

防止雪崩

极端情况下,如果大量请求进入的瞬间缓存失效,此时缓存的重建策略如果是每个连接各自通过持久层读取,则可能对持久层带来巨大压力导致雪崩效应发生。

对于这种情况,流行的做法有单连接更新其他连接等待、限流排队等处理方式,具体采用何种方式仍需要根据业务场景进行分析。

一致性缓存

一致性缓存通常作为持久层的一个中间层,为减少数据库的查询压力使用。例如 Hibernate 的一级缓存,相应的上述的非一致性缓存也可以称为是二级缓存。

一致性缓存通常是长时间的缓存,其更新时间可能是定期的,也可能是根据持久层的更新而更新。

对使用者不可见

由于一致性的保障,一致性缓存可以和持久层作为一个整体对外提供服务,使用者不需要关心数据来源于缓存还是持久层。

写操作统一入口

还是为了更好的保障一致性,缓存的写操作入口一般和持久层的写操作同时进行,且只有一处入口。

保证更新成功

对于非一致性缓存,基本不用关心缓存的更新问题,直接等缓存自然失效并重建即可,或者通过缓存的 invalidate 调用强制失效重建。

而对于一致性缓存,更新操作必须要保证缓存更新成功,如果只是简单的使原缓存失效,就会导致不一致。

看以下场景:

线程一:读取缓存 A (1) -> A 失效 -> 读取 A 的持久层数据 (2) -> 设置新的缓存 A (3)
线程二:更新 A 的持久层数据 (4) -> 使缓存 A 失效 (5)

如果上述的 (2) 操作发生在 (4) 之前,(3) 操作发生在 (5) 之后,则会创建出一个在 (4) 操作之前的历史脏数据的缓存,导致非一致性。

所幸,各个流行的缓存系统均提供了类似于 CAS (Check and Set) 的操作来保证更新成功。

批量 Kill MySQL 连接

发表于 2017-06-01 | 分类于 Database

基础知识:

1
2
3
4
5
6
7
-- 查看当前连接
show processlist;
-- 查看当前连接完整内容
show full processlist;
-- Kill 一个指定连接
-- kill {processlist_id}
kill 1234567;

当需要批量 Kill 多个连接时,可以通过查询 information_schema.processlist 表进行拼装:

1
select concat('KILL ',id,';') from information_schema.processlist where user='db_user';

同样可以利用这个表对当前连接进行相应的条件的查找,表结构如下:

1
2
3
4
5
6
7
8
9
10
11
show create table information_schema.processlist;
CREATE TEMPORARY TABLE `PROCESSLIST` (
`ID` bigint(21) unsigned NOT NULL DEFAULT '0',
`USER` varchar(16) NOT NULL DEFAULT '',
`HOST` varchar(64) NOT NULL DEFAULT '',
`DB` varchar(64) DEFAULT NULL,
`COMMAND` varchar(16) NOT NULL DEFAULT '',
`TIME` int(7) NOT NULL DEFAULT '0',
`STATE` varchar(64) DEFAULT NULL,
`INFO` longtext
) ENGINE=MyISAM DEFAULT CHARSET=utf8

Spring Data JPA 添加自定义默认方法

发表于 2017-05-18 | 分类于 Java

通常使用 Spring Data JPA 时,都会创建一个 BaseRepository,或者继承 Spring 已经预设的一些预设的接口例如 CrudRepository 等,以自建 BaseRepository 为例(为了简化只保留了一个方法):

1
2
3
4
@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
T findOne(ID id);
}

加载基础配置:

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
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "foo.bar.repository")
public class JpaConfig {
@Override
protected String entityPackagesToScan() {
return "foo.bar.entity";
}
private Properties additionalProperties() {
Properties properties = new Properties();
// 省略各种参数配置
return properties;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
// 指定要扫描的 entity 包路径
em.setPackagesToScan(entityPackagesToScan());
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
@PreDestroy
public void preDestroy() {
dataSource().close();
}
@PostConstruct
public void postConstruct() {
try {
dataSource().init();
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
}
@Bean
public DruidDataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
// 省略数据源配置
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(){
EntityManagerFactory factory = entityManagerFactory().getObject();
return new JpaTransactionManager(factory);
}
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
}

以上就完成了基本配置可以使用 Spring Data JPA 了,回到本文的目的:如何添加一个自定义的默认方法?
为单个 Repository 添加自定义实现的方法很简单略过,下面以添加一个名为 findOneForUpdate 的自定义默认方法为例进行代码示例(即全局为每个 Entity 的 Repository 添加按 ID 锁行功能)。

首先创建一个新的接口 BaseInternalRepository :

1
2
3
4
5
6
7
8
9
@NoRepositoryBean
public interface BaseInternalRepository<T, ID extends Serializable> extends Repository<T, ID> {
/**
* 锁行读写的方式通过ID获取对象
* @param id 对象ID
* @return 对应的对象
*/
T findOneForUpdate(ID id);
}

修改原先的 BaseRepository,使其继承新的这个接口:

1
2
3
4
@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends BaseInternalRepository<T, ID> {
T findOne(ID id);
}

为新创建的 BaseInternalRepository 接口添加实现:

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
public class BaseInternalRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseInternalRepository<T, ID> {
private final EntityManager entityManager;
public BaseInternalRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
public BaseInternalRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
@Override
public T findOneForUpdate(ID id) {
return entityManager.find(this.getDomainClass(), id, LockModeType.PESSIMISTIC_WRITE);
}
protected Page<T> readPageWithoutCountQuery(TypedQuery<T> query, Pageable pageable, Specification<T> spec) {
query.setFirstResult(pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
List<T> content = query.getResultList();
return new PageImpl<T>(content, pageable, content.size());
}
}

注:SimpleJpaRepository 即 Spring Data JPA 默认用来代理创建 Repository 实例的实现类。

接下来要把自定义的实现替换掉默认实现,先创建一个自定义的生成 Repository 实例的工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T,
I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {
protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
return new BaseRepositoryFactory(em);
}
private static class BaseRepositoryFactory<T, I extends Serializable>
extends JpaRepositoryFactory {
public BaseRepositoryFactory(EntityManager em) {
super(em);
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// 使用自定义的实现类
return BaseInternalRepositoryImpl.class;
}
}
}

修改 JPA 配置使用自定义的工厂类:

1
2
3
4
5
6
7
8
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "foo.bar.repository",
repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class)
public class JpaConfig {
// 省去中间代码
}

大功告成!

基本原理是利用了 Repository 实例的创建过程:

  1. 查找是否有自定义的实现,如果有使用自定义实现提供的方法
  2. 如果还有未实现的方法,使用默认代理类中的方法,即 BaseInternalRepositoryImpl

通过这种方式,可以很方便的添加各种自定义的默认方法,如果有需要定制的,也可以在每个 Repository 的自定义实现中进行覆盖。

Hello World

发表于 2017-05-17

Launch a new blog, which is based on github.io and hexo.

SpringBoot 非 Web 项目运行

发表于 2017-01-06 | 分类于 Java

有时候一些项目并不需要提供 Web 服务,例如跑定时任务的项目,如果都按照 Web 项目启动未免画蛇添足浪费资源

为了达到非 Web 运行的效果,首先调整 Maven 依赖,不再依赖 spring-boot-starter-web,转而依赖最基础的 spring-boot-starter:

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

此时按照原先的方式启动 SpringBootApplication 会发现启动加载完之后会立即退出,这时需要做点工作让主线程阻塞让程序不退出:

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class SampleApplication implements CommandLineRunner {
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
Thread.currentThread().join();
}
}

这里利用了 SpringBoot 提供的 CommandLineRunner 特性,这个名字比较有欺骗性,实际效果如下:

SpringBoot 应用程序在启动后,会遍历 CommandLineRunner 接口的实例并运行它们的 run 方法。也可以利用 @Order 注解(或者实现Order接口)来规定所有 CommandLineRunner 实例的运行顺序

OkHttp 和 AsyncHttpClient 性能对比

发表于 2017-01-05 | 分类于 Java

OkHttp: http://square.github.io/okhttp/
AsyncHttpClient: https://github.com/AsyncHttpClient/async-http-client

测试代码:

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
import com.google.common.collect.Lists;
import okhttp3.*;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.DefaultAsyncHttpClient;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
/**
* author: sunshow.
*/
public class HttpClientTest {
private static final String fetchUrl = "https://www.baidu.com";
private final OkHttpClient client = new OkHttpClient();
AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient();
public void fetchByOkhttpAsync(CountDownLatch latch) throws Exception {
Request request = new Request.Builder()
.url(fetchUrl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
response.body().string();
latch.countDown();
}
});
}
public void fetchByOkhttpSync() throws Exception {
Request request = new Request.Builder()
.url(fetchUrl)
.build();
Response response = client.newCall(request).execute();
response.body().string();
}
public void testFetchByAsyncHttpClient(int count) throws Exception {
long t = System.currentTimeMillis();
List<Future<org.asynchttpclient.Response>> futureList = Lists.newArrayList();
for (int i = 0; i < count; i++) {
futureList.add(asyncHttpClient.prepareGet(fetchUrl).execute());
}
for (Future<org.asynchttpclient.Response> future : futureList) {
org.asynchttpclient.Response response = future.get();
response.getResponseBody();
}
t = System.currentTimeMillis() - t;
System.out.println("AsyncHttpClient took: " + t + " ms");
}
public void testFetchByOkhttpAsync(int count) throws Exception {
CountDownLatch latch = new CountDownLatch(count);
long t = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
fetchByOkhttpAsync(latch);
}
latch.await();
t = System.currentTimeMillis() - t;
System.out.println("OkHttp Async took: " + t + " ms");
}
public void testFetchByOkhttpSync(int count) throws Exception {
long t = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
fetchByOkhttpSync();
}
t = System.currentTimeMillis() - t;
System.out.println("OkHttp Sync took: " + t + " ms");
}
@Test
public void testFetch() throws Exception {
int count = 1000;
testFetchByOkhttpSync(count);
testFetchByOkhttpAsync(count);
testFetchByAsyncHttpClient(count);
}
}

测试结果:

1
2
3
OkHttp Sync took: 13018 ms
OkHttp Async took: 3185 ms
AsyncHttpClient took: 4980 ms

Spring MVC 异常响应编码设置

发表于 2016-12-30 | 分类于 Java

直接代码说话

1
2
3
4
5
6
7
@ExceptionHandler(HumanReadableException.class)
public ResponseEntity<String> handleHumanReadableException(Exception e) {
logger.error("请求处理出错", e);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("text", "plain", Charset.forName("UTF-8")));
return new ResponseEntity<>(e.getMessage(), headers, HttpStatus.INTERNAL_SERVER_ERROR);
}

Apache CXF 客户端开发

发表于 2016-06-28 | 分类于 Java

通过 WSDL 生成 Java 代码

在 IDEA 配置 CXF Engine 时各种不让我选择目录,不知道什么原因,因此通过命令行来生成:

1
${apache-cxf-home}/bin/wsdl2java -encoding utf-8 -d ${output-dir} -p ${package-name} -client ${wsdl-url}
  • apache-cxf-home: CXF 目录
  • output-dir: 生成的代码输出目录
  • package-name: 生成的 Java 代码的包路径
  • wsdl-url: 远端的调用地址( ?wsdl )

添加 Maven 依赖

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxws</artifactId>
    <version>3.1.6</version>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http</artifactId>
    <version>3.1.6</version>
</dependency>

Java Config 方式与 Spring 整合

网上包括官方文档都比较老,单独写个实例:

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
/**
* @author sunshow
*/
@Configuration
public class CxfConfig {
@SuppressWarnings("unchecked")
private <T> T createJaxWsClient(Class<T> serviceClass, String address) {
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setServiceClass(serviceClass);
jaxWsProxyFactoryBean.setAddress(address);
jaxWsProxyFactoryBean.getInInterceptors().add(new LoggingInInterceptor());
jaxWsProxyFactoryBean.getOutInterceptors().add(new LoggingOutInterceptor());
T service = (T) jaxWsProxyFactoryBean.create();
Client client = ClientProxy.getClient(service);
if (client != null) {
HTTPConduit conduit = (HTTPConduit) client.getConduit();
HTTPClientPolicy policy = new HTTPClientPolicy();
policy.setConnectionTimeout(30000);
policy.setReceiveTimeout(30000);
conduit.setClient(policy);
}
return service;
}
@Bean
public ServiceAPI serviceAPI() {
return createJaxWsClient(ServiceAPI.class, "https://xxx/services/queryService");
}
}

Maven Tips

发表于 2014-03-24 | 分类于 Java

Nexus

使用本地代理仓库

  • Nexus配置好后会默认有一个Maven Central仓库的Proxy,将自己的项目配置成使用代理

    1
    2
    3
    4
    5
    6
    7
    <repositories>
    <repository>
    <id>central</id>
    <name>Central</name>
    <url>http://foo.bar/content/repositories/central</url>
    </repository>
    </repositories>
  • 将插件仓库也配置成使用本地代理

    1
    2
    3
    4
    5
    6
    7
    <pluginRepositories>
    <pluginRepository>
    <id>central</id>
    <name>Central</name>
    <url>http://foo.bar/content/repositories/central</url>
    </pluginRepository>
    </pluginRepositories>

调整Nexus缓存时间

由于国内访问Maven仓库很卡,容易发生网络故障导致Nexus缓存了未找到记录,可在Nexus后台将对应仓库的Not Found Cache TTL改成一个较短的值(例如5分钟)

为不同的开发环境创建不同的仓库

通常一个项目在不同的环境下存在不同的版本,本文创建dev、qa、production三个仓库并演示如何通过变量来选择使用哪个仓库

使用变量

使用不同的profile定制变量

以下配置声明了dev、qa、production三个profile,并分别定义了branch和skipTest两个变量,且将dev置为默认激活的profile:

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
<profiles>
<profile>
<id>dev</id>
<properties>
<branch>dev</branch>
<skipTest>false</skipTest>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>qa</id>
<properties>
<branch>qa</branch>
<skipTest>true</skipTest>
</properties>
</profile>
<profile>
<id>production</id>
<properties>
<branch>production</branch>
<skipTest>true</skipTest>
</properties>
</profile>
</profiles>
命令行可通过-P参数来指定使用不同的profile,例如:
1
mvn -Pqa install

通过变量声明仓库

在Central仓库基础上加入

1
2
3
4
5
6
7
8
9
10
11
12
<repositories>
<repository>
<id>foobar-${branch}</id>
<name>foobar ${branch}</name>
<url>http://foo.bar/content/repositories/${branch}</url>
</repository>
<repository>
<id>central</id>
<name>Central</name>
<url>http://foo.bar/content/repositories/central</url>
</repository>
</repositories>

禁用仓库中Snapshot版本的唯一版本号

1
2
3
4
5
6
7
8
9
10
11
<repository>
<id>foobar</id>
<name>foobar</name>
<url>http://foo.bar/content/repositories/central</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>

发布

配置deploy仓库

以配置snapshotRepository为例:

1
2
3
4
5
6
7
<distributionManagement>
<snapshotRepository>
<id>${branch}</id>
<name>${branch} Snapshot Repository</name>
<url>http://foo.bar/content/repositories/${branch}</url>
</snapshotRepository>
</distributionManagement>

配置deploy账号

编辑文件:

1
vim ~/.m2/settings.xml
文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>production</id>
<username>deployment</username>
<password>deployment</password>
</server>
<server>
<id>qa</id>
<username>deployment</username>
<password>deployment</password>
</server>
<server>
<id>dev</id>
<username>deployment</username>
<password>deployment</password>
</server>
</servers>
</settings>

常规发布

通过前两处配置,已经可以正常发布:

1
mvn deploy

跳过发布

通常对于J2EE的WAR项目,只需要编译打包,而不需要将WAR文件发布到仓库中,通过配置maven-deploy-plugin跳过发布环节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.1</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
<defaultGoal>install</defaultGoal>
<directory>${project.basedir}/target</directory>
<finalName>${project.artifactId}-${project.version}</finalName>
</build>

打包额外的jar

通过maven-jar-plugin可以在项目中打包出更多的自定义jar包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>foobar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<includes>
<include>**/foo/bar/**</include>
</includes>
<finalName>${project.artifactId}-${project.version}-foobar</finalName>
</configuration>
</execution>
</executions>
</plugin>
可通过配置多个execution并为其指定不同的id来打出更多的jar **注意:此方式打出的jar不会在deploy时发布到仓库中,而通过classfier方式打出的jar会发布,请注意此区别**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>foobar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>foobar</classfier>
<includes>
<include>**/foo/bar/**</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
以上两种方式在编译期都可以做到在target目录输出jar包的效果

发布额外打包的jar并指定自定义POM

以下配置将当前构建跳过发布,并打包额外的jar,然后为这个jar指定自定义的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
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.1</version>
<configuration>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>foobar</id>
<phase>deploy</phase>
<goals>
<goal>deploy-file</goal>
</goals>
<configuration>
<packaging>jar</packaging>
<generatePom>true</generatePom>
<repositoryId>${branch}</repositoryId>
<url>${project.distributionManagement.snapshotRepository.url}</url>
<groupId>foo.bar</groupId>
<artifactId>foobar</artifactId>
<version>${project.version}</version>
<file>${project.build.directory}/${project.artifactId}-${project.version}-foobar.jar</file>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<defaultGoal>install</defaultGoal>
<directory>${project.basedir}/target</directory>
<finalName>${project.artifactId}-${project.version}</finalName>
</build>

包含空目录

Maven构建时会自动忽略空目录,以下插件配置可将空目录保留:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<includeEmptyDirectories>true</includeEmptyDirectories>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.3</version>
<configuration>
<includeEmptyDirs>true</includeEmptyDirs>
</configuration>
</plugin>
</plugins>
<defaultGoal>install</defaultGoal>
<directory>${project.basedir}/target</directory>
<finalName>${project.artifactId}-${project.version}</finalName>
</build>

nginx反向代理配置keepalive

发表于 2013-08-18 | 分类于 Network

有个项目上的Keep-Alive配置一直不起作用,检查了很久代码找不到问题,然后灵光一现想到应该是反向代理配置的问题

即:client—–nginx proxy—–real server

client配置了使用keepalive,real server也支持,问题就在于nginx代理没配置对

配置方式如下:

  1. 配置upstream,keepalive需要在upstream里配置

    1
    2
    3
    4
    5
    upstream http_backend {
    server 127.0.0.1:8080;
    keepalive 16;
    }
  2. 修改proxy设置,proxy_http_version必须设置为“1.1”,且需要清空Connection头

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    server {
    ...
    location /http/ {
    proxy_pass http://http_backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    ...
    }
    }

参考资料:http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive

12…5
Sunshow

Sunshow

Beyond the Life

50 日志
12 分类
122 标签
RSS
© 2017 Sunshow
由 Hexo 强力驱动
主题 - NexT.Pisces