Skip to content

Commit

Permalink
feat: support use database as a registry (#4595)
Browse files Browse the repository at this point in the history
* feat: support use database as a registry

less dependency, config, startup fail,

more reliability,

* fix: test fail when missing bean RegistryService

* Delete RegistryController.java

* feat: add spring.cloud.discovery.enabled=false

* refactor: healthCheckInterval -> healthCheckIntervalInSecond

* fix: 'com.ctrip.framework.apollo.biz.repository.RegistryRepository' that could not be found

* refactor: Registry -> ServiceRegistry. change all config prefix to apollo.service

* feat: add service registry config heartbeatIntervalInSecond

* feat: clear service instances 1 days ago

* fix: move '@transactional' from repository up to service

* refactor: Change all to use jvm `LocalDateTime.now()`

* fix test fail

* Update apolloconfigdb.sql

* refactor: split heartbeat and deregister

* fix: catch Throwable instead of catch Exception

* refactor: use stream and lambda instead of for each loop filter

* delete USING BTREE

* fix: label -> cluster

* Update scripts/sql/apolloconfigdb.sql

Co-authored-by: Jason Song <[email protected]>

* Update scripts/sql/delta/v210-v220/apolloconfigdb-v210-v220.sql

Co-authored-by: Jason Song <[email protected]>

* test: add bean exist check

* feat: add Metadata column

* refactor: only use cluster in DatabaseDiscoveryClientImpl

* feat: add metadata config in code

-Dapollo.service.registry.metadata.a=1
-Dapollo.service.registry.metadata.isAutoRegister=true

generate {"a":"1","isAutoRegister":"true"} in database

* test: add tests of registry

* docs: Enable database-discovery to replace built-in eureka

* Update CHANGES.md

* room -> cluster

Co-authored-by: Jason Song <[email protected]>

* room -> cluster

* feat: when database fail, database-discovery still be useful. by decorator pattern, memory cache

* test: fix when DatabaseDiscoveryClient wrap by decorator

Co-authored-by: Jason Song <[email protected]>
  • Loading branch information
Anilople and nobodyiam authored Oct 23, 2022
1 parent f8d35bd commit 22e6422
Show file tree
Hide file tree
Showing 40 changed files with 2,685 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Apollo 2.1.0
* [Move apollo-core, apollo-client, apollo-mockserver, apollo-openapi and apollo-client-config-data to apollo-java repo](https://github.com/apolloconfig/apollo/pull/4594)
* [fix get the openapi interface that contains namespace information for deleted items](https://github.com/apolloconfig/apollo/pull/4596)
* [A user-friendly config management page for apollo portal](https://github.com/apolloconfig/apollo/pull/4592)
* [feat: support use database as a registry](https://github.com/apolloconfig/apollo/pull/4595)

------------------
All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/11?closed=1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#
# Copyright 2022 Apollo Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
eureka.client.enabled=false
spring.cloud.discovery.enabled=false

apollo.service.registry.enabled=true
apollo.service.registry.cluster=default
apollo.service.registry.heartbeatIntervalInSecond=10

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2022 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.biz.entity;

import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
class JpaMapFieldJsonConverter implements AttributeConverter<Map<String, String>, String> {

private static final Gson GSON = new Gson();

private static final TypeToken<HashMap<String, String>> TYPE_TOKEN = new TypeToken<HashMap<String, String>>() {
};

@SuppressWarnings("unchecked")
private static final Type TYPE = TYPE_TOKEN.getType();

@Override
public String convertToDatabaseColumn(Map<String, String> attribute) {
return GSON.toJson(attribute);
}

@Override
public Map<String, String> convertToEntityAttribute(String dbData) {
return GSON.fromJson(dbData, TYPE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright 2022 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.biz.entity;

import com.ctrip.framework.apollo.biz.registry.ServiceInstance;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.Table;

/**
* use database as a registry instead of eureka, zookeeper, consul etc.
* <p>
* persist {@link ServiceInstance}
*/
@Entity
@Table(name = "ServiceRegistry")
public class ServiceRegistry {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "Id")
private long id;

@Column(name = "ServiceName", nullable = false)
private String serviceName;

/**
* @see ServiceInstance#getUri()
*/
@Column(name = "Uri", nullable = false)
private String uri;

/**
* @see ServiceInstance#getCluster()
*/
@Column(name = "Cluster", nullable = false)
private String cluster;

@Column(name = "Metadata", nullable = false)
@Convert(converter = JpaMapFieldJsonConverter.class)
private Map<String, String> metadata;

@Column(name = "DataChange_CreatedTime", nullable = false)
private LocalDateTime dataChangeCreatedTime;

/**
* modify by heartbeat
*/
@Column(name = "DataChange_LastTime", nullable = false)
private LocalDateTime dataChangeLastModifiedTime;

@PrePersist
protected void prePersist() {
if (this.dataChangeCreatedTime == null) {
dataChangeCreatedTime = LocalDateTime.now();
}
if (this.dataChangeLastModifiedTime == null) {
dataChangeLastModifiedTime = dataChangeCreatedTime;
}
}

@Override
public String toString() {
return "Registry{" +
"id=" + id +
", serviceName='" + serviceName + '\'' +
", uri='" + uri + '\'' +
", cluster='" + cluster + '\'' +
", metadata='" + metadata + '\'' +
", dataChangeCreatedTime=" + dataChangeCreatedTime +
", dataChangeLastModifiedTime=" + dataChangeLastModifiedTime +
'}';
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getServiceName() {
return serviceName;
}

public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}

public String getUri() {
return uri;
}

public void setUri(String uri) {
this.uri = uri;
}

public String getCluster() {
return cluster;
}

public void setCluster(String cluster) {
this.cluster = cluster;
}

public Map<String, String> getMetadata() {
return metadata;
}

public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}

public LocalDateTime getDataChangeCreatedTime() {
return dataChangeCreatedTime;
}

public void setDataChangeCreatedTime(LocalDateTime dataChangeCreatedTime) {
this.dataChangeCreatedTime = dataChangeCreatedTime;
}

public LocalDateTime getDataChangeLastModifiedTime() {
return dataChangeLastModifiedTime;
}

public void setDataChangeLastModifiedTime(LocalDateTime dataChangeLastModifiedTime) {
this.dataChangeLastModifiedTime = dataChangeLastModifiedTime;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2022 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.biz.registry;

import com.ctrip.framework.apollo.biz.registry.configuration.support.ApolloServiceRegistryProperties;
import java.util.List;

/**
* @see org.springframework.cloud.client.discovery.DiscoveryClient
*/
public interface DatabaseDiscoveryClient {

/**
* find by {@link ApolloServiceRegistryProperties#getServiceName()},
* then filter by {@link ApolloServiceRegistryProperties#getCluster()}
*
* @return empty list if there is no instance
*/
List<ServiceInstance> getInstances(String serviceName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2022 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.biz.registry;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
* decorator pattern
* <p>
* when database crash, even cannot register self instance to database,
* <p>
* this decorator will ensure return's result contains self instance.
*/
public class DatabaseDiscoveryClientAlwaysAddSelfInstanceDecoratorImpl
implements DatabaseDiscoveryClient {

private final DatabaseDiscoveryClient delegate;

private final ServiceInstance selfInstance;

public DatabaseDiscoveryClientAlwaysAddSelfInstanceDecoratorImpl(
DatabaseDiscoveryClient delegate,
ServiceInstance selfInstance
) {
this.delegate = delegate;
this.selfInstance = selfInstance;
}

static boolean containSelf(List<ServiceInstance> serviceInstances, ServiceInstance selfInstance) {
final String selfServiceName = selfInstance.getServiceName();
final URI selfUri = selfInstance.getUri();
final String cluster = selfInstance.getCluster();
for (ServiceInstance serviceInstance : serviceInstances) {
if (Objects.equals(selfServiceName, serviceInstance.getServiceName())) {
if (Objects.equals(selfUri, serviceInstance.getUri())) {
if (Objects.equals(cluster, serviceInstance.getCluster())) {
return true;
}
}
}
}
return false;
}

/**
* if the serviceName is same with self, always return self's instance
* @return never be empty list when serviceName is same with self
*/
@Override
public List<ServiceInstance> getInstances(String serviceName) {
if (Objects.equals(serviceName, this.selfInstance.getServiceName())) {
List<ServiceInstance> serviceInstances = this.delegate.getInstances(serviceName);
if (containSelf(serviceInstances, this.selfInstance)) {
// contains self instance already
return serviceInstances;
}

// add self instance to result
List<ServiceInstance> result = new ArrayList<>(serviceInstances.size() + 1);
result.add(this.selfInstance);
result.addAll(serviceInstances);
return result;
} else {
return this.delegate.getInstances(serviceName);
}
}
}
Loading

0 comments on commit 22e6422

Please sign in to comment.