有没有任何框架来处理REST客户端服务器列表?

6

这个想法是REST客户端可以配置REST服务器列表,因此服务器列表将以轮询的方式在REST客户端中循环。

例如,REST客户端应用程序。我将配置服务器列表(REST_SERVER1、REST_SERVER2、REST_SERVER3)

1 request -> REST_SERVER1
2 request -> REST_SERVER2
3 request  -> REST_SERVER3
4 request -> REST_SERVER1

我搜索了很多,但没有找到一个合适的框架来支持这个功能。


4
这是一件可以通过使用负载均衡器在物理服务器层面轻松处理的事情。不是吗? - Manish
2
在你的情况下,你认为什么是失败? - Jerome Louvel
@Manish 嗯,这更多是临时性的。我们会根据季节和灾难恢复服务器不时地更改服务器。因此,理想情况下,我希望 REST 客户端(即应用程序服务器)保持不变,并能够使用属性文件中的活动服务器列表。 - Njax3SmmM2x2a0Zf7Hpd
@Strelok 确实如此。不幸的是,我们无法再控制这些服务器了。这已经超出了我们的掌控范围,而且他们也不想这样做。因此,我们的 Rest 客户端应该配置为使用给定的服务器列表。我们别无选择,只能自己处理它。 - Njax3SmmM2x2a0Zf7Hpd
1
作为一个想法,你可以通过你的服务器代理请求到他们的服务器(如果你喜欢的话,可以轮流进行)。所以基本上你总是向你的服务器发送请求,但是你的服务器只是从你可配置的列表中查询他们的服务器。 - Strelok
显示剩余7条评论
2个回答

6

我建议你将客户端包装起来,然后重复请求直到成功为止。这样你就可以清晰地跟踪哪些服务器正在工作。下面是我从我们系统中改编的代码...

import java.io.IOException;
import java.net.URL;
import java.util.Calendar;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;

public class RestClient {

    private final String mediaType = "application/json";
    private RestServer[] servers = new RestServer[] {new RestServer("server1", 8080), new RestServer("server2", 8080)};

    protected RestClient() {
    }

    protected ClientResponse post(String methodUrl, Object postData) throws IOException {
        return doRequest(methodUrl, postData, true);
    }

    protected ClientResponse get(String methodUrl) throws IOException {
        return doRequest(methodUrl, null, false);
    }

    private ClientResponse doRequest(String methodUrl, Object postData, boolean isPost) throws IOException {

        Client client = Client.create();

        for (RestServer restServer : servers) {

            if (!restServer.shouldTry()) {
                System.out.println(restServer + " not ready");
                continue;
            }

            System.out.println("Trying with " + restServer);

            try {
                URL url = new URL("http", restServer.getHost(), restServer.getPort(), '/' + methodUrl);
                WebResource webResource = client.resource(url.toString());

                System.out.println("Calling " + url);
                ClientResponse response = isPost
                    ? webResource.type(mediaType).post(ClientResponse.class, postData)
                    : webResource.type(mediaType).get(ClientResponse.class);

                if (response.getStatus() < 300) {
                    restServer.succeeded();
                    return response;
                }

                restServer.failed();

            } catch (Exception ex) {
                System.out.println(restServer + " failed with exception " + ex.getMessage());
                restServer.failed();
            }
        }

        // No servers worked
        return null;
    }
}

class RestServer {

    private final int TIME_TO_WAIT_BEFORE_TRYING_AGAIN = 1000 * 30; // 30 seconds
    private String host;
    private int port;
    private Calendar lastAttempted;
    private boolean lastCallFailed;

    public RestServer(String host, int port) {
        this.host = host;
        this.port = port;
        lastAttempted = Calendar.getInstance();
    }

    public String getHost() {
        return host;
    }

    public int getPort() {
        return port;
    }

    public void failed() {
        lastCallFailed = true;
        lastAttempted = Calendar.getInstance();
    }

    public void succeeded() {
        lastCallFailed = false;
        lastAttempted = Calendar.getInstance();
    }

    public boolean shouldTry() {
        if (!lastCallFailed)
            return true;

        return Calendar.getInstance().compareTo(lastAttempted) > TIME_TO_WAIT_BEFORE_TRYING_AGAIN;
    }

    @Override
    public String toString() {
        return new StringBuilder(host).append(':').append(port).toString();
    }
}

1

对于担心添加大量组件会使应用程序设置复杂的人来说,我可能会考虑使用一个快速而简单的纯Java解决方案。

这里有一个有趣的东西,我使用Spring的RestTemplate想出来的。如果您熟悉拦截器、切面和其他可以包装方法调用的内容,您可以将这些原则应用于所有不同的RestTemplate REST调用。请参见RestTemplate javadoc

import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.*;
import org.springframework.web.util.UriTemplate;
import org.springframework.web.util.UriUtils;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

public class Stuff {

    // Make this configurable.
    Collection<String> serverList;
    // or do something a little smarter, like this interface. I'll use this in this example.
    ServerLookup serverLookup;

    interface ServerLookup {
        Iterator<String> getValidServerListIterator();
        void markUnreachableServer(String url);
    }

    // Do it externally around RestTemplate...
    @Test
    public void testNormalRestTemplate() throws Exception {
        RestTemplate restTemplate = new RestTemplate();
        Iterator<String> serverIterator = serverLookup.getValidServerListIterator();
        while (serverIterator.hasNext()) {
            String server = serverIterator.next();
            try {
                Object obj = restTemplate.getForObject(server + "/objectIdentifier/511", Object.class);
                break;
            } catch (ResourceAccessException e) {
                serverLookup.markUnreachableServer(server);
            }
        }
    }

    // or you can try to 'enhance' RestTemplate to contain the retry logic within. It's a bit hacky, but more fun.
    @Test
    public void testMyRestTemplate() {
        RestTemplate rt = new MyRestTemplate();
        Object obj = rt.getForObject("/objectIdentifier/511", Object.class);
        rt.delete("/objectIdentifier/511");
    }

    // Here's a way to (hackily) augment RestTemplate with retry functionality
    class MyRestTemplate extends RestTemplate {

        // Unfortunately RestTemplate probably wasn't designed for much extensibility. URI objects can't be made from
        // URL fragments, so these two methods are the 'furthest in' that we can override and cover all RestTemplate
        // REST methods.
        @Override
        public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
                             ResponseExtractor<T> responseExtractor, Object... urlVariables) throws RestClientException {

            Iterator<String> serverIterator = serverLookup.getValidServerListIterator();
            while (serverIterator.hasNext()) {
                String server = serverIterator.next();
                // prefix the URL fragment passed in with a server
                String fullUrl = server + url;
                UriTemplate uriTemplate = new HttpUrlTemplate(fullUrl);
                URI expanded = uriTemplate.expand(urlVariables);
                try {
                    return doExecute(expanded, method, requestCallback, responseExtractor);
                } catch (ResourceAccessException e) {
                    serverLookup.markUnreachableServer(server);
                }
            }
            throw new RuntimeException("Unable to reach any servers in the server list for " + url);
        }

        @Override
        public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
                             ResponseExtractor<T> responseExtractor, Map<String, ?> urlVariables) throws RestClientException {

            Iterator<String> serverIterator = serverLookup.getValidServerListIterator();
            while (serverIterator.hasNext()) {
                String server = serverIterator.next();
                // prefix the URL fragment passed in with a server
                String fullUrl = server + url;
                UriTemplate uriTemplate = new HttpUrlTemplate(fullUrl);
                URI expanded = uriTemplate.expand(urlVariables);
                try {
                    return doExecute(expanded, method, requestCallback, responseExtractor);
                } catch (ResourceAccessException e) {
                    serverLookup.markUnreachableServer(server);
                }
            }
            throw new RuntimeException("Unable to reach any servers in the server list for " + url);
        }

        /** Exact duplicate of the inner class of RestTemplate. Can not touch privates. */
        class HttpUrlTemplate extends UriTemplate {
            public HttpUrlTemplate(String uriTemplate) {
                super(uriTemplate);
            }

            @Override
            protected URI encodeUri(String uri) {
                try {
                    String encoded = UriUtils.encodeHttpUrl(uri, "UTF-8");
                    return new URI(encoded);
                }
                catch (UnsupportedEncodingException ex) {
                    // should not happen, UTF-8 is always supported
                    throw new IllegalStateException(ex);
                }
                catch (URISyntaxException ex) {
                    throw new IllegalArgumentException("Could not create HTTP URL from [" + uri + "]: " + ex, ex);
                }
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接