14 June 2017
由于工作需要,最近需要连接redis实现热点数据的存储于开发。代码通过本地简易redis服务器的方式开发后,需要跑到线上。但是向公司运维申请redis机器的时候,发现redis其实有两种常用的提供可用性的方案。
1 redis主备(副本集,master slave)模式。
这种是redis现有的提供redis可用性的方案。
2 redis cluster(集群)
这种是后面提到的集群方案。
早些时候,redis只有方案1,而且方案1中,一开始也只有一主一从的方式。后来,慢慢加入了一主多从,再往后面又加入了一主多从多哨兵的方案。最后才有了cluster集群模式。
这篇博客主要是搭建1主多从多哨兵的HA方案。cluster后面会有新的blog介绍。
差异就是,cluster模式下,所有的keys是通过hash算法,打散后放到不同的master下,也就是会有多个master,每个master也会有对应的slave。这样就提高了整体的HA,也保证了查询的速度。缺点就是,这种模式下,针对不同key进行运算的时候,可能会出现因为不同key所在的server不一致,出现奇怪的问题。这些还没有详细的处理和研究 ,留待后期分析学习。cluster模式最大的用处是拆分了keys,解决了单台物理机器内存不足导致keys的量受限制的问题,实现视频扩展。
这里的重点是one master n slaves,n sentinel(哨兵)的部署方式。其中有且只有一个master,多个n slaves,一般master可读写,slaves定期copy master,通常只读,也可以可写,slaves主要是为了分散读的压力,同时,可以在master挂掉的时候,选举出一个slave,作为新的master,并且将其余slaves 自动follow 到这个新的master上。这部分监听的功能就有sentinel是维护。sentinels监听各个服务,发现master down掉的时候,触发选举。当然,为了sentinel本身不成为单点故障,sentinel本身支持HA,推荐基数台sentinel同时运行,比如3台,当有1台sentinel挂掉的时候,其余两台sentinel仍然可以进行投票选举。
一台linux机器,我这里用的是centos,其实任何可以编译redis的机器都行。一个可以打开多个窗口的terminal,当然打开多个terminal也行,我是远程访问的linux,推荐使用tmux,这里的redis是3.2.8
如果遇到一些小问题,请自行上网解决。
我这里解压缩编译的目录是
/home/hunter/sources/redis-3.2.8
编译好后,可执行的redis会出现在src目录,主要是redis-server,redis-sentinel,redis-cli
cd /home/hunter/sources/redis-3.2.8
export REDIS_HOME=/home/hunter/sources/redis-3.2.8
mkdir -p hun_replication/node1
mkdir -p hun_replication/node2
mkdir -p hun_replication/node3
mkdir -p hun_replication/sentinel
touch $REDIS_HOME/hun_replication/node1/redis_5379.conf
touch $REDIS_HOME/hun_replication/node2/redis_5380.conf
touch $REDIS_HOME/hun_replication/node3/redis_5381.conf
touch $REDIS_HOME/hun_replication/sentinel/sentinel1.conf
touch $REDIS_HOME/hun_replication/sentinel/sentinel2.conf
touch $REDIS_HOME/hun_replication/sentinel/sentinel3.conf
这里,redis开头的文件,为redis的服务的配置文件,sentinel开头的文件为sentinel哨兵的配置文件。
这里有三份节点信息,node1作为master,node2,node3作为slaves,同时启动了三个sentinel3,sentinel1,sentinel2,sentinel3
接下来就是分别编辑这几个confs
vi $REDIS_HOME/hun_replication/node1/redis_5379.conf
bind 127.0.0.1
port 5379
dir "/home/hunter/sources/redis-3.2.8/hun_replication/node1"
vi $REDIS_HOME/hun_replication/node2/redis_5380.conf
bind 127.0.0.1
port 5380
dir "/home/hunter/sources/redis-3.2.8/hun_replication/node2"
slaveof 10.100.189.30 5379
vi $REDIS_HOME/hun_replication/node2/redis_5381.conf
bind 127.0.0.1
port 5381
dir "/home/hunter/sources/redis-3.2.8/hun_replication/node3"
slaveof 10.100.189.30 5379
vi $REDIS_HOME/hun_replication/sentinel/sentinel1.conf
# Host and port we will listen for requests on
bind 127.0.0.1
port 25379
#
# "mymaster" is the name of our cluster
#
# each sentinel process is paired with a redis-server process
#
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 10000
sentinel monitor mymaster 127.0.0.1 6379 2
The third "mymaster" here is the name of our repliation.
Each sentinel server needs to have the same name and will point at the master node . The final argument (2 here) is how many sentinel nodes are required for quorum when it comes time to vote on a new master. Since we have 3 nodes, we're requiring a quorum of 2 sentinels, allowing us to lose up to one machine. If we had a repliations of 5 machines,then this should be 3, which would allow us to lose 2 machines while still maintaining a majority of nodes participating in quorum.
sentinel down-after-milliseconds mymaster 5000
a machine will have to be unresponsive for 5 seconds before being classified as down thus triggering a vote to elect a new master node.sentinel parallel-syncs mymaster 1
一次只有有一台slave进行复制
sentinel failover-timeout mymaster 10000
发生failover的时候,如果failover超过这个时间10s,就表示此次failover失败。
vi $REDIS_HOME/hun_replication/sentinel/sentinel2.conf
# Host and port we will listen for requests on
bind 127.0.0.1
port 25380
#
# "mymaster" is the name of our cluster
#
# each sentinel process is paired with a redis-server process
#
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 10000
vi $REDIS_HOME/hun_replication/sentinel/sentinel3.conf
# Host and port we will listen for requests on
bind 127.0.0.1
port 25381
#
# "mymaster" is the name of our cluster
#
# each sentinel process is paired with a redis-server process
#
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 10000
cd $REDIS_HOME/hun_replication/node1/
$REDIS_HOME/src/redis-server redis_5379.conf
cd $REDIS_HOME/hun_replication/node2/
$REDIS_HOME/src/redis-server redis_5380.conf
cd $REDIS_HOME/hun_replication/node3/
$REDIS_HOME/src/redis-server redis_5381.conf
cd $REDIS_HOME/hun_replication/sentinel
$REDIS_HOME/src/redis-sentinel sentinel1.conf
$REDIS_HOME/src/redis-sentinel sentinel2.conf
$REDIS_HOME/src/redis-sentinel sentinel3.conf
这样就完成了一个redis的master slave sentinel实例
可以干掉master节点node1,会发现redis自动切换了一个新的master
redis-cli -p 6379 debug segfault //shutdown the master node of the replciation
然后再次启动redis-server
$REDIS_HOME/src/redis-server $REDIS_HOME/hun_replication/node1/redis_5379.conf
这时候,应该会发现,master已经是node2或者node3,node1自动变成salve。并且,编辑redis_5379.conf 等文件的时候,会发现,redis已经自动修改了配置
package example.springdata.redis.sentinel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StopWatch;
import javax.annotation.PreDestroy;
/**
* @author hunter.xue
*/
@Configuration
public class RedisSentinelApplication {
static String HOST = null;
static RedisSentinelConfiguration SENTINEL_CONFIG = null;
static boolean selfReplication = false;
static {
if (selfReplication) {
HOST = "127.0.0.1";//this is my server address
SENTINEL_CONFIG = new RedisSentinelConfiguration().master("mymaster") //
.sentinel(HOST, 25379) //
.sentinel(HOST, 25380) //
.sentinel(HOST, 25381);
}
}
public
@Bean
RedisSentinelConfiguration sentinelConfig() {
return SENTINEL_CONFIG;
}
public
@Bean
RedisConnectionFactory connectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(sentinelConfig());
jedisConnectionFactory.setPassword("NSzTQdollgGQeBsd");
return jedisConnectionFactory;
}
@Autowired
RedisConnectionFactory factory;
public static void main(String[] args) throws Exception {
ApplicationContext context = SpringApplication.run(RedisSentinelApplication.class, args);
StringRedisTemplate template = context.getBean(StringRedisTemplate.class);
template.opsForValue().set("loop-forever", "0");
StopWatch stopWatch = new StopWatch();
while (true) {
try {
String value = "IT:= " + template.opsForValue().increment("loop-forever", 1);
printBackFromErrorStateInfoIfStopWatchIsRunning(stopWatch);
System.out.println(value);
} catch (RuntimeException e) {
System.err.println(e.getCause().getMessage());
startStopWatchIfNotRunning(stopWatch);
}
Thread.sleep(1000);
}
}
public
@Bean
StringRedisTemplate redisTemplate() {
return new StringRedisTemplate(connectionFactory());
}
/**
* Clear database before shut down.
*/
public
@PreDestroy
void flushTestDb() {
factory.getConnection().flushDb();
}
private static void startStopWatchIfNotRunning(StopWatch stopWatch) {
if (!stopWatch.isRunning()) {
stopWatch.start();
}
}
private static void printBackFromErrorStateInfoIfStopWatchIsRunning(StopWatch stopWatch) {
if (stopWatch.isRunning()) {
stopWatch.stop();
System.err.println("INFO: Recovered after: " + stopWatch.getLastTaskInfo().getTimeSeconds());
}
}
}
13 June 2017
mongodb是一个比较常见的document based的nosql数据库,我们目前主要存储大量的服务器心跳信息,所以要用到它。java调用mongodb的时候,会有多种database client的选型问题。
我来说说我遇到的一些问题。最早我采用的是原生的mongo db driver v3,这种方式调用和查询很方便,但是当文档类型一致,文档结构却很复杂的时候,通过手动的解析各个字段的时候,会变得很枯燥和很痛苦。
后来转型到spring data mongodb,这个可以直接映射到java object,缺点就是文档不是很人性化,特别是那些api,简直不知道在说什么。于是,我花了不少时间,学习了下通过MongoTemplate(spring data mongodb的核心类)来直接操作mongodb查询的测试用例。主要用来解决负责查询和映射的问题。
好了,直接进入主题
1 预备条件,一个简单的可以登录创建db,collection的mongo实例
2 配置下maven的依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.10.3.RELEASE</version>
</dependency>
3, 配置下mongodb的config,如果是使用spring boot,会有一个默认的spring mongodb的配置,用户只需要在application.properties or application.yaml中修改链接信息就行,当然,我们还是要知其然,也知其所以然。
这里采用的是手工配置
package com.ly.openapi.api.configs;
import com.ly.openapi.api.db.mongo.converters.DateToZonedDateTimeConverter;
import com.ly.openapi.api.db.mongo.converters.ZonedDateTimeToDateConverter;
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.util.Assert;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by xlm45038 on 2017/6/1.
*/
@Configuration
public class MongoConfig {
@Value("${mongo.hosts}")
private String[] hosts;
@Value("${mongo.ports}")
private int[] ports;
@Value("${mongo.username}")
private String username;
@Value("${mongo.password}")
private String password;
@Value("${mongo.authdb}")
private String authenticationDatabase;
@Value("${mongo.db}")
private String database;
@Bean
MongoClient mongoClient() throws UnknownHostException {
String authDb = this.authenticationDatabase == null
? this.database : this.authenticationDatabase;
Assert.isTrue(hosts.length == ports.length, "length is not equal for hosts and ports");
List<ServerAddress> serverAddresses = new ArrayList<>();
List<MongoCredential> credentials = new ArrayList<>();
ServerAddress address;
MongoCredential credential;
for (int i = 0; i < hosts.length; i++) {
address = new ServerAddress(hosts[0], (ports[0]));
serverAddresses.add(address);
}
credential = MongoCredential.createCredential(this.username,
authDb, this.password.toCharArray());
credentials.add(credential);
return new MongoClient(
serverAddresses,
credentials);
}
@Bean
public MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(mongoClient(), this.database);
}
@Bean
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MappingMongoConverter converter) throws UnknownHostException {
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter);
return mongoTemplate;
}
@Bean
public MappingMongoConverter mappingMongoConverter(MongoDbFactory mongoDbFactory) {
MappingMongoConverter converter = new MappingMongoConverter(
new DefaultDbRefResolver(mongoDbFactory), new MongoMappingContext());
converter.setCustomConversions(mongoCustomConversions());
return converter;
}
@Bean
public CustomConversions mongoCustomConversions() {
return new CustomConversions(Arrays.asList(
new DateToZonedDateTimeConverter(),
new ZonedDateTimeToDateConverter()
));
}
}
这里的dependency就是MongoTemplate -> MongoDbFactory -> MongoClient
还有就是,mongodb默认不支持zoneddateTime,需要自己添加conversion
public class DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {
@Override
public ZonedDateTime convert(Date source) {
if (source == null) {
return null;
} else {
return ZonedDateTime.ofInstant(source.toInstant(), ZoneOffset.ofHours(8));
}
}
}
package com.ly.openapi.api.db.mongo.converters;
import org.springframework.core.convert.converter.Converter;
import java.time.ZonedDateTime;
import java.util.Date;
/**
* Created by xlm45038 on 2017/6/5.
*/
public class ZonedDateTimeToDateConverter implements Converter<ZonedDateTime, Date> {
@Override
public Date convert(ZonedDateTime source) {
if (source == null)
return null;
else
return Date.from(source.toInstant());
}
}
用到的配置文件 application.properties
mongo.hosts=10.100.189.30
mongo.ports=27017
mongo.username=admin
mongo.password=o123
mongo.authdb=admin
mongo.db=studyDB
最后就是一长段testcase,看方法名就知道我在测试什么,感觉还是example代码是最好的说明文档。
后期如果有需求,我会不定期更新。
package com.ly.openapi.api.db.mongo;
import com.ly.openapi.api.configs.RootConfig;
import com.mongodb.*;
import lombok.Data;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.regex.Pattern;
/**
* Created by hunter.xue on 2017/6/13.
*/
@ContextConfiguration(classes = {RootConfig.class})
@RunWith(value = SpringJUnit4ClassRunner.class)
public class MongoSpringDataTest {
private static DBCollection collection = null;
private static DB db = null;
@Autowired
private MongoClient mongoClient;
@Autowired
private MongoTemplate mongoTemplate;
@Before
public void insertDummyDocuments() throws UnknownHostException {
db = mongoClient.getDB("studyDB");
// get a single collection
collection = db.getCollection("dummyColl");
System.out.println(mongoTemplate.getDb().getMongo().getAddress());
System.out.println(mongoTemplate.getDb().getName());
System.out.println(mongoTemplate.getCollectionNames());
List<DBObject> list = new ArrayList<DBObject>();
Calendar cal = Calendar.getInstance();
for (int i = 1; i <= 5; i++) {
BasicDBObject data = new BasicDBObject();
data.append("number", i);
data.append("name", "mkyong-" + i);
cal.add(Calendar.DATE, 1);
list.add(data);
data = new BasicDBObject();
data.append("number", i);
data.append("name", "hunter-" + i);
cal.add(Calendar.DATE, 1);
list.add(data);
}
collection.insert(list);
}
@After
public void tearDown() {
collection.drop();
db.dropDatabase();
}
@Test
public void testFindAll1() {
Query query = new Query();
// query.addCriteria(
// Criteria.where("HeartbeatTime").exists(true).andOperator(
// Criteria.where("HeartbeatTime").gte(Date.from(start.toInstant())),
// Criteria.where("HeartbeatTime").lt(Date.from(end.toInstant()))
// )
// );
System.out.println(query.toString());
List<MyObject> datas = mongoTemplate.findAll(MyObject.class, "dummyColl");
datas.stream().forEach(data -> System.out.println(data));
}
@Test
public void testFindAll_implicitCollectionName() {
Query query = new Query();
System.out.println(query.toString());
List<MyObject> datas = mongoTemplate.findAll(MyObject.class);
datas.stream().forEach(data -> System.out.println(data));
}
@Test
public void testFindNameOnly() {
Query query = new Query();
query.fields().exclude("_id");
query.fields().exclude("name");
System.out.println(query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(data -> System.out.println(data));
}
@Test
public void testFindNumberEqualsAndWithOrder() {
Query query = new Query();
query.addCriteria(
Criteria.where("number").is(5)
);
// query.with(new Sort(Sort.Direction.ASC, "number").and(new Sort(Sort.Direction.DESC,"name")));
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println(query);
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
@Test
public void testFindNumberIn() {
Query query = new Query();
query.addCriteria(
Criteria.where("number").in(2, 4, 5)
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println(query);
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
@Test
public void testBetweenAnd() {
Query query = new Query();
query.addCriteria(
Criteria.where("number").gt(2).lt(5)
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println("query>" + query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
@Test
public void testBetweenAnd2() {
Query query = new Query();
query.addCriteria(
new Criteria("number").lt(5).andOperator(
Criteria.where("number").gt(2),
new Criteria("number").lt(5))
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println("query>" + query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
@Test
public void testNe() {
Query query = new Query();
query.addCriteria(
Criteria.where("number").ne(4)
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println("query>" + query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
/**
测试基本的and查询
*/
@Test
public void testAnd() {
Query query = new Query();
query.addCriteria(
new Criteria().andOperator(
Criteria.where("number").is(2),
Criteria.where("name").is("hunter-3")
)
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println("query>" + query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
@Test
public void testRegex(){
Query query = new Query();
query.addCriteria(
Criteria.where("name").regex(Pattern.compile("HUN.*-[1-3]",Pattern.CASE_INSENSITIVE))
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println("query>" + query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
}
@Data
@Document(collection = "dummyColl")
class MyObject {
private int number;
private String name;
}
目前,spring data mongodb后台其实操作的是mongo java driver v2的代码。后期到了spring5.x时代,会迁移到mongo Java driver v3,到时候如果有api变动,我再写个new blog 追踪下。
27 May 2017
今天是我第一天开始接触elasticjob。主要是单位里领导要求做一套可以分布式的job系统,支持按间隔,或者按照指定时间点进行任务的调度。用户可以通过编写相关的包的方式来编写自己的job逻辑,然后交给我的系统进行调度运行,最好支持高可用和分布式,分片运行的概念。
如果没有分布式的要求,其实使用java世界里的job大哥大quartz就能很好的完成前面的所有的要求,
问题还是考虑到后期的扩展,高可用,还是需要考虑分布式的方案。最终通过网络搜索,找到了当当网很久前开源的elasticjob。看了下github的地址,最近还是有人提交更新的,应该值得一试。于是就有了今天的blog系列。如果方案可行且领导批准,那我就会把我的心得和使用结果记录在这里。并且应该会有一部分源码的分析。
首先试elastic-job的地址 https://github.com/dangdangdotcom/elastic-job
分布式调度协调
弹性扩容缩容
失效转移
错过执行作业重触发
作业分片一致性,保证同一分片在分布式环境中仅一个执行实例
自诊断并修复分布式不稳定造成的问题
支持并行调度
支持作业生命周期操作
丰富的作业类型
Spring整合以及命名空间提供
运维平台
elastic-job-core
elastic-job核心模块,只通过Quartz和Curator就可执行分布式作业。
elastic-job-spring
elastic-job对spring支持的模块,包括命名空间,依赖注入,占位符等。
elastic-job-console
elastic-job web控制台,可将编译之后的war放入tomcat等servlet容器中使用。
elastic-job-example
使用例子。
elastic-job-test
测试elastic-job使用的公用类,使用方无需关注。
引入maven依赖
elastic-job已经发布到中央仓库,可以在pom.xml文件中直接引入maven坐标。
<!-- 引入elastic-job核心模块 -->
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-core</artifactId>
<version>xxxlatest</version>
</dependency>
<!-- 使用springframework自定义命名空间时引入 -->
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-spring</artifactId>
<version>xxxlatest</version>
</dependency>
第一步就是clone整个项目下来,里面有源码和必要的example。源码采用maven进行编译
编译步骤:
mvn clean install -Dmaven.test.skip=true
进行elastic-job的安装,安装后,所有的源码会放到本地maven仓库里,这里跳过了所有的test
第二步,
编译所有的examples
cd elastic-job-example
mvn clean package -Dmaven.test.skip=true
然后就是使用自己心爱的ide打开源码或者example进行编译调试以及学习。我这里用的试idea。当然里面有个小插曲,这里的elastic-job的源码大量采用了lombok进行java代码的生成,主要是一些constructor,getter,setter等。当然好处是减少了模板代码的出现,眼睛不至于这么累。缺点,就是,你得多学个东西,然后idea里或者eclipse里需要自己配置下。具体的网上搜索下就行了。还有就是,有时候代码从example跳转到elasticjob源码的时候,有时候会行数与class有些差异,当然问题不大,强迫症看着有些不舒服。
先从elastic-job-lite的JavaMain开始进行运行,默认使用h2 in-memory模式记录任务状态,可以通过修改代码的方式,改用mysql记录,方便自己查询结果
另外,默认JavaMain这个example使用的是zookeeper官方测试框架提供的内存zookeeper实例进行调试,后期需要自己设置zookeeper集群进行维护和开发。
zookeeper就是elasticjob的核心之一,用于维护集群的可用性,任务的分发协商。elasticjob基于Zookeeper和其客户端Curator实现的全局作业注册控制中心。用于注册,控制和协调分布式作业执行。
以上就是简单的入门环境搭建。期待下个回合的elasticjob的开发实战。
26 May 2017
解决方法
采用阿里云的repository,具体就是配置根目录那个build.gradle文件,分别加入针对dependency和plugins的repository配置
buildscript {
repositories {
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
jcenter()
}
}
allprojects {
repositories {
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
jcenter()
}
}
其中第一段建议放在文件开始的位置,第二段一定要放在所有的plugins申明前
26 May 2017
这篇博客这要介绍下java system property 和env property的区别,以及如何在intellij idea中进行设置这两种类型的键值对
首先,property可以理解为键值对
我们开发java的时候,经常会遇到多种不同类型的property
有些是存放在内存里的property,有些是以配置文件的方式存在的,比如yaml,比如properties文件,再有就是两类比较特别的property(其实这些
在spring 中都被抽象成了property source,当然,这个是后话,具体的我会写另外的blog记录)
这里介绍的就是这两类特殊的property, java system property,和env property
前者经常通过命令行java command -Dproperty1=xxx指定
后者通过设置系统环境变量获取,常见于云环境和docker,比如在linux中export,比如在windows中通过系统属性配置
先看段 stackoverflow上的介绍
System properties are set on the Java command line using the -Dpropertyname=value syntax.
They can also be added at runtime using
System.setProperty(String key, String value)
//or
System.getProperties().load()
methods.
To get a specific system property you can use
System.getProperty(String key)// or
System.getProperty(String key, String def).
Environment variables are set in the OS, e.g. in Linux export HOME=/Users/myusername or on Windows SET WINDIR=C:\Windows etc, and, unlike properties,
may not be set at runtime.
To get a specific environment variable you can use
System.getenv(String name).
system property 是java特有的,可以运行时修改
env property 是系统层面的,只读
具体的api参见上面的代码
最后讲下,如何在idea里进行这两种属性,方便线下调试
废话不多说,上图就行
点击右上角的运行项目,如果没有运行过,先运行下
打开编辑界面
其中截图中的VM Options的就是设置java system property
而下面Enviroment Variables 就是临时设置系统环境变量。因为如果在系统属性里设置环境变量。需要重启idea。比较麻烦。用这个比较方便。
Older posts are available in the archive.