擼一擼Spring Cloud Ribbon的原理

来源:https://www.cnblogs.com/kongxianghai/archive/2018/02/21/8445030.html
-Advertisement-
Play Games

說起負載均衡一般都會想到服務端的負載均衡,常用產品包括LBS硬體或雲服務、Nginx等,都是耳熟能詳的產品。 而Spring Cloud提供了讓服務調用端具備負載均衡能力的Ribbon,通過和Eureka的緊密結合,不用在服務集群內再架設負載均衡服務,很大程度簡化了服務集群內的架構。 具體也不想多寫 ...


說起負載均衡一般都會想到服務端的負載均衡,常用產品包括LBS硬體或雲服務、Nginx等,都是耳熟能詳的產品。

而Spring Cloud提供了讓服務調用端具備負載均衡能力的Ribbon,通過和Eureka的緊密結合,不用在服務集群內再架設負載均衡服務,很大程度簡化了服務集群內的架構。

具體也不想多寫虛的介紹,反正哪裡都能看得到相關的介紹。

直接開擼代碼,通過代碼來看Ribbon是如何實現的。

 

配置

詳解:

1.RibbonAutoConfiguration配置生成RibbonLoadBalancerClient實例。

代碼位置:

spring-cloud-netflix-core-1.3.5.RELEASE.jar
org.springframework.cloud.netflix.ribbon
RibbonAutoConfiguration.class

@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties(RibbonEagerLoadProperties.class)
public class RibbonAutoConfiguration {

    //

    @Bean
    @ConditionalOnMissingBean(LoadBalancerClient.class)
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(springClientFactory());
    }

        //
}

先看配置條件項,RibbonAutoConfiguration配置必須在LoadBalancerAutoConfiguration配置前執行,因為在LoadBalancerAutoConfiguration配置中會使用RibbonLoadBalancerClient實例。

RibbonLoadBalancerClient繼承自LoadBalancerClient介面,是負載均衡客戶端,也是負載均衡策略的調用方。

 

2.LoadBalancerInterceptorConfig配置生成:
1).負載均衡攔截器LoadBalancerInterceptor實例
包含:
  LoadBalancerClient實現類的RibbonLoadBalancerClient實例
  負載均衡的請求創建工廠LoadBalancerRequestFactory:實例
2).RestTemplate自定義的RestTemplateCustomizer實例

代碼位置:

spring-cloud-commons-1.2.4.RELEASE.jar
org.springframework.cloud.client.loadbalancer
LoadBalancerAutoConfiguration.class

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    //

    @Bean
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory loadBalancerRequestFactory(
            LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
    }

    @Configuration
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    static class LoadBalancerInterceptorConfig {
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return new RestTemplateCustomizer() {
                @Override
                public void customize(RestTemplate restTemplate) {
                    List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                            restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }
    }

    //

}

先看配置條件項:

要求在項目環境中必須要有RestTemplate類。

要求必須要有LoadBalancerClient介面的實現類的實例,也就是上一步生成的RibbonLoadBalancerClient。

 

3.通過上面一步創建的RestTemplateCustomizer配置所有RestTemplate實例,就是將負載均衡攔截器設置給RestTemplate實例。

@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    //

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
            final List<RestTemplateCustomizer> customizers) {
        return new SmartInitializingSingleton() {
            @Override
            public void afterSingletonsInstantiated() {
                for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                    for (RestTemplateCustomizer customizer : customizers) {
                        customizer.customize(restTemplate);
                    }
                }
            }
        };
    }

    //

    @Configuration
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    static class LoadBalancerInterceptorConfig {
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return new RestTemplateCustomizer() {
                @Override
                public void customize(RestTemplate restTemplate) {
                    List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                            restTemplate.getInterceptors());
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }
    }

    //
}

restTemplate.setInterceptors(list)這個地方就是註入負載均衡攔截器的地方LoadBalancerInterceptor。

從這個地方實際上也可以猜出來,RestTemplate可以通過註入的攔截器來構建相應的請求實現負載均衡。

也能看出來可以自定義攔截器實現其他目的。

 

4.RibbonClientConfiguration配置生成ZoneAwareLoadBalancer實例

代碼位置:

spring-cloud-netflix-core-1.3.5.RELEASE.jar
org.springframework.cloud.netflix.ribbon
RibbonClientConfiguration.class

@SuppressWarnings("deprecation")
@Configuration
@EnableConfigurationProperties
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {

    //

    @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
            ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
            IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
        if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
            return this.propertiesFactory.get(ILoadBalancer.class, config, name);
        }
        return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
                serverListFilter, serverListUpdater);
    }

    //
}

ZoneAwareLoadBalancer繼承自ILoadBalancer介面,該介面有一個方法:

    /**
     * Choose a server from load balancer.
     * 
     * @param key An object that the load balancer may use to determine which server to return. null if 
     *         the load balancer does not use this parameter.
     * @return server chosen
     */
    public Server chooseServer(Object key);

ZoneAwareLoadBalancer就是一個具體的負載均衡實現類,也是預設的負載均衡類,通過對chooseServer方法的實現選取某個服務實例。

 

攔截&請求

1.使用RestTemplate進行Get、Post等各種請求,都是通過doExecute方法實現

代碼位置:
spring-web-4.3.12.RELEASE.jar
org.springframework.web.client
RestTemplate.class

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {

    //

    protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
            ResponseExtractor<T> responseExtractor) throws RestClientException {

        Assert.notNull(url, "'url' must not be null");
        Assert.notNull(method, "'method' must not be null");
        ClientHttpResponse response = null;
        try {
            ClientHttpRequest request = createRequest(url, method);
            if (requestCallback != null) {
                requestCallback.doWithRequest(request);
            }
            response = request.execute();
            handleResponse(url, method, response);
            if (responseExtractor != null) {
                return responseExtractor.extractData(response);
            }
            else {
                return null;
            }
        }
        catch (IOException ex) {
            String resource = url.toString();
            String query = url.getRawQuery();
            resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
            throw new ResourceAccessException("I/O error on " + method.name() +
                    " request for \"" + resource + "\": " + ex.getMessage(), ex);
        }
        finally {
            if (response != null) {
                response.close();
            }
        }
    }

    //

}

支持的各種http請求方法最終都是調用doExecute方法,該方法內調用創建方法創建請求實例,並執行請求得到響應對象。

 

2.生成請求實例創建工廠

上一步代碼中,調用createRequest方法創建請求實例,這個方法是定義在父類中。

先整理出主要的繼承關係:

 createRequest方法實際是定義在HttpAccessor抽象類中。

public abstract class HttpAccessor {

    private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();

    public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
        Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
        this.requestFactory = requestFactory;
    }

    public ClientHttpRequestFactory getRequestFactory() {
        return this.requestFactory;
    }

    protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
        ClientHttpRequest request = getRequestFactory().createRequest(url, method);
        if (logger.isDebugEnabled()) {
            logger.debug("Created " + method.name() + " request for \"" + url + "\"");
        }
        return request;
    }

}

在createRequest方法中調用getRequestFactory方法獲得請求實例創建工廠,實際上getRequestFactory並不是當前HttpAccessor類中定義的,而是在子類InterceptingHttpAccessor中定義的。

public abstract class InterceptingHttpAccessor extends HttpAccessor {

    private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();

    public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
        this.interceptors = interceptors;
    }

    public List<ClientHttpRequestInterceptor> getInterceptors() {
        return interceptors;
    }

    @Override
    public ClientHttpRequestFactory getRequestFactory() {
        ClientHttpRequestFactory delegate = super.getRequestFactory();
        if (!CollectionUtils.isEmpty(getInterceptors())) {
            return new InterceptingClientHttpRequestFactory(delegate, getInterceptors());
        }
        else {
            return delegate;
        }
    }

}

在這裡做了個小動作,首先還是通過HttpAccessor類創建並獲得SimpleClientHttpRequestFactory工廠,這個工廠主要就是在沒有攔截器的時候創建基本請求實例。

其次,在有攔截器註入的情況下,創建InterceptingClientHttpRequestFactory工廠,該工廠就是創建帶攔截器的請求實例,因為註入了負載均衡攔截器,所以這裡就從InterceptingClientHttpRequestFactory工廠創建。

 

3.通過工廠創建請求實例

創建實例就看工廠的createRequest方法。

public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {

    private final List<ClientHttpRequestInterceptor> interceptors;

    public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory,
            List<ClientHttpRequestInterceptor> interceptors) {

        super(requestFactory);
        this.interceptors = (interceptors != null ? interceptors : Collections.<ClientHttpRequestInterceptor>emptyList());
    }


    @Override
    protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
        return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
    }

}

就是new了個InterceptingClientHttpRequest實例,並且把攔截器、基本請求實例創建工廠註進去。

 

4.請求實例調用配置階段註入的負載均衡攔截器的攔截方法intercept

可從第1步看出,創建完請求實例後,通過執行請求實例的execute方法執行請求。

ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
    requestCallback.doWithRequest(request);
}
response = request.execute();

實際請求實例是InterceptingClientHttpRequest,execute實際是在它的父類中。

類定義位置:

spring-web-4.3.12.RELEASE.jar
org.springframework.http.client
InterceptingClientHttpRequest.class

看一下它們的繼承關係。

 在execute方法中實際調用了子類實現的executeInternal方法。

public abstract class AbstractClientHttpRequest implements ClientHttpRequest {

    private final HttpHeaders headers = new HttpHeaders();

    private boolean executed = false;

    @Override
    public final HttpHeaders getHeaders() {
        return (this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
    }

    @Override
    public final OutputStream getBody() throws IOException {
        assertNotExecuted();
        return getBodyInternal(this.headers);
    }

    @Override
    public final ClientHttpResponse execute() throws IOException {
        assertNotExecuted();
        ClientHttpResponse result = executeInternal(this.headers);
        this.executed = true;
        return result;
    }

    protected void assertNotExecuted() {
        Assert.state(!this.executed, "ClientHttpRequest already executed");
    }

    protected abstract OutputStream getBodyInternal(HttpHeaders headers) throws IOException;

    protected abstract ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException;

}

其實就是InterceptingClientHttpRequest類的executeInternal方法,其中,又調用了一個執行器InterceptingRequestExecution的execute,通關判斷如果有攔截器註入進來過,就調用攔截器的intercept方法。

這裡的攔截器實際上就是在配置階段註入進RestTemplate實例的負載均衡攔截器LoadBalancerInterceptor實例,可參考上面配置階段的第2步。

class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest {

    //

    @Override
    protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
        InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
        return requestExecution.execute(this, bufferedOutput);
    }


    private class InterceptingRequestExecution implements ClientHttpRequestExecution {

        private final Iterator<ClientHttpRequestInterceptor> iterator;

        public InterceptingRequestExecution() {
            this.iterator = interceptors.iterator();
        }

        @Override
        public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
            if (this.iterator.hasNext()) {
                ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
                return nextInterceptor.intercept(request, body, this);
            }
            else {
                ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
                for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
                    List<String> values = entry.getValue();
                    for (String value : values) {
                        delegate.getHeaders().add(entry.getKey(), value);
                    }
                }
                if (body.length > 0) {
                    StreamUtils.copy(body, delegate.getBody());
                }
                return delegate.execute();
            }
        }
    }

}

 

5.負載均衡攔截器調用負載均衡客戶端

在負載均衡攔截器LoadBalancerInterceptor類的intercept方法中,又調用了負載均衡客戶端LoadBalancerClient實現類的execute方法。

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private LoadBalancerClient loadBalancer;
    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        // for backwards compatibility
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        final URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
    }
}

在配置階段的第1步,可以看到實現類是RibbonLoadBalancerClient。

 

6.負載均衡客戶端調用負載均衡策略選取目標服務實例併發起請求

在RibbonLoadBalancerClient的第一個execute方法以及getServer方法中可以看到,實際上是通過ILoadBalancer的負載均衡器實現類作的chooseServer方法選取一個服務,交給接下來的請求對象發起一個請求。

這裡的負載均衡實現類預設是ZoneAwareLoadBalancer區域感知負載均衡器實例,其內部通過均衡策略選擇一個服務。

ZoneAwareLoadBalancer的創建可以參考配置階段的第4步。

public class RibbonLoadBalancerClient implements LoadBalancerClient {
    @Override
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        Server server = getServer(loadBalancer);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }
        RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
                serviceId), serverIntrospector(serviceId).getMetadata(server));

        return execute(serviceId, ribbonServer, request);
    }

    @Override
    public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
        Server server = null;
        if(serviceInstance instanceof RibbonServer) {
            server = ((RibbonServer)serviceInstance).getServer();
        }
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }

        RibbonLoadBalancerContext context = this.clientFactory
                .getLoadBalancerContext(serviceId);
        RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

        try {
            T returnVal = request.apply(serviceInstance);
            statsRecorder.recordStats(returnVal);
            return returnVal;
        }
        // catch IOException and rethrow so RestTemplate behaves correctly
        catch (IOException ex) {
            statsRecorder.recordStats(ex);
            throw ex;
        }
        catch (Exception ex) {
            statsRecorder.recordStats(ex);
            ReflectionUtils.rethrowRuntimeException(ex);
        }
        return null;
    }
       
    //

    protected Server getServer(ILoadBalancer loadBalancer) {
        if (loadBalancer == null) {
            return null;
        }
        return loadBalancer.chooseServer("default"); // TODO: better handling of key
    }

    protected ILoadBalancer getLoadBalancer(String serviceId) {
        return this.clientFactory.getLoadBalancer(serviceId);
    }

    public static class RibbonServer implements ServiceInstance {
        private final String serviceId;
        private final Server server;
        private final boolean secure;
        private Map<String, String> metadata;

        public RibbonServer(String serviceId, Server server) {
            this(serviceId, server, false, Collections.<String, String> emptyMap());
        }

        public RibbonServer(String serviceId, Server server, boolean secure,
                Map<String, String> metadata) {
            this.serviceId = serviceId;
            this.server = server;
            this.secure = secure;
            this.metadata = metadata;
        }

        //
    }

}

 

代碼擼完,總結下。

普通使用RestTemplate請求其他服務時,內部使用的就是常規的http請求實例發送請求。

為RestTemplate增加了@LoanBalanced 註解後,實際上通過配置,為RestTemplate註入負載均衡攔截器,讓負載均衡器選擇根據其對應的策略選擇合適的服務後,再發送請求。

 

End


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 該模塊作用是完成Python數值和C語言結構體的Python字元串形式間的轉換。這可以用於處理存儲在文件中或從網路連接中存儲的二進位數據,以及其他數據源。 用途: 在Python基本數據類型和二進位數據之間進行轉換 模塊提供了用於在位元組字元串和Python原生數據類型之間轉換函數,比如數字和字元串。 ...
  • Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 5934 Accepted Submission(s): 1845 Problem Descrip ...
  • 現代信息系統應該是避不開大數據處理的。作為一個通用的系統集成工具也必須具備大數據存儲和讀取能力。cassandra是一種分散式的資料庫,具備了分散式資料庫高可用性(high-availability)特性,對於一個實時大型分散式集成系統來說是核心支柱。與傳統的關係資料庫對比,cassandra從數據 ...
  • 1 學習計劃 1、實現區域導入功能 n OCUpload一鍵上傳插件使用 n 將文件上傳到Action n POI簡介 n 使用POI解析Excel文件 n 完成資料庫操作 n 使用pinyin4J生成簡碼和城市編碼 2、區域分頁查詢 n 頁面調整 n 服務端實現 3、重構分頁代碼 n BaseAc ...
  • OGNL取值範圍分兩部分,root、Context兩部分 可以放置任何對象作為ROOT,CONTEXT中必須是Map鍵值對 示例: 準備工作: User類: package bean; public class User { private String name; private Integer ...
  • Swagger 是一個規範和完整的框架,用於生成、描述、調用和可視化 RESTful 風格的 Web 服務。總體目標是使客戶端和文件系統作為伺服器以同樣的速度來更新。文件的方法,參數和模型緊密集成到伺服器端的代碼,允許API與介面方法,參數等保存同步,大大減少了介面開發人員的工作量.這個例子是我本地 ...
  • 古人雲,紙上得來終覺淺,絕知此事要躬行。翻譯過來,就是學東西哪有不踩坑的。 因為工作原因要折騰Storm,環境和第一個例子折騰了好久,搞完了回頭看,吐血的簡單。 Storm有兩種模式,單機和集群。入門當然選單機。 1、安裝JDK,配置Eclipse環境 2、建立一個Maven工程,在pom.xml加 ...
  • 字元串生成器 作用:創建成功的字元串對象,其長度是固定的,內容不能改變和編譯。雖然使用+可以達到附加新字元串的目的,但是這樣就會產生新的string實例,如果重覆對字元串進行修改,將極大增加系統的開銷。使用字元串生成器的目的就是提高改變字元串的效率。 添加 append() 插入 insert() ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...