feat: 基于Snowflake算法生成有序数字UID
# 参考百度uid-generator进行改造符合我们自身的需求
This commit is contained in:
parent
f2da9e068d
commit
ca58b0c2fa
|
@ -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<Column> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Criteria> oredCriteria;
|
||||
|
||||
public WorkerNodeExample() {
|
||||
oredCriteria = new ArrayList<Criteria>();
|
||||
}
|
||||
|
||||
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<Criteria> 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<Criterion> criteria;
|
||||
|
||||
protected GeneratedCriteria() {
|
||||
super();
|
||||
criteria = new ArrayList<Criterion>();
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return criteria.size() > 0;
|
||||
}
|
||||
|
||||
public List<Criterion> getAllCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public List<Criterion> 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<Long> values) {
|
||||
addCriterion("id in", values, "id");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andIdNotIn(List<Long> 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<String> values) {
|
||||
addCriterion("host_name in", values, "hostName");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andHostNameNotIn(List<String> 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<String> values) {
|
||||
addCriterion("port in", values, "port");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andPortNotIn(List<String> 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<Integer> values) {
|
||||
addCriterion("`type` in", values, "type");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andTypeNotIn(List<Integer> 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<Long> values) {
|
||||
addCriterion("launch_date in", values, "launchDate");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andLaunchDateNotIn(List<Long> 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<Long> values) {
|
||||
addCriterion("modified in", values, "modified");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andModifiedNotIn(List<Long> 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<Long> values) {
|
||||
addCriterion("created in", values, "created");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andCreatedNotIn(List<Long> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package io.metersphere.sdk.mapper;
|
||||
|
||||
import io.metersphere.sdk.domain.WorkerNode;
|
||||
|
||||
public interface BaseWorkerNodeMapper {
|
||||
int insert(WorkerNode record);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="io.metersphere.sdk.mapper.BaseWorkerNodeMapper">
|
||||
|
||||
<insert id="insert" parameterType="io.metersphere.sdk.domain.WorkerNode" useGeneratedKeys="true" keyProperty="id">
|
||||
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})
|
||||
</insert>
|
||||
</mapper>
|
|
@ -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)<br>
|
||||
* 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<br>
|
||||
* The highest bit used for sign, so <code>63</code> 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<br>
|
||||
* <b>Note that: </b>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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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}<br>
|
||||
* 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<Long> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Long> provide(long momentInSecond);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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.<br>
|
||||
* 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'<p>
|
||||
* <p>
|
||||
* A ring buffer is consisted of:
|
||||
* <li><b>slots:</b> each element of the array is a slot, which is be set with a UID
|
||||
* <li><b>flags:</b> flag array corresponding the same index with the slots, indicates whether can take or put slot
|
||||
* <li><b>tail:</b> a sequence of the max slot position to produce
|
||||
* <li><b>cursor:</b> 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<br>
|
||||
* Sample: paddingFactor=20, bufferSize=1000 -> threshold=1000 * 20 /100,
|
||||
* padding buffer will be triggered when tail-cursor<threshold
|
||||
*/
|
||||
public RingBuffer(int bufferSize, int paddingFactor) {
|
||||
// check buffer size is positive & a power of 2; padding factor in (0, 100)
|
||||
Assert.isTrue(bufferSize > 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<br>
|
||||
* We use 'synchronized' to guarantee the UID fill in slot & publish new tail sequence as atomic operations<br>
|
||||
*
|
||||
* <b>Note that: </b> 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<p>
|
||||
* <p>
|
||||
* Before getting the UID, we also check whether reach the padding threshold,
|
||||
* the padding buffer operation will be triggered in another thread<br>
|
||||
* If there is no more available UID to be taken, the specified {@link RejectedTakeBufferHandler} will be applied<br>
|
||||
*
|
||||
* @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 + "]";
|
||||
}
|
||||
|
||||
}
|
|
@ -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}<p>
|
||||
*/
|
||||
@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<Long> nextIdsForOneSecond(long currentSecond) {
|
||||
// Initialize result list size of (max sequence + 1)
|
||||
int listSize = (int) bitsAllocator.getMaxSequence() + 1;
|
||||
List<Long> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <T extends ValuedEnum<V>, V> T parse(Class<T> 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 extends Enum<T>> T valueOf(Class<T> enumType, String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Enum.valueOf(enumType, name);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, AtomicLong> 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<String, AtomicLong>();
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<NetworkInterface> enu = NetworkInterface.getNetworkInterfaces();
|
||||
|
||||
while (enu.hasMoreElements()) {
|
||||
NetworkInterface ni = enu.nextElement();
|
||||
if (ni.isLoopback()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Enumeration<InetAddress> 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<p>
|
||||
*
|
||||
* The CPU cache line commonly be 64 bytes, here is a sample of cache line after padding:<br>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
T value();
|
||||
}
|
|
@ -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.<p>
|
||||
* If there is host name & port in the environment, we considered that the node runs in Docker container<br>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
package io.metersphere.sdk.uid.worker;
|
||||
|
||||
import io.metersphere.sdk.uid.utils.ValuedEnum;
|
||||
|
||||
/**
|
||||
* WorkerNodeType
|
||||
* <li>CONTAINER: Such as Docker
|
||||
* <li>ACTUAL: Actual machine
|
||||
*
|
||||
*/
|
||||
public enum WorkerNodeType implements ValuedEnum<Integer> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue