es生成分布式id

雪花算法

使用机器id和时间推移,生成19位long型id。
在分布式环境下,一台机器部署多个项目,或者docker部署项目,会产生id重复

        int count = 100000;
        CountDownLatch latch = new CountDownLatch(count);
        Set<Long> set = new HashSet<>(count * 2);
        for (int i = 0; i < count; i++) {
            new Thread(() -> {
                try {
                    set.add(getSnowflakeId());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                latch.countDown();
            }).start();
        }

        latch.await();
        //99991
        System.out.println(set.size());

可见重复率很低,万分之一的重复率,在并发量低时,几乎不会重复。
防止墨菲定律,重复了,数据就很难处理了。

方案设计

1.用mysql数据库生成id
创建一个id表,专门负责生成id,写块代码实现orcale序列的功能,再加redis分布式锁,可以解决。
在分库环境下,可以指定一个数据库生成id,其他库的服务用feign调用,或者每个库都用一个id表,感觉还是指定一个库生成id好,这样可以全局看到所有id

缺点:开发稍微麻烦点。如果每个库都建立一个id表,每个服务一套逻辑,新建服务的处理,麻烦,易出错。
如果指定一个库生成id,本来只需要启动一个服务开发测试,这样每次都要至少启动两个服务,加eureka,共三个服务。

2.redis生成id
redis内存操作,速度快。lua脚本操作实现原子性,不需要用redis加分布式锁。
每个服务都连接到redis了,分布式环境操作很方便。可以说redis是性能最好的。

缺点:redis只用作缓存,不存储数据。

3.es生成id
每个服务都连接到es,操作方便。
搜索引擎查询快,redis加分布式锁。
在common模块,写个工具类,所有服务都能调用,使用方便。

本文讲的就是es。

4.mongodb生成id
和es差不多

es生成id

用es的high-level的api

依赖

        <!--es-->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>6.7.2</version>
            <exclusions>
                <exclusion>
                    <groupId>org.elasticsearch</groupId>
                    <artifactId>elasticsearch</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>6.7.2</version>
        </dependency>

        <dependency>
            <groupId>io.searchbox</groupId>
            <artifactId>jest</artifactId>
            <version>6.3.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.elasticsearch</groupId>
                    <artifactId>elasticsearch</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

配置


@Configuration
public class EsConfig {

    @Autowired
    private EsProperties esProperties;

    @Bean
    public RestHighLevelClient initClient() {
        RestClientBuilder restClientBuilder = RestClient.builder(
                new HttpHost(esProperties.getHost(), Integer.valueOf(esProperties.getPort()), esProperties.getSchema()));

        restClientBuilder.setRequestConfigCallback(
                requestConfigBuilder -> {
                    requestConfigBuilder.setConnectTimeout(esProperties.getConnectTimeout());
                    requestConfigBuilder.setSocketTimeout(esProperties.getSocketTimeout());
                    return requestConfigBuilder;
                });

        return new RestHighLevelClient(restClientBuilder);
    }

}

id索引库,实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class EsNoEntity {
    public static final String INDEX = "distribute_no";
    public static final String TYPE = "es";

    @JestId
    private String noName;

    /**
     * 因为long值太大时,es取出会四舍五入,数据失真,所以用string
     */
    private String initNo;
    private String currentNo;

}

工具类

初始化,创建索引

    @PostConstruct
    private void init() {

        try {
            //查询索引
            GetIndexRequest request = new GetIndexRequest(EsNoEntity.INDEX);
            boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);

            //索引存在
            if (exists) {
                log.info("索引{}已经存在", EsNoEntity.INDEX);
                return;
            }

            //索引不存在,创建
            CreateIndexRequest createIndexRequest = new CreateIndexRequest(EsNoEntity.INDEX);
            CreateIndexResponse response = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
            if (response != null && response.isAcknowledged()) {
                log.info("索引{}创建成功", EsNoEntity.INDEX);
                return;
            }

            log.error("索引{}创建失败", EsNoEntity.INDEX);

        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }

维护自增订单编号

    /**
     * 订单编号
     */
    private static final String ORDER_NO = "order_no";
    /**
     * 雪花算法当前生成id为1193737508566913024L,因为时间推移,id越来越大,所以以前id一定比这个小
     * 最高位+1,1->2,这样生成id和以前的数据都不会重复
     */
    private static final long ORDER_NO_INIT = 2193737508566913024L;

    /**
     * 订单编号
     * @author lipo
     * @date 2019-11-11 11:10
     */
    public long nextOrderNo() {
        try {
            return nextNo(ORDER_NO, ORDER_NO_INIT + "");
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 生成编号,分布式锁保证并发安全
     * @author lipo
     * @date 2019-11-11 11:10
     */
    private long nextNo(String noName, String initNo) throws IOException {
        Lock lock = redisLockRegistry.obtain("NO_NAME:" + noName);

        try {
            lock.lock();

            //查询数据
            EsNoEntity esNoEntity = getNo(noName);
            long currentNo;
            if (esNoEntity == null) {
                //不存在数据,插入初始值
                currentNo = Long.parseLong(initNo);
                esNoEntity = new EsNoEntity(noName, initNo, initNo);
            } else {
                //已经存在, +1
                currentNo = Long.parseLong(esNoEntity.getCurrentNo()) + 1;
                esNoEntity.setCurrentNo(currentNo + "");
            }

            //保存
            save(noName, esNoEntity);

            return currentNo;

        } finally {
            lock.unlock();
        }

    }

    /**
     * 添加更新数据,自动创建索引
     * @author lipo
     * @date 2019-11-12 09:55
     */
    private void save(String noName, EsNoEntity esNoEntity) throws IOException {
        IndexRequest request = new IndexRequest(EsNoEntity.INDEX, EsNoEntity.TYPE, noName);
        request.source(JSON.toJSONString(esNoEntity), XContentType.JSON);
        restHighLevelClient.index(request, RequestOptions.DEFAULT);
    }

    /**
     * 查询数据
     * @author lipo
     * @date 2019-11-11 11:37
     */
    private EsNoEntity getNo(String noName) throws IOException {
        GetRequest getRequest = new GetRequest(EsNoEntity.INDEX, EsNoEntity.TYPE, noName);
        GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        String source = getResponse.getSourceAsString();
        return JSON.parseObject(source, EsNoEntity.class);
    }

}

通用id,100001初始值

    /**
     * 通用编号
     */
    private static final String COMMON_NO = "common_no";
    private static final long COMMON_NO_INIT = 100001L;

    /**
     * 通用编号
     * @author lipo
     * @date 2019-11-11 11:10
     */
    public long nextCommonNo() {
        try {
            return nextNo(COMMON_NO, COMMON_NO_INIT + "");
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage());
        }
    }

扩展方便,像这个通用id一样扩展

测试

SpringBootApplication
@RestController
@Slf4j
public class EsApplication {
    public static void main(String[] args) {
        SpringApplication.run(EsApplication.class, args);
    }

    @Autowired
    private EsNoUtil esNoUtil;

    /**
     * http://localhost:8080/orderNo
     * @author lipo
     * @date 2019-11-12 10:49
     */
    @GetMapping("orderNo")
    public Long orderNo() {
        return esNoUtil.nextOrderNo();
    }
}

2193737508566913024

时间消耗

    @GetMapping("orderNoMany")
    public String orderNoMany() throws InterruptedException {
        final int count = 1000;

        LocalDateTime begin = LocalDateTime.now();

        int i = 0;
        for (; i < count; i++) {
            esNoUtil.nextOrderNo();
        }

        //PT1M40.807S,平均一个id时间0.1秒
        System.out.println(Duration.between(begin, LocalDateTime.now()));

        return "ok";
    }

平均一个id时间0.1秒,时间比雪花算法慢很多,分布式安全的代价吧,有失必有得

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值