├── butterfly-allocator ├── butterfly-allocator-db │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ └── services │ │ │ │ │ ├── com.simonalong.butterfly.sequence.WorkerLoader │ │ │ │ │ └── com.simonalong.butterfly.sequence.spi.WorkerIdHandler │ │ │ └── sql │ │ │ │ └── uuid.sql │ │ │ └── java │ │ │ └── com │ │ │ └── simonalong │ │ │ └── butterfly │ │ │ └── worker │ │ │ └── db │ │ │ ├── DbConstant.java │ │ │ ├── DbButterflyConfig.java │ │ │ ├── entity │ │ │ └── UuidGeneratorDO.java │ │ │ ├── DbWorkerLoader.java │ │ │ └── DbWorkerIdHandler.java │ └── pom.xml ├── butterfly-allocator-zookeeper │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ ├── com.simonalong.butterfly.sequence.WorkerLoader │ │ │ │ └── com.simonalong.butterfly.sequence.spi.WorkerIdHandler │ │ │ └── java │ │ │ └── com │ │ │ └── simonalong │ │ │ └── butterfly │ │ │ └── worker │ │ │ └── zookeeper │ │ │ ├── entity │ │ │ ├── SessionNodeEntity.java │ │ │ ├── WorkerNodeEntity.java │ │ │ └── ConfigNodeEntity.java │ │ │ ├── ZkButterflyConfig.java │ │ │ ├── node │ │ │ ├── WorkerIdAllocator.java │ │ │ ├── ConfigNodeHandler.java │ │ │ ├── WorkerNodeHandler.java │ │ │ ├── ZkNodeHelper.java │ │ │ ├── DefaultConfigNodeHandler.java │ │ │ ├── NamespaceNodeManager.java │ │ │ ├── DefaultWorkerNodeHandler.java │ │ │ └── DefaultWorkerIdAllocator.java │ │ │ ├── ZkConstant.java │ │ │ ├── ZkWorkerIdHandler.java │ │ │ ├── ZkWorkerLoader.java │ │ │ └── ZookeeperClient.java │ └── pom.xml ├── butterfly-allocator-distribute │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── com.simonalong.butterfly.sequence.allocator.BeanBitAllocator │ │ │ └── java │ │ │ └── com │ │ │ └── simonalong │ │ │ └── butterfly │ │ │ └── worker │ │ │ └── distribute │ │ │ ├── DistributeConstant.java │ │ │ ├── config │ │ │ ├── DistributeRestfulButterflyConfig.java │ │ │ ├── DistributeDubboButterflyConfig.java │ │ │ └── BitSequenceConfig.java │ │ │ ├── DistributeBitAllocator.java │ │ │ ├── ButterflySeqGeneratorFactory.java │ │ │ ├── SequenceClient.java │ │ │ └── BufferManager.java │ └── pom.xml └── pom.xml ├── butterfly-distribute ├── butterfly-server-restful │ ├── README.md │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── application.yml │ │ │ └── java │ │ │ └── com │ │ │ └── simonalong │ │ │ └── butterfly │ │ │ └── distribute │ │ │ ├── ButterflyApplication.java │ │ │ ├── config │ │ │ ├── ButterflySequenceServerConfig.java │ │ │ └── WebExceptionHandler.java │ │ │ ├── controller │ │ │ └── DistributeController.java │ │ │ ├── exception │ │ │ └── BusinessException.java │ │ │ ├── uti │ │ │ └── ExceptionUtil.java │ │ │ └── server │ │ │ └── DistributeService.java │ └── pom.xml ├── butterfly-server-dubbo │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── application.yml │ │ │ └── java │ │ │ └── com │ │ │ └── simonalong │ │ │ └── butterfly │ │ │ └── distribute │ │ │ ├── ButterflyApplication.java │ │ │ ├── config │ │ │ └── ButterflySequenceServerConfig.java │ │ │ └── impl │ │ │ └── ButterflyDistributeApiImpl.java │ └── pom.xml ├── butterfly-api │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── simonalong │ │ │ └── butterfly │ │ │ └── distribute │ │ │ ├── api │ │ │ └── ButterflyDistributeApi.java │ │ │ └── model │ │ │ ├── BitSequenceDTO.java │ │ │ └── Response.java │ └── pom.xml └── pom.xml ├── butterfly-sequence ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── simonalong │ │ │ └── butterfly │ │ │ └── sequence │ │ │ ├── ButterflyConfig.java │ │ │ ├── exception │ │ │ ├── WorkerIdFullException.java │ │ │ └── ButterflyException.java │ │ │ ├── allocator │ │ │ ├── ExpireBitAllocator.java │ │ │ ├── BitAllocator.java │ │ │ ├── BeanBitAllocator.java │ │ │ ├── BeanBitAllocatorFactory.java │ │ │ └── DefaultBitAllocator.java │ │ │ ├── spi │ │ │ ├── WorkerIdHandler.java │ │ │ ├── WorkerIdHandlerFactory.java │ │ │ └── ServiceLoaderFactory.java │ │ │ ├── PaddedLong.java │ │ │ ├── WorkerLoader.java │ │ │ ├── UuidSplicer.java │ │ │ ├── UuidConstant.java │ │ │ ├── TimeAdjuster.java │ │ │ └── ButterflyIdGenerator.java │ └── test │ │ └── java │ │ └── com │ │ └── simonalong │ │ └── butterfly │ │ └── sequence │ │ └── BaseTest.java └── pom.xml ├── .github └── ISSUE_TEMPLATE │ └── ----.md ├── butterfly-sample ├── src │ └── main │ │ ├── resources │ │ └── logback.xml │ │ └── java │ │ └── com │ │ └── simonalong │ │ └── buffterfly │ │ └── sample │ │ ├── ZkPressIdGeneratorTest.java │ │ ├── DistributeDubboPressIdGeneratorTest.java │ │ ├── DistributeRestfulPressIdGeneratorTest.java │ │ ├── DbPressIdGeneratorTest.java │ │ └── BaseTest.java └── pom.xml ├── .gitignore ├── README.md ├── pom.xml └── LICENSE /butterfly-allocator/butterfly-allocator-db/src/main/resources/META-INF/services/com.simonalong.butterfly.sequence.WorkerLoader: -------------------------------------------------------------------------------- 1 | com.simonalong.butterfly.worker.db.DbWorkerLoader 2 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-db/src/main/resources/META-INF/services/com.simonalong.butterfly.sequence.spi.WorkerIdHandler: -------------------------------------------------------------------------------- 1 | com.simonalong.butterfly.worker.db.DbWorkerIdHandler 2 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-restful/README.md: -------------------------------------------------------------------------------- 1 | # 服务端 - restful方式 2 | 服务端代码较少,做一个参考,只是使用butterfly-allocator-db包,获取两个对应字段,对于相关使用同学,也可以自行开发,代码很简单,按照butterfly-allocator-db用法做一个服务端即可 3 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/resources/META-INF/services/com.simonalong.butterfly.sequence.WorkerLoader: -------------------------------------------------------------------------------- 1 | com.simonalong.butterfly.worker.zookeeper.ZkWorkerLoader 2 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/resources/META-INF/services/com.simonalong.butterfly.sequence.spi.WorkerIdHandler: -------------------------------------------------------------------------------- 1 | com.simonalong.butterfly.worker.zookeeper.ZkWorkerIdHandler 2 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-distribute/src/main/resources/META-INF/services/com.simonalong.butterfly.sequence.allocator.BeanBitAllocator: -------------------------------------------------------------------------------- 1 | com.simonalong.butterfly.worker.distribute.DistributeBitAllocator 2 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/ButterflyConfig.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence; 2 | 3 | /** 4 | * @author shizi 5 | * @since 2020/4/9 12:38 AM 6 | */ 7 | public class ButterflyConfig { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-dubbo/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | dubbo: 2 | application: 3 | name: butterfly_server 4 | registry: 5 | address: 127.0.0.1:2181 6 | protocol: zookeeper 7 | protocol: 8 | name: dubbo 9 | port: 20997 10 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-restful/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8800 3 | butterfly: 4 | url: jdbc:mysql://localhost:3306/butterfly?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true 5 | username: root 6 | password: 7 | 8 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/entity/SessionNodeEntity.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.entity; 2 | 3 | /** 4 | * @author shizi 5 | * @since 2020/4/25 11:15 AM 6 | */ 7 | @SuppressWarnings("unused") 8 | public class SessionNodeEntity { 9 | 10 | private String alive = "alive"; 11 | } 12 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/exception/WorkerIdFullException.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence.exception; 2 | 3 | /** 4 | * @author shizi 5 | * @since 2020/3/21 下午4:57 6 | */ 7 | public class WorkerIdFullException extends ButterflyException { 8 | 9 | public WorkerIdFullException(String msg) { 10 | super(msg); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-distribute/src/main/java/com/simonalong/butterfly/worker/distribute/DistributeConstant.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.distribute; 2 | 3 | import static com.simonalong.butterfly.sequence.UuidConstant.LOG_PRE; 4 | 5 | /** 6 | * @author shizi 7 | * @since 2020/5/3 1:47 AM 8 | */ 9 | public interface DistributeConstant { 10 | 11 | String DTB_LOG_PRE = LOG_PRE + "[distribute]"; 12 | } 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 问题上报 3 | about: butterfly 的问题 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **问题描述** 11 | 简要概括你遇到的问题 12 | 13 | **问题出现步骤** 14 | 参考如下示例: 15 | 1. 使用'xxx'包 16 | 2. 调用'xxxx'函数 17 | 3. 出现问题 18 | 19 | **期望结果** 20 | 简要概述你期望发生的事情或者结果 21 | 22 | **实际结果** 23 | 说明程序运行的实际结果,可以的话,粘贴代码或者图片帮助我们更好的理解 24 | 25 | **isc-gobase信息** 26 | - 包的版本:xxx 27 | 28 | **其他信息** 29 | 如果还有更多信息,在这里添加任何有关该问题的相关信息 30 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/allocator/ExpireBitAllocator.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence.allocator; 2 | 3 | /** 4 | * @author shizi 5 | * @since 2020/5/1 11:00 AM 6 | */ 7 | public interface ExpireBitAllocator extends BitAllocator { 8 | 9 | /** 10 | * 获取该节点上一次的过期时间 11 | * 12 | * @param namespace 命名空间 13 | * @return 上次过期时间 14 | */ 15 | default Long getLastExpireTime(String namespace) { 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/spi/WorkerIdHandler.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence.spi; 2 | 3 | /** 4 | * 节点 worker_xx 读取和更新管理器 5 | * 6 | * @author shizi 7 | * @since 2020/2/7 1:02 上午 8 | */ 9 | public interface WorkerIdHandler { 10 | 11 | /** 12 | * 当前工作节点的下次失效时间 13 | * @return 时间的long类型 14 | */ 15 | Long getLastExpireTime(); 16 | 17 | /** 18 | * 获取节点的下表索引 19 | * @return workId 20 | */ 21 | Integer getWorkerId(); 22 | } 23 | -------------------------------------------------------------------------------- /butterfly-sequence/src/test/java/com/simonalong/butterfly/sequence/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence; 2 | 3 | import java.util.Optional; 4 | 5 | /** 6 | * @author shizi 7 | * @since 2020/4/10 12:18 AM 8 | */ 9 | public class BaseTest { 10 | 11 | public void show(Object object) { 12 | if(null == object){ 13 | show("obj is null "); 14 | return; 15 | } 16 | Optional.of(object).ifPresent(objects1 -> System.out.println(objects1.toString())); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/ZkButterflyConfig.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | /** 8 | * @author shizi 9 | * @since 2020/4/25 10:05 AM 10 | */ 11 | @Data 12 | @EqualsAndHashCode(callSuper = false) 13 | public class ZkButterflyConfig extends ButterflyConfig { 14 | 15 | private String host; 16 | } 17 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-db/src/main/java/com/simonalong/butterfly/worker/db/DbConstant.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.db; 2 | 3 | import static com.simonalong.butterfly.sequence.UuidConstant.LOG_PRE; 4 | 5 | /** 6 | * @author shizi 7 | * @since 2020/4/25 12:59 AM 8 | */ 9 | public interface DbConstant { 10 | 11 | /** 12 | * 日志前缀 13 | */ 14 | String DB_LOG_PRE = LOG_PRE + "[db]"; 15 | /** 16 | * 全局id生成器的表名 17 | */ 18 | String UUID_TABLE = "butterfly_uuid_generator"; 19 | } 20 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/PaddedLong.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence; 2 | 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | /** 6 | * @author shizi 7 | * @since 2020/2/4 11:24 上午 8 | */ 9 | @SuppressWarnings("unused") 10 | public final class PaddedLong extends AtomicLong { 11 | 12 | /** 13 | * 添加防止伪共享 14 | */ 15 | private volatile long p1, p2, p3, p4, p5, p6 = 7L; 16 | 17 | public PaddedLong(long value) { 18 | super(value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/node/WorkerIdAllocator.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.node; 2 | 3 | /** 4 | * @author shizi 5 | * @since 2020/4/25 10:45 AM 6 | */ 7 | public interface WorkerIdAllocator { 8 | 9 | /** 10 | * 获取workerId 11 | * @return workerId 12 | */ 13 | Integer getWorkerId(); 14 | 15 | /** 16 | * 获取节点 worker_x 的路径信息 17 | * @return worker节点的路径 18 | */ 19 | String getWorkerNodePath(); 20 | } 21 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-db/src/main/java/com/simonalong/butterfly/worker/db/DbButterflyConfig.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.db; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | /** 8 | * @author shizi 9 | * @since 2020/4/25 12:53 AM 10 | */ 11 | @Data 12 | @EqualsAndHashCode(callSuper = false) 13 | public class DbButterflyConfig extends ButterflyConfig { 14 | 15 | private String url; 16 | private String userName; 17 | private String password; 18 | } 19 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-restful/src/main/java/com/simonalong/butterfly/distribute/ButterflyApplication.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @author shizi 8 | * @since 2020/4/28 12:16 AM 9 | */ 10 | @SpringBootApplication 11 | public class ButterflyApplication { 12 | 13 | public static void main(String... args) { 14 | SpringApplication.run(ButterflyApplication.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/node/ConfigNodeHandler.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.node; 2 | 3 | /** 4 | * 节点 config 的读取和更新管理器 5 | * 6 | * @author shizi 7 | * @since 2020/4/25 11:17 AM 8 | */ 9 | public interface ConfigNodeHandler { 10 | 11 | /** 12 | * 读取当前最大的机器个数 13 | * @return value 14 | */ 15 | int getCurrentMaxMachineNum(); 16 | 17 | /** 18 | * 更新当前最大的机器个数 19 | * 20 | * @param value 待更新的值 21 | */ 22 | void updateCurrentMaxMachineNum(int value); 23 | } 24 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-api/src/main/java/com/simonalong/butterfly/distribute/api/ButterflyDistributeApi.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.api; 2 | 3 | import com.simonalong.butterfly.distribute.model.BitSequenceDTO; 4 | import com.simonalong.butterfly.distribute.model.Response; 5 | 6 | /** 7 | * @author shizi 8 | * @since 2020/4/26 11:04 PM 9 | */ 10 | public interface ButterflyDistributeApi { 11 | 12 | /** 13 | * 获取下一个buffer对应的配置 14 | * @param namespace 业务命名空间 15 | * @return bit序列中各个部分的值 16 | */ 17 | Response getNext(String namespace); 18 | } 19 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-api/src/main/java/com/simonalong/butterfly/distribute/model/BitSequenceDTO.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @author shizi 9 | * @since 2020/4/26 11:04 PM 10 | */ 11 | @Data 12 | public class BitSequenceDTO implements Serializable { 13 | 14 | /** 15 | * 业务空间 16 | */ 17 | private String namespace; 18 | /** 19 | * 41bit时间的值 20 | */ 21 | private Long time; 22 | /** 23 | * 13bit的workId的值 24 | */ 25 | private Integer workId; 26 | } 27 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-distribute/src/main/java/com/simonalong/butterfly/worker/distribute/config/DistributeRestfulButterflyConfig.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.distribute.config; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | /** 8 | * @author shizi 9 | * @since 2020/10/28 5:43 下午 10 | */ 11 | @Data 12 | @EqualsAndHashCode(callSuper = false) 13 | public class DistributeRestfulButterflyConfig extends ButterflyConfig { 14 | 15 | /** 16 | * 域名和端口配置,比如:host:port 17 | */ 18 | private String hostAndPort; 19 | } 20 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-distribute/src/main/java/com/simonalong/butterfly/worker/distribute/config/DistributeDubboButterflyConfig.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.distribute.config; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | /** 8 | * @author shizi 9 | * @since 2020/4/26 11:32 PM 10 | */ 11 | @Data 12 | @EqualsAndHashCode(callSuper = false) 13 | public class DistributeDubboButterflyConfig extends ButterflyConfig { 14 | 15 | /** 16 | * zookeeper的域名和端口配置,比如:host:port 17 | */ 18 | private String zkHostAndPort; 19 | } 20 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-dubbo/src/main/java/com/simonalong/butterfly/distribute/ButterflyApplication.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute; 2 | 3 | import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | /** 8 | * @author shizi 9 | * @since 2020/4/28 12:16 AM 10 | */ 11 | @EnableDubbo 12 | @SpringBootApplication 13 | public class ButterflyApplication { 14 | 15 | public static void main(String... args) { 16 | SpringApplication.run(ButterflyApplication.class, args); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-db/src/main/resources/sql/uuid.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `butterfly_uuid_generator` ( 2 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id', 3 | `namespace` varchar(128) DEFAULT '' COMMENT '命名空间', 4 | `work_id` int(16) COMMENT '工作id', 5 | `last_expire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '下次失效时间', 6 | `uid` varchar(128) DEFAULT '0' COMMENT '本次启动唯一id', 7 | `ip` varchar(20) NOT NULL DEFAULT '0' COMMENT 'ip', 8 | `process_id` varchar(128) NOT NULL DEFAULT '0' COMMENT '进程id', 9 | PRIMARY KEY (`id`), 10 | UNIQUE KEY `uk_name_work` (`namespace`,`work_id`) 11 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='发号器表'; 12 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/exception/ButterflyException.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence.exception; 2 | 3 | /** 4 | * @author shizi 5 | * @since 2020/4/9 12:11 AM 6 | */ 7 | public class ButterflyException extends RuntimeException { 8 | 9 | public ButterflyException() { 10 | super(); 11 | } 12 | 13 | public ButterflyException(String msg) { 14 | super(msg); 15 | } 16 | 17 | public ButterflyException(Throwable throwable) { 18 | super(throwable); 19 | } 20 | 21 | public ButterflyException(String msg, Throwable throwable) { 22 | super(msg, throwable); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-distribute/src/main/java/com/simonalong/butterfly/worker/distribute/config/BitSequenceConfig.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.distribute.config; 2 | 3 | import com.simonalong.butterfly.distribute.model.BitSequenceDTO; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author shizi 8 | * @since 2020/4/26 11:11 PM 9 | */ 10 | @Data 11 | public class BitSequenceConfig { 12 | 13 | private Long time; 14 | private Integer sequence; 15 | private Integer workId; 16 | 17 | public void update(BitSequenceDTO bitSequenceDTO) { 18 | this.time = bitSequenceDTO.getTime(); 19 | this.workId = bitSequenceDTO.getWorkId(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/allocator/BitAllocator.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence.allocator; 2 | 3 | /** 4 | * 序列中的bit对应的值的分配器 5 | *

6 | * 1bit符号 + 41bit时间 + 9bit序列 + 13bit的workId 7 | * 8 | * @author shizi 9 | * @since 2020/3/21 上午1:00 10 | */ 11 | public interface BitAllocator { 12 | 13 | /** 14 | * 获取序列中的时间值 15 | * 16 | * @return 对应的时间 17 | */ 18 | long getTimeValue(); 19 | 20 | /** 21 | * 获取序列中的自增序列对应的值 22 | * 23 | * @return seq 24 | */ 25 | long getSequenceValue(); 26 | 27 | /** 28 | * 获取序列中的workId对应的值 29 | * 30 | * @return workId 31 | */ 32 | int getWorkIdValue(); 33 | } 34 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | butterfly-distribute 7 | com.github.simonalong 8 | 1.1.2 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | butterfly-api 14 | jar 15 | 分布式模式(distribute)下的api 16 | 17 | 18 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/WorkerLoader.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence; 2 | 3 | import com.simonalong.butterfly.sequence.spi.WorkerIdHandler; 4 | 5 | /** 6 | * @author shizi 7 | * @since 2020/4/9 12:38 AM 8 | */ 9 | public interface WorkerLoader { 10 | 11 | /** 12 | * 是否接收当前配置 13 | * 14 | * @param butterflyConfig 发号器对应的配置 15 | * @return true:接受,false:不接受 16 | */ 17 | boolean acceptConfig(ButterflyConfig butterflyConfig); 18 | 19 | /** 20 | * 获取workerId的实例 21 | * 22 | * @param namespace 命名空间 23 | * @param butterflyConfig 具体的配置 24 | * @return workerId处理器 25 | */ 26 | WorkerIdHandler loadIdHandler(String namespace, ButterflyConfig butterflyConfig); 27 | } 28 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/entity/WorkerNodeEntity.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.entity; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @author shizi 10 | * @since 2020/4/25 11:16 AM 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class WorkerNodeEntity implements Serializable { 15 | 16 | /** 17 | * 当前业务的key 18 | */ 19 | private String uidKey; 20 | /** 21 | * 当前工作节点的下次失效时间 22 | */ 23 | private Long lastExpireTime; 24 | /** 25 | * ip信息 26 | */ 27 | private String ip; 28 | /** 29 | * 进程id 30 | */ 31 | private String processId; 32 | } 33 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/allocator/BeanBitAllocator.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence.allocator; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | 5 | /** 6 | * @author shizi 7 | * @since 2020/4/27 11:49 PM 8 | */ 9 | public interface BeanBitAllocator extends BitAllocator { 10 | 11 | /** 12 | * 是否接受对应的配置 13 | * 14 | * @param butterflyConfig 具体的配置 15 | * @return true:接受,false:不接受 16 | */ 17 | default boolean acceptConfig(ButterflyConfig butterflyConfig) { 18 | return true; 19 | } 20 | 21 | /** 22 | * 初始化的配置的处理 23 | * 24 | * @param namespace 命名空间 25 | * @param butterflyConfig 配置 26 | */ 27 | default void postConstruct(String namespace, ButterflyConfig butterflyConfig) {} 28 | } 29 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/entity/ConfigNodeEntity.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.entity; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @author shizi 10 | * @since 2020/4/25 11:14 AM 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class ConfigNodeEntity implements Serializable { 15 | 16 | /** 17 | * 当前业务的最大机器个数,为2的次幂 18 | */ 19 | private Integer currentMaxMachine; 20 | /** 21 | * 时间戳占用bit 22 | */ 23 | private Integer timestampBits; 24 | /** 25 | * 机器占用的bit个数 26 | */ 27 | private Integer workerBits; 28 | /** 29 | * 自增序列占用的bit个数 30 | */ 31 | private Integer sequenceBits; 32 | } 33 | -------------------------------------------------------------------------------- /butterfly-sequence/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | butterfly 7 | com.github.simonalong 8 | 1.1.2 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | butterfly-sequence 14 | jar 15 | 16 | 17 | 18 | org.slf4j 19 | slf4j-api 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /butterfly-allocator/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | butterfly 7 | com.github.simonalong 8 | 1.1.2 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | butterfly-allocator 14 | pom 15 | 16 | 17 | butterfly-allocator-db 18 | butterfly-allocator-zookeeper 19 | butterfly-allocator-distribute 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /butterfly-distribute/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | butterfly 7 | com.github.simonalong 8 | 1.1.2 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | butterfly-distribute 14 | pom 15 | 分布式模式(distribute)的pom 16 | 17 | 18 | butterfly-api 19 | butterfly-server-dubbo 20 | butterfly-server-restful 21 | 22 | 23 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-api/src/main/java/com/simonalong/butterfly/distribute/model/Response.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @author shizi 9 | * @since 2020/4/26 11:05 PM 10 | */ 11 | @Data 12 | public class Response implements Serializable { 13 | 14 | private String errCode; 15 | private String errMsg; 16 | private T data; 17 | 18 | public static Response success(V data) { 19 | Response response = new Response<>(); 20 | response.setData(data); 21 | return response; 22 | } 23 | 24 | public static Response fail(String errCode, String errMsg) { 25 | Response response = new Response<>(); 26 | response.setErrCode(errCode); 27 | response.setErrMsg(errMsg); 28 | return response; 29 | } 30 | 31 | public Boolean isSuccess() { 32 | return null == errCode; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-dubbo/src/main/java/com/simonalong/butterfly/distribute/config/ButterflySequenceServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.config; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyIdGenerator; 4 | import com.simonalong.butterfly.worker.zookeeper.ZkButterflyConfig; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | /** 10 | * @author shizi 11 | * @since 2020/4/28 12:21 AM 12 | */ 13 | @Configuration 14 | public class ButterflySequenceServerConfig { 15 | 16 | @Value("${dubbo.registry.address}") 17 | private String zookeeperHost; 18 | 19 | @Bean 20 | public ButterflyIdGenerator butterflyIdGenerator() { 21 | ZkButterflyConfig config = new ZkButterflyConfig(); 22 | config.setHost(zookeeperHost); 23 | return ButterflyIdGenerator.getInstance(config); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/ZkConstant.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper; 2 | 3 | import static com.simonalong.butterfly.sequence.UuidConstant.LOG_PRE; 4 | 5 | /** 6 | * @author shizi 7 | * @since 2020/4/25 10:06 AM 8 | */ 9 | public interface ZkConstant { 10 | 11 | /** 12 | * 日志前缀 13 | */ 14 | String ZK_LOG_PRE = LOG_PRE + "[zk]"; 15 | /** 16 | * 根路径 17 | */ 18 | String ROOT_PATH = "/butterfly/sequence"; 19 | /** 20 | * 机器节点的左前缀 21 | */ 22 | String WORKER_NODE = "/worker"; 23 | /** 24 | * 每个业务中的配置节点路径 25 | */ 26 | String CONFIG_NODE = "/config"; 27 | /** 28 | * 业务机器不足时候的扩容锁 29 | */ 30 | String BIZ_EXPAND_LOCK = "/expand_lock"; 31 | /** 32 | * 临时节点的路径 33 | */ 34 | String SESSION_NODE = "/session"; 35 | /** 36 | * session创建时候的锁 37 | */ 38 | String SESSION_CREATE_LOCK = "/session_lock"; 39 | } 40 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-db/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | butterfly-allocator 7 | com.github.simonalong 8 | 1.1.2 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | butterfly-allocator-db 14 | jar 15 | 16 | 17 | 18 | com.github.simonalong 19 | butterfly-sequence 20 | 21 | 22 | com.github.simonalong 23 | Neo 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/node/WorkerNodeHandler.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.node; 2 | 3 | /** 4 | * 节点 worker_xx 读取和更新管理器 5 | * 6 | * @author shizi 7 | * @since 2020/4/25 11:19 AM 8 | */ 9 | public interface WorkerNodeHandler { 10 | 11 | /** 12 | * 当前业务的key 13 | * @return uidKey 14 | */ 15 | String getUidKey(); 16 | 17 | /** 18 | * 当前工作节点的下次失效时间 19 | * @return 时间 20 | */ 21 | Long getLastExpireTime(); 22 | 23 | /** 24 | * ip信息 25 | * @return ip 26 | */ 27 | String getIp(); 28 | 29 | /** 30 | * 进程id 31 | * @return pid 32 | */ 33 | String getProcessId(); 34 | 35 | /** 36 | * 获取节点的下表索引 37 | * @return 节点名下标 38 | */ 39 | Integer getWorkerId(); 40 | 41 | /** 42 | * 刷新节点信息 43 | */ 44 | void refreshNodeInfo(); 45 | 46 | /** 47 | * 刷新worker节点信息 48 | * @param workerNodePath 工作节点路径 49 | */ 50 | void refreshNodeInfo(String workerNodePath); 51 | } 52 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/ZkWorkerIdHandler.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper; 2 | 3 | import com.simonalong.butterfly.sequence.spi.WorkerIdHandler; 4 | import com.simonalong.butterfly.worker.zookeeper.node.NamespaceNodeManager; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | /** 8 | * @author shizi 9 | * @since 2020/4/25 10:05 AM 10 | */ 11 | @Slf4j 12 | public class ZkWorkerIdHandler implements WorkerIdHandler { 13 | 14 | private NamespaceNodeManager namespaceNodeManager = NamespaceNodeManager.getInstance(); 15 | private String namespace; 16 | 17 | @Override 18 | public Long getLastExpireTime() { 19 | return namespaceNodeManager.getExpireTime(namespace); 20 | } 21 | 22 | @Override 23 | public Integer getWorkerId() { 24 | return namespaceNodeManager.getWorkerId(namespace); 25 | } 26 | 27 | public ZkWorkerIdHandler(String namespace, ZookeeperClient zookeeperClient) { 28 | namespaceNodeManager.setZkClient(zookeeperClient); 29 | namespaceNodeManager.add(namespace); 30 | this.namespace = namespace; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-restful/src/main/java/com/simonalong/butterfly/distribute/config/ButterflySequenceServerConfig.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.config; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyIdGenerator; 4 | import com.simonalong.butterfly.worker.db.DbButterflyConfig; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | /** 10 | * @author shizi 11 | * @since 2020/10/28 8:04 下午 12 | */ 13 | @Configuration 14 | public class ButterflySequenceServerConfig { 15 | 16 | @Value("${butterfly.url}") 17 | private String url; 18 | @Value("${butterfly.username}") 19 | private String username; 20 | @Value("${butterfly.password}") 21 | private String password; 22 | 23 | @Bean 24 | public ButterflyIdGenerator butterflyIdGenerator() { 25 | DbButterflyConfig config = new DbButterflyConfig(); 26 | config.setUrl(url); 27 | config.setUserName(username); 28 | config.setPassword(password); 29 | return ButterflyIdGenerator.getInstance(config); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-restful/src/main/java/com/simonalong/butterfly/distribute/controller/DistributeController.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.controller; 2 | 3 | import com.simonalong.butterfly.distribute.model.BitSequenceDTO; 4 | import com.simonalong.butterfly.distribute.model.Response; 5 | import com.simonalong.butterfly.distribute.server.DistributeService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | /** 13 | * @author shizi 14 | * @since 2020/10/28 5:22 下午 15 | */ 16 | @RequestMapping("api/butterfly/v1/worker") 17 | @RestController 18 | public class DistributeController { 19 | 20 | @Autowired 21 | private DistributeService distributeService; 22 | 23 | @GetMapping("getNext/{namespace}") 24 | public Response getNext(@PathVariable("namespace") String namespace) { 25 | return Response.success(distributeService.getNext(namespace)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-restful/src/main/java/com/simonalong/butterfly/distribute/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.exception; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 业务异常 7 | */ 8 | public class BusinessException extends RuntimeException { 9 | 10 | @Getter 11 | private String errCode; 12 | private String errMsg; 13 | 14 | public BusinessException() { 15 | super(); 16 | } 17 | 18 | public BusinessException(Throwable e) { 19 | super(e); 20 | } 21 | 22 | public BusinessException(String message) { 23 | super(message); 24 | this.errMsg = message; 25 | } 26 | 27 | public BusinessException(String message, Throwable cause) { 28 | super(message, cause); 29 | this.errMsg = message; 30 | } 31 | 32 | public BusinessException(String errCode, String message) { 33 | super(message); 34 | this.errCode = errCode; 35 | this.errMsg = message; 36 | } 37 | 38 | public BusinessException(String errCode, String message, Throwable cause) { 39 | super(message, cause); 40 | this.errCode = errCode; 41 | this.errMsg = message; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/spi/WorkerIdHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence.spi; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 5 | import com.simonalong.butterfly.sequence.WorkerLoader; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * @author shizi 11 | * @since 2020/4/9 12:36 AM 12 | */ 13 | public final class WorkerIdHandlerFactory { 14 | 15 | static { 16 | ServiceLoaderFactory.init(WorkerLoader.class); 17 | } 18 | 19 | public static WorkerIdHandler getWorkerIdHandler(String namespace, ButterflyConfig butterflyConfig) { 20 | Collection workerLoaderCollection = ServiceLoaderFactory.getChildObject(WorkerLoader.class); 21 | for (WorkerLoader allocator : workerLoaderCollection) { 22 | if (allocator.acceptConfig(butterflyConfig)) { 23 | return allocator.loadIdHandler(namespace, butterflyConfig); 24 | } 25 | } 26 | // throw new ButterflyException("not find workerId allocator, please add butterfly-worker-allocator-db or butterfly-worker-allocator-distribute"); 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/allocator/BeanBitAllocatorFactory.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence.allocator; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import com.simonalong.butterfly.sequence.spi.ServiceLoaderFactory; 5 | import lombok.experimental.UtilityClass; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * @author shizi 11 | * @since 2020/4/27 12:19 PM 12 | */ 13 | @UtilityClass 14 | public class BeanBitAllocatorFactory { 15 | 16 | static { 17 | ServiceLoaderFactory.init(BeanBitAllocator.class); 18 | } 19 | 20 | public BitAllocator getBitAllocator(String namespace, ButterflyConfig butterflyConfig) { 21 | Collection bitAllocatorCollection = ServiceLoaderFactory.getChildObject(BeanBitAllocator.class); 22 | if (null != bitAllocatorCollection) { 23 | for (BeanBitAllocator allocator : bitAllocatorCollection) { 24 | if (allocator.acceptConfig(butterflyConfig)) { 25 | allocator.postConstruct(namespace, butterflyConfig); 26 | return allocator; 27 | } 28 | } 29 | } 30 | return new DefaultBitAllocator(namespace, butterflyConfig); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/ZkWorkerLoader.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import com.simonalong.butterfly.sequence.WorkerLoader; 5 | import com.simonalong.butterfly.sequence.spi.WorkerIdHandler; 6 | 7 | /** 8 | * @author shizi 9 | * @since 2020/4/25 10:03 AM 10 | */ 11 | public class ZkWorkerLoader implements WorkerLoader { 12 | 13 | private ZookeeperClient zkClient; 14 | 15 | @Override 16 | public boolean acceptConfig(ButterflyConfig butterflyConfig) { 17 | if (null == butterflyConfig) { 18 | return false; 19 | } 20 | if (!(butterflyConfig instanceof ZkButterflyConfig)) { 21 | return false; 22 | } 23 | 24 | ZkButterflyConfig zkConfig = (ZkButterflyConfig) butterflyConfig; 25 | String host = zkConfig.getHost(); 26 | zkClient = ZookeeperClient.getInstance(); 27 | zkClient.connect(host); 28 | return true; 29 | } 30 | 31 | @Override 32 | public WorkerIdHandler loadIdHandler(String namespace, ButterflyConfig butterflyConfig) { 33 | return new ZkWorkerIdHandler(namespace, zkClient); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/node/ZkNodeHelper.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.node; 2 | 3 | import lombok.experimental.UtilityClass; 4 | 5 | import static com.simonalong.butterfly.worker.zookeeper.ZkConstant.*; 6 | 7 | /** 8 | * @author shizi 9 | * @since 2020/4/25 11:07 AM 10 | */ 11 | @UtilityClass 12 | public class ZkNodeHelper { 13 | 14 | public String getNamespacePath(String namespace) { 15 | return ROOT_PATH + "/" + namespace; 16 | } 17 | 18 | public String getConfigPath(String namespace) { 19 | return getNamespacePath(namespace) + CONFIG_NODE; 20 | } 21 | 22 | public String getWorkerPath(String namespace, Integer workerId) { 23 | return getNamespacePath(namespace) + "/worker_" + workerId; 24 | } 25 | 26 | public String getSession(String namespace, Integer workerId) { 27 | return getWorkerPath(namespace, workerId) + SESSION_NODE; 28 | } 29 | 30 | public String getBizExpandLock(String namespace) { 31 | return ROOT_PATH + "/" + namespace + BIZ_EXPAND_LOCK; 32 | } 33 | 34 | public String getSessionCreateLock(String namespace) { 35 | return ROOT_PATH + "/" + namespace + SESSION_CREATE_LOCK; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-db/src/main/java/com/simonalong/butterfly/worker/db/entity/UuidGeneratorDO.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.db.entity; 2 | 3 | import com.simonalong.neo.annotation.Column; 4 | import com.simonalong.neo.annotation.Table; 5 | import lombok.Data; 6 | import lombok.experimental.Accessors; 7 | 8 | import java.sql.Timestamp; 9 | 10 | /** 11 | * @author shizi 12 | * @since 2020/4/26 10:28 PM 13 | */ 14 | @Data 15 | @Table("butterfly_uuid_generator") 16 | @Accessors(chain = true) 17 | public class UuidGeneratorDO { 18 | 19 | 20 | /** 21 | * 主键id 22 | */ 23 | @Column("id") 24 | private Long id; 25 | 26 | /** 27 | * 命名空间 28 | */ 29 | @Column("namespace") 30 | private String namespace; 31 | 32 | /** 33 | * 工作id 34 | */ 35 | @Column("work_id") 36 | private Integer workId; 37 | 38 | /** 39 | * 下次失效时间 40 | */ 41 | @Column("last_expire_time") 42 | private Timestamp lastExpireTime; 43 | 44 | /** 45 | * 本次启动唯一id 46 | */ 47 | @Column("uid") 48 | private String uid; 49 | 50 | /** 51 | * 进程id 52 | */ 53 | @Column("process_id") 54 | private String processId; 55 | 56 | /** 57 | * ip 58 | */ 59 | @Column("ip") 60 | private String ip; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /butterfly-sample/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ${PATTERN} 8 | UTF-8 9 | 10 | 11 | 12 | 13 | ${LOG_HOME}/butter.log 14 | 15 | INFO 16 | 17 | 18 | ${PATTERN} 19 | UTF-8 20 | 21 | 22 | ${LOG_HOME}/butter.log.%d{yyyyMMdd} 23 | 30 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-restful/src/main/java/com/simonalong/butterfly/distribute/uti/ExceptionUtil.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.uti; 2 | 3 | import lombok.experimental.UtilityClass; 4 | 5 | /** 6 | * @author shizi 7 | * @since 2020/6/2 4:24 PM 8 | */ 9 | @UtilityClass 10 | public class ExceptionUtil { 11 | 12 | /** 13 | * 获取异常堆栈中的匹配的异常 14 | * 15 | * @param throwable 异常类 16 | * @param tClass 异常类型的类 17 | * @param 异常类型 18 | * @return 具体的某个异常类 19 | */ 20 | @SuppressWarnings("unchecked") 21 | public T getCause(Throwable throwable, Class tClass) { 22 | while (null != throwable) { 23 | if (tClass.isAssignableFrom(throwable.getClass())) { 24 | return (T) throwable; 25 | } 26 | throwable = throwable.getCause(); 27 | } 28 | return null; 29 | } 30 | 31 | /** 32 | * 获取异常堆栈中异常 33 | * 34 | * @param throwable 异常类 35 | * @param 异常类型 36 | * @return 具体的某个异常类 37 | */ 38 | @SuppressWarnings("unchecked") 39 | public T unwrapException(Throwable throwable) { 40 | Throwable e = throwable; 41 | while (null != throwable) { 42 | e = throwable; 43 | if (null == throwable.getClass()) { 44 | return (T) e; 45 | } 46 | throwable = throwable.getCause(); 47 | } 48 | return (T) e; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-db/src/main/java/com/simonalong/butterfly/worker/db/DbWorkerLoader.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.db; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import com.simonalong.butterfly.sequence.WorkerLoader; 5 | import com.simonalong.butterfly.sequence.spi.WorkerIdHandler; 6 | import com.simonalong.neo.Neo; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import static com.simonalong.butterfly.worker.db.DbConstant.DB_LOG_PRE; 10 | 11 | /** 12 | * @author shizi 13 | * @since 2020/4/25 12:49 AM 14 | */ 15 | @Slf4j 16 | public class DbWorkerLoader implements WorkerLoader { 17 | 18 | private static Neo db; 19 | 20 | @Override 21 | public boolean acceptConfig(ButterflyConfig butterflyConfig) { 22 | if (null == butterflyConfig) { 23 | return false; 24 | } 25 | if (!(butterflyConfig instanceof DbButterflyConfig)) { 26 | return false; 27 | } 28 | 29 | try { 30 | DbButterflyConfig dbButterflyConfig = (DbButterflyConfig) butterflyConfig; 31 | String url = dbButterflyConfig.getUrl(); 32 | String userName = dbButterflyConfig.getUserName(); 33 | String password = dbButterflyConfig.getPassword(); 34 | db = Neo.connect(url, userName, password); 35 | } catch (Throwable e) { 36 | log.error(DB_LOG_PRE + "config is illegal ", e); 37 | } 38 | return true; 39 | } 40 | 41 | @Override 42 | public WorkerIdHandler loadIdHandler(String namespace, ButterflyConfig butterflyConfig) { 43 | return new DbWorkerIdHandler(namespace, db); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | butterfly-allocator 7 | com.github.simonalong 8 | 1.1.2 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | butterfly-allocator-zookeeper 14 | jar 15 | 16 | 17 | 18 | com.github.simonalong 19 | butterfly-sequence 20 | 21 | 22 | com.alibaba 23 | fastjson 24 | 1.2.79 25 | 26 | 27 | org.apache.zookeeper 28 | zookeeper 29 | 3.4.14 30 | 31 | 32 | slf4j-log4j12 33 | org.slf4j 34 | 35 | 36 | 37 | 38 | 39 | junit 40 | junit 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/UuidSplicer.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence; 2 | 3 | import com.simonalong.butterfly.sequence.allocator.BitAllocator; 4 | import com.simonalong.butterfly.sequence.allocator.BeanBitAllocatorFactory; 5 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 6 | import lombok.Getter; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import static com.simonalong.butterfly.sequence.UuidConstant.*; 10 | 11 | /** 12 | * @author shizi 13 | * @since 2020/4/25 12:38 AM 14 | */ 15 | @Slf4j 16 | final class UuidSplicer { 17 | 18 | @Getter 19 | private BitAllocator bitAllocator; 20 | 21 | UuidSplicer(String bizNamespace, ButterflyConfig butterflyConfig) { 22 | synchronized (this) { 23 | this.bitAllocator = BeanBitAllocatorFactory.getBitAllocator(bizNamespace, butterflyConfig); 24 | 25 | // 延迟启动固定时间10ms 26 | try { 27 | this.wait(DELAY_START_TIME); 28 | } catch (InterruptedException e) { 29 | log.warn(LOG_PRE + "delay start fail"); 30 | Thread.currentThread().interrupt(); 31 | } 32 | } 33 | } 34 | 35 | synchronized Long splice() { 36 | if (null == bitAllocator) { 37 | throw new ButterflyException("bitAllocator not init"); 38 | } 39 | int workerId = bitAllocator.getWorkIdValue(); 40 | long seq = bitAllocator.getSequenceValue(); 41 | long time = bitAllocator.getTimeValue(); 42 | 43 | return (time << ((SEQ_HIGH_BITS + WORKER_BITS + SEQ_LOW_BITS)) | (((seq << WORKER_BITS) & SEQ_HIGH_MARK)) | ((workerId << SEQ_LOW_BITS) & WORKER_MARK) | (seq & SEQ_LOW_MARK)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-restful/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | butterfly-distribute 7 | com.github.simonalong 8 | 1.1.2 9 | 10 | 4.0.0 11 | 12 | butterfly-server-restful 13 | 分布式模式(distribute)下的服务端 14 | 15 | 16 | 17 | com.github.simonalong 18 | butterfly-api 19 | 20 | 21 | com.github.simonalong 22 | butterfly-allocator-db 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | mysql 35 | mysql-connector-java 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-maven-plugin 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/node/DefaultConfigNodeHandler.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.node; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 5 | import com.simonalong.butterfly.worker.zookeeper.ZookeeperClient; 6 | import com.simonalong.butterfly.worker.zookeeper.entity.ConfigNodeEntity; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import static com.simonalong.butterfly.worker.zookeeper.ZkConstant.ZK_LOG_PRE; 10 | 11 | /** 12 | * 对配置节点的处理 13 | * 14 | * @author shizi 15 | * @since 2020/4/25 11:17 AM 16 | */ 17 | @Slf4j 18 | public class DefaultConfigNodeHandler implements ConfigNodeHandler { 19 | 20 | private String namespace; 21 | private ConfigNodeEntity configNodeEntity; 22 | private ZookeeperClient zookeeperClient; 23 | 24 | public DefaultConfigNodeHandler(String namespace, ZookeeperClient zookeeperClient) { 25 | this.zookeeperClient = zookeeperClient; 26 | this.namespace = namespace; 27 | init(); 28 | } 29 | 30 | @Override 31 | public int getCurrentMaxMachineNum() { 32 | return configNodeEntity.getCurrentMaxMachine(); 33 | } 34 | 35 | @Override 36 | public void updateCurrentMaxMachineNum(int maxMachine) { 37 | configNodeEntity.setCurrentMaxMachine(maxMachine); 38 | try { 39 | zookeeperClient.writeNodeData(ZkNodeHelper.getConfigPath(namespace), JSON.toJSONString(configNodeEntity.setCurrentMaxMachine(maxMachine))); 40 | } catch (Throwable e) { 41 | log.error(ZK_LOG_PRE + "update workerId fail"); 42 | throw new ButterflyException("update workerId fail"); 43 | } 44 | } 45 | 46 | private void init() { 47 | configNodeEntity = zookeeperClient.readDataJson(ZkNodeHelper.getConfigPath(namespace), ConfigNodeEntity.class); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-distribute/src/main/java/com/simonalong/butterfly/worker/distribute/DistributeBitAllocator.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.distribute; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import com.simonalong.butterfly.sequence.allocator.BeanBitAllocator; 5 | import com.simonalong.butterfly.worker.distribute.config.DistributeDubboButterflyConfig; 6 | import com.simonalong.butterfly.worker.distribute.config.DistributeRestfulButterflyConfig; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * @author shizi 11 | * @since 2020/4/27 7:52 PM 12 | */ 13 | @Slf4j 14 | public class DistributeBitAllocator implements BeanBitAllocator { 15 | 16 | private BufferManager bufferManager; 17 | /** 18 | * 是否接受对应的配置 19 | * 20 | * @param butterflyConfig 具体的配置 21 | * @return true:接受,false:不接受 22 | */ 23 | @Override 24 | public boolean acceptConfig(ButterflyConfig butterflyConfig) { 25 | if (null == butterflyConfig) { 26 | return false; 27 | } 28 | return butterflyConfig instanceof DistributeDubboButterflyConfig || butterflyConfig instanceof DistributeRestfulButterflyConfig; 29 | } 30 | 31 | /** 32 | * 初始化的配置的处理 33 | * 34 | * @param namespace 命名空间 35 | * @param butterflyConfig 配置 36 | */ 37 | @Override 38 | public void postConstruct(String namespace, ButterflyConfig butterflyConfig) { 39 | // 初始化客户端 40 | SequenceClient.getInstance().init(butterflyConfig); 41 | 42 | // 首次回调 43 | this.bufferManager = new BufferManager(SequenceClient.getInstance().getNext(namespace)); 44 | } 45 | 46 | @Override 47 | public long getTimeValue() { 48 | return bufferManager.getTime(); 49 | } 50 | 51 | @Override 52 | public long getSequenceValue() { 53 | return bufferManager.getSequence(); 54 | } 55 | 56 | @Override 57 | public int getWorkIdValue() { 58 | return bufferManager.getWorkId(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/spi/ServiceLoaderFactory.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence.spi; 2 | 3 | import lombok.experimental.UtilityClass; 4 | 5 | import java.util.*; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | /** 9 | * @author shizi 10 | * @since 2020/4/9 12:21 AM 11 | */ 12 | @UtilityClass 13 | public class ServiceLoaderFactory { 14 | 15 | private static final Map, Collection> SERVICE_MAP = new ConcurrentHashMap<>(); 16 | 17 | /** 18 | * 初始化某些类 19 | * 20 | * @param interfaceClasses 多个待扩展的spi类型接口 21 | */ 22 | public void init(Class... interfaceClasses) { 23 | Arrays.stream(interfaceClasses).forEach(interfaceClass -> { 24 | for (Object instance : ServiceLoader.load(interfaceClass)) { 25 | SERVICE_MAP.compute(interfaceClass, (k, v) -> { 26 | if (null == v) { 27 | Collection valueCollection = new LinkedList<>(); 28 | valueCollection.add(instance); 29 | return valueCollection; 30 | } else { 31 | v.add(instance); 32 | return v; 33 | } 34 | }); 35 | } 36 | }); 37 | } 38 | 39 | /** 40 | * 获取接口类对应的所有spi对象集合 41 | * 42 | * @param interfaceClass 目标接口类 43 | * @param 类型 44 | * @return spi对象集合 45 | */ 46 | @SuppressWarnings("unchecked") 47 | public Collection getChildObject(Class interfaceClass) { 48 | return (Collection) SERVICE_MAP.get(interfaceClass); 49 | } 50 | 51 | /** 52 | * 获取指定类的对象 53 | * 54 | * @param tClass 目标类 55 | * @param 类型 56 | * @return 目标类的实例 57 | */ 58 | @SuppressWarnings("unchecked") 59 | public T getTarget(Class tClass) { 60 | return (T) SERVICE_MAP.entrySet().stream().flatMap(e -> { 61 | if (e.getKey().isAssignableFrom(tClass)) { 62 | return e.getValue().stream(); 63 | } 64 | return null; 65 | }).filter(Objects::nonNull).filter(o -> o.getClass().equals(tClass)).findFirst().orElse(null); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-distribute/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | butterfly-allocator 7 | com.github.simonalong 8 | 1.1.2 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | butterfly-allocator-distribute 14 | jar 15 | 16 | 17 | 18 | com.github.simonalong 19 | butterfly-sequence 20 | 21 | 22 | com.github.simonalong 23 | butterfly-api 24 | 25 | 26 | 27 | 28 | org.apache.dubbo 29 | dubbo 30 | true 31 | 32 | 33 | org.apache.dubbo 34 | dubbo-spring-boot-starter 35 | true 36 | 37 | 38 | org.apache.curator 39 | curator-framework 40 | true 41 | 42 | 43 | org.apache.curator 44 | curator-recipes 45 | true 46 | 47 | 48 | 49 | 50 | com.squareup.okhttp3 51 | okhttp 52 | 3.9.1 53 | 54 | 55 | com.alibaba 56 | fastjson 57 | 58 | 59 | 60 | com.github.simonalong 61 | Neo 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/UuidConstant.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * @author shizi 7 | * @since 2020/2/3 11:54 上午 8 | */ 9 | public interface UuidConstant { 10 | 11 | /** 12 | * log前缀 13 | */ 14 | String LOG_PRE = "[butterfly]"; 15 | 16 | /** 17 | * 自增域的低位bit数 18 | */ 19 | int SEQ_LOW_BITS = 1; 20 | /** 21 | * 机器域占用的bit数 22 | */ 23 | int WORKER_BITS = 13; 24 | /** 25 | * 自增域的高位bit数 26 | */ 27 | int SEQ_HIGH_BITS = 8; 28 | /** 29 | * 时间域占用的bit数 30 | */ 31 | int TIME_BITS = 41; 32 | /** 33 | * 符号域占用的bit数 34 | */ 35 | int SYMBOL_BITS = 1; 36 | /** 37 | * 自增域占用的bit数 38 | */ 39 | int SEQ_BITS = SEQ_HIGH_BITS + SEQ_LOW_BITS; 40 | 41 | /** 42 | * 自增域的低位的掩码 43 | */ 44 | long SEQ_LOW_MARK = ~(-1L << SEQ_LOW_BITS); 45 | /** 46 | * 机器id的掩码 47 | */ 48 | long WORKER_MARK = (~(-1L << WORKER_BITS)) << SEQ_LOW_BITS; 49 | /** 50 | * 自增域的高位的掩码 51 | */ 52 | long SEQ_HIGH_MARK = ~(-1L << (SEQ_HIGH_BITS)) << (WORKER_BITS + SEQ_LOW_BITS); 53 | /** 54 | * 时间域的高位的掩码 55 | */ 56 | long TIME_MARK = ~(-1L << (TIME_BITS)) << (SEQ_HIGH_BITS + WORKER_BITS + SEQ_LOW_BITS); 57 | /** 58 | * 符号域的高位的掩码 59 | */ 60 | long SYMBOL_MARK = ~(-1L << (SYMBOL_BITS)) << (TIME_BITS + SEQ_HIGH_BITS + WORKER_BITS + SEQ_LOW_BITS); 61 | 62 | /** 63 | * 自增域的虚拟掩码 64 | */ 65 | long SEQ_MARK = ~(-1L << (SEQ_LOW_BITS + SEQ_HIGH_BITS)); 66 | 67 | /** 68 | * worker节点的最大值 69 | */ 70 | long MAX_WORKER_SIZE = 1 << WORKER_BITS; 71 | /** 72 | * 自增域最大值 73 | */ 74 | long SEQ_MAX_SIZE = 1 << SEQ_BITS; 75 | 76 | /** 77 | * 时间过慢后域当前时间的门限,当前暂时设置为20个小时 78 | */ 79 | long DELAY_THREAD_HOLD = TimeUnit.HOURS.toMillis(20); 80 | /** 81 | * 节点保留的时间 82 | */ 83 | long KEEP_NODE_EXIST_TIME = TimeUnit.HOURS.toMillis(24); 84 | /** 85 | * 时间回拨的容忍度,2秒 86 | */ 87 | long TIME_BACK = 2000L; 88 | /** 89 | * 延迟启动时间,10ms 90 | */ 91 | long DELAY_START_TIME = 10L; 92 | /** 93 | * 分布式模式下的业务节点 94 | */ 95 | String DISTRIBUTE_SERVER = "butterfly-server"; 96 | /** 97 | * 心跳间隔时间单位秒 98 | */ 99 | long HEART_TIME = 5L; 100 | } 101 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-distribute/src/main/java/com/simonalong/butterfly/worker/distribute/ButterflySeqGeneratorFactory.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.distribute; 2 | 3 | import com.simonalong.butterfly.distribute.api.ButterflyDistributeApi; 4 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.dubbo.config.ApplicationConfig; 7 | import org.apache.dubbo.config.ReferenceConfig; 8 | import org.apache.dubbo.config.RegistryConfig; 9 | 10 | /** 11 | * @author shizi 12 | * @since 2020/4/26 11:13 PM 13 | */ 14 | @Slf4j 15 | public class ButterflySeqGeneratorFactory { 16 | 17 | private static volatile ButterflySeqGeneratorFactory INSTANCE = null; 18 | private static volatile ButterflyDistributeApi butterflyDistributeApi; 19 | private String zkAddress = null; 20 | 21 | private ButterflySeqGeneratorFactory(){} 22 | 23 | public static ButterflySeqGeneratorFactory getInstance() { 24 | if (null != INSTANCE) { 25 | return INSTANCE; 26 | } 27 | 28 | synchronized (ButterflySeqGeneratorFactory.class) { 29 | if (null == INSTANCE) { 30 | INSTANCE = new ButterflySeqGeneratorFactory(); 31 | } 32 | } 33 | return INSTANCE; 34 | } 35 | 36 | public synchronized ButterflySeqGeneratorFactory init(String zkAddress) { 37 | this.zkAddress = zkAddress; 38 | // 首次调用 39 | getSequenceApi(); 40 | return this; 41 | } 42 | 43 | public static boolean haveInitialized() { 44 | return butterflyDistributeApi != null; 45 | } 46 | 47 | public ButterflyDistributeApi getSequenceApi() { 48 | if (null != butterflyDistributeApi) { 49 | return butterflyDistributeApi; 50 | } 51 | assert null != zkAddress : "please set zookeeper's address"; 52 | 53 | try { 54 | // 硬编码引用 55 | ReferenceConfig rc = new ReferenceConfig<>(); 56 | rc.setApplication(new ApplicationConfig("butterfly-consumer")); 57 | rc.setRegistry(new RegistryConfig(zkAddress, "zookeeper")); 58 | rc.setInterface(ButterflyDistributeApi.class); 59 | 60 | butterflyDistributeApi = rc.get(); 61 | return butterflyDistributeApi; 62 | } catch (Throwable e) { 63 | log.error("getSequenceApi exception", e); 64 | throw new ButterflyException("getSequenceApi exception"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/TimeAdjuster.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence; 2 | 3 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 4 | import lombok.experimental.UtilityClass; 5 | 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | 9 | import static com.simonalong.butterfly.sequence.UuidConstant.DELAY_THREAD_HOLD; 10 | import static com.simonalong.butterfly.sequence.UuidConstant.TIME_BACK; 11 | 12 | /** 13 | * 时间调整工具 14 | * 15 | * @author shizi 16 | * @since 2020/2/7 3:07 下午 17 | */ 18 | @UtilityClass 19 | public class TimeAdjuster { 20 | 21 | /** 22 | * 优化调整时间,防止时间过快或者过慢 23 | * 24 | *

注意下面的{@code @SuppressWarnings("all")}这个不能删除,因为代码{@code Thread.sleep}通过不了sonar检测,添加 25 | * 这个可以使其通过,sonar检测建议采用{@code wait()},但是{@code wait}这个会释放锁,但是这里的逻辑是不能释放锁,释放锁会有时间 26 | * 增速过快问题 27 | * 28 | * @param usedTime 序列中使用的时间 29 | */ 30 | public void adjustTime(PaddedLong usedTime) { 31 | long currentUsedTime = usedTime.get(); 32 | long now = System.currentTimeMillis(); 33 | if (currentUsedTime < now) { 34 | // 如果过慢,则向前前进一段时间 35 | if (now - currentUsedTime > DELAY_THREAD_HOLD) { 36 | usedTime.addAndGet(DELAY_THREAD_HOLD >>> 2); 37 | } 38 | } else if (currentUsedTime > now) { 39 | // 时间过快,如果超过当前时间,则等待一定时间 40 | long offset = currentUsedTime - now; 41 | if (offset <= TIME_BACK) { 42 | try { 43 | //时间偏差大于门限值,则等待两倍 44 | Thread.sleep(offset << 1); 45 | if (currentUsedTime > System.currentTimeMillis()) { 46 | throw new ButterflyException("回拨补偿尝试失败"); 47 | } 48 | } catch (InterruptedException ignored) { 49 | Thread.currentThread().interrupt(); 50 | } 51 | } else { 52 | throw new ButterflyException("回拨时间过大"); 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * 获取相对一个固定时间起点的时间,用于延长年的使用时间 59 | * 60 | * @param currentTime 当前时间 61 | * @return 调整后的时间 62 | */ 63 | public long getRelativeTime(long currentTime) { 64 | if (currentTime <= ButterflyIdGenerator.startTime) { 65 | throw new ButterflyException("回拨时间超过:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S").format(new Date(currentTime))); 66 | } 67 | return currentTime - ButterflyIdGenerator.startTime; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-dubbo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | butterfly-distribute 7 | com.github.simonalong 8 | 1.1.2 9 | 10 | 4.0.0 11 | 12 | butterfly-server-dubbo 13 | 分布式模式(distribute)下的服务端(dubbo) 14 | 15 | 16 | 17 | com.github.simonalong 18 | butterfly-api 19 | 20 | 21 | com.github.simonalong 22 | butterfly-allocator-zookeeper 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | 35 | org.apache.dubbo 36 | dubbo 37 | 38 | 39 | org.apache.dubbo 40 | dubbo-spring-boot-starter 41 | 42 | 43 | org.apache.curator 44 | curator-framework 45 | 46 | 47 | zookeeper 48 | org.apache.zookeeper 49 | 50 | 51 | 52 | 53 | org.apache.curator 54 | curator-recipes 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-maven-plugin 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-restful/src/main/java/com/simonalong/butterfly/distribute/server/DistributeService.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.server; 2 | 3 | import com.simonalong.butterfly.distribute.model.BitSequenceDTO; 4 | import com.simonalong.butterfly.sequence.ButterflyIdGenerator; 5 | import com.simonalong.butterfly.sequence.PaddedLong; 6 | import com.simonalong.butterfly.sequence.TimeAdjuster; 7 | import com.simonalong.butterfly.sequence.allocator.DefaultBitAllocator; 8 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 9 | import org.springframework.beans.factory.InitializingBean; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.Set; 14 | import java.util.concurrent.ConcurrentSkipListSet; 15 | 16 | /** 17 | * @author shizi 18 | * @since 2020/10/28 5:26 下午 19 | */ 20 | @Service 21 | public class DistributeService implements InitializingBean { 22 | 23 | @Autowired 24 | private ButterflyIdGenerator butterflyIdGenerator; 25 | private Set namespaceSet = new ConcurrentSkipListSet<>(); 26 | private PaddedLong currentTime; 27 | 28 | public BitSequenceDTO getNext(String namespace) { 29 | DefaultBitAllocator bitAllocator; 30 | if (!namespaceSet.contains(namespace)) { 31 | butterflyIdGenerator.addNamespaces(namespace); 32 | namespaceSet.add(namespace); 33 | } 34 | bitAllocator = (DefaultBitAllocator) butterflyIdGenerator.getBitAllocator(namespace); 35 | 36 | BitSequenceDTO sequenceDTO = new BitSequenceDTO(); 37 | sequenceDTO.setNamespace(namespace); 38 | sequenceDTO.setTime(getTimeValue(namespace, bitAllocator)); 39 | sequenceDTO.setWorkId(bitAllocator.getWorkIdValue()); 40 | return sequenceDTO; 41 | } 42 | 43 | /** 44 | * 获取序列中的时间值 45 | */ 46 | private long getTimeValue(String namespace, DefaultBitAllocator bitAllocator) { 47 | long time = TimeAdjuster.getRelativeTime(currentTime.getAndIncrement()); 48 | 49 | TimeAdjuster.adjustTime(currentTime); 50 | currentTimeIsValid(namespace, bitAllocator); 51 | return time; 52 | } 53 | 54 | /** 55 | * 判断当前系统是否还是可用的 56 | *

57 | * 如果当前时间和上次的过期时间之间相差达到一定的阈值,则让当前应用系统不可用 58 | */ 59 | private void currentTimeIsValid(String namespace, DefaultBitAllocator bitAllocator) { 60 | Long expireTime = bitAllocator.getLastExpireTime(namespace); 61 | if (null == expireTime) { 62 | return; 63 | } 64 | long now = System.currentTimeMillis(); 65 | if (now >= expireTime) { 66 | throw new ButterflyException("zk连接失效,超过最大过期时间"); 67 | } 68 | } 69 | 70 | @Override 71 | public void afterPropertiesSet() { 72 | currentTime = new PaddedLong(System.currentTimeMillis()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-dubbo/src/main/java/com/simonalong/butterfly/distribute/impl/ButterflyDistributeApiImpl.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.impl; 2 | 3 | import com.simonalong.butterfly.distribute.api.ButterflyDistributeApi; 4 | import com.simonalong.butterfly.distribute.model.BitSequenceDTO; 5 | import com.simonalong.butterfly.distribute.model.Response; 6 | import com.simonalong.butterfly.sequence.ButterflyIdGenerator; 7 | import com.simonalong.butterfly.sequence.PaddedLong; 8 | import com.simonalong.butterfly.sequence.TimeAdjuster; 9 | import com.simonalong.butterfly.sequence.allocator.DefaultBitAllocator; 10 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.apache.dubbo.config.annotation.Service; 13 | import org.springframework.beans.factory.InitializingBean; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | 16 | import java.util.Set; 17 | import java.util.concurrent.ConcurrentSkipListSet; 18 | 19 | /** 20 | * @author shizi 21 | * @since 2020/4/28 12:11 AM 22 | */ 23 | @Slf4j 24 | @Service 25 | public class ButterflyDistributeApiImpl implements ButterflyDistributeApi, InitializingBean { 26 | 27 | @Autowired 28 | private ButterflyIdGenerator butterflyIdGenerator; 29 | private final Set namespaceSet = new ConcurrentSkipListSet<>(); 30 | private PaddedLong currentTime; 31 | 32 | @Override 33 | public Response getNext(String namespace) { 34 | DefaultBitAllocator bitAllocator; 35 | if (!namespaceSet.contains(namespace)) { 36 | butterflyIdGenerator.addNamespaces(namespace); 37 | namespaceSet.add(namespace); 38 | } 39 | bitAllocator = (DefaultBitAllocator) butterflyIdGenerator.getBitAllocator(namespace); 40 | 41 | BitSequenceDTO sequenceDTO = new BitSequenceDTO(); 42 | sequenceDTO.setNamespace(namespace); 43 | sequenceDTO.setTime(getTimeValue(namespace, bitAllocator)); 44 | sequenceDTO.setWorkId(bitAllocator.getWorkIdValue()); 45 | return Response.success(sequenceDTO); 46 | } 47 | 48 | /** 49 | * 获取序列中的时间值 50 | */ 51 | private long getTimeValue(String namespace, DefaultBitAllocator bitAllocator) { 52 | long time = TimeAdjuster.getRelativeTime(currentTime.getAndIncrement()); 53 | 54 | TimeAdjuster.adjustTime(currentTime); 55 | currentTimeIsValid(namespace, bitAllocator); 56 | return time; 57 | } 58 | 59 | /** 60 | * 判断当前系统是否还是可用的 61 | *

62 | * 如果当前时间和上次的过期时间之间相差达到一定的阈值,则让当前应用系统不可用 63 | */ 64 | private void currentTimeIsValid(String namespace, DefaultBitAllocator bitAllocator) { 65 | Long expireTime = bitAllocator.getLastExpireTime(namespace); 66 | if (null == expireTime) { 67 | return; 68 | } 69 | long now = System.currentTimeMillis(); 70 | if (now >= expireTime) { 71 | throw new ButterflyException("zk连接失效,超过最大过期时间"); 72 | } 73 | } 74 | 75 | @Override 76 | public void afterPropertiesSet() { 77 | currentTime = new PaddedLong(System.currentTimeMillis()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-distribute/src/main/java/com/simonalong/butterfly/worker/distribute/SequenceClient.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.distribute; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.simonalong.butterfly.distribute.api.ButterflyDistributeApi; 5 | import com.simonalong.butterfly.distribute.model.BitSequenceDTO; 6 | import com.simonalong.butterfly.distribute.model.Response; 7 | import com.simonalong.butterfly.sequence.ButterflyConfig; 8 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 9 | import com.simonalong.butterfly.worker.distribute.config.DistributeDubboButterflyConfig; 10 | import com.simonalong.butterfly.worker.distribute.config.DistributeRestfulButterflyConfig; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.util.HashMap; 14 | 15 | import static com.simonalong.butterfly.worker.distribute.DistributeConstant.DTB_LOG_PRE; 16 | 17 | /** 18 | * @author shizi 19 | * @since 2020/10/28 5:45 下午 20 | */ 21 | @Slf4j 22 | public class SequenceClient { 23 | 24 | private static volatile SequenceClient INSTANCE = null; 25 | private String serverHostAndPort = null; 26 | 27 | private SequenceClient() {} 28 | 29 | public static SequenceClient getInstance() { 30 | if (null != INSTANCE) { 31 | return INSTANCE; 32 | } 33 | 34 | synchronized (SequenceClient.class) { 35 | if (null == INSTANCE) { 36 | INSTANCE = new SequenceClient(); 37 | } 38 | } 39 | return INSTANCE; 40 | } 41 | 42 | public void init(ButterflyConfig butterflyConfig) { 43 | if(null == butterflyConfig) { 44 | return; 45 | } 46 | 47 | if(butterflyConfig instanceof DistributeDubboButterflyConfig) { 48 | // 初始化dubbo的处理方式 49 | ButterflySeqGeneratorFactory.getInstance().init(((DistributeDubboButterflyConfig) butterflyConfig).getZkHostAndPort()); 50 | } else if(butterflyConfig instanceof DistributeRestfulButterflyConfig) { 51 | serverHostAndPort = ((DistributeRestfulButterflyConfig) butterflyConfig).getHostAndPort(); 52 | } 53 | } 54 | 55 | public BitSequenceDTO getNext(String namespace) { 56 | // 优先判断dubbo,如果dubbo没有则判断restful,如果都没有,则报异常 57 | if (ButterflySeqGeneratorFactory.haveInitialized()) { 58 | ButterflyDistributeApi api = ButterflySeqGeneratorFactory.getInstance().getSequenceApi(); 59 | 60 | Response response = api.getNext(namespace); 61 | if (!response.isSuccess()) { 62 | log.error(DTB_LOG_PRE + "get seq fail,namespace={}, errCode={}, errMsg={}", namespace, response.getErrCode(), response.getErrMsg()); 63 | throw new RuntimeException("get seq fail:" + response.getErrMsg()); 64 | } 65 | return response.getData(); 66 | } else if (null != serverHostAndPort) { 67 | return HttpHelper.getOfStandard(BitSequenceDTO.class, "http://" + serverHostAndPort + "/api/butterfly/v1/worker/getNext/" + namespace); 68 | } else { 69 | throw new ButterflyException("No available server configuration found"); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # Sensitive or high-churn files 13 | .idea/**/dataSources/ 14 | .idea/**/dataSources.ids 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | .idea/**/dbnavigator.xml 20 | 21 | # Gradle 22 | .idea/**/gradle.xml 23 | .idea/**/libraries 24 | 25 | # CMake 26 | cmake-build-debug/ 27 | cmake-build-release/ 28 | 29 | # Mongo Explorer plugin 30 | .idea/**/mongoSettings.xml 31 | 32 | # File-based project format 33 | *.iws 34 | 35 | # IntelliJ 36 | out/ 37 | 38 | *.iml 39 | *.idea/** 40 | 41 | # mpeltonen/sbt-idea plugin 42 | .idea_modules/ 43 | 44 | # JIRA plugin 45 | atlassian-ide-plugin.xml 46 | 47 | # Cursive Clojure plugin 48 | .idea/replstate.xml 49 | 50 | # Crashlytics plugin (for Android Studio and IntelliJ) 51 | com_crashlytics_export_strings.xml 52 | crashlytics.properties 53 | crashlytics-build.properties 54 | fabric.properties 55 | 56 | # Editor-based Rest Client 57 | .idea/httpRequests 58 | ### Java template 59 | # Compiled class file 60 | *.class 61 | 62 | # Log file 63 | *.log 64 | 65 | # BlueJ files 66 | *.ctxt 67 | 68 | # Mobile Tools for Java (J2ME) 69 | .mtj.tmp/ 70 | 71 | # Package Files # 72 | *.jar 73 | *.war 74 | *.nar 75 | *.ear 76 | *.zip 77 | *.tar.gz 78 | *.rar 79 | 80 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 81 | hs_err_pid* 82 | ### Eclipse template 83 | 84 | .metadata 85 | bin/ 86 | tmp/ 87 | *.tmp 88 | *.bak 89 | *.swp 90 | *~.nib 91 | local.properties 92 | .settings/ 93 | .loadpath 94 | .recommenders 95 | 96 | # External tool builders 97 | .externalToolBuilders/ 98 | 99 | # Locally stored "Eclipse launch configurations" 100 | *.launch 101 | 102 | # PyDev specific (Python IDE for Eclipse) 103 | *.pydevproject 104 | 105 | # CDT-specific (C/C++ Development Tooling) 106 | .cproject 107 | 108 | # CDT- autotools 109 | .autotools 110 | 111 | # Java annotation processor (APT) 112 | .factorypath 113 | 114 | # PDT-specific (PHP Development Tools) 115 | .buildpath 116 | 117 | # sbteclipse plugin 118 | .target 119 | 120 | # Tern plugin 121 | .tern-project 122 | 123 | # TeXlipse plugin 124 | .texlipse 125 | 126 | # STS (Spring Tool Suite) 127 | .springBeans 128 | 129 | # Code Recommenders 130 | .recommenders/ 131 | 132 | # Scala IDE specific (Scala & Java development for Eclipse) 133 | .cache-main 134 | .scala_dependencies 135 | .worksheet 136 | ### Maven template 137 | target/ 138 | pom.xml.tag 139 | pom.xml.releaseBackup 140 | pom.xml.versionsBackup 141 | pom.xml.next 142 | release.properties 143 | dependency-reduced-pom.xml 144 | buildNumber.properties 145 | .mvn/timing.properties 146 | 147 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 148 | !/.mvn/wrapper/maven-wrapper.jar 149 | 150 | !/.gitignore 151 | *.log.* 152 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/allocator/DefaultBitAllocator.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence.allocator; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import com.simonalong.butterfly.sequence.TimeAdjuster; 5 | import com.simonalong.butterfly.sequence.spi.WorkerIdHandlerFactory; 6 | import com.simonalong.butterfly.sequence.PaddedLong; 7 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 8 | import com.simonalong.butterfly.sequence.spi.WorkerIdHandler; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static com.simonalong.butterfly.sequence.UuidConstant.SEQ_MARK; 14 | 15 | /** 16 | * @author shizi 17 | * @since 2020/2/3 10:15 下午 18 | */ 19 | public class DefaultBitAllocator implements BeanBitAllocator, ExpireBitAllocator { 20 | 21 | private String namespace; 22 | private PaddedLong currentTime; 23 | private PaddedLong sequence; 24 | /** 25 | * worker节点操作map 26 | */ 27 | private Map workerIdHandlerMap = new HashMap<>(12); 28 | 29 | public DefaultBitAllocator(String namespace, ButterflyConfig butterflyConfig) { 30 | super(); 31 | this.namespace = namespace; 32 | currentTime = new PaddedLong(System.currentTimeMillis()); 33 | sequence = new PaddedLong(0); 34 | workerIdHandlerMap.putIfAbsent(namespace, WorkerIdHandlerFactory.getWorkerIdHandler(namespace, butterflyConfig)); 35 | } 36 | 37 | /** 38 | * 获取序列中的时间值 39 | */ 40 | @Override 41 | public long getTimeValue() { 42 | return TimeAdjuster.getRelativeTime(currentTime.get()); 43 | } 44 | 45 | /** 46 | * 获取序列中的自增序列对应的值 47 | */ 48 | @Override 49 | public long getSequenceValue() { 50 | if ((sequence.incrementAndGet() & SEQ_MARK) == 0) { 51 | currentTime.incrementAndGet(); 52 | // 调整时间,防止时间过快或者过慢 53 | TimeAdjuster.adjustTime(currentTime); 54 | currentTimeIsValid(); 55 | sequence.set(0); 56 | return 0; 57 | } 58 | return sequence.get(); 59 | } 60 | 61 | /** 62 | * 获取序列中的workId对应的值 63 | */ 64 | @Override 65 | public int getWorkIdValue() { 66 | return getWorkerIdHandler(namespace).getWorkerId(); 67 | } 68 | 69 | /** 70 | * 获取过期时间 71 | */ 72 | @Override 73 | public Long getLastExpireTime(String namespace) { 74 | return getWorkerIdHandler(namespace).getLastExpireTime(); 75 | } 76 | 77 | private WorkerIdHandler getWorkerIdHandler(String namespace) { 78 | check(namespace); 79 | return workerIdHandlerMap.get(namespace); 80 | } 81 | 82 | /** 83 | * 判断当前系统是否还是可用的 84 | *

85 | * 如果当前时间和上次的过期时间之间相差达到一定的阈值,则让当前应用系统不可用 86 | */ 87 | private void currentTimeIsValid() { 88 | Long lastExpireTime = getLastExpireTime(namespace); 89 | if (null == lastExpireTime) { 90 | return; 91 | } 92 | long now = System.currentTimeMillis(); 93 | if (now >= lastExpireTime) { 94 | throw new ButterflyException("数据库链接崩溃,超过最大过期时间"); 95 | } 96 | } 97 | 98 | /** 99 | * 核查命名空间 100 | */ 101 | private void check(String namespace) { 102 | if (!workerIdHandlerMap.containsKey(namespace)) { 103 | throw new ButterflyException("命名空间" + namespace + "不存在"); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /butterfly-distribute/butterfly-server-restful/src/main/java/com/simonalong/butterfly/distribute/config/WebExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.distribute.config; 2 | 3 | 4 | import com.simonalong.butterfly.distribute.exception.BusinessException; 5 | import com.simonalong.butterfly.distribute.model.Response; 6 | import com.simonalong.butterfly.distribute.uti.ExceptionUtil; 7 | import lombok.Data; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.tomcat.util.buf.StringUtils; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.core.MethodParameter; 12 | import org.springframework.core.env.Environment; 13 | import org.springframework.http.HttpInputMessage; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.converter.HttpMessageConverter; 16 | import org.springframework.web.bind.annotation.ExceptionHandler; 17 | import org.springframework.web.bind.annotation.ResponseStatus; 18 | import org.springframework.web.bind.annotation.RestControllerAdvice; 19 | import org.springframework.web.context.annotation.RequestScope; 20 | import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter; 21 | 22 | import javax.annotation.ManagedBean; 23 | import javax.servlet.http.HttpServletRequest; 24 | import java.lang.reflect.Type; 25 | import java.util.Arrays; 26 | import java.util.stream.Collectors; 27 | 28 | /** 29 | * @author shizi 30 | * @since 2020/6/16 4:50 PM 31 | */ 32 | @Slf4j 33 | @RestControllerAdvice("com.simonalong.butterfly.distribute.server.controller") 34 | public class WebExceptionHandler extends RequestBodyAdviceAdapter { 35 | 36 | @Autowired 37 | private RequestContext requestContext; 38 | 39 | public static final String INTERNAL_SERVER_ERROR_CODE = "internal.server.error"; 40 | public static final String INTERNAL_SERVER_ERROR_MESSAGE_DEFAULT = "Oops_服务器开小差啦,过会儿再试吧"; 41 | 42 | @Autowired 43 | private Environment environment; 44 | 45 | /** 46 | * 业务异常 47 | * @param request request 48 | * @param e 异常 49 | * @return 返回值 50 | */ 51 | @ExceptionHandler(BusinessException.class) 52 | @ResponseStatus(HttpStatus.BAD_REQUEST) 53 | public Response handleMyException(HttpServletRequest request, BusinessException e) { 54 | log.error("请求:{},参数: {}, 异常: ", request.getRequestURI(), getParam(request), e); 55 | return Response.fail(e.getErrCode(), e.getMessage()); 56 | } 57 | 58 | /** 59 | * 内部异常 60 | * @param request request 61 | * @param e 异常 62 | * @return 返回值 63 | */ 64 | @ExceptionHandler(Throwable.class) 65 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 66 | public Response handleMyException(HttpServletRequest request, Throwable e) { 67 | log.error("请求:{},参数: {}, 异常: ", request.getRequestURI(), getParam(request), e); 68 | String code; 69 | String message; 70 | if (Arrays.asList(environment.getActiveProfiles()).contains("pro")) { 71 | code = INTERNAL_SERVER_ERROR_CODE; 72 | message = INTERNAL_SERVER_ERROR_MESSAGE_DEFAULT; 73 | } else { 74 | code = INTERNAL_SERVER_ERROR_CODE; 75 | message = "【系统异常】\n" + ExceptionUtil.unwrapException(e).toString(); 76 | } 77 | return Response.fail(code, message); 78 | } 79 | 80 | private String getParam(HttpServletRequest request) { 81 | return requestContext.getParam() != null ? requestContext.getParam().toString() : getParamFromRequest(request); 82 | } 83 | 84 | private static String getParamFromRequest(HttpServletRequest request) { 85 | return request.getParameterMap().entrySet().stream().map(e -> String.format("%s=%s", e.getKey(), StringUtils.join(e.getValue()))).collect(Collectors.joining(";")); 86 | } 87 | 88 | @Override 89 | public boolean supports(MethodParameter methodParameter, Type targetType,Class> converterType) { 90 | return true; 91 | } 92 | 93 | @Override 94 | public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { 95 | requestContext.setParam(body); 96 | return body; 97 | } 98 | 99 | @ManagedBean 100 | @RequestScope 101 | @Data 102 | public static class RequestContext { 103 | private Object param; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /butterfly-sequence/src/main/java/com/simonalong/butterfly/sequence/ButterflyIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.sequence; 2 | 3 | import com.simonalong.butterfly.sequence.allocator.BitAllocator; 4 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 5 | 6 | import java.text.DateFormat; 7 | import java.text.SimpleDateFormat; 8 | import java.time.LocalDateTime; 9 | import java.time.ZoneId; 10 | import java.util.Arrays; 11 | import java.util.Date; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | import static com.simonalong.butterfly.sequence.UuidConstant.*; 16 | 17 | /** 18 | * @author shizi 19 | * @since 2020/4/8 11:55 PM 20 | */ 21 | public final class ButterflyIdGenerator { 22 | 23 | /** 24 | * 2020-02-22 00:00:00.000 对应的时间 25 | */ 26 | static Long startTime = 1582300800000L; 27 | private static final Integer FIRST_YEAR = 2020; 28 | private ButterflyConfig butterflyConfig; 29 | private static volatile ButterflyIdGenerator instance; 30 | /** 31 | * key为对应业务命名空间,value为uuid的序列构造器 32 | */ 33 | private final Map uUidBuilderMap = new HashMap<>(); 34 | 35 | /** 36 | * 全局id生成器的构造函数 37 | * 38 | * @param butterflyConfig 配置 39 | * @return 全局id生成器对象 40 | */ 41 | public static ButterflyIdGenerator getInstance(ButterflyConfig butterflyConfig) { 42 | if (null == instance) { 43 | synchronized (ButterflyIdGenerator.class) { 44 | if (null == instance) { 45 | instance = new ButterflyIdGenerator(); 46 | instance.butterflyConfig = butterflyConfig; 47 | } 48 | } 49 | } 50 | return instance; 51 | } 52 | 53 | /** 54 | * 声明命名空间 55 | *

56 | * 如果命名空间不存在,则新建,如果存在,则采用已经存在的命名空间 57 | * 58 | * @param namespaces 命名空间 59 | */ 60 | public void addNamespaces(String... namespaces) { 61 | Arrays.stream(namespaces).forEach(n -> uUidBuilderMap.putIfAbsent(n, new UuidSplicer(n, butterflyConfig))); 62 | } 63 | 64 | /** 65 | * 获取对应命名空间的全局id 66 | * 67 | * @param namespace 业务的命名空间 68 | * @return 全局id生成器 69 | */ 70 | public long getUUid(String namespace) { 71 | return getUUidSplicer(namespace).splice(); 72 | } 73 | 74 | public BitAllocator getBitAllocator(String namespace) { 75 | return getUUidSplicer(namespace).getBitAllocator(); 76 | } 77 | 78 | /** 79 | * 设置启动时间 80 | *

81 | * 目前当前的启动时间是按照2020年2月22号算起,如果不设置,则最久可以用到2083年左右 82 | * @param year 起始时间 83 | * @param month 起始时间 84 | * @param dayOfMonth 起始时间 85 | * @param hour 起始时间 86 | * @param minute 起始时间 87 | * @param second 起始时间 88 | */ 89 | public void setStartTime(int year, int month, int dayOfMonth, int hour, int minute, int second) { 90 | if (year < FIRST_YEAR) { 91 | throw new ButterflyException("请设置未来时间"); 92 | } 93 | LocalDateTime localDateTime = LocalDateTime.of(year, month, dayOfMonth, hour, minute, second); 94 | startTime = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()).getTime(); 95 | } 96 | 97 | private UuidSplicer getUUidSplicer(String namespace) { 98 | if (!uUidBuilderMap.containsKey(namespace)) { 99 | throw new ButterflyException("命名空间" + namespace + "不存在,请先添加命名空间"); 100 | } 101 | return uUidBuilderMap.get(namespace); 102 | } 103 | 104 | /** 105 | * 解析uuid 106 | * 107 | *

    108 | *
  • uuid:对应的是当前的全局id
  • 109 | *
  • symbol:对应的符号位
  • 110 | *
  • time:对应的时间值
  • 111 | *
  • abstractTime:相对起始时间的具体时间
  • 112 | *
  • sequence:序列值
  • 113 | *
  • workerId:分配的机器id
  • 114 | *
115 | * @param uid 全局id 116 | * @return 解析的数据 117 | */ 118 | public static Map parseUid(Long uid) { 119 | Map resultMap = new HashMap<>(); 120 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 121 | 122 | resultMap.put("uuid", uid); 123 | resultMap.put("symbol", (uid & SYMBOL_MARK)); 124 | resultMap.put("time", (uid & TIME_MARK) >> (SEQ_HIGH_BITS + WORKER_BITS + SEQ_LOW_BITS)); 125 | resultMap.put("abstractTime", dateFormat.format(new Date(((uid & TIME_MARK) >> (SEQ_HIGH_BITS + WORKER_BITS + SEQ_LOW_BITS)) + startTime))); 126 | resultMap.put("sequence", ((uid & SEQ_HIGH_MARK) >> WORKER_BITS) | (uid & SEQ_LOW_MARK)); 127 | resultMap.put("workerId", (uid & WORKER_MARK) >> SEQ_LOW_BITS); 128 | return resultMap; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /butterfly-sample/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | butterfly 8 | com.github.simonalong 9 | 1.1.2 10 | ../pom.xml 11 | 12 | 13 | butterfly-sample 14 | 1.1.2 15 | 16 | 17 | true 18 | 4.13.1 19 | 1.7.28 20 | 1.2.10 21 | 22 | 23 | 24 | 25 | org.projectlombok 26 | lombok 27 | 1.18.0 28 | true 29 | 30 | 31 | com.github.simonalong 32 | butterfly-allocator-zookeeper 33 | ${project.version} 34 | 35 | 36 | com.github.simonalong 37 | butterfly-allocator-db 38 | ${project.version} 39 | 40 | 41 | com.github.simonalong 42 | butterfly-allocator-distribute 43 | ${project.version} 44 | 45 | 46 | junit 47 | junit 48 | ${junit.version} 49 | 50 | 51 | 52 | mysql 53 | mysql-connector-java 54 | 55 | 56 | 57 | org.mariadb.jdbc 58 | mariadb-java-client 59 | 2.7.3 60 | 61 | 62 | 63 | org.slf4j 64 | slf4j-api 65 | ${slf4j.version} 66 | 67 | 68 | ch.qos.logback 69 | logback-core 70 | ${logback.version} 71 | 72 | 73 | ch.qos.logback 74 | logback-access 75 | ${logback.version} 76 | 77 | 78 | ch.qos.logback 79 | logback-classic 80 | ${logback.version} 81 | 82 | 83 | 84 | 85 | org.apache.dubbo 86 | dubbo 87 | 88 | 89 | org.apache.dubbo 90 | dubbo-spring-boot-starter 91 | 92 | 93 | org.apache.curator 94 | curator-framework 95 | 96 | 97 | zookeeper 98 | org.apache.zookeeper 99 | 100 | 101 | 102 | 103 | org.apache.curator 104 | curator-recipes 105 | 106 | 107 | 108 | 109 | 110 | 111 | org.apache.maven.plugins 112 | maven-compiler-plugin 113 | 3.8.0 114 | 115 | 1.8 116 | 1.8 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /butterfly-sample/src/main/java/com/simonalong/buffterfly/sample/ZkPressIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.buffterfly.sample; 2 | 3 | import com.simonalong.butterfly.worker.zookeeper.ZkButterflyConfig; 4 | import lombok.SneakyThrows; 5 | import org.junit.BeforeClass; 6 | import org.junit.Test; 7 | 8 | /** 9 | * @author shizi 10 | * @since 2020/5/3 2:11 AM 11 | */ 12 | public class ZkPressIdGeneratorTest extends BaseTest { 13 | 14 | 15 | @BeforeClass 16 | public static void beforeClass() { 17 | config = new ZkButterflyConfig(); 18 | ((ZkButterflyConfig)config).setHost("localhost:2181"); 19 | } 20 | 21 | /** 22 | * 基本测试 23 | */ 24 | @Test 25 | public void baseRunTest() { 26 | baseRun(); 27 | //{symbol=0, sequence=1, workerId=12, abstractTime=2020-05-07 22:58:25.787, time=601105787, uuid=2521220406845452} 28 | //{symbol=0, sequence=2, workerId=12, abstractTime=2020-05-07 22:58:25.787, time=601105787, uuid=2521220406853644} 29 | //{symbol=0, sequence=1, workerId=7, abstractTime=2020-05-07 22:58:26.040, time=601106040, uuid=2521221468004359} 30 | //{symbol=0, sequence=2, workerId=7, abstractTime=2020-05-07 22:58:26.040, time=601106040, uuid=2521221468012551} 31 | //{symbol=0, sequence=3, workerId=7, abstractTime=2020-05-07 22:58:26.040, time=601106040, uuid=2521221468020743} 32 | //{symbol=0, sequence=4, workerId=7, abstractTime=2020-05-07 22:58:26.040, time=601106040, uuid=2521221468028935} 33 | //{symbol=0, sequence=1, workerId=4, abstractTime=2020-05-07 22:58:26.065, time=601106065, uuid=2521221572861956} 34 | //{symbol=0, sequence=1, workerId=8, abstractTime=2020-05-07 22:58:26.093, time=601106093, uuid=2521221690302472} 35 | } 36 | 37 | /** 38 | * 持续的低qps的压测,则这个QPS还是比较低,完全满足业务需求 39 | */ 40 | @Test 41 | @SneakyThrows 42 | public void testQps1() { 43 | lowPressRun(); 44 | 45 | //biz=biz0, qps = 2.9411764705882355单位(w/s) 46 | //biz=biz0, qps = 6.666666666666667单位(w/s) 47 | //biz=biz0, qps = 7.6923076923076925单位(w/s) 48 | //biz=biz0, qps = 5.0单位(w/s) 49 | //biz=biz0, qps = 6.666666666666667单位(w/s) 50 | //biz=biz0, qps = 8.333333333333334单位(w/s) 51 | //biz=biz0, qps = 7.6923076923076925单位(w/s) 52 | //biz=biz0, qps = 10.0单位(w/s) 53 | //biz=biz0, qps = 10.0单位(w/s) 54 | //biz=biz0, qps = 10.0单位(w/s) 55 | } 56 | 57 | /** 58 | * 低qps一段时间后到高QPS,可以支撑更高,但是一旦持续的高并发,则后面会慢慢降下来 59 | */ 60 | @Test 61 | @SneakyThrows 62 | public void testQps2() { 63 | lowToHighPressRun(); 64 | 65 | //biz=biz0, qps = 6.666666666666667单位(w/s) 66 | //biz=biz0, qps = 75.625单位(w/s) 67 | //biz=biz0, qps = 273.3333333333333单位(w/s) 68 | //biz=biz0, qps = 785.9090909090909单位(w/s) 69 | //biz=biz0, qps = 820.7843137254902单位(w/s) 70 | //biz=biz0, qps = 836.6666666666666单位(w/s) 71 | //biz=biz0, qps = 830.3单位(w/s) 72 | //biz=biz0, qps = 68.43609733049847单位(w/s) 73 | //biz=biz0, qps = 57.405300738230665单位(w/s) 74 | //biz=biz0, qps = 55.03019909029901单位(w/s) 75 | } 76 | 77 | /** 78 | * 持续的高QPS,则只能达到最高的理论值(51.2w/s) 79 | */ 80 | @Test 81 | @SneakyThrows 82 | public void testQps3() { 83 | highPressRun(); 84 | 85 | //biz=biz0, qps = 52.25752508361204单位(w/s) 86 | //biz=biz0, qps = 53.96945328943818单位(w/s) 87 | //biz=biz0, qps = 53.972366148531954单位(w/s) 88 | //biz=biz0, qps = 53.972366148531954单位(w/s) 89 | //biz=biz0, qps = 53.98110661268556单位(w/s) 90 | //biz=biz0, qps = 53.96945328943818单位(w/s) 91 | //biz=biz0, qps = 53.97527932207049单位(w/s) 92 | //biz=biz0, qps = 53.963628514381305单位(w/s) 93 | //biz=biz0, qps = 53.972366148531954单位(w/s) 94 | //biz=biz0, qps = 53.963628514381305单位(w/s) 95 | } 96 | 97 | /** 98 | * 多业务的压测:低qps一段时间后到高QPS,可以支撑更高 99 | */ 100 | @Test 101 | @SneakyThrows 102 | public void testQps4() { 103 | lowToHighMultiBizPressRun(); 104 | 105 | //biz=biz0, qps = 5.555555555555555单位(w/s) 106 | //biz=biz0, qps = 67.22222222222223单位(w/s) 107 | //biz=biz0, qps = 574.0单位(w/s) 108 | //biz=biz0, qps = 720.4166666666666单位(w/s) 109 | //biz=biz0, qps = 664.4444444444445单位(w/s) 110 | //biz=biz0, qps = 821.0280373831775单位(w/s) 111 | //biz=biz0, qps = 775.981308411215单位(w/s) 112 | //biz=biz0, qps = 74.47043701799485单位(w/s) 113 | //biz=biz0, qps = 59.53062248995984单位(w/s) 114 | //biz=biz0, qps = 57.143631436314365单位(w/s) 115 | 116 | //biz=biz1, qps = 20.0单位(w/s) 117 | //biz=biz1, qps = 134.44444444444446单位(w/s) 118 | //biz=biz1, qps = 574.0单位(w/s) 119 | //biz=biz1, qps = 960.5555555555555单位(w/s) 120 | //biz=biz1, qps = 930.2222222222222单位(w/s) 121 | //biz=biz1, qps = 844.7115384615385单位(w/s) 122 | //biz=biz1, qps = 1044.4025157232704单位(w/s) 123 | //biz=biz1, qps = 995.4982817869416单位(w/s) 124 | //biz=biz1, qps = 960.2024291497976单位(w/s) 125 | //biz=biz1, qps = 894.5575757575757单位(w/s) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /butterfly-sample/src/main/java/com/simonalong/buffterfly/sample/DistributeDubboPressIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.buffterfly.sample; 2 | 3 | import com.simonalong.butterfly.worker.distribute.config.DistributeDubboButterflyConfig; 4 | import lombok.SneakyThrows; 5 | import org.junit.BeforeClass; 6 | import org.junit.Test; 7 | 8 | /** 9 | * 运行该用例,请先将dubbo服务端创建起来 10 | * 11 | * @author shizi 12 | * @since 2020/5/3 2:14 AM 13 | */ 14 | public class DistributeDubboPressIdGeneratorTest extends BaseTest { 15 | 16 | @BeforeClass 17 | public static void beforeClass() { 18 | config = new DistributeDubboButterflyConfig(); 19 | ((DistributeDubboButterflyConfig)config).setZkHostAndPort("localhost:2181"); 20 | } 21 | 22 | /** 23 | * 基本测试 24 | */ 25 | @Test 26 | public void baseRunTest() { 27 | baseRun(); 28 | 29 | //{symbol=0, sequence=0, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083984969730} 30 | //{symbol=0, sequence=1, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083984977922} 31 | //{symbol=0, sequence=2, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083984986114} 32 | //{symbol=0, sequence=3, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083984994306} 33 | //{symbol=0, sequence=4, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083985002498} 34 | //{symbol=0, sequence=5, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083985010690} 35 | //{symbol=0, sequence=6, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083985018882} 36 | //{symbol=0, sequence=7, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083985027074} 37 | } 38 | 39 | /** 40 | * 持续的低qps的压测,则这个QPS还是比较低,完全满足业务需求 41 | */ 42 | @Test 43 | @SneakyThrows 44 | public void testQps1() { 45 | lowPressRun(); 46 | 47 | //biz=biz0, qps = 5.2631578947368425单位(w/s) 48 | //biz=biz0, qps = 7.6923076923076925单位(w/s) 49 | //biz=biz0, qps = 7.6923076923076925单位(w/s) 50 | //biz=biz0, qps = 8.333333333333334单位(w/s) 51 | //biz=biz0, qps = 7.142857142857143单位(w/s) 52 | //biz=biz0, qps = 7.142857142857143单位(w/s) 53 | //biz=biz0, qps = 8.333333333333334单位(w/s) 54 | //biz=biz0, qps = 10.0单位(w/s) 55 | //biz=biz0, qps = 9.090909090909092单位(w/s) 56 | //biz=biz0, qps = 8.333333333333334单位(w/s) 57 | } 58 | 59 | /** 60 | * 低qps一段时间后到高QPS,可以支撑更高 61 | */ 62 | @Test 63 | @SneakyThrows 64 | public void testQps2() { 65 | lowToHighPressRun(); 66 | 67 | //biz=biz0, qps = 4.3478260869565215单位(w/s) 68 | //biz=biz0, qps = 28.80952380952381单位(w/s) 69 | //biz=biz0, qps = 58.57142857142857单位(w/s) 70 | //biz=biz0, qps = 83.52657004830918单位(w/s) 71 | //biz=biz0, qps = 100.86746987951807单位(w/s) 72 | //biz=biz0, qps = 146.41666666666666单位(w/s) 73 | //biz=biz0, qps = 183.89811738648947单位(w/s) 74 | //biz=biz0, qps = 193.90227576974564单位(w/s) 75 | //biz=biz0, qps = 239.0826612903226单位(w/s) 76 | //biz=biz0, qps = 222.49321676213447单位(w/s) 77 | } 78 | 79 | /** 80 | * 持续的高QPS,则只能达到最高的理论值(51.2w/s) 81 | */ 82 | @Test 83 | @SneakyThrows 84 | public void testQps3() { 85 | highPressRun(); 86 | 87 | //biz=biz0, qps = 132.17023526301878单位(w/s) 88 | //biz=biz0, qps = 79.14523149980214单位(w/s) 89 | //biz=biz0, qps = 45.77077993409008单位(w/s) 90 | //biz=biz0, qps = 46.010858562620776单位(w/s) 91 | //biz=biz0, qps = 46.140358971992804单位(w/s) 92 | //biz=biz0, qps = 46.112699437425064单位(w/s) 93 | //biz=biz0, qps = 46.20858555519616单位(w/s) 94 | //biz=biz0, qps = 45.76030750926646单位(w/s) 95 | //biz=biz0, qps = 45.54148829583751单位(w/s) 96 | //biz=biz0, qps = 45.57261996992207单位(w/s) 97 | } 98 | 99 | /** 100 | * 多业务的压测:低qps一段时间后到高QPS,可以支撑更高 101 | */ 102 | @Test 103 | @SneakyThrows 104 | public void testQps4() { 105 | lowToHighMultiBizPressRun(); 106 | 107 | //biz=biz0, qps = 5.0单位(w/s) 108 | //biz=biz0, qps = 44.81481481481482单位(w/s) 109 | //biz=biz0, qps = 56.27450980392157单位(w/s) 110 | //biz=biz0, qps = 80.79439252336448单位(w/s) 111 | //biz=biz0, qps = 114.68493150684931单位(w/s) 112 | //biz=biz0, qps = 170.91439688715954单位(w/s) 113 | //biz=biz0, qps = 194.906103286385单位(w/s) 114 | //biz=biz0, qps = 233.4327155519742单位(w/s) 115 | //biz=biz0, qps = 232.06457925636008单位(w/s) 116 | //biz=biz0, qps = 218.4107724178751单位(w/s) 117 | 118 | //biz=biz1, qps = 14.285714285714286单位(w/s) 119 | //biz=biz1, qps = 55.0单位(w/s) 120 | //biz=biz1, qps = 88.3076923076923单位(w/s) 121 | //biz=biz1, qps = 114.50331125827815单位(w/s) 122 | //biz=biz1, qps = 175.14644351464435单位(w/s) 123 | //biz=biz1, qps = 196.09375单位(w/s) 124 | //biz=biz1, qps = 214.8253557567917单位(w/s) 125 | //biz=biz1, qps = 239.6112489660877单位(w/s) 126 | //biz=biz1, qps = 226.84839789574366单位(w/s) 127 | //biz=biz1, qps = 212.989898989899单位(w/s) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /butterfly-sample/src/main/java/com/simonalong/buffterfly/sample/DistributeRestfulPressIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.buffterfly.sample; 2 | 3 | import com.simonalong.butterfly.worker.distribute.config.DistributeRestfulButterflyConfig; 4 | import lombok.SneakyThrows; 5 | import org.junit.BeforeClass; 6 | import org.junit.Test; 7 | 8 | /** 9 | * 运行该用例,请先将restful服务端创建起来 10 | * 11 | * @author shizi 12 | * @since 2020/10/28 6:18 下午 13 | */ 14 | public class DistributeRestfulPressIdGeneratorTest extends BaseTest { 15 | 16 | @BeforeClass 17 | public static void beforeClass() { 18 | config = new DistributeRestfulButterflyConfig(); 19 | ((DistributeRestfulButterflyConfig)config).setHostAndPort("localhost:8800"); 20 | } 21 | 22 | /** 23 | * 基本测试 24 | */ 25 | @Test 26 | public void baseRunTest() { 27 | baseRun(); 28 | 29 | //{symbol=0, sequence=0, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083984969730} 30 | //{symbol=0, sequence=1, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083984977922} 31 | //{symbol=0, sequence=2, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083984986114} 32 | //{symbol=0, sequence=3, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083984994306} 33 | //{symbol=0, sequence=4, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083985002498} 34 | //{symbol=0, sequence=5, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083985010690} 35 | //{symbol=0, sequence=6, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083985018882} 36 | //{symbol=0, sequence=7, workerId=2, abstractTime=2020-05-03 14:48:17.407, time=6187697407, uuid=25953083985027074} 37 | } 38 | 39 | /** 40 | * 持续的低qps的压测,则这个QPS还是比较低,完全满足业务需求 41 | */ 42 | @Test 43 | @SneakyThrows 44 | public void testQps1() { 45 | lowPressRun(); 46 | 47 | //biz=biz0, qps = 5.2631578947368425单位(w/s) 48 | //biz=biz0, qps = 7.6923076923076925单位(w/s) 49 | //biz=biz0, qps = 7.6923076923076925单位(w/s) 50 | //biz=biz0, qps = 8.333333333333334单位(w/s) 51 | //biz=biz0, qps = 7.142857142857143单位(w/s) 52 | //biz=biz0, qps = 7.142857142857143单位(w/s) 53 | //biz=biz0, qps = 8.333333333333334单位(w/s) 54 | //biz=biz0, qps = 10.0单位(w/s) 55 | //biz=biz0, qps = 9.090909090909092单位(w/s) 56 | //biz=biz0, qps = 8.333333333333334单位(w/s) 57 | } 58 | 59 | /** 60 | * 低qps一段时间后到高QPS,可以支撑更高 61 | */ 62 | @Test 63 | @SneakyThrows 64 | public void testQps2() { 65 | lowToHighPressRun(); 66 | 67 | //biz=biz0, qps = 4.3478260869565215单位(w/s) 68 | //biz=biz0, qps = 28.80952380952381单位(w/s) 69 | //biz=biz0, qps = 58.57142857142857单位(w/s) 70 | //biz=biz0, qps = 83.52657004830918单位(w/s) 71 | //biz=biz0, qps = 100.86746987951807单位(w/s) 72 | //biz=biz0, qps = 146.41666666666666单位(w/s) 73 | //biz=biz0, qps = 183.89811738648947单位(w/s) 74 | //biz=biz0, qps = 193.90227576974564单位(w/s) 75 | //biz=biz0, qps = 239.0826612903226单位(w/s) 76 | //biz=biz0, qps = 222.49321676213447单位(w/s) 77 | } 78 | 79 | /** 80 | * 持续的高QPS,则只能达到最高的理论值(51.2w/s) 81 | */ 82 | @Test 83 | @SneakyThrows 84 | public void testQps3() { 85 | highPressRun(); 86 | 87 | //biz=biz0, qps = 132.17023526301878单位(w/s) 88 | //biz=biz0, qps = 79.14523149980214单位(w/s) 89 | //biz=biz0, qps = 45.77077993409008单位(w/s) 90 | //biz=biz0, qps = 46.010858562620776单位(w/s) 91 | //biz=biz0, qps = 46.140358971992804单位(w/s) 92 | //biz=biz0, qps = 46.112699437425064单位(w/s) 93 | //biz=biz0, qps = 46.20858555519616单位(w/s) 94 | //biz=biz0, qps = 45.76030750926646单位(w/s) 95 | //biz=biz0, qps = 45.54148829583751单位(w/s) 96 | //biz=biz0, qps = 45.57261996992207单位(w/s) 97 | } 98 | 99 | /** 100 | * 多业务的压测:低qps一段时间后到高QPS,可以支撑更高 101 | */ 102 | @Test 103 | @SneakyThrows 104 | public void testQps4() { 105 | lowToHighMultiBizPressRun(); 106 | 107 | //biz=biz0, qps = 5.0单位(w/s) 108 | //biz=biz0, qps = 44.81481481481482单位(w/s) 109 | //biz=biz0, qps = 56.27450980392157单位(w/s) 110 | //biz=biz0, qps = 80.79439252336448单位(w/s) 111 | //biz=biz0, qps = 114.68493150684931单位(w/s) 112 | //biz=biz0, qps = 170.91439688715954单位(w/s) 113 | //biz=biz0, qps = 194.906103286385单位(w/s) 114 | //biz=biz0, qps = 233.4327155519742单位(w/s) 115 | //biz=biz0, qps = 232.06457925636008单位(w/s) 116 | //biz=biz0, qps = 218.4107724178751单位(w/s) 117 | 118 | //biz=biz1, qps = 14.285714285714286单位(w/s) 119 | //biz=biz1, qps = 55.0单位(w/s) 120 | //biz=biz1, qps = 88.3076923076923单位(w/s) 121 | //biz=biz1, qps = 114.50331125827815单位(w/s) 122 | //biz=biz1, qps = 175.14644351464435单位(w/s) 123 | //biz=biz1, qps = 196.09375单位(w/s) 124 | //biz=biz1, qps = 214.8253557567917单位(w/s) 125 | //biz=biz1, qps = 239.6112489660877单位(w/s) 126 | //biz=biz1, qps = 226.84839789574366单位(w/s) 127 | //biz=biz1, qps = 212.989898989899单位(w/s) 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-distribute/src/main/java/com/simonalong/butterfly/worker/distribute/BufferManager.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.distribute; 2 | 3 | import com.simonalong.butterfly.distribute.model.BitSequenceDTO; 4 | import com.simonalong.butterfly.distribute.model.Response; 5 | import com.simonalong.butterfly.sequence.PaddedLong; 6 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 7 | import com.simonalong.butterfly.worker.distribute.config.BitSequenceConfig; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.util.concurrent.CompletableFuture; 11 | 12 | import static com.simonalong.butterfly.sequence.UuidConstant.SEQ_MAX_SIZE; 13 | import static com.simonalong.butterfly.worker.distribute.DistributeConstant.DTB_LOG_PRE; 14 | 15 | /** 16 | * @author shizi 17 | * @since 2020/4/26 11:00 PM 18 | */ 19 | @Slf4j 20 | public class BufferManager { 21 | 22 | /** 23 | * 业务命名空间 24 | */ 25 | private String namespace; 26 | private BitSequenceConfig buffer1 = new BitSequenceConfig(); 27 | private BitSequenceConfig buffer2 = new BitSequenceConfig(); 28 | private BitSequenceConfig currentBuffer; 29 | private PaddedLong currentSequence; 30 | /** 31 | * 刷新的状态 32 | */ 33 | private volatile RefreshEnum refreshState = RefreshEnum.NON; 34 | /** 35 | * 这里暂时设置刷新比率为0.4 36 | */ 37 | private final float refreshRatio = 0.4f; 38 | private final int refreshValue = (int) (SEQ_MAX_SIZE * refreshRatio); 39 | 40 | public BufferManager(BitSequenceDTO bitSequenceDTO) { 41 | if (null == bitSequenceDTO) { 42 | return; 43 | } 44 | this.namespace = bitSequenceDTO.getNamespace(); 45 | buffer1.update(bitSequenceDTO); 46 | this.currentBuffer = buffer1; 47 | this.currentSequence = new PaddedLong(0); 48 | } 49 | 50 | public long getTime() { 51 | return currentBuffer.getTime(); 52 | } 53 | 54 | /** 55 | * 双buffer方式获取新的序列值 56 | * 57 | * @return 新的序列 58 | */ 59 | public long getSequence() { 60 | long sequence; 61 | while (true) { 62 | sequence = currentSequence.get(); 63 | 64 | // 二级buffer刷新处理 65 | if (reachRefreshBuffer(sequence)) { 66 | if (refreshState == RefreshEnum.READY_ASYNC) { 67 | continue; 68 | } 69 | 70 | synchronized (this) { 71 | if (refreshState == RefreshEnum.READY_ASYNC) { 72 | continue; 73 | } 74 | refreshState = RefreshEnum.READY_ASYNC; 75 | } 76 | 77 | // 刷新二级buffer 78 | refreshBuffer(); 79 | } 80 | 81 | // 到达最后 82 | if (sequence >= SEQ_MAX_SIZE) { 83 | if (refreshState != RefreshEnum.READY_RPC && refreshState != RefreshEnum.FINISH) { 84 | throw new ButterflyException("server get new buffer fail"); 85 | } 86 | switchBuffer(); 87 | } 88 | return currentSequence.getAndIncrement(); 89 | } 90 | } 91 | 92 | public int getWorkId() { 93 | return currentBuffer.getWorkId(); 94 | } 95 | 96 | /** 97 | * 到达刷新二级buffer的点 98 | */ 99 | private boolean reachRefreshBuffer(long sequence) { 100 | return (sequence >= refreshValue && refreshState != RefreshEnum.READY_RPC && refreshState != RefreshEnum.FINISH); 101 | } 102 | 103 | private void refreshBuffer() { 104 | CompletableFuture.runAsync(() -> { 105 | try { 106 | synchronized (this) { 107 | refreshState = RefreshEnum.READY_RPC; 108 | updateBuffer(SequenceClient.getInstance().getNext(namespace)); 109 | refreshState = RefreshEnum.FINISH; 110 | } 111 | } catch (Throwable e) { 112 | log.error(DTB_LOG_PRE + "rpc fail", e); 113 | } 114 | }); 115 | } 116 | 117 | /** 118 | * 到达最后则进行buffer切换 119 | */ 120 | private synchronized void switchBuffer() { 121 | if (refreshState == RefreshEnum.READY_RPC) { 122 | throw new ButterflyException("server get new buffer fail"); 123 | } 124 | // 表示切换完成 125 | if (refreshState == RefreshEnum.NON) { 126 | return; 127 | } 128 | 129 | if (currentBuffer == buffer1) { 130 | currentBuffer = buffer2; 131 | } else if (currentBuffer == buffer2) { 132 | currentBuffer = buffer1; 133 | } 134 | 135 | currentSequence.set(0); 136 | refreshState = RefreshEnum.NON; 137 | } 138 | 139 | private void updateBuffer(BitSequenceDTO bitSequence) { 140 | if (currentBuffer == buffer1) { 141 | buffer2.update(bitSequence); 142 | } else if (currentBuffer == buffer2) { 143 | buffer1.update(bitSequence); 144 | } 145 | } 146 | 147 | /** 148 | * 二级buffer的刷新状态 149 | */ 150 | enum RefreshEnum { 151 | /** 152 | * 无状态 153 | */ 154 | NON, 155 | /** 156 | * 准备创建异步 157 | */ 158 | READY_ASYNC, 159 | /** 160 | * 准备rpc获取远端数据 161 | */ 162 | READY_RPC, 163 | /** 164 | * 完成 165 | */ 166 | FINISH 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/node/NamespaceNodeManager.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.node; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 5 | import com.simonalong.butterfly.worker.zookeeper.ZookeeperClient; 6 | import com.simonalong.butterfly.worker.zookeeper.entity.ConfigNodeEntity; 7 | import lombok.Setter; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static com.simonalong.butterfly.sequence.UuidConstant.*; 14 | import static com.simonalong.butterfly.worker.zookeeper.ZkConstant.ROOT_PATH; 15 | import static com.simonalong.butterfly.worker.zookeeper.ZkConstant.ZK_LOG_PRE; 16 | 17 | /** 18 | * @author shizi 19 | * @since 2020/4/25 11:03 AM 20 | */ 21 | @Slf4j 22 | public class NamespaceNodeManager { 23 | 24 | private static volatile NamespaceNodeManager INSTANCE = null; 25 | /** 26 | * 默认的最大机器个数 27 | */ 28 | private static final Integer DEFAULT_MAX_MACHINE_NUM = 16; 29 | /** 30 | * config节点操作map 31 | */ 32 | private Map configNodeHandlerMap = new HashMap<>(12); 33 | /** 34 | * worker节点操作map 35 | */ 36 | private Map workerNodeHandlerMap = new HashMap<>(12); 37 | @Setter 38 | private ZookeeperClient zkClient; 39 | 40 | private NamespaceNodeManager() { 41 | } 42 | 43 | public static NamespaceNodeManager getInstance() { 44 | if (null == INSTANCE) { 45 | synchronized (NamespaceNodeManager.class) { 46 | if (null == INSTANCE) { 47 | INSTANCE = new NamespaceNodeManager(); 48 | } 49 | } 50 | } 51 | return INSTANCE; 52 | } 53 | 54 | public void add(String namespace) { 55 | if (configNodeHandlerMap.containsKey(namespace)) { 56 | return; 57 | } 58 | if (null == zkClient) { 59 | throw new ButterflyException("zkClient不可为空"); 60 | } 61 | 62 | tryAddNamespace(namespace); 63 | 64 | ConfigNodeHandler configNodeHandler = new DefaultConfigNodeHandler(namespace, zkClient); 65 | configNodeHandlerMap.putIfAbsent(namespace, configNodeHandler); 66 | workerNodeHandlerMap.putIfAbsent(namespace, new DefaultWorkerNodeHandler(namespace, zkClient, configNodeHandler)); 67 | } 68 | 69 | public int getWorkerId(String namespace) { 70 | return getWorkerNodeHandler(namespace).getWorkerId(); 71 | } 72 | 73 | public Long getExpireTime(String namespace) { 74 | return getWorkerNodeHandler(namespace).getLastExpireTime(); 75 | } 76 | 77 | public ConfigNodeHandler getConfigNodeHandler(String namespace) { 78 | check(namespace); 79 | return configNodeHandlerMap.get(namespace); 80 | } 81 | 82 | public WorkerNodeHandler getWorkerNodeHandler(String namespace) { 83 | check(namespace); 84 | return workerNodeHandlerMap.get(namespace); 85 | } 86 | 87 | /** 88 | * 核查命名空间 89 | */ 90 | private void check(String namespace) { 91 | if (!configNodeHandlerMap.containsKey(namespace)) { 92 | throw new ButterflyException("命名空间" + namespace + "不存在"); 93 | } 94 | } 95 | 96 | /** 97 | * 尝试创建命名空间 98 | * 99 | * @param namespace 命名空间 100 | */ 101 | private void tryAddNamespace(String namespace) { 102 | if (null == namespace || "".equals(namespace)) { 103 | throw new ButterflyException("namespace is null"); 104 | } 105 | 106 | if (namespace.equals(DISTRIBUTE_SERVER)) { 107 | throw new ButterflyException("node [" + namespace + "] is distribute'node, forbidden to add"); 108 | } 109 | 110 | try { 111 | if (!zkClient.nodeExist(ZkNodeHelper.getNamespacePath(namespace))) { 112 | // 创建根节点 113 | zkClient.addPersistentNodeWithRecurse(ROOT_PATH); 114 | createNamespaceNode(namespace); 115 | } 116 | } catch (Throwable e) { 117 | log.error(ZK_LOG_PRE + "fail to add node[{}] to zookeeper", namespace); 118 | throw new ButterflyException("fail to add node[" + namespace + "] to zookeeper"); 119 | } 120 | } 121 | 122 | /** 123 | * 创建命名空间节点 业务节点、config节点和对应的worker节点 124 | */ 125 | private void createNamespaceNode(String namespace) { 126 | String namespacePath = ZkNodeHelper.getNamespacePath(namespace); 127 | if (!zkClient.nodeExist(namespacePath)) { 128 | // 创建业务节点 129 | zkClient.addPersistentNode(namespacePath); 130 | 131 | ConfigNodeEntity configEntity = new ConfigNodeEntity(); 132 | configEntity.setCurrentMaxMachine(DEFAULT_MAX_MACHINE_NUM); 133 | configEntity.setTimestampBits(TIME_BITS); 134 | configEntity.setSequenceBits(SEQ_HIGH_BITS + SEQ_LOW_BITS); 135 | configEntity.setWorkerBits(WORKER_BITS); 136 | 137 | // 创建 config 节点 138 | zkClient.addPersistentNode(namespacePath + "/config", JSON.toJSONString(configEntity)); 139 | 140 | // 创建 worker 节点 141 | for (int index = 0; index < DEFAULT_MAX_MACHINE_NUM; index++) { 142 | zkClient.addPersistentNode(namespacePath + "/worker_" + index); 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /butterfly-sample/src/main/java/com/simonalong/buffterfly/sample/DbPressIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.buffterfly.sample; 2 | 3 | import com.simonalong.butterfly.worker.db.DbButterflyConfig; 4 | import lombok.SneakyThrows; 5 | import org.junit.BeforeClass; 6 | import org.junit.Test; 7 | 8 | /** 9 | * @author shizi 10 | * @since 2020/5/3 2:01 AM 11 | */ 12 | public class DbPressIdGeneratorTest extends BaseTest { 13 | 14 | @BeforeClass 15 | public static void beforeClass() { 16 | config = new DbButterflyConfig(); 17 | ((DbButterflyConfig)config).setUrl("jdbc:mysql://127.0.0.1:3306/neo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&&allowPublicKeyRetrieval=true"); 18 | ((DbButterflyConfig)config).setUserName("neo_test"); 19 | ((DbButterflyConfig)config).setPassword("neo@Test123"); 20 | } 21 | 22 | /** 23 | * 基本测试 24 | */ 25 | @Test 26 | public void baseRunTest() { 27 | baseRun(); 28 | //命名空间:test1:{symbol=0, sequence=1, workerId=0, abstractTime=2021-04-09 17:22:52.658, time=29697772658, uuid=124561486650540033} 29 | //命名空间:test1:{symbol=0, sequence=2, workerId=0, abstractTime=2021-04-09 17:22:52.658, time=29697772658, uuid=124561486650556416} 30 | 31 | //命名空间:test2:{symbol=0, sequence=1, workerId=0, abstractTime=2021-04-09 17:22:53.127, time=29697773127, uuid=124561488617668609} 32 | //命名空间:test2:{symbol=0, sequence=2, workerId=0, abstractTime=2021-04-09 17:22:53.127, time=29697773127, uuid=124561488617684992} 33 | //命名空间:test2:{symbol=0, sequence=3, workerId=0, abstractTime=2021-04-09 17:22:53.127, time=29697773127, uuid=124561488617684993} 34 | //命名空间:test2:{symbol=0, sequence=4, workerId=0, abstractTime=2021-04-09 17:22:53.127, time=29697773127, uuid=124561488617701376} 35 | //命名空间:test2:{symbol=0, sequence=5, workerId=0, abstractTime=2021-04-09 17:22:53.127, time=29697773127, uuid=124561488617701377} 36 | 37 | //命名空间:test3:{symbol=0, sequence=1, workerId=0, abstractTime=2021-04-09 17:22:53.157, time=29697773157, uuid=124561488743497729} 38 | 39 | //命名空间:test4:{symbol=0, sequence=1, workerId=0, abstractTime=2021-04-09 17:22:53.186, time=29697773186, uuid=124561488865132545} 40 | } 41 | 42 | /** 43 | * 持续的低qps的压测,则这个QPS还是比较低,完全满足业务需求 44 | */ 45 | @Test 46 | @SneakyThrows 47 | public void testQps1() { 48 | lowPressRun(); 49 | 50 | //biz=biz0, qps = 5.555555555555555单位(w/s) 51 | //biz=biz0, qps = 4.761904761904762单位(w/s) 52 | //biz=biz0, qps = 6.25单位(w/s) 53 | //biz=biz0, qps = 7.142857142857143单位(w/s) 54 | //biz=biz0, qps = 6.666666666666667单位(w/s) 55 | //biz=biz0, qps = 5.555555555555555单位(w/s) 56 | //biz=biz0, qps = 6.25单位(w/s) 57 | //biz=biz0, qps = 7.142857142857143单位(w/s) 58 | //biz=biz0, qps = 7.142857142857143单位(w/s) 59 | //biz=biz0, qps = 8.333333333333334单位(w/s) 60 | } 61 | 62 | /** 63 | * 低qps一段时间后到高QPS,可以支撑更高,但是一旦持续的高并发,则后面会慢慢降下来 64 | */ 65 | @Test 66 | @SneakyThrows 67 | public void testQps2() { 68 | lowToHighPressRun(); 69 | 70 | //biz=biz0, qps = 14.285714285714286单位(w/s) 71 | //biz=biz0, qps = 71.17647058823529单位(w/s) 72 | //biz=biz0, qps = 287.0单位(w/s) 73 | //biz=biz0, qps = 540.3125单位(w/s) 74 | //biz=biz0, qps = 951.3636363636364单位(w/s) 75 | //biz=biz0, qps = 915.1041666666666单位(w/s) 76 | //biz=biz0, qps = 1230.0740740740741单位(w/s) 77 | //biz=biz0, qps = 71.75873173148378单位(w/s) 78 | //biz=biz0, qps = 57.405300738230665单位(w/s) 79 | //biz=biz0, qps = 55.034302759134974单位(w/s) 80 | } 81 | 82 | /** 83 | * 持续的高QPS,则只能达到最高的理论值(51.2w/s) 84 | */ 85 | @Test 86 | @SneakyThrows 87 | public void testQps3() { 88 | highPressRun(); 89 | 90 | //biz=biz0, qps = 51.956149010235364单位(w/s) 91 | //biz=biz0, qps = 53.98402072986396单位(w/s) 92 | //biz=biz0, qps = 53.97527932207049单位(w/s) 93 | //biz=biz0, qps = 53.972366148531954单位(w/s) 94 | //biz=biz0, qps = 53.97527932207049单位(w/s) 95 | //biz=biz0, qps = 53.978192810104716单位(w/s) 96 | //biz=biz0, qps = 53.97527932207049单位(w/s) 97 | //biz=biz0, qps = 53.97527932207049单位(w/s) 98 | //biz=biz0, qps = 53.972366148531954单位(w/s) 99 | //biz=biz0, qps = 53.98402072986396单位(w/s) 100 | } 101 | 102 | /** 103 | * 多业务的压测:低qps一段时间后到高QPS,可以支撑更高 104 | */ 105 | @Test 106 | @SneakyThrows 107 | public void testQps4() { 108 | lowToHighMultiBizPressRun(); 109 | 110 | //biz=biz0, qps = 12.5单位(w/s) 111 | //biz=biz0, qps = 86.42857142857143单位(w/s) 112 | //biz=biz0, qps = 358.75单位(w/s) 113 | //biz=biz0, qps = 596.2068965517242单位(w/s) 114 | //biz=biz0, qps = 837.2单位(w/s) 115 | //biz=biz0, qps = 1009.7701149425287单位(w/s) 116 | //biz=biz0, qps = 1031.4285714285713单位(w/s) 117 | //biz=biz0, qps = 77.8736559139785单位(w/s) 118 | //biz=biz0, qps = 60.86744514307712单位(w/s) 119 | //biz=biz0, qps = 57.96497015394282单位(w/s) 120 | 121 | //biz=biz1, qps = 7.6923076923076925单位(w/s) 122 | //biz=biz1, qps = 134.44444444444446单位(w/s) 123 | //biz=biz1, qps = 717.5单位(w/s) 124 | //biz=biz1, qps = 910.0单位(w/s) 125 | //biz=biz1, qps = 1196.0单位(w/s) 126 | //biz=biz1, qps = 1311.1940298507463单位(w/s) 127 | //biz=biz1, qps = 1194.6762589928057单位(w/s) 128 | //biz=biz1, qps = 616.3617021276596单位(w/s) 129 | //biz=biz1, qps = 695.5131964809384单位(w/s) 130 | //biz=biz1, qps = 1244.5362563237775单位(w/s) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Butterfly 简介 2 | 3 | Butterfly(蝴蝶)是一个超高性能的发号器框架。框架通过引入多种新的方案不仅解决了雪花算法存在的所有问题,而且还能够提供比雪花算法更高的性能。在单机版QPS理论值为51.2(w/s)这种情况下,新的方案在一些机器上可达 1200(w/s) 甚至更高。 4 | 起名Butterfly是用世界上没有完全相同的蝴蝶翅膀来表示该算法的唯一性。 5 |

6 | 雪花算法是twitter提出的分布式id生成器方案,但是有三个问题,其中前两个问题在业内很常见: 7 | 8 | - 时间回拨问题 9 | - 机器id的分配和回收问题 10 | - 机器id的上限问题 11 | 12 | 其中业内针对前两个问题都有个自己的解决方式,但是都不是很完美,或者说没有完全解决。我们这里从新的思路出发,通过改造雪花算法以及其他相关方式彻底解决了以上的三个问题。
该方案算是对雪花算法比较完美的一种实现方式。方案请见[方案介绍](https://www.yuque.com/simonalong/butterfly) 13 | 14 | 15 | 16 | ### 使用场景说明 17 | 在发号器内,业内常用的是有这么几种 18 | 1. 单机雪花:性能高,但是有时间回拨问题 19 | 2. 单buffer + db:性能还可以,但是有buffer切换的延迟,一般都是用双buffer 20 | 3. 双buffer + db:性能不错,不同业务根据对应的buffer调整,需要人工接入,存在定制化,不过一般业内都是用这个 21 | 22 | 对于一般的业务场景,以上业内都是可以满足,所以一般情况下使用业内的就可以,但是对于一些并发需要非常高的情况,方案3的调整需要人工自己调整,方案1有雪花问题,这个时候才可以考虑使用改进版雪花 23 | 24 | 25 | --- 26 | # !!!!!!注意:!!!!!! 27 | 28 | 该方案其实更多的是对雪花本身问题的一种技术探索,进而引入了一些新的方案,新方案就带来了一些新的特性,有些朋友会直接拿过来放到生产环境中使用,有些可能不合适;因此强烈建议使用这个的朋友一定要将原理弄清楚再看下自己的业务是否适合使用;否则的话建议使用业内的成熟方案 29 | 30 | --- 31 | 32 | 33 | ## 新的方案 34 | 对于以上三个问题,我们这里简述下我们的方案。 35 | 1. 时间回拨问题
36 | 这里采用新的方案:大概思路是:时间戳采用的是“历史时间”,每次请求只增序列值,序列值增满,然后“历史时间”增1,序列值归0,其中"历史时间"的初始值是应用启动时候的当前时间。 37 | 2. 机器id分配和回收
38 | 这里机器id分配和回收具体有两种方案:zookeeper和db。理论上分配方案zk是通过哈希和扩容机器,而db是通过查找机制。回收方案,zk采用的是永久节点,节点中存储下次过期时间,客户端定时上报(设置心跳),db是添加过期时间字段,查找时候判断过期字段。 39 | 3. 机器id上限
40 | 这个采用将改造版雪花+zookeeper分配id方案作为服务端的节点,客户端采用双Buffer+异步获取提高性能,服务端采用每次请求时间戳增1。 41 | 42 | ## 特性 43 | 1. 全局唯一 44 | 2. 无时间回拨问题 45 | 3. 超高性能 46 | 47 | 4. 生成的id跟无法反解析时间 48 | 5. 非严格的整体自增 49 | 50 | 适用场景: 51 | 1. 超高并发情况(单机百万千万这种) 52 | 2. 对时间不敏感,只要求唯一性 53 | 3. 不要求完全自增 54 | 55 | ## 框架指标 56 | 全局唯一:最基本的要求,目前是对一个业务而言是唯一性
57 | 超高性能:纯内存化操作,性能特别高。采用时间预留,解决时钟回拨问题,同时可以使得qps达到更高,理论上最高可到 51.2w/s ~ more(不同的机器上限不同,自己的笔记本可达 1200(w/s))
58 | 趋势递增:整体递增,对用Mysql这种用b+树作为索引的结构可以提高性能
59 | 信息安全:自增位放在高位,id不是完全连续的,防止外部恶意的数据爬取
60 | 易用性:开发接入非常简单
61 | 62 | ## 快速入门 63 | 对于使用根据机器id的分配方式不同,这里有三种方式: 64 | - (单机版)zookeeper分配workerId 65 | - (单机版)db分配workerId 66 | - (分布式版)distribute分配workerId 67 | 68 | 目前已发布到maven中央仓库 69 | ### zookeeper分配workerId 70 | ```xml 71 | 72 | com.github.simonalong 73 | butterfly-allocator-zookeeper 74 | 75 | ${last.version.release} 76 | 77 | ``` 78 | #### 使用示例 79 | ```java 80 | @Test 81 | public void test(){ 82 | ZkButterflyConfig config = new ZkButterflyConfig(); 83 | config.setHost("localhost:2181"); 84 | 85 | ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config); 86 | // 设置起始时间,如果不设置,则默认从2020年2月22日开始 87 | generator.setStartTime(2020, 5, 1, 0, 0, 0); 88 | 89 | // 添加业务空间,如果业务空间不存在,则会注册 90 | generator.addNamespaces("test1", "test2"); 91 | Long uuid = generator.getUUid("test1"); 92 | System.out.println(uuid); 93 | } 94 | ``` 95 | ### db分配workerId 96 | ```xml 97 | 98 | com.github.simonalong 99 | butterfly-allocator-db 100 | 101 | ${last.version.release} 102 | 103 | ``` 104 | #### 使用示例 105 | 在对应的公共库中创建表 106 | ```sql 107 | CREATE TABLE `butterfly_uuid_generator` ( 108 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id', 109 | `namespace` varchar(128) DEFAULT '' COMMENT '命名空间', 110 | `work_id` int(16) COMMENT '工作id', 111 | `last_expire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '下次失效时间', 112 | `uid` varchar(128) DEFAULT '0' COMMENT '本次启动唯一id', 113 | `ip` varchar(20) NOT NULL DEFAULT '0' COMMENT 'ip', 114 | `process_id` varchar(128) NOT NULL DEFAULT '0' COMMENT '进程id', 115 | PRIMARY KEY (`id`), 116 | UNIQUE KEY `uk_name_work` (`namespace`,`work_id`) 117 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='发号器表'; 118 | ``` 119 | 编写db方式的测试 120 | ```java 121 | @Test 122 | public void test(){ 123 | DbButterflyConfig config = new DbButterflyConfig(); 124 | config.setUrl("jdbc:mysql://127.0.0.1:3306/neo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&&allowPublicKeyRetrieval=true"); 125 | config.setUserName("neo_test"); 126 | config.setPassword("neo@Test123"); 127 | 128 | ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config); 129 | // 设置起始时间,如果不设置,则默认从2020年2月22日开始 130 | generator.setStartTime(2020, 5, 1, 0, 0, 0); 131 | 132 | // 添加业务空间,如果业务空间不存在,则会注册 133 | generator.addNamespaces("test1", "test2"); 134 | Long uuid = generator.getUUid("test1"); 135 | System.out.println(uuid); 136 | } 137 | ``` 138 | 139 | ### 分布式模式 140 | 分布式模式客户端一个,但是对应的服务端(代码很简单可以也自己写服务端)这边有两个: 141 | - dubbo方式的:服务端采用的是butterfly-allocator-zk方式获取workerId和time字段,然后将对应字段给客户端 142 | - restful方式的:服务端采用的是butterfly-allocator-db方式获取workerId和time字段,然后将对应字段给客户端 143 | ```xml 144 | 145 | com.github.simonalong 146 | butterfly-allocator-distribute 147 | 148 | ${last.version.release} 149 | 150 | ``` 151 | #### 使用示例 - dubbo方式获取 152 | 首先启动服务端butterfly-server模块,然后客户端这边使用如下即可 153 | ```java 154 | @Test 155 | public void test(){ 156 | DistributeDubboButterflyConfig config = new DistributeDubboButterflyConfig(); 157 | config.setZkHoseAndPort("localhost:2181"); 158 | 159 | ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config); 160 | // 设置起始时间,如果不设置,则默认从2020年2月22日开始 161 | generator.setStartTime(2020, 5, 1, 0, 0, 0); 162 | 163 | // 添加业务空间,如果业务空间不存在,则会注册 164 | generator.addNamespaces("test1", "test2"); 165 | Long uuid = generator.getUUid("test1"); 166 | System.out.println(uuid); 167 | } 168 | ``` 169 | 170 | #### 使用示例 - restful方式获取 171 | 首先启动服务端butterfly-server模块,然后客户端这边使用如下即可 172 | ```java 173 | @Test 174 | public void test(){ 175 | DistributeRestfulButterflyConfig config = new DistributeRestfulButterflyConfig(); 176 | config.setHostAndPort("localhost:8800"); 177 | 178 | ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config); 179 | // 设置起始时间,如果不设置,则默认从2020年2月22日开始 180 | generator.setStartTime(2020, 5, 1, 0, 0, 0); 181 | 182 | // 添加业务空间,如果业务空间不存在,则会注册 183 | generator.addNamespaces("test1", "test2"); 184 | Long uuid = generator.getUUid("test1"); 185 | System.out.println(uuid); 186 | } 187 | ``` 188 | 189 | ## 更多内容 190 | 对于详细内容介绍,请见文档[Butterfly说明文档](https://www.yuque.com/simonalong/butterfly) 191 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/node/DefaultWorkerNodeHandler.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.node; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.simonalong.butterfly.worker.zookeeper.ZookeeperClient; 5 | import com.simonalong.butterfly.worker.zookeeper.entity.WorkerNodeEntity; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.lang.management.ManagementFactory; 9 | import java.net.InetAddress; 10 | import java.net.UnknownHostException; 11 | import java.util.UUID; 12 | import java.util.concurrent.ScheduledThreadPoolExecutor; 13 | import java.util.concurrent.ThreadFactory; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.atomic.AtomicInteger; 16 | 17 | import static com.simonalong.butterfly.sequence.UuidConstant.HEART_TIME; 18 | import static com.simonalong.butterfly.sequence.UuidConstant.KEEP_NODE_EXIST_TIME; 19 | import static com.simonalong.butterfly.worker.zookeeper.ZkConstant.SESSION_NODE; 20 | import static com.simonalong.butterfly.worker.zookeeper.ZkConstant.ZK_LOG_PRE; 21 | 22 | /** 23 | * worker节点处理器 24 | * 25 | * @author shizi 26 | * @since 2020/4/25 11:01 AM 27 | */ 28 | @Slf4j 29 | public class DefaultWorkerNodeHandler implements WorkerNodeHandler { 30 | 31 | /** 32 | * 当前启动的唯一健 33 | */ 34 | private String uidKey; 35 | /** 36 | * worker_x 节点信息 37 | */ 38 | private WorkerNodeEntity workerNodeEntity; 39 | private ScheduledThreadPoolExecutor scheduler; 40 | private ZookeeperClient zookeeperClient; 41 | private WorkerIdAllocator workerIdAllocator; 42 | 43 | public DefaultWorkerNodeHandler(String namespace, ZookeeperClient zookeeperClient, ConfigNodeHandler configNodeHandler) { 44 | this.zookeeperClient = zookeeperClient; 45 | init(); 46 | this.workerIdAllocator = new DefaultWorkerIdAllocator(namespace, zookeeperClient, this, configNodeHandler); 47 | this.workerNodeEntity = getWorkerNodeEntity(); 48 | } 49 | 50 | private void init() { 51 | // 初始胡uidKey 52 | initKey(); 53 | 54 | // 初始化心跳上报 55 | initHeartBeatReport(); 56 | 57 | // 添加进程退出钩子 58 | addShutdownHook(); 59 | } 60 | 61 | 62 | @Override 63 | public String getUidKey() { 64 | return uidKey; 65 | } 66 | 67 | @Override 68 | public Long getLastExpireTime() { 69 | return workerNodeEntity.getLastExpireTime(); 70 | } 71 | 72 | @Override 73 | public String getIp() { 74 | return workerNodeEntity.getIp(); 75 | } 76 | 77 | @Override 78 | public String getProcessId() { 79 | return workerNodeEntity.getProcessId(); 80 | } 81 | 82 | @Override 83 | public Integer getWorkerId() { 84 | return workerIdAllocator.getWorkerId(); 85 | } 86 | 87 | /** 88 | * 刷新节点信息 89 | */ 90 | @Override 91 | public void refreshNodeInfo() { 92 | try { 93 | updateWorkerNodeInfo(getWorkerNodeEntity()); 94 | } catch (Throwable e) { 95 | log.error("刷新节点信息异常:", e); 96 | } 97 | } 98 | 99 | /** 100 | * 刷新指定节点的信息 101 | */ 102 | @Override 103 | public void refreshNodeInfo(String workerNodePath) { 104 | updateWorkerNodeInfo(workerNodePath, getWorkerNodeEntity()); 105 | } 106 | 107 | /** 108 | * 获取本次进程启动的唯一编号 109 | */ 110 | private void initKey() { 111 | uidKey = UUID.randomUUID().toString(); 112 | } 113 | 114 | /** 115 | * 初始化数据的心跳上报 116 | */ 117 | private void initHeartBeatReport() { 118 | scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { 119 | private AtomicInteger threadNum = new AtomicInteger(0); 120 | 121 | @Override 122 | @SuppressWarnings("all") 123 | public Thread newThread(Runnable r) { 124 | Thread thread = new Thread(r, "Thread-Butterfly-Heart" + threadNum.getAndIncrement()); 125 | thread.setDaemon(true); 126 | return thread; 127 | } 128 | }); 129 | 130 | // 每5秒上报一次数据 131 | scheduler.scheduleWithFixedDelay(this::refreshNodeInfo, 10, HEART_TIME, TimeUnit.SECONDS); 132 | } 133 | 134 | /** 135 | * 进程关闭时候清理业务数据 136 | */ 137 | private void addShutdownHook() { 138 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 139 | log.info(ZK_LOG_PRE + "process ready to quit, clear resources of zookeeper"); 140 | updateWorkerNodeInfo(null); 141 | zookeeperClient.deleteNode(workerIdAllocator.getWorkerNodePath() + SESSION_NODE); 142 | if (null != scheduler) { 143 | scheduler.shutdown(); 144 | } 145 | })); 146 | } 147 | 148 | private WorkerNodeEntity getWorkerNodeEntity() { 149 | return new WorkerNodeEntity().setIp(getIpStr()).setProcessId(getProcessIdStr()).setLastExpireTime(afterHour()).setUidKey(uidKey); 150 | } 151 | 152 | private void updateWorkerNodeInfo(WorkerNodeEntity workerNodeInfo) { 153 | updateWorkerNodeInfo(workerIdAllocator.getWorkerNodePath(), workerNodeInfo); 154 | } 155 | 156 | /** 157 | * 更新节点worker_x 中的信息 158 | */ 159 | private void updateWorkerNodeInfo(String workerNodePath, WorkerNodeEntity workerNodeInfo) { 160 | try { 161 | if (null != workerNodeInfo) { 162 | zookeeperClient.writeNodeData(workerNodePath, JSON.toJSONString(workerNodeInfo)); 163 | } else { 164 | zookeeperClient.writeNodeData(workerNodePath, ""); 165 | } 166 | } catch (Throwable e) { 167 | log.error(ZK_LOG_PRE + "node(worker_" + getWorkerId() + ") update fail", e); 168 | } 169 | 170 | this.workerNodeEntity = workerNodeInfo; 171 | } 172 | 173 | private String getIpStr() { 174 | try { 175 | return InetAddress.getLocalHost().getHostAddress(); 176 | } catch (UnknownHostException e) { 177 | return "null"; 178 | } 179 | } 180 | 181 | /** 182 | * 将时间向未来延长固定的小时 183 | */ 184 | private long afterHour() { 185 | return System.currentTimeMillis() + KEEP_NODE_EXIST_TIME; 186 | } 187 | 188 | /** 189 | * 获取进程id字符串 190 | */ 191 | private String getProcessIdStr() { 192 | return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /butterfly-sample/src/main/java/com/simonalong/buffterfly/sample/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.buffterfly.sample; 2 | 3 | import com.simonalong.butterfly.sequence.ButterflyConfig; 4 | import com.simonalong.butterfly.sequence.ButterflyIdGenerator; 5 | import lombok.SneakyThrows; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | import java.util.Map; 10 | import java.util.concurrent.CountDownLatch; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.ThreadFactory; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | 16 | import static com.simonalong.butterfly.sequence.UuidConstant.*; 17 | 18 | /** 19 | * @author shizi 20 | * @since 2020/5/3 6:33 PM 21 | */ 22 | public class BaseTest { 23 | 24 | public static ButterflyConfig config; 25 | 26 | @SuppressWarnings("all") 27 | public ExecutorService executorService = Executors.newFixedThreadPool(1000, new ThreadFactory() { 28 | private AtomicInteger count = new AtomicInteger(0); 29 | 30 | @Override 31 | @SuppressWarnings("all") 32 | public Thread newThread(Runnable r) { 33 | return new Thread(r, "test_" + count.getAndIncrement()); 34 | } 35 | }); 36 | 37 | 38 | public void show(Object obj) { 39 | if (null == obj) { 40 | show("obj is null"); 41 | } else { 42 | System.out.println(obj.toString()); 43 | } 44 | } 45 | 46 | /** 47 | * 基本测试 48 | */ 49 | public void baseRun() { 50 | ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config); 51 | 52 | // 设置起始时间,如果不设置,则默认从2020年2月22日开始 53 | generator.setStartTime(2020, 5, 1, 0, 0, 0); 54 | 55 | generator.addNamespaces("test1", "test2", "test3", "test4"); 56 | 57 | // 测试test1 58 | showId("test1", generator); 59 | showId("test1", generator); 60 | 61 | // 测试test2 62 | showId("test2", generator); 63 | showId("test2", generator); 64 | showId("test2", generator); 65 | showId("test2", generator); 66 | showId("test2", generator); 67 | 68 | // 测试test3 69 | showId("test3", generator); 70 | 71 | // 测试test4 72 | showId("test4", generator); 73 | } 74 | 75 | private void showId(String namespace, ButterflyIdGenerator generator) { 76 | show("命名空间:" + namespace + ":" + ButterflyIdGenerator.parseUid(generator.getUUid(namespace))); 77 | } 78 | 79 | /** 80 | * 持续的低qps的压测,则这个QPS还是比较低,完全满足业务需求 81 | */ 82 | @SneakyThrows 83 | public void lowPressRun() { 84 | ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config); 85 | generator.addNamespaces("biz0"); 86 | int count = 10; 87 | int callNum = 10; 88 | int concurrentNum = 100; 89 | 90 | for (int i = 0; i < count; i++) { 91 | generateFun(generator, "biz0", callNum, concurrentNum); 92 | Thread.sleep(1000); 93 | } 94 | } 95 | 96 | /** 97 | * 低qps一段时间后到高QPS,可以支撑更高,但是一旦持续的高并发,则后面会慢慢降下来 98 | */ 99 | @SneakyThrows 100 | public void lowToHighPressRun() { 101 | ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config); 102 | generator.addNamespaces("biz0"); 103 | int count = 10; 104 | int callNum = 10; 105 | int concurrentNum = 100; 106 | 107 | for (int i = 0; i < count; i++) { 108 | generateFun(generator, "biz0", callNum + i * i * 100, concurrentNum + i * 10 * i); 109 | Thread.sleep(1000); 110 | } 111 | } 112 | 113 | /** 114 | * 持续的高QPS,则只能达到最高的理论值(51.2w/s) 115 | */ 116 | @SneakyThrows 117 | public void highPressRun() { 118 | ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config); 119 | generator.addNamespaces("biz0"); 120 | int count = 10; 121 | int callNum = 1000; 122 | int concurrentNum = 10000; 123 | 124 | for (int i = 0; i < count; i++) { 125 | generateFun(generator, "biz0", callNum, concurrentNum); 126 | Thread.sleep(1000); 127 | } 128 | } 129 | 130 | /** 131 | * 多业务的压测:低qps一段时间后到高QPS,可以支撑更高 132 | */ 133 | @SneakyThrows 134 | public void lowToHighMultiBizPressRun() { 135 | ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config); 136 | generator.addNamespaces("biz0", "biz1"); 137 | int count = 10; 138 | int callNum = 10; 139 | int concurrentNum = 100; 140 | 141 | for (int i = 0; i < count; i++) { 142 | generateFun(generator, "biz0", callNum + i * i * 100, concurrentNum + i * 10 * i); 143 | generateFun(generator, "biz1", callNum + i * i * 100, concurrentNum + i * 10 * i); 144 | Thread.sleep(1000); 145 | } 146 | } 147 | 148 | /** 149 | * 每次调用的统计,统计其中的QPS 150 | * 151 | * @param generator id生成器 152 | * @param biz 业务名 153 | * @param callNum 调用多少次 154 | * @param concurrentNum 每次的并发量 155 | */ 156 | @SneakyThrows 157 | public void generateFun(ButterflyIdGenerator generator, String biz, Integer callNum, Integer concurrentNum) { 158 | long start = System.currentTimeMillis(); 159 | CountDownLatch latch = new CountDownLatch(callNum * concurrentNum); 160 | for (int index = 0; index < concurrentNum; index++) { 161 | executorService.execute(() -> { 162 | for (int j = 0; j < callNum; j++) { 163 | generator.getUUid(biz); 164 | latch.countDown(); 165 | } 166 | }); 167 | } 168 | 169 | latch.await(); 170 | long duration = System.currentTimeMillis() - start; 171 | show("biz=" + biz + ", qps = " + (callNum * concurrentNum) / (duration * 10.0) + "单位(w/s)"); 172 | } 173 | 174 | @Test 175 | public void test2() { 176 | int workerId = 0; 177 | long seq = 1; 178 | long time = System.currentTimeMillis(); 179 | 180 | Map map1 = ButterflyIdGenerator.parseUid(getData(time, seq, workerId)); 181 | Assert.assertEquals(map1.get("sequence"), seq); 182 | 183 | Map map2 = ButterflyIdGenerator.parseUid(getData(time, seq + 1, workerId)); 184 | Assert.assertEquals(map2.get("sequence"), seq + 1); 185 | 186 | Map map3 = ButterflyIdGenerator.parseUid(getData(time, seq + 2, workerId)); 187 | Assert.assertEquals(map3.get("sequence"), seq + 2); 188 | 189 | Map map4 = ButterflyIdGenerator.parseUid(getData(time, seq + 3, workerId)); 190 | Assert.assertEquals(map4.get("sequence"), seq + 3); 191 | 192 | Map map5 = ButterflyIdGenerator.parseUid(getData(time, seq + 4, workerId)); 193 | Assert.assertEquals(map5.get("sequence"), seq + 4); 194 | } 195 | 196 | private long getData(long time, long seq, long workerId) { 197 | return (time << ((SEQ_HIGH_BITS + WORKER_BITS + SEQ_LOW_BITS)) | (((seq << WORKER_BITS) & SEQ_HIGH_MARK)) | ((workerId << SEQ_LOW_BITS) & WORKER_MARK) | (seq & SEQ_LOW_MARK)); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/node/DefaultWorkerIdAllocator.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper.node; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 5 | import com.simonalong.butterfly.worker.zookeeper.ZookeeperClient; 6 | import com.simonalong.butterfly.worker.zookeeper.entity.SessionNodeEntity; 7 | import com.simonalong.butterfly.worker.zookeeper.entity.WorkerNodeEntity; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | import static com.simonalong.butterfly.sequence.UuidConstant.MAX_WORKER_SIZE; 11 | import static com.simonalong.butterfly.worker.zookeeper.ZkConstant.*; 12 | 13 | /** 14 | * @author shizi 15 | * @since 2020/4/25 10:45 AM 16 | */ 17 | @Slf4j 18 | public class DefaultWorkerIdAllocator implements WorkerIdAllocator { 19 | 20 | /** 21 | * 当前分配的索引(就是workerId) 22 | */ 23 | private Integer index; 24 | private String namespace; 25 | protected ZookeeperClient zkClient; 26 | private WorkerNodeHandler workerNodeHandler; 27 | private ConfigNodeHandler configNodeHandler; 28 | 29 | public DefaultWorkerIdAllocator(String namespace, ZookeeperClient zkClient, WorkerNodeHandler workerNodeHandler, ConfigNodeHandler configNodeHandler) { 30 | this.namespace = namespace; 31 | this.zkClient = zkClient; 32 | this.workerNodeHandler = workerNodeHandler; 33 | this.configNodeHandler = configNodeHandler; 34 | init(); 35 | } 36 | 37 | @Override 38 | public Integer getWorkerId() { 39 | return index; 40 | } 41 | 42 | @Override 43 | public String getWorkerNodePath() { 44 | return getNodePathWithIndex(getWorkerId()); 45 | } 46 | 47 | /** 48 | * 连接zk并初始化 49 | * 50 | *
    51 | *
  • 1.读取业务对应的配置数据
  • 52 | *
  • 2.通过哈希找到对应的工作节点
  • 53 | *
  • 3.节点如果被占用则继续查找
  • 54 | *
  • 4.如果没找到,标识机器都满了,则考虑扩容
  • 55 | *
56 | */ 57 | private void init() { 58 | zkClient.reconnect().registerDisconnectCallback(this::init); 59 | 60 | // 初始化索引 61 | index = getWorkId(workerNodeHandler.getUidKey().hashCode()); 62 | 63 | // 初始化节点信息 64 | initNode(); 65 | } 66 | 67 | private void initNode() { 68 | if (findNode(index)) { 69 | return; 70 | } 71 | // 没有找到可用worker节点,则扩充worker节点 72 | expandWorker(); 73 | } 74 | 75 | /** 76 | * 发现一个可用节点 77 | * 78 | * @param index 当前分配的临时的workerId 79 | * @return true:查找成功(找到插入的数据),false:没有找到可用的worker节点,进行扩容 80 | */ 81 | private Boolean findNode(final Integer index) { 82 | log.info(ZK_LOG_PRE + " find one node to create session, workerId = " + index); 83 | String workerNodePathTem = getNodePathWithIndex(index); 84 | if (zkClient.nodeExist(workerNodePathTem)) { 85 | if (addSessionNode(workerNodePathTem)) { 86 | savePath(index); 87 | return true; 88 | } 89 | } 90 | 91 | // 如果转了一圈都没有找到,则考虑扩容 92 | Integer nextIndex = getWorkId(index + 1); 93 | if (nextIndex.equals(this.index)) { 94 | return false; 95 | } 96 | return findNode(nextIndex); 97 | } 98 | 99 | /** 100 | * 添加session节点 101 | * 102 | *
    103 | *
  • 1.worker节点中数据不存在(节点刚初始化完或者session异常没有删除),则创建session并添加worker节点中的时间和key
  • 104 | *
  • 2.数据存在,数据如果过期,则创建session并更新worker节点中的时间和key
  • 105 | *
  • 3.数据存在,数据没有过期,则判断是否是自己,如果是,则删除之前的session,并创建新session然后更新worker节点中的时间和key
  • 106 | *
107 | * 108 | * @param workerPath 业务占用的机器的节点 109 | * @return true:添加成功,false:添加失败 110 | */ 111 | private Boolean addSessionNode(String workerPath) { 112 | // 节点首次读取或者session异常没有删除 113 | WorkerNodeEntity workerNodeEntity = zkClient.readDataJson(workerPath, WorkerNodeEntity.class); 114 | if (null == workerNodeEntity) { 115 | return createSession(workerPath); 116 | } 117 | 118 | Long lastTime = workerNodeEntity.getLastExpireTime(); 119 | String uidKeyTem = workerNodeEntity.getUidKey(); 120 | 121 | // 节点过期:创建session节点 122 | if (null != lastTime && lastTime < System.currentTimeMillis()) { 123 | return zkClient.distributeLock(ZkNodeHelper.getSessionCreateLock(namespace), () -> createSession(workerPath)); 124 | } else { 125 | // 节点未过期:如果是自己,则可以使用 126 | if (null != uidKeyTem && workerNodeEntity.getUidKey().equals(workerNodeHandler.getUidKey())) { 127 | return createSession(workerPath); 128 | } 129 | } 130 | 131 | return false; 132 | } 133 | 134 | private Boolean createSession(String workerPath) { 135 | String sessionPath = workerPath + SESSION_NODE; 136 | if (zkClient.nodeExist(sessionPath)) { 137 | zkClient.deleteNode(sessionPath); 138 | } 139 | 140 | // 记录ip和进程id到session节点 141 | SessionNodeEntity sessionNodeEntity = new SessionNodeEntity(); 142 | if (zkClient.addPersistentNode(sessionPath, JSON.toJSONString(sessionNodeEntity))) { 143 | // 记录下次过期时间和本次启动的唯一编号 144 | workerNodeHandler.refreshNodeInfo(workerPath); 145 | return true; 146 | } 147 | return false; 148 | } 149 | 150 | /** 151 | * 扩展机器的个数 152 | *

153 | * 如果节点不存在,则创建worker节点并添加session节点,如果节点存在,看下是否能够添加到数据中(这个时候认为已经有其他进程也参与了扩容) 154 | */ 155 | private void expandWorker() { 156 | // 当前最大的机器个数 157 | Integer maxMachineNum = configNodeHandler.getCurrentMaxMachineNum(); 158 | log.info(ZK_LOG_PRE + " ready to expand, biz=" + namespace + ", maxMachineNum=" + maxMachineNum); 159 | 160 | // 禁止扩容 161 | if (maxMachineNum >= getMaxMachineNum()) { 162 | throw new ButterflyException("当前最大值" + maxMachineNum + "已到机器最大值"); 163 | } 164 | 165 | zkClient.distributeLock(ZkNodeHelper.getBizExpandLock(namespace), () -> { 166 | // 扩容 167 | if (innerExpand(maxMachineNum)) { 168 | log.info(ZK_LOG_PRE + " expand success, ready to find one node to create session"); 169 | // 扩容成功,重新分配节点 170 | findNode(maxMachineNum); 171 | } 172 | }); 173 | } 174 | 175 | private Boolean innerExpand(Integer maxMachineNum) { 176 | String leftPath = getNodePathWithPre(); 177 | for (int index = maxMachineNum; index < maxMachineNum * 2; index++) { 178 | String workerPath = leftPath + index; 179 | // 添加永久节点 180 | zkClient.addPersistentNode(workerPath); 181 | } 182 | 183 | log.debug(ZK_LOG_PRE + " maxMachineNum * 2 have created finished"); 184 | 185 | configNodeHandler.updateCurrentMaxMachineNum(maxMachineNum * 2); 186 | return true; 187 | } 188 | 189 | private void savePath(Integer workerId) { 190 | this.index = workerId; 191 | } 192 | 193 | /** 194 | * 获取节点的哈希值 195 | *

196 | * 根据预发和线上环境可能冲突的问题,采用workId划分,预发分配一定个数,然后线上按照最大个数往后算 197 | */ 198 | private Integer getWorkId(Integer index) { 199 | int maxNum = configNodeHandler.getCurrentMaxMachineNum(); 200 | assert maxNum != 0 : "当前机器个数不为0"; 201 | assert maxNum <= MAX_WORKER_SIZE : "当前机器最大个数设置有误"; 202 | return index & (maxNum - 1); 203 | } 204 | 205 | /** 206 | * 获取当前业务的最大机器个数 207 | *

208 | * 注意: 209 | * 这个个数是bit内部约束的最大个数 210 | */ 211 | private Integer getMaxMachineNum() { 212 | return Math.toIntExact(MAX_WORKER_SIZE); 213 | } 214 | 215 | /** 216 | * 获取要分配的节点的带索引的路径 217 | * 218 | * @param workerId 节点索引 219 | */ 220 | private String getNodePathWithIndex(final Integer workerId) { 221 | return getNodePathWithPre() + workerId; 222 | } 223 | 224 | private String getNodePathWithPre() { 225 | return ROOT_PATH + "/" + namespace + WORKER_NODE + "_"; 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-db/src/main/java/com/simonalong/butterfly/worker/db/DbWorkerIdHandler.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.db; 2 | 3 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 4 | import com.simonalong.butterfly.sequence.exception.WorkerIdFullException; 5 | import com.simonalong.butterfly.sequence.spi.WorkerIdHandler; 6 | import com.simonalong.butterfly.worker.db.entity.UuidGeneratorDO; 7 | import com.simonalong.neo.Neo; 8 | import com.simonalong.neo.NeoMap; 9 | import com.simonalong.neo.TableMap; 10 | import com.simonalong.neo.util.LocalDateTimeUtil; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.lang.management.ManagementFactory; 14 | import java.net.InetAddress; 15 | import java.net.UnknownHostException; 16 | import java.time.LocalDate; 17 | import java.util.Date; 18 | import java.util.UUID; 19 | import java.util.concurrent.ScheduledThreadPoolExecutor; 20 | import java.util.concurrent.ThreadFactory; 21 | import java.util.concurrent.TimeUnit; 22 | import java.util.concurrent.atomic.AtomicInteger; 23 | 24 | import static com.simonalong.butterfly.sequence.UuidConstant.*; 25 | import static com.simonalong.butterfly.worker.db.DbConstant.DB_LOG_PRE; 26 | import static com.simonalong.butterfly.worker.db.DbConstant.UUID_TABLE; 27 | 28 | /** 29 | * @author shizi 30 | * @since 2020/4/25 12:51 AM 31 | */ 32 | @Slf4j 33 | public class DbWorkerIdHandler implements WorkerIdHandler { 34 | 35 | /** 36 | * 本次启动的唯一key 37 | */ 38 | private String uidKey; 39 | /** 40 | * 进程id 41 | */ 42 | private String processId; 43 | private String ip; 44 | /** 45 | * worker_x 节点信息 46 | */ 47 | private ScheduledThreadPoolExecutor scheduler; 48 | private Neo neo; 49 | private String namespace; 50 | private UuidGeneratorDO uuidGeneratorDO; 51 | 52 | public DbWorkerIdHandler(String namespace, Neo neo) { 53 | this.namespace = namespace; 54 | this.neo = neo; 55 | this.uuidGeneratorDO = new UuidGeneratorDO(); 56 | init(); 57 | } 58 | 59 | private void init() { 60 | checkNamespace(namespace); 61 | 62 | // 配置基本信息 63 | initBaseInfo(); 64 | 65 | // 分配worker 66 | allocateWorker(); 67 | 68 | // 初始化心跳上报 69 | initHeartBeatReport(); 70 | 71 | // 添加进程退出钩子 72 | addShutdownHook(); 73 | } 74 | 75 | @Override 76 | public Long getLastExpireTime() { 77 | return LocalDateTimeUtil.timestampToLong(uuidGeneratorDO.getLastExpireTime()); 78 | } 79 | 80 | @Override 81 | public Integer getWorkerId() { 82 | return uuidGeneratorDO.getWorkId(); 83 | } 84 | 85 | private void initBaseInfo() { 86 | uidKey = UUID.randomUUID().toString(); 87 | processId = getProcessIdStr(); 88 | ip = getIpStr(); 89 | } 90 | 91 | private UuidGeneratorDO generateUuidGeneratorDo(Long id, Integer workerId) { 92 | UuidGeneratorDO uuidGeneratorDO = new UuidGeneratorDO(); 93 | uuidGeneratorDO.setId(id); 94 | uuidGeneratorDO.setWorkId(workerId); 95 | uuidGeneratorDO.setNamespace(namespace); 96 | uuidGeneratorDO.setLastExpireTime(LocalDateTimeUtil.longToTimestamp(afterHour())); 97 | uuidGeneratorDO.setUid(uidKey); 98 | uuidGeneratorDO.setProcessId(processId); 99 | uuidGeneratorDO.setIp(ip); 100 | return uuidGeneratorDO; 101 | } 102 | 103 | /** 104 | * 初始化数据的心跳上报 105 | */ 106 | private void initHeartBeatReport() { 107 | scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { 108 | private final AtomicInteger threadNum = new AtomicInteger(0); 109 | 110 | @Override 111 | @SuppressWarnings("all") 112 | public Thread newThread(Runnable r) { 113 | Thread thread = new Thread(r, "Neo-Heart-Thread-" + threadNum.getAndIncrement()); 114 | thread.setDaemon(true); 115 | return thread; 116 | } 117 | }); 118 | 119 | // 延迟10秒上报,每5秒上报一次数据 120 | scheduler.scheduleWithFixedDelay(this::refreshNodeInfo, 10, HEART_TIME, TimeUnit.SECONDS); 121 | } 122 | 123 | /** 124 | * 进程关闭时候清理业务数据 125 | */ 126 | private void addShutdownHook() { 127 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 128 | log.info(DB_LOG_PRE + "process ready to quit, clear resources of db"); 129 | neo.delete(UUID_TABLE, uuidGeneratorDO.getId()); 130 | if (null != scheduler) { 131 | scheduler.shutdown(); 132 | } 133 | })); 134 | } 135 | 136 | /** 137 | * 刷新节点信息 138 | *

139 | * 主要为更新下次失效时间 140 | */ 141 | private void refreshNodeInfo() { 142 | try { 143 | long lastExpireTime = afterHour(); 144 | UuidGeneratorDO newGenerate = new UuidGeneratorDO(); 145 | newGenerate.setId(uuidGeneratorDO.getId()); 146 | newGenerate.setLastExpireTime(LocalDateTimeUtil.longToTimestamp(lastExpireTime)); 147 | newGenerate = neo.update(UUID_TABLE, newGenerate); 148 | if (null != newGenerate && null != newGenerate.getId()) { 149 | uuidGeneratorDO.setLastExpireTime(LocalDateTimeUtil.longToTimestamp(lastExpireTime)); 150 | } 151 | } catch (Throwable e) { 152 | log.error("刷新节点信息异常:", e); 153 | } 154 | } 155 | 156 | private void allocateWorker() { 157 | if (applyWorkerFromExistExpire()) { 158 | return; 159 | } 160 | 161 | insertWorker(); 162 | } 163 | 164 | private void checkNamespace(String namespace) { 165 | if (null == namespace || "".equals(namespace)) { 166 | throw new ButterflyException("命名空间为空"); 167 | } 168 | 169 | if (namespace.equals(DISTRIBUTE_SERVER)) { 170 | throw new ButterflyException("node [" + namespace + "] is distribute'node, forbidden to add"); 171 | } 172 | } 173 | 174 | /** 175 | * 从已存在的worker里面查看过期的,有过期的则获取并更新数据 176 | * 177 | * @return true:分配成功,false:分配失败 178 | */ 179 | private Boolean applyWorkerFromExistExpire() { 180 | Integer minId = neo.exeValue(Integer.class, "select min(id) from %s where namespace =? and last_expire_time < ?", UUID_TABLE, namespace, new Date()); 181 | if (null == minId) { 182 | return false; 183 | } 184 | 185 | return neo.tx(() -> { 186 | TableMap result = neo.exeOne("select id, work_id, last_expire_time from %s where id = ? for update", UUID_TABLE, minId); 187 | if (null == result) { 188 | return false; 189 | } 190 | NeoMap selectOne = result.getNeoMap(UUID_TABLE); 191 | if (null != selectOne && selectOne.get(Date.class, "last_expire_time").compareTo(new Date()) < 0) { 192 | uuidGeneratorDO = neo.update(UUID_TABLE, generateUuidGeneratorDo(selectOne.getLong("id"), selectOne.getInteger("work_id"))); 193 | return true; 194 | } 195 | return false; 196 | }); 197 | } 198 | 199 | /** 200 | * 新增一个worker 201 | *

202 | * 如果数据达到最大,则阻止进程启动 203 | */ 204 | private void insertWorker() { 205 | try { 206 | // 强制加表锁 207 | neo.execute("lock tables %s write", UUID_TABLE); 208 | Integer maxWorkerId = neo.exeValue(Integer.class, "select max(work_id) from %s where namespace = ?", UUID_TABLE, namespace); 209 | if (null == maxWorkerId) { 210 | uuidGeneratorDO = neo.insert(UUID_TABLE, generateUuidGeneratorDo(null, 0)); 211 | } else { 212 | if (maxWorkerId + 1 < MAX_WORKER_SIZE) { 213 | uuidGeneratorDO = neo.insert(UUID_TABLE, generateUuidGeneratorDo(null, maxWorkerId + 1)); 214 | } else { 215 | log.error(DB_LOG_PRE + "namespace {} have full worker, init fail", namespace); 216 | throw new WorkerIdFullException("namespace " + namespace + " have full worker, init fail"); 217 | } 218 | } 219 | } finally { 220 | // 解锁 221 | neo.execute("unlock tables"); 222 | } 223 | } 224 | 225 | private String getIpStr() { 226 | try { 227 | return InetAddress.getLocalHost().getHostAddress(); 228 | } catch (UnknownHostException e) { 229 | return "127.0.0.1"; 230 | } 231 | } 232 | 233 | /** 234 | * 将时间向未来延长固定的小时 235 | */ 236 | private long afterHour() { 237 | return System.currentTimeMillis() + KEEP_NODE_EXIST_TIME; 238 | } 239 | 240 | /** 241 | * 获取进程id字符串 242 | */ 243 | private String getProcessIdStr() { 244 | return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.0.4.RELEASE 10 | 11 | 12 | com.github.simonalong 13 | butterfly 14 | pom 15 | 1.1.2 16 | 17 | butterfly 18 | https://github.com/SimonAlong/Butterfly 19 | 发号器框架:完美解决雪花算法所有问题 20 | 21 | 22 | butterfly-sequence 23 | butterfly-distribute 24 | butterfly-allocator 25 | butterfly-sample 26 | 27 | 28 | 29 | 30 | shizi 31 | simonalongs@126.com 32 | 33 | owner 34 | 35 | 36 | 37 | 38 | 39 | UTF-8 40 | UTF-8 41 | 1.7.28 42 | 0.7.6 43 | 2.7.15 44 | 4.0.1 45 | 4.13.1 46 | 2.0.4 47 | 48 | 49 | 50 | 51 | org.projectlombok 52 | lombok 53 | true 54 | 55 | 56 | 57 | 58 | 59 | 60 | com.github.simonalong 61 | butterfly-sequence 62 | ${project.version} 63 | 64 | 65 | com.github.simonalong 66 | butterfly-allocator-db 67 | ${project.version} 68 | 69 | 70 | com.github.simonalong 71 | butterfly-allocator-distribute 72 | ${project.version} 73 | 74 | 75 | com.github.simonalong 76 | butterfly-allocator-zookeeper 77 | ${project.version} 78 | 79 | 80 | com.github.simonalong 81 | butterfly-api 82 | ${project.version} 83 | 84 | 85 | 86 | mysql 87 | mysql-connector-java 88 | 8.0.28 89 | 90 | 91 | 92 | org.slf4j 93 | slf4j-api 94 | ${slf4j.version} 95 | 96 | 97 | com.github.simonalong 98 | Neo 99 | ${neo.version} 100 | 101 | 102 | junit 103 | junit 104 | ${junit.version} 105 | 106 | 107 | 108 | 109 | org.apache.dubbo 110 | dubbo 111 | ${dubbo.version} 112 | 113 | 114 | org.apache.dubbo 115 | dubbo-spring-boot-starter 116 | ${dubbo.version} 117 | 118 | 119 | org.apache.curator 120 | curator-framework 121 | ${curator.version} 122 | 123 | 124 | org.apache.curator 125 | curator-recipes 126 | ${curator.version} 127 | 128 | 129 | 130 | com.alibaba 131 | fastjson 132 | ${fastjson.version} 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-source-plugin 142 | 2.1 143 | 144 | true 145 | 146 | 147 | 148 | compile 149 | 150 | jar 151 | 152 | 153 | 154 | 155 | 156 | 157 | org.apache.maven.plugins 158 | maven-compiler-plugin 159 | 3.8.0 160 | 161 | 1.8 162 | 1.8 163 | 164 | 165 | 166 | 167 | org.apache.maven.plugins 168 | maven-javadoc-plugin 169 | 2.10.4 170 | 171 | 172 | attach-javadocs 173 | 174 | jar 175 | 176 | 177 | 178 | 179 | 180 | 181 | org.codehaus.mojo 182 | versions-maven-plugin 183 | 184 | 185 | 186 | 187 | org.apache.maven.plugins 188 | maven-gpg-plugin 189 | 1.6 190 | 191 | 192 | sign-artifacts 193 | verify 194 | 195 | sign 196 | 197 | 198 | 199 | 200 | 201 | org.sonatype.plugins 202 | nexus-staging-maven-plugin 203 | 1.6.8 204 | true 205 | 206 | oss 207 | https://oss.sonatype.org/ 208 | true 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | oss 217 | https://oss.sonatype.org/content/repositories/snapshots/ 218 | 219 | 220 | oss 221 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /butterfly-allocator/butterfly-allocator-zookeeper/src/main/java/com/simonalong/butterfly/worker/zookeeper/ZookeeperClient.java: -------------------------------------------------------------------------------- 1 | package com.simonalong.butterfly.worker.zookeeper; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.simonalong.butterfly.sequence.exception.ButterflyException; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.zookeeper.*; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.concurrent.Callable; 12 | import java.util.concurrent.Phaser; 13 | import java.util.stream.Collectors; 14 | import java.util.stream.Stream; 15 | 16 | import static com.simonalong.butterfly.sequence.UuidConstant.LOG_PRE; 17 | 18 | /** 19 | * @author shizi 20 | * @since 2020/4/25 10:38 AM 21 | */ 22 | @Slf4j 23 | public class ZookeeperClient { 24 | 25 | private static final String ZK_LOG_PRE = LOG_PRE + "[zookeeper]:"; 26 | private static final int SESSION_TIMEOUT = 10000; 27 | 28 | private String connectString; 29 | private ZooKeeper zookeeper = null; 30 | /** 31 | * 用于服务启动的同步 32 | */ 33 | private Phaser phaser = new Phaser(1); 34 | private static volatile ZookeeperClient zookeeperClient = null; 35 | /** 36 | * 观察者回调器 37 | */ 38 | private CallbackWatcher watcher = new CallbackWatcher(); 39 | /** 40 | * 连接断开或者会话超时回调 41 | */ 42 | private Runnable disconnectCallback; 43 | private Runnable connectSuccessCallback; 44 | 45 | private ZookeeperClient() { 46 | } 47 | 48 | public static ZookeeperClient getInstance() { 49 | if (null == zookeeperClient) { 50 | synchronized (ZookeeperClient.class) { 51 | if (null == zookeeperClient) { 52 | zookeeperClient = new ZookeeperClient(); 53 | } 54 | } 55 | } 56 | return zookeeperClient; 57 | } 58 | 59 | /** 60 | * 创建zookeeper的客户端 61 | * 62 | * @param connectString 链接字符 63 | * @return zk客户端 64 | */ 65 | public ZookeeperClient connect(String connectString) { 66 | try { 67 | this.close(); 68 | phaser.register(); 69 | this.connectString = connectString; 70 | zookeeper = new ZooKeeper(connectString, SESSION_TIMEOUT, watcher); 71 | log.info(ZK_LOG_PRE + "connect zookeeper: " + connectString); 72 | phaser.arriveAndAwaitAdvance(); 73 | } catch (Throwable e) { 74 | log.error(ZK_LOG_PRE + "connect fail", e); 75 | throw new ButterflyException("connect fail"); 76 | } 77 | return zookeeperClient; 78 | } 79 | 80 | public ZookeeperClient reconnect() { 81 | return connect(connectString); 82 | } 83 | 84 | /** 85 | * 注册在连接断开或者会话超时时候的回调 86 | * 87 | * @param disconnectCallback 断开的回调 88 | * @return zk客户端 89 | */ 90 | public ZookeeperClient registerDisconnectCallback(Runnable disconnectCallback) { 91 | this.disconnectCallback = disconnectCallback; 92 | return this; 93 | } 94 | 95 | public ZookeeperClient registerConnectSuccessCallback(Runnable connectSuccessCallback) { 96 | this.connectSuccessCallback = connectSuccessCallback; 97 | return this; 98 | } 99 | 100 | /** 101 | * 添加永久节点 102 | * 103 | * @param nodePath 节点路径 104 | * @param data 节点数据 105 | * @return zk的客户端类 106 | */ 107 | public Boolean addPersistentNode(String nodePath, String data) { 108 | return createNode(nodePath, data, CreateMode.PERSISTENT); 109 | } 110 | 111 | /** 112 | * 添加永久临时节点 113 | * 114 | * @param nodePath 父级节点路径 115 | * @param data 节点数据 116 | * @return 新生成的节点路径 117 | */ 118 | public Boolean addPersistentSeqNode(String nodePath, String data) { 119 | return createNode(nodePath, data, CreateMode.PERSISTENT_SEQUENTIAL); 120 | } 121 | 122 | /** 123 | * 添加临时节点 124 | * 125 | * @param nodePath 节点路径 126 | * @param data 节点数据 127 | * @return 新生成的节点路径 128 | */ 129 | public Boolean addEphemeralNode(String nodePath, String data) { 130 | return createNode(nodePath, data, CreateMode.EPHEMERAL); 131 | } 132 | 133 | /** 134 | * 添加临时节点 135 | * 136 | * @param nodePath 节点路径 137 | * @param data 节点数据 138 | * @return 新生成的节点路径 139 | */ 140 | public Boolean addEphemeralSeqNode(String nodePath, String data) { 141 | return createNode(nodePath, data, CreateMode.EPHEMERAL_SEQUENTIAL); 142 | } 143 | 144 | /** 145 | * 添加永久节点 146 | * 147 | * @param nodePath 节点路径 148 | * @return zk的客户端类 149 | */ 150 | public Boolean addPersistentNode(String nodePath) { 151 | return createNode(nodePath, "", CreateMode.PERSISTENT); 152 | } 153 | 154 | /** 155 | * 添加永久临时节点 156 | * 157 | * @param nodePath 父级节点路径 158 | * @return 新生成的节点路径 159 | */ 160 | public Boolean addPersistentSeqNode(String nodePath) { 161 | return createNode(nodePath, "", CreateMode.PERSISTENT_SEQUENTIAL); 162 | } 163 | 164 | /** 165 | * 添加临时节点 166 | * 167 | * @param nodePath 节点路径 168 | * @return 新生成的节点路径 169 | */ 170 | public Boolean addEphemeralNode(String nodePath) { 171 | return createNode(nodePath, "", CreateMode.EPHEMERAL); 172 | } 173 | 174 | /** 175 | * 添加临时节点 176 | * 177 | * @param nodePath 节点路径 178 | * @return 新生成的节点路径 179 | */ 180 | public Boolean addEphemeralSeqNode(String nodePath) { 181 | return createNode(nodePath, "", CreateMode.EPHEMERAL_SEQUENTIAL); 182 | } 183 | 184 | /** 185 | * 循环创建永久节点 186 | * 187 | * @param nodePath 节点路径 188 | * @return 结果 189 | */ 190 | public Boolean addPersistentNodeWithRecurse(String nodePath) { 191 | List nodeList = Stream.of(nodePath.split("/")).filter(d -> !strIsEmpty(d)).collect(Collectors.toList()); 192 | 193 | String path = ""; 194 | for (String node : nodeList) { 195 | path += "/" + node; 196 | if (!nodeExist(path)) { 197 | addPersistentNode(path); 198 | } 199 | } 200 | return true; 201 | } 202 | 203 | /** 204 | * 删除节点 205 | * 206 | * @param nodePath 节点路径 207 | */ 208 | public void deleteNode(String nodePath) { 209 | try { 210 | if (nodeExist(nodePath)) { 211 | this.zookeeper.delete(nodePath, -1); 212 | log.info(ZK_LOG_PRE + "node({}) delete success", nodePath); 213 | } else { 214 | log.warn(ZK_LOG_PRE + "node({}) not exist, no need to delete", nodePath); 215 | } 216 | } catch (InterruptedException | KeeperException e) { 217 | log.error(ZK_LOG_PRE + "delete node (" + nodePath + ") fail", e); 218 | throw new RuntimeException("delete node (" + nodePath + ") fail"); 219 | } 220 | } 221 | 222 | /** 223 | * 循环删除节点以及子节点 224 | * 225 | * @param nodePath 路径 226 | */ 227 | public void deleteNodeCycle(String nodePath) { 228 | List pathList = getChildrenPathList(nodePath); 229 | if (!pathList.isEmpty()) { 230 | pathList.forEach(this::deleteNodeCycle); 231 | } 232 | deleteNode(nodePath); 233 | } 234 | 235 | /** 236 | * 修改zk中的数据 237 | * 238 | * @param nodePath 节点的全路径 239 | * @param data 对应的数据 240 | */ 241 | public void writeNodeData(String nodePath, String data) { 242 | if (null != nodePath) { 243 | try { 244 | this.zookeeper.setData(nodePath, data.getBytes(), -1); 245 | } catch (KeeperException | InterruptedException e) { 246 | log.warn(ZK_LOG_PRE + "write data({}) to node({}) fail", data, nodePath); 247 | throw new RuntimeException("write data(" + data + ") to node(" + nodePath + ") fail"); 248 | } 249 | } 250 | } 251 | 252 | public class CallbackWatcher implements Watcher { 253 | 254 | @Override 255 | public void process(WatchedEvent event) { 256 | log.info(ZK_LOG_PRE + "---------------------start-------------------"); 257 | Event.KeeperState state = event.getState(); 258 | Event.EventType type = event.getType(); 259 | String path = event.getPath(); 260 | 261 | log.info(ZK_LOG_PRE + "receive Watcher notify"); 262 | log.info(ZK_LOG_PRE + "connect status:\t" + state.toString()); 263 | log.info(ZK_LOG_PRE + "event type:\t" + type.toString()); 264 | if (Event.KeeperState.SyncConnected == state) { 265 | // 成功连接上ZK服务器 266 | if (Event.EventType.None == type) { 267 | log.info(ZK_LOG_PRE + "connect zookeeper server success"); 268 | phaser.arriveAndDeregister(); 269 | if (null != connectSuccessCallback) { 270 | connectSuccessCallback.run(); 271 | } 272 | } 273 | } else if (Event.KeeperState.Disconnected == state) { 274 | log.error(ZK_LOG_PRE + "disconnect to zookeeper server", new ButterflyException("disconnect to zookeeper server")); 275 | if (null != disconnectCallback) { 276 | disconnectCallback.run(); 277 | } 278 | } else if (Event.KeeperState.Expired == state) { 279 | log.error(ZK_LOG_PRE + "session expire,ready to reconnect"); 280 | if (null != disconnectCallback) { 281 | disconnectCallback.run(); 282 | } 283 | } 284 | log.info(ZK_LOG_PRE + "---------------------end-------------------"); 285 | } 286 | } 287 | 288 | /** 289 | * 关闭ZK连接 290 | */ 291 | public void close() { 292 | if (null != this.zookeeper) { 293 | try { 294 | this.zookeeper.close(); 295 | } catch (Exception e) { 296 | log.error(ZK_LOG_PRE + "close connect fail"); 297 | } 298 | } 299 | } 300 | 301 | /** 302 | * 获取对应路径的子节点名称列表 303 | * 304 | * @param path 路径 305 | * @return 节点路径 306 | */ 307 | public List getChildrenPathList(String path) { 308 | try { 309 | return zookeeper.getChildren(path, false).stream().map(r -> path + "/" + r).collect(Collectors.toList()); 310 | } catch (KeeperException | InterruptedException e) { 311 | log.error(ZK_LOG_PRE + "read path(" + path + ")'child path fail"); 312 | } 313 | return Collections.emptyList(); 314 | } 315 | 316 | /** 317 | * 获取对应路径的子节点名称列表 318 | * 319 | * @param path 路径 320 | * @return 节点路径 321 | */ 322 | public List getChildrenNameList(String path) { 323 | try { 324 | return new ArrayList<>(zookeeper.getChildren(path, false)); 325 | } catch (KeeperException | InterruptedException e) { 326 | log.error(ZK_LOG_PRE + "read path(" + path + ")'child path name fail"); 327 | } 328 | return Collections.emptyList(); 329 | } 330 | 331 | /** 332 | * 读取指定节点数据内容 333 | * 334 | * @param path 路径 335 | * @return 节点数据 336 | */ 337 | public String readData(String path) { 338 | try { 339 | if (null != zookeeper.exists(path, false)) { 340 | return new String(this.zookeeper.getData(path, false, null)); 341 | } 342 | return null; 343 | } catch (KeeperException e) { 344 | log.error(ZK_LOG_PRE + "read data fail,path: " + path); 345 | return null; 346 | } catch (InterruptedException e) { 347 | log.error(ZK_LOG_PRE + "read data fail,path: " + path); 348 | Thread.currentThread().interrupt(); 349 | return null; 350 | } 351 | } 352 | 353 | public T readDataJson(String path, Class tClass) { 354 | String json = readData(path); 355 | if (null != json) { 356 | return JSON.parseObject(json, tClass); 357 | } 358 | return null; 359 | } 360 | 361 | /** 362 | * 判断节点是否存在 363 | * 364 | * @param nodePath 节点的全路径 365 | * @return 节点是否存在 366 | */ 367 | public Boolean nodeExist(String nodePath) { 368 | try { 369 | return null != this.zookeeper.exists(nodePath, false); 370 | } catch (KeeperException | InterruptedException e) { 371 | log.error(ZK_LOG_PRE + "judge node exist fail", e); 372 | } 373 | return false; 374 | } 375 | 376 | /** 377 | * 分布式锁 378 | * 379 | * @param lockPath 锁路径 380 | * @param callable 加锁成功后的处理 381 | * @param 类型 382 | * @return 对象 383 | */ 384 | public T distributeLock(String lockPath, Callable callable) { 385 | try { 386 | // 添加分布式锁 387 | if (!addEphemeralNode(lockPath)) { 388 | throw new RuntimeException("加锁失败"); 389 | } 390 | 391 | return callable.call(); 392 | } catch (Exception e) { 393 | throw new RuntimeException("执行异常", e); 394 | } finally { 395 | if (nodeExist(lockPath)) { 396 | deleteNode(lockPath); 397 | } 398 | } 399 | } 400 | 401 | /** 402 | * 分布式锁 403 | * 404 | * @param lockPath 锁路径 405 | * @param runnable 加锁成功后的处理 406 | */ 407 | public void distributeLock(String lockPath, Runnable runnable) { 408 | try { 409 | // 添加分布式锁 410 | if (!addEphemeralNode(lockPath)) { 411 | throw new RuntimeException("加锁失败"); 412 | } 413 | 414 | runnable.run(); 415 | } catch (Exception e) { 416 | throw new RuntimeException("执行异常", e); 417 | } finally { 418 | if (nodeExist(lockPath)) { 419 | deleteNode(lockPath); 420 | } 421 | } 422 | } 423 | 424 | /** 425 | * 给这个节点中创建临时节点 426 | */ 427 | private Boolean createNode(String node, String data, CreateMode createMode) { 428 | try { 429 | if (null == this.zookeeper.exists(node, false)) { 430 | String realPath = zookeeper.create(node, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, createMode); 431 | log.info(ZK_LOG_PRE + "node create success, Path: " + realPath); 432 | return true; 433 | } else { 434 | log.info(ZK_LOG_PRE + "node(" + node + ") has existed"); 435 | return false; 436 | } 437 | } catch (KeeperException e) { 438 | log.error(ZK_LOG_PRE + "node(" + node + ") create fail"); 439 | } catch (InterruptedException e) { 440 | log.error(ZK_LOG_PRE + "node(" + node + ") create fail"); 441 | Thread.currentThread().interrupt(); 442 | } 443 | return false; 444 | } 445 | 446 | private Boolean strIsEmpty(String str) { 447 | return null == str || "".equals(str); 448 | } 449 | } 450 | --------------------------------------------------------------------------------