redis 本地副本集搭建

14 June 2017

redis 本地副本集搭建

由于工作需要,最近需要连接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仍然可以进行投票选举。

理论讲完,开始实践:

1, Pre requirement,

一台linux机器,我这里用的是centos,其实任何可以编译redis的机器都行。一个可以打开多个窗口的terminal,当然打开多个terminal也行,我是远程访问的linux,推荐使用tmux,这里的redis是3.2.8

2,download redis。 unexpress it

3,make redis

如果遇到一些小问题,请自行上网解决。

4, 准备configuration

我这里解压缩编译的目录是
/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已经自动修改了配置

接下来是我们用spring data redis连接下这个replication

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());
        }
    }
}

spring data mongo 初级教程

13 June 2017

spring data mongo 初级教程

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 追踪下。

elastic-job 当当开源分布式job系列(1)

27 May 2017

elastic-job 当当开源分布式job系列(1)

今天是我第一天开始接触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的开发实战。

gradle 国内connection error

26 May 2017

 国内因为某些不可知的原因》》》,导致gradle编译的时候,无法正常下载依赖和插件

解决方法
采用阿里云的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申明前

idea,Java system property and env property

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里进行这两种属性,方便线下调试
废话不多说,上图就行
点击右上角的运行项目,如果没有运行过,先运行下
打开编辑界面
idea_system_env_setting.png

其中截图中的VM Options的就是设置java system property
而下面Enviroment Variables 就是临时设置系统环境变量。因为如果在系统属性里设置环境变量。需要重启idea。比较麻烦。用这个比较方便。


Older posts are available in the archive.