From ca58b0c2fa988bd3b971c7f6b09f3af8fca16eaa Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 4 Sep 2023 17:16:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9F=BA=E4=BA=8ESnowflake=E7=AE=97?= =?UTF-8?q?=E6=B3=95=E7=94=9F=E6=88=90=E6=9C=89=E5=BA=8F=E6=95=B0=E5=AD=97?= =?UTF-8?q?UID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 参考百度uid-generator进行改造符合我们自身的需求 --- .../io/metersphere/sdk/domain/WorkerNode.java | 125 ++++ .../sdk/domain/WorkerNodeExample.java | 640 ++++++++++++++++++ .../migration/3.0.0/ddl/V3.0.0_2__sdk_ddl.sql | 15 + .../sdk/mapper/BaseWorkerNodeMapper.java | 7 + .../sdk/mapper/BaseWorkerNodeMapper.xml | 9 + .../io/metersphere/sdk/uid/BitsAllocator.java | 115 ++++ .../java/io/metersphere/sdk/uid/UUID.java | 26 + .../sdk/uid/buffer/BufferPaddingExecutor.java | 168 +++++ .../sdk/uid/buffer/BufferedUidProvider.java | 16 + .../uid/buffer/RejectedPutBufferHandler.java | 15 + .../uid/buffer/RejectedTakeBufferHandler.java | 15 + .../sdk/uid/buffer/RingBuffer.java | 251 +++++++ .../sdk/uid/impl/CachedUidGenerator.java | 147 ++++ .../sdk/uid/impl/DefaultUidGenerator.java | 179 +++++ .../sdk/uid/utils/DockerUtils.java | 90 +++ .../metersphere/sdk/uid/utils/EnumUtils.java | 39 ++ .../sdk/uid/utils/NamingThreadFactory.java | 148 ++++ .../metersphere/sdk/uid/utils/NetUtils.java | 70 ++ .../sdk/uid/utils/PaddedAtomicLong.java | 38 ++ .../metersphere/sdk/uid/utils/TimeUtils.java | 53 ++ .../metersphere/sdk/uid/utils/ValuedEnum.java | 11 + .../worker/DisposableWorkerIdAssigner.java | 66 ++ .../sdk/uid/worker/WorkerIdAssigner.java | 18 + .../sdk/uid/worker/WorkerNodeType.java | 33 + .../io/metersphere/sdk/util/LogUtils.java | 14 +- 25 files changed, 2302 insertions(+), 6 deletions(-) create mode 100644 backend/framework/domain/src/main/java/io/metersphere/sdk/domain/WorkerNode.java create mode 100644 backend/framework/domain/src/main/java/io/metersphere/sdk/domain/WorkerNodeExample.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/mapper/BaseWorkerNodeMapper.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/mapper/BaseWorkerNodeMapper.xml create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/BitsAllocator.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/UUID.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/BufferPaddingExecutor.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/BufferedUidProvider.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RejectedPutBufferHandler.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RejectedTakeBufferHandler.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RingBuffer.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/impl/CachedUidGenerator.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/impl/DefaultUidGenerator.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/DockerUtils.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/EnumUtils.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/NamingThreadFactory.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/NetUtils.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/PaddedAtomicLong.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/TimeUtils.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/ValuedEnum.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/DisposableWorkerIdAssigner.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/WorkerIdAssigner.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/WorkerNodeType.java diff --git a/backend/framework/domain/src/main/java/io/metersphere/sdk/domain/WorkerNode.java b/backend/framework/domain/src/main/java/io/metersphere/sdk/domain/WorkerNode.java new file mode 100644 index 0000000000..a8c2fc6d77 --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/sdk/domain/WorkerNode.java @@ -0,0 +1,125 @@ +package io.metersphere.sdk.domain; + +import io.metersphere.validation.groups.Created; +import io.metersphere.validation.groups.Updated; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; + +@Data +public class WorkerNode implements Serializable { + @Schema(description = "auto increment id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{worker_node.id.not_blank}", groups = {Updated.class}) + private Long id; + + @Schema(description = "host name", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{worker_node.host_name.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 64, message = "{worker_node.host_name.length_range}", groups = {Created.class, Updated.class}) + private String hostName; + + @Schema(description = "port", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{worker_node.port.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 64, message = "{worker_node.port.length_range}", groups = {Created.class, Updated.class}) + private String port; + + @Schema(description = "node type: ACTUAL or CONTAINER", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{worker_node.type.not_blank}", groups = {Created.class}) + private Integer type; + + @Schema(description = "launch date", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{worker_node.launch_date.not_blank}", groups = {Created.class}) + private Long launchDate; + + @Schema(description = "modified time", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{worker_node.modified.not_blank}", groups = {Created.class}) + private Long modified; + + @Schema(description = "created time", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{worker_node.created.not_blank}", groups = {Created.class}) + private Long created; + + private static final long serialVersionUID = 1L; + + public enum Column { + id("id", "id", "BIGINT", false), + hostName("host_name", "hostName", "VARCHAR", false), + port("port", "port", "VARCHAR", false), + type("type", "type", "INTEGER", true), + launchDate("launch_date", "launchDate", "BIGINT", false), + modified("modified", "modified", "BIGINT", false), + created("created", "created", "BIGINT", false); + + private static final String BEGINNING_DELIMITER = "`"; + + private static final String ENDING_DELIMITER = "`"; + + private final String column; + + private final boolean isColumnNameDelimited; + + private final String javaProperty; + + private final String jdbcType; + + public String value() { + return this.column; + } + + public String getValue() { + return this.column; + } + + public String getJavaProperty() { + return this.javaProperty; + } + + public String getJdbcType() { + return this.jdbcType; + } + + Column(String column, String javaProperty, String jdbcType, boolean isColumnNameDelimited) { + this.column = column; + this.javaProperty = javaProperty; + this.jdbcType = jdbcType; + this.isColumnNameDelimited = isColumnNameDelimited; + } + + public String desc() { + return this.getEscapedColumnName() + " DESC"; + } + + public String asc() { + return this.getEscapedColumnName() + " ASC"; + } + + public static Column[] excludes(Column ... excludes) { + ArrayList columns = new ArrayList<>(Arrays.asList(Column.values())); + if (excludes != null && excludes.length > 0) { + columns.removeAll(new ArrayList<>(Arrays.asList(excludes))); + } + return columns.toArray(new Column[]{}); + } + + public static Column[] all() { + return Column.values(); + } + + public String getEscapedColumnName() { + if (this.isColumnNameDelimited) { + return new StringBuilder().append(BEGINNING_DELIMITER).append(this.column).append(ENDING_DELIMITER).toString(); + } else { + return this.column; + } + } + + public String getAliasedEscapedColumnName() { + return this.getEscapedColumnName(); + } + } +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/sdk/domain/WorkerNodeExample.java b/backend/framework/domain/src/main/java/io/metersphere/sdk/domain/WorkerNodeExample.java new file mode 100644 index 0000000000..7494df3397 --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/sdk/domain/WorkerNodeExample.java @@ -0,0 +1,640 @@ +package io.metersphere.sdk.domain; + +import java.util.ArrayList; +import java.util.List; + +public class WorkerNodeExample { + protected String orderByClause; + + protected boolean distinct; + + protected List oredCriteria; + + public WorkerNodeExample() { + oredCriteria = new ArrayList(); + } + + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; + } + + public String getOrderByClause() { + return orderByClause; + } + + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + + public boolean isDistinct() { + return distinct; + } + + public List getOredCriteria() { + return oredCriteria; + } + + public void or(Criteria criteria) { + oredCriteria.add(criteria); + } + + public Criteria or() { + Criteria criteria = createCriteriaInternal(); + oredCriteria.add(criteria); + return criteria; + } + + public Criteria createCriteria() { + Criteria criteria = createCriteriaInternal(); + if (oredCriteria.size() == 0) { + oredCriteria.add(criteria); + } + return criteria; + } + + protected Criteria createCriteriaInternal() { + Criteria criteria = new Criteria(); + return criteria; + } + + public void clear() { + oredCriteria.clear(); + orderByClause = null; + distinct = false; + } + + protected abstract static class GeneratedCriteria { + protected List criteria; + + protected GeneratedCriteria() { + super(); + criteria = new ArrayList(); + } + + public boolean isValid() { + return criteria.size() > 0; + } + + public List getAllCriteria() { + return criteria; + } + + public List getCriteria() { + return criteria; + } + + protected void addCriterion(String condition) { + if (condition == null) { + throw new RuntimeException("Value for condition cannot be null"); + } + criteria.add(new Criterion(condition)); + } + + protected void addCriterion(String condition, Object value, String property) { + if (value == null) { + throw new RuntimeException("Value for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value)); + } + + protected void addCriterion(String condition, Object value1, Object value2, String property) { + if (value1 == null || value2 == null) { + throw new RuntimeException("Between values for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value1, value2)); + } + + public Criteria andIdIsNull() { + addCriterion("id is null"); + return (Criteria) this; + } + + public Criteria andIdIsNotNull() { + addCriterion("id is not null"); + return (Criteria) this; + } + + public Criteria andIdEqualTo(Long value) { + addCriterion("id =", value, "id"); + return (Criteria) this; + } + + public Criteria andIdNotEqualTo(Long value) { + addCriterion("id <>", value, "id"); + return (Criteria) this; + } + + public Criteria andIdGreaterThan(Long value) { + addCriterion("id >", value, "id"); + return (Criteria) this; + } + + public Criteria andIdGreaterThanOrEqualTo(Long value) { + addCriterion("id >=", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLessThan(Long value) { + addCriterion("id <", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLessThanOrEqualTo(Long value) { + addCriterion("id <=", value, "id"); + return (Criteria) this; + } + + public Criteria andIdIn(List values) { + addCriterion("id in", values, "id"); + return (Criteria) this; + } + + public Criteria andIdNotIn(List values) { + addCriterion("id not in", values, "id"); + return (Criteria) this; + } + + public Criteria andIdBetween(Long value1, Long value2) { + addCriterion("id between", value1, value2, "id"); + return (Criteria) this; + } + + public Criteria andIdNotBetween(Long value1, Long value2) { + addCriterion("id not between", value1, value2, "id"); + return (Criteria) this; + } + + public Criteria andHostNameIsNull() { + addCriterion("host_name is null"); + return (Criteria) this; + } + + public Criteria andHostNameIsNotNull() { + addCriterion("host_name is not null"); + return (Criteria) this; + } + + public Criteria andHostNameEqualTo(String value) { + addCriterion("host_name =", value, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameNotEqualTo(String value) { + addCriterion("host_name <>", value, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameGreaterThan(String value) { + addCriterion("host_name >", value, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameGreaterThanOrEqualTo(String value) { + addCriterion("host_name >=", value, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameLessThan(String value) { + addCriterion("host_name <", value, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameLessThanOrEqualTo(String value) { + addCriterion("host_name <=", value, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameLike(String value) { + addCriterion("host_name like", value, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameNotLike(String value) { + addCriterion("host_name not like", value, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameIn(List values) { + addCriterion("host_name in", values, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameNotIn(List values) { + addCriterion("host_name not in", values, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameBetween(String value1, String value2) { + addCriterion("host_name between", value1, value2, "hostName"); + return (Criteria) this; + } + + public Criteria andHostNameNotBetween(String value1, String value2) { + addCriterion("host_name not between", value1, value2, "hostName"); + return (Criteria) this; + } + + public Criteria andPortIsNull() { + addCriterion("port is null"); + return (Criteria) this; + } + + public Criteria andPortIsNotNull() { + addCriterion("port is not null"); + return (Criteria) this; + } + + public Criteria andPortEqualTo(String value) { + addCriterion("port =", value, "port"); + return (Criteria) this; + } + + public Criteria andPortNotEqualTo(String value) { + addCriterion("port <>", value, "port"); + return (Criteria) this; + } + + public Criteria andPortGreaterThan(String value) { + addCriterion("port >", value, "port"); + return (Criteria) this; + } + + public Criteria andPortGreaterThanOrEqualTo(String value) { + addCriterion("port >=", value, "port"); + return (Criteria) this; + } + + public Criteria andPortLessThan(String value) { + addCriterion("port <", value, "port"); + return (Criteria) this; + } + + public Criteria andPortLessThanOrEqualTo(String value) { + addCriterion("port <=", value, "port"); + return (Criteria) this; + } + + public Criteria andPortLike(String value) { + addCriterion("port like", value, "port"); + return (Criteria) this; + } + + public Criteria andPortNotLike(String value) { + addCriterion("port not like", value, "port"); + return (Criteria) this; + } + + public Criteria andPortIn(List values) { + addCriterion("port in", values, "port"); + return (Criteria) this; + } + + public Criteria andPortNotIn(List values) { + addCriterion("port not in", values, "port"); + return (Criteria) this; + } + + public Criteria andPortBetween(String value1, String value2) { + addCriterion("port between", value1, value2, "port"); + return (Criteria) this; + } + + public Criteria andPortNotBetween(String value1, String value2) { + addCriterion("port not between", value1, value2, "port"); + return (Criteria) this; + } + + public Criteria andTypeIsNull() { + addCriterion("`type` is null"); + return (Criteria) this; + } + + public Criteria andTypeIsNotNull() { + addCriterion("`type` is not null"); + return (Criteria) this; + } + + public Criteria andTypeEqualTo(Integer value) { + addCriterion("`type` =", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeNotEqualTo(Integer value) { + addCriterion("`type` <>", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeGreaterThan(Integer value) { + addCriterion("`type` >", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeGreaterThanOrEqualTo(Integer value) { + addCriterion("`type` >=", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeLessThan(Integer value) { + addCriterion("`type` <", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeLessThanOrEqualTo(Integer value) { + addCriterion("`type` <=", value, "type"); + return (Criteria) this; + } + + public Criteria andTypeIn(List values) { + addCriterion("`type` in", values, "type"); + return (Criteria) this; + } + + public Criteria andTypeNotIn(List values) { + addCriterion("`type` not in", values, "type"); + return (Criteria) this; + } + + public Criteria andTypeBetween(Integer value1, Integer value2) { + addCriterion("`type` between", value1, value2, "type"); + return (Criteria) this; + } + + public Criteria andTypeNotBetween(Integer value1, Integer value2) { + addCriterion("`type` not between", value1, value2, "type"); + return (Criteria) this; + } + + public Criteria andLaunchDateIsNull() { + addCriterion("launch_date is null"); + return (Criteria) this; + } + + public Criteria andLaunchDateIsNotNull() { + addCriterion("launch_date is not null"); + return (Criteria) this; + } + + public Criteria andLaunchDateEqualTo(Long value) { + addCriterion("launch_date =", value, "launchDate"); + return (Criteria) this; + } + + public Criteria andLaunchDateNotEqualTo(Long value) { + addCriterion("launch_date <>", value, "launchDate"); + return (Criteria) this; + } + + public Criteria andLaunchDateGreaterThan(Long value) { + addCriterion("launch_date >", value, "launchDate"); + return (Criteria) this; + } + + public Criteria andLaunchDateGreaterThanOrEqualTo(Long value) { + addCriterion("launch_date >=", value, "launchDate"); + return (Criteria) this; + } + + public Criteria andLaunchDateLessThan(Long value) { + addCriterion("launch_date <", value, "launchDate"); + return (Criteria) this; + } + + public Criteria andLaunchDateLessThanOrEqualTo(Long value) { + addCriterion("launch_date <=", value, "launchDate"); + return (Criteria) this; + } + + public Criteria andLaunchDateIn(List values) { + addCriterion("launch_date in", values, "launchDate"); + return (Criteria) this; + } + + public Criteria andLaunchDateNotIn(List values) { + addCriterion("launch_date not in", values, "launchDate"); + return (Criteria) this; + } + + public Criteria andLaunchDateBetween(Long value1, Long value2) { + addCriterion("launch_date between", value1, value2, "launchDate"); + return (Criteria) this; + } + + public Criteria andLaunchDateNotBetween(Long value1, Long value2) { + addCriterion("launch_date not between", value1, value2, "launchDate"); + return (Criteria) this; + } + + public Criteria andModifiedIsNull() { + addCriterion("modified is null"); + return (Criteria) this; + } + + public Criteria andModifiedIsNotNull() { + addCriterion("modified is not null"); + return (Criteria) this; + } + + public Criteria andModifiedEqualTo(Long value) { + addCriterion("modified =", value, "modified"); + return (Criteria) this; + } + + public Criteria andModifiedNotEqualTo(Long value) { + addCriterion("modified <>", value, "modified"); + return (Criteria) this; + } + + public Criteria andModifiedGreaterThan(Long value) { + addCriterion("modified >", value, "modified"); + return (Criteria) this; + } + + public Criteria andModifiedGreaterThanOrEqualTo(Long value) { + addCriterion("modified >=", value, "modified"); + return (Criteria) this; + } + + public Criteria andModifiedLessThan(Long value) { + addCriterion("modified <", value, "modified"); + return (Criteria) this; + } + + public Criteria andModifiedLessThanOrEqualTo(Long value) { + addCriterion("modified <=", value, "modified"); + return (Criteria) this; + } + + public Criteria andModifiedIn(List values) { + addCriterion("modified in", values, "modified"); + return (Criteria) this; + } + + public Criteria andModifiedNotIn(List values) { + addCriterion("modified not in", values, "modified"); + return (Criteria) this; + } + + public Criteria andModifiedBetween(Long value1, Long value2) { + addCriterion("modified between", value1, value2, "modified"); + return (Criteria) this; + } + + public Criteria andModifiedNotBetween(Long value1, Long value2) { + addCriterion("modified not between", value1, value2, "modified"); + return (Criteria) this; + } + + public Criteria andCreatedIsNull() { + addCriterion("created is null"); + return (Criteria) this; + } + + public Criteria andCreatedIsNotNull() { + addCriterion("created is not null"); + return (Criteria) this; + } + + public Criteria andCreatedEqualTo(Long value) { + addCriterion("created =", value, "created"); + return (Criteria) this; + } + + public Criteria andCreatedNotEqualTo(Long value) { + addCriterion("created <>", value, "created"); + return (Criteria) this; + } + + public Criteria andCreatedGreaterThan(Long value) { + addCriterion("created >", value, "created"); + return (Criteria) this; + } + + public Criteria andCreatedGreaterThanOrEqualTo(Long value) { + addCriterion("created >=", value, "created"); + return (Criteria) this; + } + + public Criteria andCreatedLessThan(Long value) { + addCriterion("created <", value, "created"); + return (Criteria) this; + } + + public Criteria andCreatedLessThanOrEqualTo(Long value) { + addCriterion("created <=", value, "created"); + return (Criteria) this; + } + + public Criteria andCreatedIn(List values) { + addCriterion("created in", values, "created"); + return (Criteria) this; + } + + public Criteria andCreatedNotIn(List values) { + addCriterion("created not in", values, "created"); + return (Criteria) this; + } + + public Criteria andCreatedBetween(Long value1, Long value2) { + addCriterion("created between", value1, value2, "created"); + return (Criteria) this; + } + + public Criteria andCreatedNotBetween(Long value1, Long value2) { + addCriterion("created not between", value1, value2, "created"); + return (Criteria) this; + } + } + + public static class Criteria extends GeneratedCriteria { + + protected Criteria() { + super(); + } + } + + public static class Criterion { + private String condition; + + private Object value; + + private Object secondValue; + + private boolean noValue; + + private boolean singleValue; + + private boolean betweenValue; + + private boolean listValue; + + private String typeHandler; + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } + + protected Criterion(String condition) { + super(); + this.condition = condition; + this.typeHandler = null; + this.noValue = true; + } + + protected Criterion(String condition, Object value, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.typeHandler = typeHandler; + if (value instanceof List) { + this.listValue = true; + } else { + this.singleValue = true; + } + } + + protected Criterion(String condition, Object value) { + this(condition, value, null); + } + + protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.secondValue = secondValue; + this.typeHandler = typeHandler; + this.betweenValue = true; + } + + protected Criterion(String condition, Object value, Object secondValue) { + this(condition, value, secondValue, null); + } + } +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_2__sdk_ddl.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_2__sdk_ddl.sql index 49ee4e6cb7..422086d535 100644 --- a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_2__sdk_ddl.sql +++ b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_2__sdk_ddl.sql @@ -115,6 +115,21 @@ CREATE TABLE IF NOT EXISTS project_parameters( CREATE INDEX idx_project_id ON project_parameters(project_id); +DROP TABLE IF EXISTS worker_node; +CREATE TABLE worker_node +( + id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'auto increment id', + host_name VARCHAR(64) NOT NULL COMMENT 'host name', + port VARCHAR(64) NOT NULL COMMENT 'port', + type INT NOT NULL COMMENT 'node type: ACTUAL or CONTAINER', + launch_date BIGINT NOT NULL COMMENT 'launch date', + modified BIGINT NOT NULL COMMENT 'modified time', + created BIGINT NOT NULL COMMENT 'created time', + PRIMARY KEY(ID) +) + ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci COMMENT = 'DB WorkerID Assigner for UID Generator'; -- set innodb lock wait timeout to default SET SESSION innodb_lock_wait_timeout = DEFAULT; diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/mapper/BaseWorkerNodeMapper.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/mapper/BaseWorkerNodeMapper.java new file mode 100644 index 0000000000..0ac4cdb0c3 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/mapper/BaseWorkerNodeMapper.java @@ -0,0 +1,7 @@ +package io.metersphere.sdk.mapper; + +import io.metersphere.sdk.domain.WorkerNode; + +public interface BaseWorkerNodeMapper { + int insert(WorkerNode record); +} \ No newline at end of file diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/mapper/BaseWorkerNodeMapper.xml b/backend/framework/sdk/src/main/java/io/metersphere/sdk/mapper/BaseWorkerNodeMapper.xml new file mode 100644 index 0000000000..7461a19492 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/mapper/BaseWorkerNodeMapper.xml @@ -0,0 +1,9 @@ + + + + + + insert into worker_node (host_name, port, type, launch_date, modified, created) + values (#{hostName,jdbcType=VARCHAR}, #{port,jdbcType=VARCHAR}, #{type,jdbcType=INTEGER}, #{launchDate,jdbcType=BIGINT}, #{modified,jdbcType=BIGINT}, #{created,jdbcType=BIGINT}) + + \ No newline at end of file diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/BitsAllocator.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/BitsAllocator.java new file mode 100644 index 0000000000..24d5cc871d --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/BitsAllocator.java @@ -0,0 +1,115 @@ + +package io.metersphere.sdk.uid; + +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.springframework.util.Assert; + +/** + * Allocate 64 bits for the UID(long)
+ * sign (fixed 1bit) -> deltaSecond -> workerId -> sequence(within the same second) + **/ +public class BitsAllocator { + /** + * Total 64 bits + */ + public static final int TOTAL_BITS = 1 << 6; + + /** + * Bits for [sign-> second-> workId-> sequence] + */ + private int signBits = 1; + private final int timestampBits; + private final int workerIdBits; + private final int sequenceBits; + + /** + * Max value for workId & sequence + */ + private final long maxDeltaSeconds; + private final long maxWorkerId; + private final long maxSequence; + + /** + * Shift for timestamp & workerId + */ + private final int timestampShift; + private final int workerIdShift; + + /** + * Constructor with timestampBits, workerIdBits, sequenceBits
+ * The highest bit used for sign, so 63 bits for timestampBits, workerIdBits, sequenceBits + */ + public BitsAllocator(int timestampBits, int workerIdBits, int sequenceBits) { + // make sure allocated 64 bits + int allocateTotalBits = signBits + timestampBits + workerIdBits + sequenceBits; + Assert.isTrue(allocateTotalBits == TOTAL_BITS, "allocate not enough 64 bits"); + + // initialize bits + this.timestampBits = timestampBits; + this.workerIdBits = workerIdBits; + this.sequenceBits = sequenceBits; + + // initialize max value + this.maxDeltaSeconds = ~(-1L << timestampBits); + this.maxWorkerId = ~(-1L << workerIdBits); + this.maxSequence = ~(-1L << sequenceBits); + + // initialize shift + this.timestampShift = workerIdBits + sequenceBits; + this.workerIdShift = sequenceBits; + } + + /** + * Allocate bits for UID according to delta seconds & workerId & sequence
+ * Note that: The highest bit will always be 0 for sign + */ + public long allocate(long deltaSeconds, long workerId, long sequence) { + return (deltaSeconds << timestampShift) | (workerId << workerIdShift) | sequence; + } + + /** + * Getters + */ + public int getSignBits() { + return signBits; + } + + public int getTimestampBits() { + return timestampBits; + } + + public int getWorkerIdBits() { + return workerIdBits; + } + + public int getSequenceBits() { + return sequenceBits; + } + + public long getMaxDeltaSeconds() { + return maxDeltaSeconds; + } + + public long getMaxWorkerId() { + return maxWorkerId; + } + + public long getMaxSequence() { + return maxSequence; + } + + public int getTimestampShift() { + return timestampShift; + } + + public int getWorkerIdShift() { + return workerIdShift; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } + +} \ No newline at end of file diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/UUID.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/UUID.java new file mode 100644 index 0000000000..00a398a6df --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/UUID.java @@ -0,0 +1,26 @@ +package io.metersphere.sdk.uid; + +import io.metersphere.sdk.uid.impl.CachedUidGenerator; +import io.metersphere.sdk.util.CommonBeanFactory; + +public class UUID { + private static final CachedUidGenerator DEFAULT_UID_GENERATOR; + + static { + DEFAULT_UID_GENERATOR = CommonBeanFactory.getBean(CachedUidGenerator.class); + } + + /** + * 生成一个唯一的数字 + */ + public static Long randomUUID() { + return DEFAULT_UID_GENERATOR.getUID(); + } + + /** + * 生成一个唯一的字符串 + */ + public static String nextStr() { + return String.valueOf(DEFAULT_UID_GENERATOR.getUID()); + } +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/BufferPaddingExecutor.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/BufferPaddingExecutor.java new file mode 100644 index 0000000000..211920cef6 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/BufferPaddingExecutor.java @@ -0,0 +1,168 @@ + +package io.metersphere.sdk.uid.buffer; + +import io.metersphere.sdk.uid.utils.NamingThreadFactory; +import io.metersphere.sdk.uid.utils.PaddedAtomicLong; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Represents an executor for padding {@link RingBuffer}
+ * There are two kinds of executors: one for scheduled padding, the other for padding immediately. + */ +public class BufferPaddingExecutor { + private static final Logger LOGGER = LoggerFactory.getLogger(RingBuffer.class); + + /** + * Constants + */ + private static final String WORKER_NAME = "RingBuffer-Padding-Worker"; + private static final String SCHEDULE_NAME = "RingBuffer-Padding-Schedule"; + private static final long DEFAULT_SCHEDULE_INTERVAL = 5 * 60L; // 5 minutes + + /** + * Whether buffer padding is running + */ + private final AtomicBoolean running; + + /** + * We can borrow UIDs from the future, here store the last second we have consumed + */ + private final PaddedAtomicLong lastSecond; + + /** + * RingBuffer & BufferUidProvider + */ + private final RingBuffer ringBuffer; + private final BufferedUidProvider uidProvider; + + /** + * Padding immediately by the thread pool + */ + private final ExecutorService bufferPadExecutors; + /** + * Padding schedule thread + */ + private final ScheduledExecutorService bufferPadSchedule; + + /** + * Schedule interval Unit as seconds + */ + private long scheduleInterval = DEFAULT_SCHEDULE_INTERVAL; + + /** + * Constructor with {@link RingBuffer} and {@link BufferedUidProvider}, default use schedule + * + * @param ringBuffer {@link RingBuffer} + * @param uidProvider {@link BufferedUidProvider} + */ + public BufferPaddingExecutor(RingBuffer ringBuffer, BufferedUidProvider uidProvider) { + this(ringBuffer, uidProvider, true); + } + + /** + * Constructor with {@link RingBuffer}, {@link BufferedUidProvider}, and whether use schedule padding + * + * @param ringBuffer {@link RingBuffer} + * @param uidProvider {@link BufferedUidProvider} + */ + public BufferPaddingExecutor(RingBuffer ringBuffer, BufferedUidProvider uidProvider, boolean usingSchedule) { + this.running = new AtomicBoolean(false); + this.lastSecond = new PaddedAtomicLong(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); + this.ringBuffer = ringBuffer; + this.uidProvider = uidProvider; + + // initialize thread pool + int cores = Runtime.getRuntime().availableProcessors(); + bufferPadExecutors = Executors.newFixedThreadPool(cores * 2, new NamingThreadFactory(WORKER_NAME)); + + // initialize schedule thread + if (usingSchedule) { + bufferPadSchedule = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(SCHEDULE_NAME)); + } else { + bufferPadSchedule = null; + } + } + + /** + * Start executors such as schedule + */ + public void start() { + if (bufferPadSchedule != null) { + bufferPadSchedule.scheduleWithFixedDelay(this::paddingBuffer, scheduleInterval, scheduleInterval, TimeUnit.SECONDS); + } + } + + /** + * Shutdown executors + */ + public void shutdown() { + if (!bufferPadExecutors.isShutdown()) { + bufferPadExecutors.shutdownNow(); + } + + if (bufferPadSchedule != null && !bufferPadSchedule.isShutdown()) { + bufferPadSchedule.shutdownNow(); + } + } + + /** + * Whether is padding + */ + public boolean isRunning() { + return running.get(); + } + + /** + * Padding buffer in the thread pool + */ + public void asyncPadding() { + bufferPadExecutors.submit(this::paddingBuffer); + } + + /** + * Padding buffer fill the slots until to catch the cursor + */ + public void paddingBuffer() { + LOGGER.info("Ready to padding buffer lastSecond:{}. {}", lastSecond.get(), ringBuffer); + + // is still running + if (!running.compareAndSet(false, true)) { + LOGGER.info("Padding buffer is still running. {}", ringBuffer); + return; + } + + // fill the rest slots until to catch the cursor + boolean isFullRingBuffer = false; + while (!isFullRingBuffer) { + List uidList = uidProvider.provide(lastSecond.incrementAndGet()); + for (Long uid : uidList) { + isFullRingBuffer = !ringBuffer.put(uid); + if (isFullRingBuffer) { + break; + } + } + } + + // not running now + running.compareAndSet(true, false); + LOGGER.info("End to padding buffer lastSecond:{}. {}", lastSecond.get(), ringBuffer); + } + + /** + * Setters + */ + public void setScheduleInterval(long scheduleInterval) { + Assert.isTrue(scheduleInterval > 0, "Schedule interval must positive!"); + this.scheduleInterval = scheduleInterval; + } + +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/BufferedUidProvider.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/BufferedUidProvider.java new file mode 100644 index 0000000000..74aeaf89c1 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/BufferedUidProvider.java @@ -0,0 +1,16 @@ + +package io.metersphere.sdk.uid.buffer; + +import java.util.List; + +/** + * Buffered UID provider(Lambda supported), which provides UID in the same one second + */ +@FunctionalInterface +public interface BufferedUidProvider { + + /** + * Provides UID in one second + */ + List provide(long momentInSecond); +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RejectedPutBufferHandler.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RejectedPutBufferHandler.java new file mode 100644 index 0000000000..a57ab83994 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RejectedPutBufferHandler.java @@ -0,0 +1,15 @@ + +package io.metersphere.sdk.uid.buffer; + +/** + * If tail catches the cursor it means that the ring buffer is full, any more buffer put request will be rejected. + * Specify the policy to handle the reject. This is a Lambda supported interface + */ +@FunctionalInterface +public interface RejectedPutBufferHandler { + + /** + * Reject put buffer request + */ + void rejectPutBuffer(RingBuffer ringBuffer, long uid); +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RejectedTakeBufferHandler.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RejectedTakeBufferHandler.java new file mode 100644 index 0000000000..7b6fd78a8e --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RejectedTakeBufferHandler.java @@ -0,0 +1,15 @@ + +package io.metersphere.sdk.uid.buffer; + +/** + * If cursor catches the tail it means that the ring buffer is empty, any more buffer take request will be rejected. + * Specify the policy to handle the reject. This is a Lambda supported interface + */ +@FunctionalInterface +public interface RejectedTakeBufferHandler { + + /** + * Reject take buffer request + */ + void rejectTakeBuffer(RingBuffer ringBuffer); +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RingBuffer.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RingBuffer.java new file mode 100644 index 0000000000..879ac20eff --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/buffer/RingBuffer.java @@ -0,0 +1,251 @@ + +package io.metersphere.sdk.uid.buffer; + +import io.metersphere.sdk.uid.utils.PaddedAtomicLong; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Represents a ring buffer based on array.
+ * Using array could improve read element performance due to the CUP cache line. To prevent + * the side effect of False Sharing, {@link PaddedAtomicLong} is using on 'tail' and 'cursor'

+ *

+ * A ring buffer is consisted of: + *

  • slots: each element of the array is a slot, which is be set with a UID + *
  • flags: flag array corresponding the same index with the slots, indicates whether can take or put slot + *
  • tail: a sequence of the max slot position to produce + *
  • cursor: a sequence of the min slot position to consume + */ +public class RingBuffer { + private static final Logger LOGGER = LoggerFactory.getLogger(RingBuffer.class); + + /** + * Constants + */ + private static final int START_POINT = -1; + private static final long CAN_PUT_FLAG = 0L; + private static final long CAN_TAKE_FLAG = 1L; + public static final int DEFAULT_PADDING_PERCENT = 50; + + /** + * The size of RingBuffer's slots, each slot hold a UID + */ + @Getter + private final int bufferSize; + private final long indexMask; + private final long[] slots; + private final PaddedAtomicLong[] flags; + + /** + * Tail: last position sequence to produce + */ + private final AtomicLong tail = new PaddedAtomicLong(START_POINT); + + /** + * Cursor: current position sequence to consume + */ + private final AtomicLong cursor = new PaddedAtomicLong(START_POINT); + + /** + * Threshold for trigger padding buffer + */ + private final int paddingThreshold; + + /** + * Reject put/take buffer handle policy + */ + private RejectedPutBufferHandler rejectedPutHandler = this::discardPutBuffer; + private RejectedTakeBufferHandler rejectedTakeHandler = this::exceptionRejectedTakeBuffer; + + /** + * Executor of padding buffer + */ + private BufferPaddingExecutor bufferPaddingExecutor; + + /** + * Constructor with buffer size, paddingFactor default as {@value #DEFAULT_PADDING_PERCENT} + * + * @param bufferSize must be positive & a power of 2 + */ + public RingBuffer(int bufferSize) { + this(bufferSize, DEFAULT_PADDING_PERCENT); + } + + /** + * Constructor with buffer size & padding factor + * + * @param bufferSize must be positive & a power of 2 + * @param paddingFactor percent in (0 - 100). When the count of rest available UIDs reach the threshold, it will trigger padding buffer
    + * Sample: paddingFactor=20, bufferSize=1000 -> threshold=1000 * 20 /100, + * padding buffer will be triggered when tail-cursor 0L, "RingBuffer size must be positive"); + Assert.isTrue(Integer.bitCount(bufferSize) == 1, "RingBuffer size must be a power of 2"); + Assert.isTrue(paddingFactor > 0 && paddingFactor < 100, "RingBuffer size must be positive"); + + this.bufferSize = bufferSize; + this.indexMask = bufferSize - 1; + this.slots = new long[bufferSize]; + this.flags = initFlags(bufferSize); + + this.paddingThreshold = bufferSize * paddingFactor / 100; + } + + /** + * Put an UID in the ring & tail moved
    + * We use 'synchronized' to guarantee the UID fill in slot & publish new tail sequence as atomic operations
    + * + * Note that: It is recommended to put UID in a serialize way, cause we once batch generate a series UIDs and put + * the one by one into the buffer, so it is unnecessary put in multi-threads + * * @return false means that the buffer is full, apply {@link RejectedPutBufferHandler} + */ + public synchronized boolean put(long uid) { + long currentTail = tail.get(); + long currentCursor = cursor.get(); + + // tail catches the cursor, means that you can't put any cause of RingBuffer is full + long distance = currentTail - (currentCursor == START_POINT ? 0 : currentCursor); + if (distance == bufferSize - 1) { + rejectedPutHandler.rejectPutBuffer(this, uid); + return false; + } + + // 1. pre-check whether the flag is CAN_PUT_FLAG + int nextTailIndex = calSlotIndex(currentTail + 1); + if (flags[nextTailIndex].get() != CAN_PUT_FLAG) { + rejectedPutHandler.rejectPutBuffer(this, uid); + return false; + } + + // 2. put UID in the next slot + // 3. update next slot' flag to CAN_TAKE_FLAG + // 4. publish tail with sequence increase by one + slots[nextTailIndex] = uid; + flags[nextTailIndex].set(CAN_TAKE_FLAG); + tail.incrementAndGet(); + + // The atomicity of operations above, guarantees by 'synchronized'. In another word, + // the take operation can't consume the UID we just put, until the tail is published(tail.incrementAndGet()) + return true; + } + + /** + * Take an UID of the ring at the next cursor, this is a lock free operation by using atomic cursor

    + *

    + * Before getting the UID, we also check whether reach the padding threshold, + * the padding buffer operation will be triggered in another thread
    + * If there is no more available UID to be taken, the specified {@link RejectedTakeBufferHandler} will be applied
    + * + * @return UID + * @throws IllegalStateException if the cursor moved back + */ + public long take() { + // spin get next available cursor + long currentCursor = cursor.get(); + long nextCursor = cursor.updateAndGet(old -> old == tail.get() ? old : old + 1); + + // check for safety consideration, it never occurs + Assert.isTrue(nextCursor >= currentCursor, "Curosr can't move back"); + + // trigger padding in an async-mode if reach the threshold + long currentTail = tail.get(); + if (currentTail - nextCursor < paddingThreshold) { + LOGGER.info("Reach the padding threshold:{}. tail:{}, cursor:{}, rest:{}", paddingThreshold, currentTail, + nextCursor, currentTail - nextCursor); + bufferPaddingExecutor.asyncPadding(); + } + + // cursor catch the tail, means that there is no more available UID to take + if (nextCursor == currentCursor) { + rejectedTakeHandler.rejectTakeBuffer(this); + } + + // 1. check next slot flag is CAN_TAKE_FLAG + int nextCursorIndex = calSlotIndex(nextCursor); + Assert.isTrue(flags[nextCursorIndex].get() == CAN_TAKE_FLAG, "Curosr not in can take status"); + + // 2. get UID from next slot + // 3. set next slot flag as CAN_PUT_FLAG. + long uid = slots[nextCursorIndex]; + flags[nextCursorIndex].set(CAN_PUT_FLAG); + + // Note that: Step 2,3 can not swap. If we set flag before get value of slot, the producer may overwrite the + // slot with a new UID, and this may cause the consumer take the UID twice after walk a round the ring + return uid; + } + + /** + * Calculate slot index with the slot sequence (sequence % bufferSize) + */ + protected int calSlotIndex(long sequence) { + return (int) (sequence & indexMask); + } + + /** + * Discard policy for {@link RejectedPutBufferHandler}, we just do logging + */ + protected void discardPutBuffer(RingBuffer ringBuffer, long uid) { + LOGGER.warn("Rejected putting buffer for uid:{}. {}", uid, ringBuffer); + } + + /** + * Policy for {@link RejectedTakeBufferHandler}, throws {@link RuntimeException} after logging + */ + protected void exceptionRejectedTakeBuffer(RingBuffer ringBuffer) { + LOGGER.warn("Rejected take buffer. {}", ringBuffer); + throw new RuntimeException("Rejected take buffer. " + ringBuffer); + } + + /** + * Initialize flags as CAN_PUT_FLAG + */ + private PaddedAtomicLong[] initFlags(int bufferSize) { + PaddedAtomicLong[] flags = new PaddedAtomicLong[bufferSize]; + for (int i = 0; i < bufferSize; i++) { + flags[i] = new PaddedAtomicLong(CAN_PUT_FLAG); + } + + return flags; + } + + /** + * Getters + */ + public long getTail() { + return tail.get(); + } + + public long getCursor() { + return cursor.get(); + } + + /** + * Setters + */ + public void setBufferPaddingExecutor(BufferPaddingExecutor bufferPaddingExecutor) { + this.bufferPaddingExecutor = bufferPaddingExecutor; + } + + public void setRejectedPutHandler(RejectedPutBufferHandler rejectedPutHandler) { + this.rejectedPutHandler = rejectedPutHandler; + } + + public void setRejectedTakeHandler(RejectedTakeBufferHandler rejectedTakeHandler) { + this.rejectedTakeHandler = rejectedTakeHandler; + } + + @Override + public String toString() { + return "RingBuffer [bufferSize=" + bufferSize + + ", tail=" + tail + + ", cursor=" + cursor + + ", paddingThreshold=" + paddingThreshold + "]"; + } + +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/impl/CachedUidGenerator.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/impl/CachedUidGenerator.java new file mode 100644 index 0000000000..7319584f70 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/impl/CachedUidGenerator.java @@ -0,0 +1,147 @@ + +package io.metersphere.sdk.uid.impl; + +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.uid.BitsAllocator; +import io.metersphere.sdk.uid.buffer.BufferPaddingExecutor; +import io.metersphere.sdk.uid.buffer.RejectedPutBufferHandler; +import io.metersphere.sdk.uid.buffer.RejectedTakeBufferHandler; +import io.metersphere.sdk.uid.buffer.RingBuffer; +import io.metersphere.sdk.util.LogUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.List; + +/** + * from {@link DefaultUidGenerator}, based on a lock free {@link RingBuffer}

    + */ +@Service +public class CachedUidGenerator extends DefaultUidGenerator implements DisposableBean, InitializingBean { + private static final int DEFAULT_BOOST_POWER = 3; + + /** + * Spring properties + */ + private int boostPower = DEFAULT_BOOST_POWER; + private Long scheduleInterval; + + private RejectedPutBufferHandler rejectedPutBufferHandler; + private RejectedTakeBufferHandler rejectedTakeBufferHandler; + + /** + * RingBuffer + */ + private RingBuffer ringBuffer; + private BufferPaddingExecutor bufferPaddingExecutor; + + @Override + public void afterPropertiesSet() { + // initialize workerId & bitsAllocator + super.afterPropertiesSet(); + + // initialize RingBuffer & RingBufferPaddingExecutor + this.initRingBuffer(); + LogUtils.info("Initialized RingBuffer successfully."); + } + + @Override + public long getUID() { + try { + return ringBuffer.take(); + } catch (Exception e) { + LogUtils.error("Generate unique id exception. ", e); + throw new MSException(e); + } + } + + @Override + public String parseUID(long uid) { + return super.parseUID(uid); + } + + @Override + public void destroy() throws Exception { + bufferPaddingExecutor.shutdown(); + } + + /** + * Get the UIDs in the same specified second under the max sequence + * * @return UID list, size of {@link BitsAllocator#getMaxSequence()} + 1 + */ + protected List nextIdsForOneSecond(long currentSecond) { + // Initialize result list size of (max sequence + 1) + int listSize = (int) bitsAllocator.getMaxSequence() + 1; + List uidList = new ArrayList<>(listSize); + + // Allocate the first sequence of the second, the others can be calculated with the offset + long firstSeqUid = bitsAllocator.allocate(currentSecond - epochSeconds, workerId, 0L); + for (int offset = 0; offset < listSize; offset++) { + uidList.add(firstSeqUid + offset); + } + + return uidList; + } + + /** + * Initialize RingBuffer & RingBufferPaddingExecutor + */ + private void initRingBuffer() { + // initialize RingBuffer + int bufferSize = ((int) bitsAllocator.getMaxSequence() + 1) << boostPower; + int paddingFactor = RingBuffer.DEFAULT_PADDING_PERCENT; + this.ringBuffer = new RingBuffer(bufferSize, paddingFactor); + LogUtils.info("Initialized ring buffer size:{}, paddingFactor:{}", bufferSize, paddingFactor); + + // initialize RingBufferPaddingExecutor + boolean usingSchedule = (scheduleInterval != null); + this.bufferPaddingExecutor = new BufferPaddingExecutor(ringBuffer, this::nextIdsForOneSecond, usingSchedule); + if (usingSchedule) { + bufferPaddingExecutor.setScheduleInterval(scheduleInterval); + } + + LogUtils.info("Initialized BufferPaddingExecutor. Using schdule:{}, interval:{}", usingSchedule, scheduleInterval); + + // set rejected put/take handle policy + this.ringBuffer.setBufferPaddingExecutor(bufferPaddingExecutor); + if (rejectedPutBufferHandler != null) { + this.ringBuffer.setRejectedPutHandler(rejectedPutBufferHandler); + } + if (rejectedTakeBufferHandler != null) { + this.ringBuffer.setRejectedTakeHandler(rejectedTakeBufferHandler); + } + + // fill in all slots of the RingBuffer + bufferPaddingExecutor.paddingBuffer(); + + // start buffer padding threads + bufferPaddingExecutor.start(); + } + + /** + * Setters for spring property + */ + public void setBoostPower(int boostPower) { + Assert.isTrue(boostPower > 0, "Boost power must be positive!"); + this.boostPower = boostPower; + } + + public void setRejectedPutBufferHandler(RejectedPutBufferHandler rejectedPutBufferHandler) { + Assert.notNull(rejectedPutBufferHandler, "RejectedPutBufferHandler can't be null!"); + this.rejectedPutBufferHandler = rejectedPutBufferHandler; + } + + public void setRejectedTakeBufferHandler(RejectedTakeBufferHandler rejectedTakeBufferHandler) { + Assert.notNull(rejectedTakeBufferHandler, "RejectedTakeBufferHandler can't be null!"); + this.rejectedTakeBufferHandler = rejectedTakeBufferHandler; + } + + public void setScheduleInterval(long scheduleInterval) { + Assert.isTrue(scheduleInterval > 0, "Schedule interval must positive!"); + this.scheduleInterval = scheduleInterval; + } + +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/impl/DefaultUidGenerator.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/impl/DefaultUidGenerator.java new file mode 100644 index 0000000000..92b4a8815a --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/impl/DefaultUidGenerator.java @@ -0,0 +1,179 @@ + +package io.metersphere.sdk.uid.impl; + +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.uid.BitsAllocator; +import io.metersphere.sdk.uid.utils.TimeUtils; +import io.metersphere.sdk.uid.worker.WorkerIdAssigner; +import io.metersphere.sdk.util.LogUtils; +import jakarta.annotation.Resource; +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +@Service +public class DefaultUidGenerator { + /** + * Bits allocate + */ + protected int timeBits = 28; + protected int workerBits = 22; + protected int seqBits = 13; + + /** + * Customer epoch, unit as second. For example 2023-09-01 (ms: 1693548784) + */ + protected String epochStr = "2023-09-01"; + protected long epochSeconds = TimeUnit.MILLISECONDS.toSeconds(1693548784); + + /** + * Stable fields after spring bean initializing + */ + protected BitsAllocator bitsAllocator; + protected long workerId; + + /** + * Volatile fields caused by nextId() + */ + protected long sequence = 0L; + protected long lastSecond = -1L; + + /** + * Spring property + */ + @Resource + protected WorkerIdAssigner workerIdAssigner; + + public void afterPropertiesSet() { + // init bitsAllocator + this.setTimeBits(29); + this.setWorkerBits(21); + this.setSeqBits(13); + this.setEpochStr(TimeUtils.getDataStr(System.currentTimeMillis())); + + // initialize bits allocator + bitsAllocator = new BitsAllocator(timeBits, workerBits, seqBits); + + // initialize worker id + workerId = workerIdAssigner.assignWorkerId(); + if (workerId > bitsAllocator.getMaxWorkerId()) { + throw new RuntimeException("Worker id " + workerId + " exceeds the max " + bitsAllocator.getMaxWorkerId()); + } + + LogUtils.info("Initialized bits(1, {}, {}, {}) for workerID:{}", timeBits, workerBits, seqBits, workerId); + } + + public long getUID() throws MSException { + try { + return nextId(); + } catch (Exception e) { + LogUtils.error("Generate unique id exception. ", e); + throw new RuntimeException(e); + } + } + + public String parseUID(long uid) { + long totalBits = BitsAllocator.TOTAL_BITS; + long signBits = bitsAllocator.getSignBits(); + long timestampBits = bitsAllocator.getTimestampBits(); + long workerIdBits = bitsAllocator.getWorkerIdBits(); + long sequenceBits = bitsAllocator.getSequenceBits(); + + // parse UID + long sequence = (uid << (totalBits - sequenceBits)) >>> (totalBits - sequenceBits); + long workerId = (uid << (timestampBits + signBits)) >>> (totalBits - workerIdBits); + long deltaSeconds = uid >>> (workerIdBits + sequenceBits); + + Date thatTime = new Date(TimeUnit.SECONDS.toMillis(epochSeconds + deltaSeconds)); + String thatTimeStr = TimeUtils.formatByDateTimePattern(thatTime); + + // format as string + return String.format("{\"UID\":\"%d\",\"timestamp\":\"%s\",\"workerId\":\"%d\",\"sequence\":\"%d\"}", + uid, thatTimeStr, workerId, sequence); + } + + /** + * Get UID + * + * @return UID + * @throws MSException in the case: Clock moved backwards; Exceeds the max timestamp + */ + protected synchronized long nextId() { + long currentSecond = getCurrentSecond(); + + // Clock moved backwards, refuse to generate uid + if (currentSecond < lastSecond) { + long refusedSeconds = lastSecond - currentSecond; + throw new MSException("Clock moved backwards. Refusing for %d seconds" + refusedSeconds); + } + + // At the same second, increase sequence + if (currentSecond == lastSecond) { + sequence = (sequence + 1) & bitsAllocator.getMaxSequence(); + // Exceed the max sequence, we wait the next second to generate uid + if (sequence == 0) { + currentSecond = getNextSecond(lastSecond); + } + + // At the different second, sequence restart from zero + } else { + sequence = 0L; + } + + lastSecond = currentSecond; + + // Allocate bits for UID + return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence); + } + + /** + * Get next millisecond + */ + private long getNextSecond(long lastTimestamp) { + long timestamp = getCurrentSecond(); + while (timestamp <= lastTimestamp) { + timestamp = getCurrentSecond(); + } + + return timestamp; + } + + /** + * Get current second + */ + private long getCurrentSecond() { + long currentSecond = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); + if (currentSecond - epochSeconds > bitsAllocator.getMaxDeltaSeconds()) { + throw new MSException("Timestamp bits is exhausted. Refusing UID generate. Now: " + currentSecond); + } + + return currentSecond; + } + + public void setTimeBits(int timeBits) { + if (timeBits > 0) { + this.timeBits = timeBits; + } + } + + public void setWorkerBits(int workerBits) { + if (workerBits > 0) { + this.workerBits = workerBits; + } + } + + public void setSeqBits(int seqBits) { + if (seqBits > 0) { + this.seqBits = seqBits; + } + } + + public void setEpochStr(String epochStr) { + if (StringUtils.isNotBlank(epochStr)) { + this.epochStr = epochStr; + this.epochSeconds = TimeUnit.MILLISECONDS.toSeconds(TimeUtils.parseByDayPattern(epochStr).getTime()); + } + } +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/DockerUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/DockerUtils.java new file mode 100644 index 0000000000..9e4c2dff5e --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/DockerUtils.java @@ -0,0 +1,90 @@ + +package io.metersphere.sdk.uid.utils; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * DockerUtils + * + + */ +public abstract class DockerUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(DockerUtils.class); + + /** Environment param keys */ + private static final String ENV_KEY_HOST = "JPAAS_HOST"; + private static final String ENV_KEY_PORT = "JPAAS_HTTP_PORT"; + private static final String ENV_KEY_PORT_ORIGINAL = "JPAAS_HOST_PORT_8080"; + + /** Docker host & port */ + private static String DOCKER_HOST = ""; + private static String DOCKER_PORT = ""; + + /** Whether is docker */ + private static boolean IS_DOCKER; + + static { + retrieveFromEnv(); + } + + /** + * Retrieve docker host + * + * @return empty string if not a docker + */ + public static String getDockerHost() { + return DOCKER_HOST; + } + + /** + * Retrieve docker port + * + * @return empty string if not a docker + */ + public static String getDockerPort() { + return DOCKER_PORT; + } + + /** + * Whether a docker + * + * @return + */ + public static boolean isDocker() { + return IS_DOCKER; + } + + /** + * Retrieve host & port from environment + */ + private static void retrieveFromEnv() { + // retrieve host & port from environment + DOCKER_HOST = System.getenv(ENV_KEY_HOST); + DOCKER_PORT = System.getenv(ENV_KEY_PORT); + + // not found from 'JPAAS_HTTP_PORT', then try to find from 'JPAAS_HOST_PORT_8080' + if (StringUtils.isBlank(DOCKER_PORT)) { + DOCKER_PORT = System.getenv(ENV_KEY_PORT_ORIGINAL); + } + + boolean hasEnvHost = StringUtils.isNotBlank(DOCKER_HOST); + boolean hasEnvPort = StringUtils.isNotBlank(DOCKER_PORT); + + // docker can find both host & port from environment + if (hasEnvHost && hasEnvPort) { + IS_DOCKER = true; + + // found nothing means not a docker, maybe an actual machine + } else if (!hasEnvHost && !hasEnvPort) { + IS_DOCKER = false; + + } else { + LOGGER.error("Missing host or port from env for Docker. host:{}, port:{}", DOCKER_HOST, DOCKER_PORT); + throw new RuntimeException( + "Missing host or port from env for Docker. host:" + DOCKER_HOST + ", port:" + DOCKER_PORT); + } + } + +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/EnumUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/EnumUtils.java new file mode 100644 index 0000000000..e65bb4e7d1 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/EnumUtils.java @@ -0,0 +1,39 @@ + +package io.metersphere.sdk.uid.utils; + +import org.springframework.util.Assert; + +/** + * EnumUtils provides the operations for {@link ValuedEnum} such as Parse, value of... + */ +public abstract class EnumUtils { + + /** + * Parse the bounded value into ValuedEnum + */ + public static , V> T parse(Class clz, V value) { + Assert.notNull(clz, "clz can not be null"); + if (value == null) { + return null; + } + + for (T t : clz.getEnumConstants()) { + if (value.equals(t.value())) { + return t; + } + } + return null; + } + + /** + * Null-safe valueOf function + */ + public static > T valueOf(Class enumType, String name) { + if (name == null) { + return null; + } + + return Enum.valueOf(enumType, name); + } + +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/NamingThreadFactory.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/NamingThreadFactory.java new file mode 100644 index 0000000000..4b833c5aa2 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/NamingThreadFactory.java @@ -0,0 +1,148 @@ + +package io.metersphere.sdk.uid.utils; + +import org.apache.commons.lang.ClassUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Named thread in ThreadFactory. If there is no specified name for thread, it + * will auto detect using the invoker classname instead. + */ +public class NamingThreadFactory implements ThreadFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(NamingThreadFactory.class); + + /** + * Thread name pre + */ + private String name; + /** + * Is daemon thread + */ + private boolean daemon; + /** + * UncaughtExceptionHandler + */ + private UncaughtExceptionHandler uncaughtExceptionHandler; + /** + * Sequences for multi thread name prefix + */ + private final ConcurrentHashMap sequences; + + /** + * Constructors + */ + public NamingThreadFactory() { + this(null, false, null); + } + + public NamingThreadFactory(String name) { + this(name, false, null); + } + + public NamingThreadFactory(String name, boolean daemon) { + this(name, daemon, null); + } + + public NamingThreadFactory(String name, boolean daemon, UncaughtExceptionHandler handler) { + this.name = name; + this.daemon = daemon; + this.uncaughtExceptionHandler = handler; + this.sequences = new ConcurrentHashMap(); + } + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(this.daemon); + + // If there is no specified name for thread, it will auto detect using the invoker classname instead. + // Notice that auto detect may cause some performance overhead + String prefix = this.name; + if (StringUtils.isBlank(prefix)) { + prefix = getInvoker(2); + } + thread.setName(prefix + "-" + getSequence(prefix)); + + // no specified uncaughtExceptionHandler, just do logging. + if (this.uncaughtExceptionHandler != null) { + thread.setUncaughtExceptionHandler(this.uncaughtExceptionHandler); + } else { + thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { + public void uncaughtException(Thread t, Throwable e) { + LOGGER.error("unhandled exception in thread: " + t.getId() + ":" + t.getName(), e); + } + }); + } + + return thread; + } + + /** + * Get the method invoker's class name + * + * @param depth + * @return + */ + private String getInvoker(int depth) { + Exception e = new Exception(); + StackTraceElement[] stes = e.getStackTrace(); + if (stes.length > depth) { + return ClassUtils.getShortClassName(stes[depth].getClassName()); + } + return getClass().getSimpleName(); + } + + /** + * Get sequence for different naming prefix + * + * @param invoker + * @return + */ + private long getSequence(String invoker) { + AtomicLong r = this.sequences.get(invoker); + if (r == null) { + r = new AtomicLong(0); + AtomicLong previous = this.sequences.putIfAbsent(invoker, r); + if (previous != null) { + r = previous; + } + } + + return r.incrementAndGet(); + } + + /** + * Getters & Setters + */ + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isDaemon() { + return daemon; + } + + public void setDaemon(boolean daemon) { + this.daemon = daemon; + } + + public UncaughtExceptionHandler getUncaughtExceptionHandler() { + return uncaughtExceptionHandler; + } + + public void setUncaughtExceptionHandler(UncaughtExceptionHandler handler) { + this.uncaughtExceptionHandler = handler; + } + +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/NetUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/NetUtils.java new file mode 100644 index 0000000000..4db73e9060 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/NetUtils.java @@ -0,0 +1,70 @@ + +package io.metersphere.sdk.uid.utils; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +/** + * NetUtils + * + + */ +public abstract class NetUtils { + + /** + * Pre-loaded local address + */ + public static InetAddress localAddress; + + static { + try { + localAddress = getLocalInetAddress(); + } catch (SocketException e) { + throw new RuntimeException("fail to get local ip."); + } + } + + /** + * Retrieve the first validated local ip address(the Public and LAN ip addresses are validated). + * + * @return the local address + * @throws SocketException the socket exception + */ + public static InetAddress getLocalInetAddress() throws SocketException { + // enumerates all network interfaces + Enumeration enu = NetworkInterface.getNetworkInterfaces(); + + while (enu.hasMoreElements()) { + NetworkInterface ni = enu.nextElement(); + if (ni.isLoopback()) { + continue; + } + + Enumeration addressEnumeration = ni.getInetAddresses(); + while (addressEnumeration.hasMoreElements()) { + InetAddress address = addressEnumeration.nextElement(); + + // ignores all invalidated addresses + if (address.isLinkLocalAddress() || address.isLoopbackAddress() || address.isAnyLocalAddress()) { + continue; + } + + return address; + } + } + + throw new RuntimeException("No validated local address!"); + } + + /** + * Retrieve local address + * + * @return the string local address + */ + public static String getLocalAddress() { + return localAddress.getHostAddress(); + } + +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/PaddedAtomicLong.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/PaddedAtomicLong.java new file mode 100644 index 0000000000..7f96d0fe47 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/PaddedAtomicLong.java @@ -0,0 +1,38 @@ + +package io.metersphere.sdk.uid.utils; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Represents a padded {@link AtomicLong} to prevent the FalseSharing problem

    + * + * The CPU cache line commonly be 64 bytes, here is a sample of cache line after padding:
    + * 64 bytes = 8 bytes (object reference) + 6 * 8 bytes (padded long) + 8 bytes (a long value) + * + + */ +public class PaddedAtomicLong extends AtomicLong { + private static final long serialVersionUID = -3415778863941386253L; + + /** Padded 6 long (48 bytes) */ + public volatile long p1, p2, p3, p4, p5, p6 = 7L; + + /** + * Constructors from {@link AtomicLong} + */ + public PaddedAtomicLong() { + super(); + } + + public PaddedAtomicLong(long initialValue) { + super(initialValue); + } + + /** + * To prevent GC optimizations for cleaning unused padded references + */ + public long sumPaddingToPreventOptimization() { + return p1 + p2 + p3 + p4 + p5 + p6; + } + +} \ No newline at end of file diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/TimeUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/TimeUtils.java new file mode 100644 index 0000000000..4b173010f7 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/TimeUtils.java @@ -0,0 +1,53 @@ +package io.metersphere.sdk.uid.utils; + +import org.apache.commons.lang.time.DateFormatUtils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * DateUtils provides date formatting, parsing + */ +public abstract class TimeUtils extends org.apache.commons.lang.time.DateUtils { + /** + * Patterns + */ + public static final String DAY_PATTERN = "yyyy-MM-dd"; + public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + + /** + * Parse date by 'yyyy-MM-dd' pattern + */ + public static Date parseByDayPattern(String str) { + return parseDate(str, DAY_PATTERN); + } + + /** + * Parse date without Checked exception + * + * @throws RuntimeException when ParseException occurred + */ + public static Date parseDate(String str, String pattern) { + try { + return parseDate(str, new String[]{pattern}); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + + /** + * Format date by 'yyyy-MM-dd HH:mm:ss' pattern + */ + public static String formatByDateTimePattern(Date date) { + return DateFormatUtils.format(date, DATETIME_PATTERN); + } + + + public static String getDataStr(long timeStamp) { + SimpleDateFormat dateFormat = new SimpleDateFormat(DAY_PATTERN); + return dateFormat.format(timeStamp); + } +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/ValuedEnum.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/ValuedEnum.java new file mode 100644 index 0000000000..d729b2de1a --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/utils/ValuedEnum.java @@ -0,0 +1,11 @@ + +package io.metersphere.sdk.uid.utils; + +/** + * {@code ValuedEnum} defines an enumeration which is bounded to a value, you + * may implements this interface when you defines such kind of enumeration, that + * you can use {@link EnumUtils} to simplify parse and valueOf operation. + */ +public interface ValuedEnum { + T value(); +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/DisposableWorkerIdAssigner.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/DisposableWorkerIdAssigner.java new file mode 100644 index 0000000000..2ab0edce85 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/DisposableWorkerIdAssigner.java @@ -0,0 +1,66 @@ + +package io.metersphere.sdk.uid.worker; + +import io.metersphere.sdk.domain.WorkerNode; +import io.metersphere.sdk.mapper.BaseWorkerNodeMapper; +import io.metersphere.sdk.uid.utils.DockerUtils; +import io.metersphere.sdk.uid.utils.NetUtils; +import io.metersphere.sdk.util.LogUtils; +import jakarta.annotation.Resource; +import org.apache.commons.lang.math.RandomUtils; +import org.springframework.stereotype.Service; + +/** + * Represents an implementation of {@link WorkerIdAssigner}, + * the worker id will be discarded after assigned to the UidGenerator + */ +@Service +public class DisposableWorkerIdAssigner implements WorkerIdAssigner { + @Resource + private BaseWorkerNodeMapper workerNodeMapper; + + /** + * Assign worker id base on database.

    + * If there is host name & port in the environment, we considered that the node runs in Docker container
    + * Otherwise, the node runs on an actual machine. + * + * @return assigned worker id + */ + public long assignWorkerId() { + // build worker node entity + try { + WorkerNode workerNode = buildWorkerNode(); + + // add worker node for new (ignore the same IP + PORT) + workerNodeMapper.insert(workerNode); + LogUtils.info("Add worker node:" + workerNode); + + return workerNode.getId(); + } catch (Exception e) { + LogUtils.error("Assign worker id exception. ", e); + return 1; + } + } + + /** + * Build worker node entity by IP and PORT + */ + private WorkerNode buildWorkerNode() { + WorkerNode workerNode = new WorkerNode(); + if (DockerUtils.isDocker()) { + workerNode.setType(WorkerNodeType.CONTAINER.value()); + workerNode.setHostName(DockerUtils.getDockerHost()); + workerNode.setPort(DockerUtils.getDockerPort()); + + } else { + workerNode.setType(WorkerNodeType.ACTUAL.value()); + workerNode.setHostName(NetUtils.getLocalAddress()); + workerNode.setPort(System.currentTimeMillis() + "-" + RandomUtils.nextInt(100000)); + } + workerNode.setCreated(System.currentTimeMillis()); + workerNode.setModified(System.currentTimeMillis()); + workerNode.setLaunchDate(System.currentTimeMillis()); + return workerNode; + } + +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/WorkerIdAssigner.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/WorkerIdAssigner.java new file mode 100644 index 0000000000..78a90593a3 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/WorkerIdAssigner.java @@ -0,0 +1,18 @@ + +package io.metersphere.sdk.uid.worker; + +/** + * Represents a worker id assigner for {@link io.metersphere.sdk.uid.impl.DefaultUidGenerator} + * + + */ +public interface WorkerIdAssigner { + + /** + * Assign worker id for {@link io.metersphere.sdk.uid.impl.DefaultUidGenerator} + * + * @return assigned worker id + */ + long assignWorkerId(); + +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/WorkerNodeType.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/WorkerNodeType.java new file mode 100644 index 0000000000..5e34536a9d --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/uid/worker/WorkerNodeType.java @@ -0,0 +1,33 @@ + +package io.metersphere.sdk.uid.worker; + +import io.metersphere.sdk.uid.utils.ValuedEnum; + +/** + * WorkerNodeType + *

  • CONTAINER: Such as Docker + *
  • ACTUAL: Actual machine + * + */ +public enum WorkerNodeType implements ValuedEnum { + + CONTAINER(1), ACTUAL(2); + + /** + * Lock type + */ + private final Integer type; + + /** + * Constructor with field of type + */ + private WorkerNodeType(Integer type) { + this.type = type; + } + + @Override + public Integer value() { + return type; + } + +} diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/LogUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/LogUtils.java index 482a99bc9f..87b0c45c8a 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/LogUtils.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/LogUtils.java @@ -17,8 +17,6 @@ public class LogUtils { /** * 初始化日志 - * - * @return */ public static Logger getLogger() { return LoggerFactory.getLogger(LogUtils.getLogClass()); @@ -57,6 +55,14 @@ public class LogUtils { } } + public static void info(String var1, Object... var2) { + Logger logger = LogUtils.getLogger(); + if (logger != null && logger.isInfoEnabled()) { + logger.info(var1, var2); + } + } + + public static void info(Object msg, Object o1) { Logger logger = LogUtils.getLogger(); if (logger != null && logger.isInfoEnabled()) { @@ -197,8 +203,6 @@ public class LogUtils { /** * 得到调用类名称 - * - * @return */ private static String getLogClass() { StackTraceElement[] stack = (new Throwable()).getStackTrace(); @@ -211,8 +215,6 @@ public class LogUtils { /** * 得到调用方法名称 - * - * @return */ private static String getLogMethod() { StackTraceElement[] stack = (new Throwable()).getStackTrace();