<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>专注于Java、Golang、软件架构、项目管理</title><description>希望每天快乐！Talk is cheap, show me the bug/code.</description><link>https://leyou240.github.io</link><item><title>Pingora网关</title><link>https://leyou240.github.io/blog/pingora01</link><guid isPermaLink="true">https://leyou240.github.io/blog/pingora01</guid><description>反向代理与缓存核心代码</description><pubDate>Fri, 24 May 2024 09:58:43 GMT</pubDate><content:encoded>&lt;p&gt;欣闻cloudflare开源了他们的反向代理或者说微服务网关组件 Pingora -  https://github.com/cloudflare/pingora&lt;/p&gt;
&lt;p&gt;如下，是pingora网关反向代理与缓存核心代码&lt;/p&gt;
&lt;p&gt;pingora-proxy/src/proxy_trait.rs v0.3.0&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use super::*;
use pingora_cache::{key::HashBinary, CacheKey, CacheMeta, RespCacheable, RespCacheable::*};
use std::time::Duration;

/// The interface to control the HTTP proxy
/// 控制HTTP代理的接口
/// The methods in [ProxyHttp] are filters/callbacks which will be performed on all requests at their
/// particular stage (if applicable).
/// [ProxyHttp] 中的方法是过滤器/回调，它们将在特定阶段对所有请求执行（如果适用）。
/// If any of the filters returns [Result::Err], the request will fail, and the error will be logged.
/// 如果任何过滤器返回错误，请求就会失败，错误会被日志记录
#[cfg_attr(not(doc_async_trait), async_trait)]
pub trait ProxyHttp {
    /// The per request object to share state across the different filters
    /// 每个请求对象在不同的过滤器之间共享状态
    type CTX;

    /// Define how the `ctx` should be created.
    /// 创建上下文对象的方法
    fn new_ctx(&amp;#x26;self) -&gt; Self::CTX;

    /// Define where the proxy should send the request to.
    /// 定义代理应将请求发生到哪个后端服务器。
    /// The returned [HttpPeer] contains the information regarding where and how this request should
    /// be forwarded to.
    async fn upstream_peer(
        &amp;#x26;self,
        session: &amp;#x26;mut Session,
        ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;Box&amp;#x3C;HttpPeer&gt;&gt;;

    /// Set up downstream modules.
    ///
    /// In this phase, users can add or configure [HttpModules] before the server starts up.
    ///
    /// In the default implementation of this method, [ResponseCompressionBuilder] is added
    /// and disabled.
    /// 在默认实现的方法中，返回压缩被添加并设置为无效
    fn init_downstream_modules(&amp;#x26;self, modules: &amp;#x26;mut HttpModules) {
        // Add disabled downstream compression module by default
        modules.add_module(ResponseCompressionBuilder::enable(0));
    }

    /// Handle the incoming request.
    /// 处理进来的请求
    /// In this phase, users can parse, validate, rate limit, perform access control and/or
    /// return a response for this request.
    /// 在此阶段，用户可以解析、验证、限速、执行访问控制和/或返回此请求的响应。
    /// If the user already sent a response to this request, an `Ok(true)` should be returned so that
    /// the proxy would exit. The proxy continues to the next phases when `Ok(false)` is returned.
    /// 如果用户想对请求做拦截和处理，处理后返回Ok(true)。否则返回Ok(false)，让代理走后续流程
    /// By default this filter does nothing and returns `Ok(false)`.
    async fn request_filter(&amp;#x26;self, _session: &amp;#x26;mut Session, _ctx: &amp;#x26;mut Self::CTX) -&gt; Result&amp;#x3C;bool&gt;
    where
        Self::CTX: Send + Sync,
    {
        Ok(false)
    }

    /// Handle the incoming request before any downstream module is executed.
    /// 在执行任何下游模块之前处理传入的请求。
    /// This function is similar to [Self::request_filter()] but execute before any other logic
    /// especially the downstream modules. The main purpose of this function is to provide finer
    /// grained control of behavior of the modules.
    /// 此函数类似于 [Self::request_filter()]，但在任何其他逻辑（尤其是下游模块）之前执行。此函数的主要目的是提供对模块行为的更细粒度的控制。
    /// Note that because this function is executed before any module that might provide access
    /// control or rate limiting, logic should stay in request_filter() if it can in order to be
    /// protected by said modules.
    /// 请注意，由于此函数在任何可能提供访问控制或速率限制的模块之前执行，因此逻辑应该保留在 request_filter() 中（如果可以的话），以便受到所述模块的保护。
    async fn early_request_filter(&amp;#x26;self, _session: &amp;#x26;mut Session, _ctx: &amp;#x26;mut Self::CTX) -&gt; Result&amp;#x3C;()&gt;
    where
        Self::CTX: Send + Sync,
    {
        Ok(())
    }

    /// Handle the incoming request body.
    /// 处理请求体
    /// This function will be called every time a piece of request body is received. The `body` is
    /// **not the entire request body**.
    /// 每次收到一段请求主体时都会调用此函数。`body` 不是整个请求主体。
    /// The async nature of this function allows to throttle the upload speed and/or executing
    /// heavy computation logic such as WAF rules on offloaded threads without blocking the threads
    /// who process the requests themselves.
    /// 该函数的异步特性允许限制上传速度和/或在卸载线程上执行繁重的计算逻辑（例如 WAF 规则），而不会阻塞处理请求本身的线程。
    async fn request_body_filter(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _body: &amp;#x26;mut Option&amp;#x3C;Bytes&gt;,
        _end_of_stream: bool,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;()&gt;
    where
        Self::CTX: Send + Sync,
    {
        Ok(())
    }

    /// This filter decides if the request is cacheable and what cache backend to use
    /// 此过滤器决定请求是否可缓存以及使用哪个缓存后端
    /// The caller can interact with `Session.cache` to enable caching.
    /// 调用者可以与`Session.cache`交互来启用缓存。
    /// By default this filter does nothing which effectively disables caching.
    // Ideally only session.cache should be modified, TODO: reflect that in this interface
    // 默认情况下，此过滤器不执行任何操作，从而有效地禁用缓存。理想情况下，只应修改 session.cache
    fn request_cache_filter(&amp;#x26;self, _session: &amp;#x26;mut Session, _ctx: &amp;#x26;mut Self::CTX) -&gt; Result&amp;#x3C;()&gt; {
        Ok(())
    }

    /// This callback generates the cache key
    /// 在此生成缓存key
    /// This callback is called only when cache is enabled for this request
    /// 仅在启用缓存的时候才调用此方法
    /// By default this callback returns a default cache key generated from the request.
    /// 默认情况下，此回调返回从请求生成的默认缓存键。
    fn cache_key_callback(&amp;#x26;self, session: &amp;#x26;Session, _ctx: &amp;#x26;mut Self::CTX) -&gt; Result&amp;#x3C;CacheKey&gt; {
        let req_header = session.req_header();
        Ok(CacheKey::default(req_header))
    }

    /// This callback is invoked when a cacheable response is ready to be admitted to cache
    /// 当可缓存响应准备好被缓存时，将调用此回调
    fn cache_miss(&amp;#x26;self, session: &amp;#x26;mut Session, _ctx: &amp;#x26;mut Self::CTX) {
        session.cache.cache_miss();
    }

    /// This filter is called after a successful cache lookup and before the cache asset is ready to
    /// be used.
    /// 缓存命中之后，调用此方法
    /// This filter allow the user to log or force expire the asset.
    /// flex purge, other filtering, returns whether asset is should be force expired or not
    /// 在此方法中允许用户记录或者强制资源过期，返回资源是否需要被强制过期
    async fn cache_hit_filter(
        &amp;#x26;self,
        _session: &amp;#x26;Session,
        _meta: &amp;#x26;CacheMeta,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;bool&gt;
    where
        Self::CTX: Send + Sync,
    {
        Ok(false)
    }

    /// Decide if a request should continue to upstream after not being served from cache.
    /// 决定请求在未从缓存中获取服务后是否应继续向上游传输。
    /// returns: Ok(true) if the request should continue, Ok(false) if a response was written by the
    /// callback and the session should be finished, or an error
    /// 如果请求应该继续，则返回 Ok（true）；如果回调写入响应并且会话应该完成，则返回 Ok（false）；或者返回错误
    /// This filter can be used for deferring checks like rate limiting or access control to when they
    /// actually needed after cache miss.
    /// 该过滤器可用于将速率限制或访问控制等检查推迟到缓存未命中后真正需要时进行。
    async fn proxy_upstream_filter(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;bool&gt;
    where
        Self::CTX: Send + Sync,
    {
        Ok(true)
    }

    /// Decide if the response is cacheable
    /// 决定返回是否需要缓存
    fn response_cache_filter(
        &amp;#x26;self,
        _session: &amp;#x26;Session,
        _resp: &amp;#x26;ResponseHeader,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;RespCacheable&gt; {
        Ok(Uncacheable(NoCacheReason::Custom(&quot;default&quot;)))
    }

    /// Decide how to generate cache vary key from both request and response
    /// 决定如何根据请求和响应生成缓存变化键
    /// None means no variance is needed.
    /// None表示不需要variance
    fn cache_vary_filter(
        &amp;#x26;self,
        _meta: &amp;#x26;CacheMeta,
        _ctx: &amp;#x26;mut Self::CTX,
        _req: &amp;#x26;RequestHeader,
    ) -&gt; Option&amp;#x3C;HashBinary&gt; {
        // default to None for now to disable vary feature
        None
    }

    /// Decide if the incoming request&apos;s condition _fails_ against the cached response.
    /// 确定传入请求的条件是否与缓存的响应不符。
    /// Returning `Ok(true)` means that the response does _not_ match against the condition, and
    /// that the proxy can return `304 Not Modified` downstream.
    /// 返回“Ok(true)”意味着响应与条件不匹配，并且代理可以向下游返回“304 Not Modified”。
    /// An example is a conditional GET request with `If-None-Match: &quot;foobar&quot;`. If the cached
    /// response contains the `ETag: &quot;foobar&quot;`, then the condition fails, and `304 Not Modified`
    /// should be returned. Else, the condition passes which means the full `200 OK` response must
    /// be sent.
    /// 一个例子是带有“If-None-Match: &quot;foobar&quot;”的条件 GET 请求。如果缓存的响应包含“ETag: &quot;foobar&quot;”，则条件失败，并且应返回“304 Not Modified”。否则，条件通过，这意味着必须发送完整的“200 OK”响应。
    fn cache_not_modified_filter(
        &amp;#x26;self,
        session: &amp;#x26;Session,
        resp: &amp;#x26;ResponseHeader,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;bool&gt; {
        Ok(
            pingora_core::protocols::http::conditional_filter::not_modified_filter(
                session.req_header(),
                resp,
            ),
        )
    }

    /// Modify the request before it is sent to the upstream
    /// 在将请求发送到上游之前对其进行修改，仅能修改header
    /// Unlike [Self::request_filter()], this filter allows to change the request headers to send
    /// to the upstream.
    /// 与 [Self::request_filter()] 不同，此过滤器允许更改要发送到上游的请求头。
    async fn upstream_request_filter(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _upstream_request: &amp;#x26;mut RequestHeader,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;()&gt;
    where
        Self::CTX: Send + Sync,
    {
        Ok(())
    }

    /// Modify the response header from the upstream
    /// 修改上游的响应头
    /// The modification is before caching, so any change here will be stored in the cache if enabled.
    /// 修改是在缓存之前，因此如果启用，此处的任何更改都将存储在缓存中。
    /// Responses served from cache won&apos;t trigger this filter. If the cache needed revalidation,
    /// only the 304 from upstream will trigger the filter (though it will be merged into the
    /// cached header, not served directly to downstream).
    /// 来自缓存的响应不会触发此过滤器。如果缓存需要重新验证，则只有来自上游的 304 才会触发过滤器（尽管它将合并到缓存的标头中，而不是直接提供给下游）。
    fn upstream_response_filter(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _upstream_response: &amp;#x26;mut ResponseHeader,
        _ctx: &amp;#x26;mut Self::CTX,
    ) {
    }

    /// Modify the response header before it is send to the downstream
    /// 在发送给下游之前修改响应头
    /// The modification is after caching. This filter is called for all responses including
    /// responses served from cache.
    /// 修改是在缓存之后进行的。所有响应（包括从缓存提供的响应）都会调用此过滤器。
    async fn response_filter(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _upstream_response: &amp;#x26;mut ResponseHeader,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;()&gt;
    where
        Self::CTX: Send + Sync,
    {
        Ok(())
    }

    /// Similar to [Self::upstream_response_filter()] but for response body
    /// 类似于 [Self::upstream_response_filter()]，但用于响应主体
    /// This function will be called every time a piece of response body is received. The `body` is
    /// **not the entire response body**.
    /// 每次收到一段响应体时都会调用此函数。`body`不是整个响应体。
    fn upstream_response_body_filter(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _body: &amp;#x26;mut Option&amp;#x3C;Bytes&gt;,
        _end_of_stream: bool,
        _ctx: &amp;#x26;mut Self::CTX,
    ) {
    }

    /// Similar to [Self::upstream_response_filter()] but for response trailers
    /// 类似于 [Self::upstream_response_filter()]，但用于响应尾部
    fn upstream_response_trailer_filter(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _upstream_trailers: &amp;#x26;mut header::HeaderMap,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;()&gt; {
        Ok(())
    }

    /// Similar to [Self::response_filter()] but for response body chunks
    /// 类似于 [Self::response_filter()]，但用于响应主体块
    fn response_body_filter(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _body: &amp;#x26;mut Option&amp;#x3C;Bytes&gt;,
        _end_of_stream: bool,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;Option&amp;#x3C;Duration&gt;&gt;
    where
        Self::CTX: Send + Sync,
    {
        Ok(None)
    }

    /// Similar to [Self::response_filter()] but for response trailers.
    /// Note, returning an Ok(Some(Bytes)) will result in the downstream response
    /// trailers being written to the response body.
    /// 类似于 [Self::response_filter()]，但用于响应尾部。请注意，返回 Ok(Some(Bytes)) 将导致下游响应尾部被写入响应主体。
    /// TODO: make this interface more intuitive
    async fn response_trailer_filter(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _upstream_trailers: &amp;#x26;mut header::HeaderMap,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;Option&amp;#x3C;Bytes&gt;&gt;
    where
        Self::CTX: Send + Sync,
    {
        Ok(None)
    }

    /// This filter is called when the entire response is sent to the downstream successfully or
    /// there is a fatal error that terminate the request.
    /// 当整个响应成功发送到下游或出现终止请求的致命错误时，将调用此过滤器。
    /// An error log is already emitted if there is any error. This phase is used for collecting
    /// metrics and sending access logs.
    /// 如果发生任何错误，则会发出错误日志。此阶段用于收集指标和发送访问日志。
    async fn logging(&amp;#x26;self, _session: &amp;#x26;mut Session, _e: Option&amp;#x3C;&amp;#x26;Error&gt;, _ctx: &amp;#x26;mut Self::CTX)
    where
        Self::CTX: Send + Sync,
    {
    }

    /// A value of true means that the log message will be suppressed. The default value is false.
    /// true 表示将抑制日志消息。默认值为 false。
    fn suppress_error_log(&amp;#x26;self, _session: &amp;#x26;Session, _ctx: &amp;#x26;Self::CTX, _error: &amp;#x26;Error) -&gt; bool {
        false
    }

    /// This filter is called when there is an error **after** a connection is established (or reused)
    /// to the upstream.
    /// 当与上游建立（或重用）连接后出现错误时，将调用此过滤器。
    fn error_while_proxy(
        &amp;#x26;self,
        peer: &amp;#x26;HttpPeer,
        session: &amp;#x26;mut Session,
        e: Box&amp;#x3C;Error&gt;,
        _ctx: &amp;#x26;mut Self::CTX,
        client_reused: bool,
    ) -&gt; Box&amp;#x3C;Error&gt; {
        let mut e = e.more_context(format!(&quot;Peer: {}&quot;, peer));
        // only reused client connections where retry buffer is not truncated
        e.retry
            .decide_reuse(client_reused &amp;#x26;&amp;#x26; !session.as_ref().retry_buffer_truncated());
        e
    }

    /// This filter is called when there is an error in the process of establishing a connection
    /// to the upstream.
    /// 当与上游建立连接的过程中出现错误时，将调用此过滤器。
    /// In this filter the user can decide whether the error is retry-able by marking the error `e`.
    /// 在这个过滤器中，用户可以通过标记错误“e”来决定该错误是否可以重试。
    /// If the error can be retried, [Self::upstream_peer()] will be called again so that the user
    /// can decide whether to send the request to the same upstream or another upstream that is possibly
    /// available.
    /// 如果错误可以重试，则会再次调用 [Self::upstream_peer()]，以便用户可以决定是否将请求发送到同一个上游或可能可用的另一个上游。
    fn fail_to_connect(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _peer: &amp;#x26;HttpPeer,
        _ctx: &amp;#x26;mut Self::CTX,
        e: Box&amp;#x3C;Error&gt;,
    ) -&gt; Box&amp;#x3C;Error&gt; {
        e
    }

    /// This filter is called when the request encounters a fatal error.
    /// 当请求遇到致命错误时，将调用此过滤器。
    /// Users may write an error response to the downstream if the downstream is still writable.
    /// 如果下游仍然可写，用户可以向下游写入错误响应。
    /// The response status code of the error response maybe returned for logging purpose.
    /// 错误响应的响应状态代码可能会出于日志记录目的而返回。
    async fn fail_to_proxy(&amp;#x26;self, session: &amp;#x26;mut Session, e: &amp;#x26;Error, _ctx: &amp;#x26;mut Self::CTX) -&gt; u16
    where
        Self::CTX: Send + Sync,
    {
        let server_session = session.as_mut();
        let code = match e.etype() {
            HTTPStatus(code) =&gt; *code,
            _ =&gt; {
                match e.esource() {
                    ErrorSource::Upstream =&gt; 502,
                    ErrorSource::Downstream =&gt; {
                        match e.etype() {
                            WriteError | ReadError | ConnectionClosed =&gt; {
                                /* conn already dead */
                                0
                            }
                            _ =&gt; 400,
                        }
                    }
                    ErrorSource::Internal | ErrorSource::Unset =&gt; 500,
                }
            }
        };
        if code &gt; 0 {
            server_session.respond_error(code).await
        }
        code
    }

    /// Decide whether should serve stale when encountering an error or during revalidation
    /// 决定在遇到错误时或重新验证期间是否应提供过时的服务
    /// An implementation should follow
    /// &amp;#x3C;https://datatracker.ietf.org/doc/html/rfc9111#section-4.2.4&gt;
    /// &amp;#x3C;https://www.rfc-editor.org/rfc/rfc5861#section-4&gt;
    ///
    /// This filter is only called if cache is enabled.
    /// 仅当启用缓存时才会调用此过滤器。
    // 5xx HTTP status will be encoded as ErrorType::HTTPStatus(code)
    fn should_serve_stale(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _ctx: &amp;#x26;mut Self::CTX,
        error: Option&amp;#x3C;&amp;#x26;Error&gt;, // None when it is called during stale while revalidate
    ) -&gt; bool {
        // A cache MUST NOT generate a stale response unless
        // it is disconnected
        // or doing so is explicitly permitted by the client or origin server
        // (e.g. headers or an out-of-band contract)
        error.map_or(false, |e| e.esource() == &amp;#x26;ErrorSource::Upstream)
    }

    /// This filter is called when the request just established or reused a connection to the upstream
    /// 当请求刚刚建立或重新使用与上游的连接时，将调用此过滤器
    /// This filter allows user to log timing and connection related info.
    /// 该过滤器允许用户记录时间和连接相关的信息。
    async fn connected_to_upstream(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        _reused: bool,
        _peer: &amp;#x26;HttpPeer,
        _fd: std::os::unix::io::RawFd,
        _digest: Option&amp;#x3C;&amp;#x26;Digest&gt;,
        _ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;()&gt;
    where
        Self::CTX: Send + Sync,
    {
        Ok(())
    }

    /// This callback is invoked every time request related error log needs to be generated
    ///
    /// Users can define what is important to be written about this request via the returned string.
    fn request_summary(&amp;#x26;self, session: &amp;#x26;Session, _ctx: &amp;#x26;Self::CTX) -&gt; String {
        session.as_ref().request_summary()
    }

    /// Whether the request should be used to invalidate(delete) the HTTP cache
    /// 请求是否应该用于使HTTP缓存失效（删除）。
    /// - `true`: this request will be used to invalidate the cache.
    /// true：设置缓存失效
    /// - `false`: this request is a treated as a normal request
    fn is_purge(&amp;#x26;self, _session: &amp;#x26;Session, _ctx: &amp;#x26;Self::CTX) -&gt; bool {
        false
    }

    /// This filter is called after the proxy cache generates the downstream response to the purge
    /// request (to invalidate or delete from the HTTP cache), based on the purge status, which
    /// indicates whether the request succeeded or failed.
    /// 在代理缓存根据清除状态（指示请求是成功还是失败）生成对清除请求的下游响应（使 HTTP 缓存无效或删除）后，将调用此过滤器。
    /// The filter allows the user to modify or replace the generated downstream response.
    /// If the filter returns `Err`, the proxy will instead send a 500 response.
    /// 该过滤器允许用户修改或替换生成的下游响应。如果过滤器返回“Err”，则代理将发送 500 响应。
    fn purge_response_filter(
        &amp;#x26;self,
        _session: &amp;#x26;Session,
        _ctx: &amp;#x26;mut Self::CTX,
        _purge_status: PurgeStatus,
        _purge_response: &amp;#x26;mut std::borrow::Cow&amp;#x3C;&apos;static, ResponseHeader&gt;,
    ) -&gt; Result&amp;#x3C;()&gt; {
        Ok(())
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;pingora非常强大，据他们文章所说缓存相关的方法，被他们用来做cdn。很容易可以看出多数方法都是和缓存相关的&lt;/p&gt;
&lt;p&gt;在本文中仅讨论其反向代理、微服务网关中需要编写的核心代码。以及自己写的一些例子&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;//upstream_peer：每个代理都必须实现，每个请求都会调用到，常用来做负载均衡、灰度发布、请求计数等等
pub struct LB(Arc&amp;#x3C;LoadBalancer&amp;#x3C;RoundRobin&gt;&gt;);
#[async_trait]
impl ProxyHttp for LB {
    type CTX = ();
    fn new_ctx(&amp;#x26;self) -&gt; Self::CTX {}

    async fn upstream_peer(&amp;#x26;self, _session: &amp;#x26;mut Session, _ctx: &amp;#x26;mut ()) -&gt; Result&amp;#x3C;Box&amp;#x3C;HttpPeer&gt;&gt; {
        let upstream = self
            .0
            .select(b&quot;&quot;, 256) 
            .unwrap();

        info!(&quot;后端服务地址是: {:?}&quot;, upstream);

        let peer = Box::new(HttpPeer::new(upstream, true, &quot;&quot;.to_string()));
        Ok(peer)
    }
}
//request_filter：解析、验证、请求限速、访问控制等等。
async fn request_filter(&amp;#x26;self, session: &amp;#x26;mut Session, _ctx: &amp;#x26;mut Self::CTX) -&gt; Result&amp;#x3C;bool&gt; {
        if let Some(token) = session.req_header().headers.get(&quot;token&quot;) {
            // 业务逻辑，校验token，获得id、name等信息
            let header = session.req_header_mut();
            header.remove_header(&quot;token&quot;);
            header.insert_header(&quot;id&quot;,&quot;xxx&quot;)?;
            header.insert_header(&quot;name&quot;,&quot;yyy&quot;)?;
            return Ok(false);
        }
        Ok(true)
    }

// response_body_filter 修改响应体，一般用来做返回转换。json-&gt;xml，pb-&gt;json,json-&gt;yaml等等
fn response_body_filter(
        &amp;#x26;self,
        _session: &amp;#x26;mut Session,
        body: &amp;#x26;mut Option&amp;#x3C;Bytes&gt;,
        end_of_stream: bool,
        ctx: &amp;#x26;mut Self::CTX,
    ) -&gt; Result&amp;#x3C;Option&amp;#x3C;std::time::Duration&gt;&gt;
    where
        Self::CTX: Send + Sync,
    {
        // 返回未完成的时候，将流存储在本地
        if let Some(b) = body {
            ctx.buffer.extend(&amp;#x26;b[..]);
            // 丢弃原始的body
            b.clear();
        }
        if end_of_stream {
            // 这是最后一块，我们现在可以处理数据了
            let json_body: Resp = serde_json::de::from_slice(&amp;#x26;ctx.buffer).unwrap();
            let yaml_body = serde_yaml::to_string(&amp;#x26;json_body).unwrap();
            *body = Some(Bytes::copy_from_slice(yaml_body.as_bytes()));
        }

        Ok(None)
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是目前应该还不完善，无法完全取代nginx，不支持服务注册与发现。期待后续进一步的版本，若条件成熟则会研究一下逐步替代现有微服务网关或者Nginx的替换&lt;/p&gt;</content:encoded><category>rust</category></item><item><title>java使用虚拟线程遇到的问题</title><link>https://leyou240.github.io/blog/virtualthreads</link><guid isPermaLink="true">https://leyou240.github.io/blog/virtualthreads</guid><description>java使用虚拟线程遇到的问题</description><pubDate>Tue, 12 Dec 2023 04:22:44 GMT</pubDate><content:encoded>&lt;h2&gt;虚拟线程&lt;/h2&gt;
&lt;p&gt;虚拟线程（Virtual Thread）是 JDK 而不是 OS 实现的轻量级线程，由 JVM 调度。许多虚拟线程共享同一个操作系统线程，虚拟线程的数量可以远大于操作系统线程的数量。它们可以更有效地运行以thread-per-request（每个请求一个线程，就大大降低了线程上下文的切换）的方式编写的服务器应用程序，从而实现更高的吞吐量和更少的硬件浪费。&lt;/p&gt;
&lt;p&gt;虚拟线程有几个核心的对象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Continuation&lt;/strong&gt;：译为“续延”，是用户真实任务的包装器，&lt;strong&gt;虚拟线程会把任务包装到一个Continuation实例中&lt;/strong&gt;，当任务需要阻塞挂起的时候，会调用Continuation的yield操作进行阻塞&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scheduler&lt;/strong&gt;：译为“调度器”，会把任务提交到一个平台线程池中执行，虚拟线程中维护了一个默认的调度器DEFAULT_SCHEDULER，&lt;strong&gt;这是一个 ForkJoinPool 实例&lt;/strong&gt;，最大线程数默认是系统核心线程数，最大为 256，可以通过 jdk.virtualThreadScheduler.maxPoolSize 进行设置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;carrier&lt;/strong&gt;：载体线程（Thread对象），指的是&lt;strong&gt;负责执行虚拟线程中任务的平台线程。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;runContinuation&lt;/strong&gt;：一个Runnable对象，&lt;strong&gt;用于在任务运行或继续之前，虚拟线程将装载到当前线程上。当任务完成或完成时，将其卸载&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;如何创建虚拟线程&lt;/h3&gt;
&lt;p&gt;1、使用**&lt;code&gt;Thread.startVirtualThread&lt;/code&gt;**&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class VirtualThreadTest {
  public static void main(String[] args) {
    CustomThread customThread = new CustomThread();
    Thread.startVirtualThread(customThread);
  }
}
static class CustomThread implements Runnable {
  @Override
  public void run() {
    System.out.println(&quot;CustomThread run&quot;);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2、使用**&lt;code&gt;Thread.ofVirtual&lt;/code&gt;**&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class VirtualThreadTest {
  public static void main(String[] args) {
    CustomThread customThread = new CustomThread();
    // 创建不启动
    Thread unStarted = Thread.ofVirtual().unstarted(customThread);
    unStarted.start();
    // 创建直接启动
    Thread.ofVirtual().start(customThread);
  }
}
static class CustomThread implements Runnable {
  @Override
  public void run() {
    System.out.println(&quot;CustomThread run&quot;);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3、&lt;strong&gt;使用 &lt;code&gt;ThreadFactory&lt;/code&gt; 创建&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class VirtualThreadTest {
  public static void main(String[] args) {
    CustomThread customThread = new CustomThread();
    ThreadFactory factory = Thread.ofVirtual().factory();
    Thread thread = factory.newThread(customThread);
    thread.start();
  }
}
static class CustomThread implements Runnable {
  @Override
  public void run() {
    System.out.println(&quot;CustomThread run&quot;);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4、&lt;strong&gt;使用&lt;code&gt;Executors.newVirtualThreadPerTaskExecutor&lt;/code&gt;创建&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class VirtualThreadTest {
  public static void main(String[] args) {
    CustomThread customThread = new CustomThread();
    ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
    executor.submit(customThread);
  }
}
static class CustomThread implements Runnable {
  @Override
  public void run() {
    System.out.println(&quot;CustomThread run&quot;);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;springboot3.2与虚拟线程&lt;/h2&gt;
&lt;p&gt;随着springboot3.2发布，在使用jdk21的情况下。可以使用如下配置开启虚拟线程&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;spring.threads.virtual.enabled=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们写了个简单的demo，进行性能测试。结果如预期所料，响应时间和吞吐率都是虚拟线程完胜。&lt;/p&gt;
&lt;p&gt;但改为使用数据库以后，发现性能相比于不使用虚拟线程反而下降了。并且随着请求数量增加，发现数据库连接被占满。&lt;strong&gt;Connection is not available, request timed out after 906ms&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是为什么呢？&lt;/p&gt;
&lt;h2&gt;虚拟线程与synchronized&lt;/h2&gt;
&lt;p&gt;通过阅读官方虚拟线程文档，目前虚拟线程与synchronized关键字的适配尚未完成。如果遇到synchronized，性能反而会下降。&lt;/p&gt;
&lt;p&gt;There are two scenarios in which a virtual thread cannot be unmounted during blocking operations because it is &lt;em&gt;pinned&lt;/em&gt; to its carrier:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When it executes code inside a &lt;code&gt;synchronized&lt;/code&gt; block or method, or&lt;/li&gt;
&lt;li&gt;When it executes a &lt;code&gt;native&lt;/code&gt; method or a &lt;a href=&quot;https://openjdk.java.net/jeps/424&quot;&gt;foreign function&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;显然，我们的代码中并无此类场景。所以问题应该是在jdbc或者mysql驱动。起初我们以为是hikari的问题，但在报错的时候，我们看到了如下异常。基本上可以确定是mysql驱动的问题&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/mysql%E9%A9%B1%E5%8A%A8%E4%BD%BF%E7%94%A8synchronized.C67E_zz1_Z24u5AL.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;需要将使用synchronized的代码，全部改为使用**&lt;code&gt;ReentrantLock&lt;/code&gt;**来实现。同时我们也看到了mysql和mybatis社区已经有人反馈此问题，至此我们决定暂时不启用虚拟线程。&lt;/p&gt;
&lt;p&gt;**UPDATE2024-05-08：**由于我们在持续跟踪此问题，随着&lt;a href=&quot;https://github.com/mysql/mysql-connector-j&quot;&gt;mysql-connector-j&lt;/a&gt; 9.0版本和mybatis3.5.16版本发布，代码均已按预期修改。在我们未对代码做额外优化的情况下，各方面性能得到了提升！&lt;/p&gt;
&lt;p&gt;我们在4c8g的机器上，使用虚拟线程的配置如下。可以达到最高10k并发、40k tps&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;spring.threads.virtual.enabled=true
#set tomcat thread pool
server.tomcat.threads.max=3000
server.tomcat.threads.min-spare=3000
server.tomcat.max-connections=10000
server.tomcat.accept-count=1000
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.maximum-pool-size=3000
spring.datasource.hikari.minimum-idle=2000
spring.datasource.hikari.connection-timeout=3000
spring.datasource.hikari.max-lifetime=3600000
spring.datasource.hikari.idle-timeout=1200000
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;1、强制使用**&lt;code&gt;ReentrantLock&lt;/code&gt;**：虚拟线程可以更好的利用cpu，支持了java版本的协程。但在当前版本无法和*&lt;code&gt;synchronized&lt;/code&gt;*关键字很好的适配。在项目中如果需要线程同步的场景，&lt;/p&gt;
&lt;p&gt;2、统一使用虚拟线程：在大多数场景可以抛弃掉原有的各种线程池，由jvm为我们做调度。再也不怕因线程太多而导致oom了&lt;/p&gt;
&lt;p&gt;3、在会调用到native方法和jni的地方：不应使用虚拟线程&lt;/p&gt;
&lt;p&gt;4、虚拟线程适用于长时间的IO密集型任务，而不适用于长时间的CPU密集型任务&lt;/p&gt;
&lt;p&gt;5、虚拟线程开启后，应使用较新版本的mybatis和mysql驱动&lt;/p&gt;
&lt;p&gt;6、经过测试，虚拟线程和webfulx时吞吐量大致相当。但使用虚拟线程无需额外的学习成本。虚拟线程吞吐量远超普通线程池！&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://mikemybytes.com/2024/02/28/curiosities-of-java-virtual-threads-pinning-with-synchronized/&quot;&gt;虚拟线程与synchronized&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://openjdk.org/jeps/444&quot;&gt;虚拟线程&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Release-Notes&quot;&gt;SpringBoot3.2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mybatis/mybatis-3/releases/tag/mybatis-3.5.16&quot;&gt;mybatis-3.5.16&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mysql/mysql-connector-j/commit/00d43c5e8b24f1d516f93eea900b3487c15a489c&quot;&gt;Replace synchronized with ReentrantLock&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP/issues/2151&quot;&gt;hikari&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/G1-qLXqHva193LBJ6pSbgg&quot;&gt;淘宝对虚拟线程的研究&lt;/a&gt;&lt;/p&gt;</content:encoded><category>jdk21</category></item><item><title>升级到springboot3与jdk21需要做的工作</title><link>https://leyou240.github.io/blog/springboot3jdk21</link><guid isPermaLink="true">https://leyou240.github.io/blog/springboot3jdk21</guid><description>升级到springboot3与jdk21需要做的工作</description><pubDate>Tue, 12 Sep 2023 04:22:44 GMT</pubDate><content:encoded>&lt;p&gt;随着springboot3的正式版推出，最小可用的jdk版本为17。最大的亮点是颠覆性的zgc，可以将stw时间控制在亚毫秒级别，接着jdk21推出了分代zgc。根据jdk官方说法，此gc算法适用于任何内存条件下，多核cpu环境。我们也在各个环境的生产系统做到了验证（2c4g、4c8g、8c16g）。对于中间件，rocketmq、kafka、es、nacos等。因所用jdk版本尚未完全支持，且目前也没有遇到过问题，暂未做升级。&lt;/p&gt;
&lt;p&gt;对于新项目来说，springboot3+jdk21已经可用，基本上不会有问题。&lt;/p&gt;
&lt;p&gt;对于springboot2+jdk8项目的升级，也相对较简单。&lt;/p&gt;
&lt;h2&gt;升级步骤&lt;/h2&gt;
&lt;p&gt;1、jdk我们选择zulu21 https://www.azul.com/downloads/#zulu&lt;/p&gt;
&lt;p&gt;2、pom配置统一到21&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;properties&gt;
    &amp;#x3C;maven.compiler.source&gt;21&amp;#x3C;/maven.compiler.source&gt;
    &amp;#x3C;maven.compiler.target&gt;21&amp;#x3C;/maven.compiler.target&gt;
&amp;#x3C;/properties&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3、从2.7.x升级springboot到最新版本，根据官方的Migration Guide升级即可 https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide&lt;/p&gt;
&lt;p&gt;4、循环依赖，某些项目目前碰到了此类情况。修改配置即可&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;spring.main.allow-circular-references=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5、lombok版本升级到最新&lt;/p&gt;
&lt;p&gt;6、找不到相关的类，一般是因为从jdk11开始删除了j2ee相关的类。根据报错引入jakarta相关包即可，代码中将j2ee相关代码，改为使用jakarta&lt;/p&gt;
&lt;p&gt;7、某些应用，会使用到反射。新jdk版本中因为安全问题，做了一些限制。目前做得比较粗暴，在jvm中增加如下配置&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;8、swagger改为使用springdoc-openapi 依赖，并做相应的代码修改&lt;/p&gt;
&lt;p&gt;9、docker打包基础镜像修改为统一使用zulu21&lt;/p&gt;
&lt;h2&gt;升级后，所有生产环境jvm配置统一修改&lt;/h2&gt;
&lt;p&gt;以下是用得最多的4c8g配置&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;-Xmx6g -Xms6g #堆内存初始化6g
-XX:+UseZGC #使用zgc
-XX:-ZUncommit #关闭将不使用的内存返回给操作系统
-XX:CICompilerCount=3 #4核cpuJIT编译线程数 https://help.aliyun.com/document_detail/2856899.html?spm=a2c4g.11186623.help-menu-2584271.d_2_16_6.1ab423085DdeTz&amp;#x26;scm=20140722.H_2856899._.OR_help-T_cn#DAS#zh-V_1
-XX:+UseDynamicNumberOfGCThreads #当开启时，ZGC 会根据GC运行状态（例如GC耗时、堆空余空间、对象分配频率等）由内置的启发式算法自动选择并发阶段的 GC 线程数量（最小为 1，最大为 -XX:ConcGCThreads）。当关闭时，则会固定使用 -XX:ConcGCThreads 数量的线程。
-XX:+ZGenerational #启用jdk21生产可用的分代zgc算法，会在gc的时候消耗更多的cpu
-XX:+AlwaysPreTouch #启用内存预分配，需要内核版本4.3+
#gc日志记录到/opt/logs/文件名为启动的时间戳，每个日志记录包含时间戳、日志级别、线程id、标签。文件大小限制到50M，达到后创建新的文件继续记录
-Xlog:gc*=info:file=/opt/logs/gc-%t.log:time,level,tid,tags:filesize=50M  
#发生OOM时dump内存日志
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/apps/errorDump.hprof
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为目前公司的java服务均跑在容器内部，Huge Pages、NUMA Support等未做开启&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;本文探讨了如何升级到jdk21，并使用最新的分代zgc。jdk21也带来了很多简便的新语法和适合IO密集的虚拟线程。虚拟线程相比于平台线程在某些场景下优势明显。只是需要注意仅使用可重入锁对象，而不使用synchronize关键字（反而降低性能）。相关资料：https://openjdk.org/jeps/444&lt;/p&gt;
&lt;p&gt;经过实践和测试，我们发现升级后的系统在垃圾回收方面表现出色，暂停时间确实如官方所说被控制到了亚毫秒内。尽管这个优化会额外消耗cpu资源，但获得了超低stw时间，权衡这下显然非常值得。&lt;/p&gt;
&lt;p&gt;相比其他已经经过生产环境调优的垃圾回收器（G1、Parallel、CMS），ZGC的性能和稳定性都非常优秀，几乎不需要太多额外的调优，就可以得到响应时间的降低和吞吐量的增加。即使在以前吞吐量有优势的Parallel，依然具有优势。&lt;/p&gt;
&lt;p&gt;ZGC可以统一江湖了！另据得到的消息，某些大厂主要服务均升级到了jdk21。但也进一步说明了，（分代）zgc和虚拟线程的强大。你们还在用jdk8吗？&lt;/p&gt;
&lt;h2&gt;更新&lt;/h2&gt;
&lt;p&gt;新增一些中大厂使用jdk17/jdk21和zgc的案例，作为以后项目的参考&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide&quot;&gt;Spring-Boot-3.0-Migration-Guide&lt;/a&gt;:迁移到springboot3&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.openjdk.org/display/zgc/Main&quot;&gt;ZGC&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/bDp_4EAZoaGdXLV17s4vjw&quot;&gt;转转&lt;/a&gt;:&quot;分代ZGC在转转商列服务中的实践&quot;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7245145645748928573&quot;&gt;得物&lt;/a&gt;:&quot;亚毫秒GC暂停到底有多香？JDK17+ZGC初体验｜得物技术&quot;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.jdcloud.com/article/3428&quot;&gt;京东&lt;/a&gt;:&quot;JDK11升级JDK17最全实践干货来了&quot;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.jdcloud.com/article/3173&quot;&gt;京东营销&lt;/a&gt;:&quot;JDK 17 营销初体验 —— 亚毫秒停顿 ZGC 落地实践&quot;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html&quot;&gt;美团&lt;/a&gt;: &quot;新一代垃圾回收器ZGC的探索与实践&quot;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.alibabacloud.com/blog/the-past-and-present-of-jdk8-and-jdk17-to-the-future-of-jdk21_599805&quot;&gt;阿里云&lt;/a&gt;:&quot;The Past and Present of JDK8 and JDK17 to the Future of JDK21&quot;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.automq.com/blog/in-depth-analysis-of-java-zgc-and-practical-insights-on-building-low-latency-streaming-systems&quot;&gt;Automq&lt;/a&gt;：Java ZGC 深度剖析及其在构建低延迟流系统中的实践心得&lt;/p&gt;</content:encoded></item><item><title>慢sql经验总结</title><link>https://leyou240.github.io/blog/project03</link><guid isPermaLink="true">https://leyou240.github.io/blog/project03</guid><description>慢sql经验总结</description><pubDate>Mon, 20 Jun 2022 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在过去的工作中，如何在各个环境中（开发、测试、生产）快速准确的发现应用的慢sql，并进行高效的推动，是一个很大的挑战。以下是一些经验&lt;/p&gt;
&lt;h2&gt;慢sql的定义&lt;/h2&gt;
&lt;p&gt;我们将执行时间超过1秒的sql都定义为慢sql。同时会导致&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;系统的响应时间延迟，影响用户体验&lt;/li&gt;
&lt;li&gt;资源占用增加，增高了系统的负载，其他请求响应时间也可能会收到影响。&lt;/li&gt;
&lt;li&gt;慢SQL占用数据库连接的时间长,如果有大量慢SQL查询同时执行，可能会导致数据库连接池的连接被全部占用，导致数据连接池打满、缓冲区溢出等问题，使数据库无法响应其他请求。&lt;/li&gt;
&lt;li&gt;还有可能造成锁竞争增加、数据不一致等问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，需要及时发现和优先慢sql，对系统稳定性是非常重要的&lt;/p&gt;
&lt;h2&gt;慢sql常见原因&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;缺乏索引/索引未生效，导致数据库全表扫描，会产生大量的IO消耗，产生慢SQL。&lt;/li&gt;
&lt;li&gt;单表数据量太大，会导致加索引的效果不够明显。&lt;/li&gt;
&lt;li&gt;SQL语句书写不当，例如join或者子查询过多、in元素过多、limit深分页问题、order by导致文件排序、group by使用临时表等。&lt;/li&gt;
&lt;li&gt;数据库在刷“脏页”，redo log写满了，导致所有系统更新被堵住，无法写入了。&lt;/li&gt;
&lt;li&gt;执行SQL的时候，遇到表锁或者行锁，只能等待锁被释放，导致了慢SQL。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;如何发现慢sql&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;数据库开启慢sql日志：数据库会将执行时间超过一定阈值的sql记录到日志中&lt;/li&gt;
&lt;li&gt;链路追踪：通过慢接口以及代码，定位慢sql&lt;/li&gt;
&lt;li&gt;开发过程中，进行代码review。通过对sql语句分析，发现索引使用不当、造成全表扫描、sql扫描行数过多、出现文件排序等。强制其修改后再提交&lt;/li&gt;
&lt;li&gt;对数据库进行监控，重点对cpu使用情况、会话数，发现异常及时告警同时安排值班同时定位问题，尽量在第一时间解决数据库风险。同事建立问题跟踪，若因业务影响则指定负责人推进。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;制定sql规范&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;**【强制】**不允许使用select *&lt;/li&gt;
&lt;li&gt;**【强制】**不要使用count(列名)或count(常量)来替代count(&lt;em&gt;)，count(&lt;/em&gt;)就是SQL92定义的标准统计行数的语法，跟数据库无关，跟NULL和非NULL无关。&lt;/li&gt;
&lt;li&gt;**【强制】**count(distinct col) 计算该列除NULL之外的不重复数量。注意 count(distinct col1, col2) 如果其中一列全为NULL，那么即使另一列有不同的值，也返回为0。&lt;/li&gt;
&lt;li&gt;**【强制】**若列会执行计算，则强制其使用默认值不允许为NULL，当某一列的值全是NULL时，count(col)的返回结果为0，但sum(col)的返回结果为NULL，因此使用sum()时需注意NPE问题。&lt;/li&gt;
&lt;li&gt;**【强制】**使用ISNULL()来判断是否为NULL值。&lt;/li&gt;
&lt;li&gt;**【强制】**对于数据库中表记录的查询和变更，只要涉及多个表，都需要在列名前加表的别名（或表名）进行限定。&lt;/li&gt;
&lt;li&gt;**【强制】**在代码中写分页查询逻辑时，若count为0应直接返回，避免执行后面的分页语句。&lt;/li&gt;
&lt;li&gt;**【强制】**不得使用外键与级联，一切外键概念必须在应用层解决。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;执行计划相关解释&lt;/h2&gt;
&lt;p&gt;通过explain语句可以提供关于SQL查询执行的详细信息和执行计划，并且可以了解sql的索引使用情况以及数据访问方式。通过使用Explain语句，可以了解SQL是如何执行的，并且可以看出其可能存在的性能问题。&lt;/p&gt;
&lt;p&gt;| 字段          | 解析                                                         |
| ------------- | ------------------------------------------------------------ |
| id            | 查询的每个操作的唯一标识符，无特殊含义                       |
| select type   | 查询操作的类型，常见的包括SIMPLE(简单查询)、PRIMARY(主键查询)、SUBQUERY(子查询)、DERIVED(派生表查询)等 |
| table         | 表示查询操作设计的表名                                       |
| partitions    | 表示查询操作涉及的分区信息                                   |
| type          | 表示查询操作的连接类型或访问类型，常见的包括ALL(全表扫描)、index(索引扫描)、range(范围扫描)、ref(基于索引的引用)、eq_ref(唯一索引使用)、const(常数表)、system(系统表)等 |
| possible_keys | 表示查询可能使用的索引                                       |
| key           | 表示查询操作实际使用的索引                                   |
| key_len       | 表示索引使用的字节数                                         |
| ref           | 表示查询操作使用的索引之间的引用关系                         |
| rows          | 执行查询操作时，mysql认为必须要去检查和判断的记录条数        |
| filtered      | 表示符合查询条件的数据百分比                                 |
| Extra         | 表示额外的信息和备注，如“Using where”表示使用了WHERE子句过滤，&quot;Usingindex“表示只使用了索引而没有访问表等。 |&lt;/p&gt;
&lt;p&gt;需要重点关注几个特别的情况，出现以下几点都可能造成sql性能的降低。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用全表扫描，性能最差，即type=&quot;ALL&quot;&lt;/li&gt;
&lt;li&gt;扫描行数过多，即rows&gt;阈值，阈值一般为10w&lt;/li&gt;
&lt;li&gt;查询时使用了排序操作，也比较耗时，即Extra包含&quot;Using filesort&quot;&lt;/li&gt;
&lt;li&gt;索引类型为index，代表全盘扫描了索引的数据，Extra信息为Using where，代表要搜索的列没有被索引覆盖，需要回表，性能较差。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;规范&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;主键不允许使用uuid，小表可以使用自助主键，大表的主键必须使用递增的id组件&lt;/li&gt;
&lt;li&gt;尽量保证核心sql查询的，where、order by、group by 都可以用上索引。建立联合索引，尽量让区分度大的在索引左边，经常做范围查询的字段放在最后一个&lt;/li&gt;
&lt;li&gt;索引原则上不应该超过3个。尽量利用1到2个多字段联合索引，抗下80%以上的查询。然后在剩下20%场景，建立一个联合索引。保证99%以上的查询都能充分利用索引&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;慢sql治理流程&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;高危慢sql，建立任务持续跟踪，每个版本汇报直到问题解决。并指定处理优先级，不同的优先级可以容忍的时间不同，若超过容忍时间，则影响团队评价。&lt;/li&gt;
&lt;li&gt;针对重点业务或慢sql高发团队，安排有经验的同事协助推进。&lt;/li&gt;
&lt;li&gt;新功能开发过程中，根据实际情况，及时处理或排期处理。必须在排期时间内完成改造，超期则影响团队评价&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;慢sql可能引起很严重的系统性能问题，影响系统的可用性和稳定性。需要及时发现和治理。因此，我们建立了一套发现-分析-治理的流程，极大的减少了因慢sql引起的系统性能问题&lt;/p&gt;</content:encoded><category>项目</category><category>性能优化</category></item><item><title>java对象转换神器-mapstruct</title><link>https://leyou240.github.io/blog/project02</link><guid isPermaLink="true">https://leyou240.github.io/blog/project02</guid><description>java对象转换神器-mapstruct</description><pubDate>Tue, 14 Jun 2022 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在java开发中，对象之间的转换是一项常见的任务。通常有2种做法：手写和使用&lt;code&gt;JavaBeanUtils&lt;/code&gt;。前者降低工作效率还容易出错，而后者因为使用了反射又会导致性能下降。为了解决这个问题，java社区出现了&lt;code&gt;Mapstruct&lt;/code&gt;框架。它是一个强大的代码生成器，在编译时生成代码，使对象间的映射变得简单、高效和高性能，降低了维护成本。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Mapstruct&lt;/code&gt;通过注解处理器集成到构件工具和IDE中，简化了集成。&lt;/p&gt;
&lt;h2&gt;引入&lt;/h2&gt;
&lt;p&gt;jar包依赖，如果lombok和mapstruct配合使用，一定注意此配置中的注释。因这两个都是使用注解处理器在编译时运行，并且通过annotationProcessorPaths强制规定注解处理的顺序。保证了编译的时候一定按此顺序运行。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;dependencies&gt;
    &amp;#x3C;dependency&gt;
        &amp;#x3C;groupId&gt;org.mapstruct&amp;#x3C;/groupId&gt;
        &amp;#x3C;artifactId&gt;mapstruct&amp;#x3C;/artifactId&gt;
        &amp;#x3C;version&gt;${org.mapstruct.version}&amp;#x3C;/version&gt;
    &amp;#x3C;/dependency&gt;
    &amp;#x3C;!-- lombok dependency should not end up on classpath --&gt;
    &amp;#x3C;dependency&gt;
        &amp;#x3C;groupId&gt;org.projectlombok&amp;#x3C;/groupId&gt;
        &amp;#x3C;artifactId&gt;lombok&amp;#x3C;/artifactId&gt;
        &amp;#x3C;version&gt;${org.projectlombok.version}&amp;#x3C;/version&gt;
        &amp;#x3C;scope&gt;provided&amp;#x3C;/scope&gt;
    &amp;#x3C;/dependency&gt;
&amp;#x3C;/dependencies&gt;
...
&amp;#x3C;build&gt;
    &amp;#x3C;plugins&gt;
        &amp;#x3C;plugin&gt;
            &amp;#x3C;groupId&gt;org.apache.maven.plugins&amp;#x3C;/groupId&gt;
            &amp;#x3C;artifactId&gt;maven-compiler-plugin&amp;#x3C;/artifactId&gt;
            &amp;#x3C;version&gt;3.13.0&amp;#x3C;/version&gt;
            &amp;#x3C;configuration&gt;
                &amp;#x3C;source&gt;17&amp;#x3C;/source&gt;
                &amp;#x3C;target&gt;17&amp;#x3C;/target&gt;
                &amp;#x3C;annotationProcessorPaths&gt;
                    &amp;#x3C;!-- 以下path必须固定顺序 --&gt;
                    &amp;#x3C;path&gt;
                        &amp;#x3C;groupId&gt;org.projectlombok&amp;#x3C;/groupId&gt;
                        &amp;#x3C;artifactId&gt;lombok&amp;#x3C;/artifactId&gt;
                        &amp;#x3C;version&gt;${org.projectlombok.version}&amp;#x3C;/version&gt;
                    &amp;#x3C;/path&gt;
                    &amp;#x3C;path&gt;
                        &amp;#x3C;groupId&gt;org.mapstruct&amp;#x3C;/groupId&gt;
                        &amp;#x3C;artifactId&gt;mapstruct-processor&amp;#x3C;/artifactId&gt;
                        &amp;#x3C;version&gt;${org.mapstruct.version}&amp;#x3C;/version&gt;
                    &amp;#x3C;/path&gt;
                    &amp;#x3C;!-- additional annotation processor required as of Lombok 1.18.16 --&gt;
                    &amp;#x3C;path&gt;
                        &amp;#x3C;groupId&gt;org.projectlombok&amp;#x3C;/groupId&gt;
                        &amp;#x3C;artifactId&gt;lombok-mapstruct-binding&amp;#x3C;/artifactId&gt;
                        &amp;#x3C;version&gt;0.2.0&amp;#x3C;/version&gt;
                    &amp;#x3C;/path&gt;
                &amp;#x3C;/annotationProcessorPaths&gt;
            &amp;#x3C;/configuration&gt;
        &amp;#x3C;/plugin&gt;
    &amp;#x3C;/plugins&gt;
&amp;#x3C;/build&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;安装插件&lt;/h2&gt;
&lt;p&gt;搜索MapStruct Support安装即可，可以在使用MapStruct时获得更加丰富代码提示。&lt;/p&gt;
&lt;h2&gt;简单示例&lt;/h2&gt;
&lt;p&gt;定义两个类做转换&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructorpublic
class UserPo {
    private Long id;
    private Date gmtCreate;
    private Date createTime;
    private Long buyerId;
    private Long age;
    private String userNick;
    private String userVerified;
}
@Data
publicclass UserEntity {
    private Long id;
    private Date gmtCreate;
    private Date createTime;
    private Long buyerId;
    private Long age;
    private String userNick;
    private String userVerified;

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;定义转换接口，后续直接通过spring注入UserConverter即可使用。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import org.mapstruct.Mapper;

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) //此处的model是mapstruct的，并且将UserConverter注入到了spring容器中
public interface UserConverter {
	UserEntity po2entity(UserPo userPo);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;1、mapstruct与lombok配合使用，一定注意导包的问题。搭配IDE插件，使用起来更方便&lt;/p&gt;
&lt;p&gt;2、本文只讲了一些基础的使用，其他深入的使用，可以参考其官网 https://mapstruct.org/ 或者 https://juejin.cn/post/6956190395319451679&lt;/p&gt;</content:encoded><category>项目</category></item><item><title>java项目中的一些通用处理</title><link>https://leyou240.github.io/blog/project01</link><guid isPermaLink="true">https://leyou240.github.io/blog/project01</guid><description>java项目中的一些通用处理</description><pubDate>Sat, 11 Jun 2022 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、全局异常处理&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/**
 * 统一异常处理、数据预处理等
 */
@ControllerAdvice
public class ControllerExceptionHandler {
    private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class);
    /**
     * 所有异常统一处理，出现这个情况很可能有bug
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    //状态码自定义，出现这个状态码大概率就有未处理异常或者bug
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) 
    public CommonResp exceptionHandler(Exception e) {
        CommonResp commonResp = new CommonResp();
        LOG.error(&quot;系统异常：&quot;, e);
        commonResp.setSuccess(false);
        commonResp.setMessage(&quot;系统出现异常，请联系管理员&quot;);
        return commonResp;
    }
    /**
     * 所有自定义异常统一处理
     * @param e
     * @return
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public CommonResp businessExceptionHandler(BusinessException e) {
        CommonResp commonResp = new CommonResp();
        LOG.error(&quot;业务异常：&quot;, e);
        commonResp.setSuccess(false);
        commonResp.setMessage(e.getMessage());
        return commonResp;
    }
    /**
     * 校验异常统一处理
     * @param e
     * @return
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    public CommonResp validExceptionHandler(BindException e) {
        CommonResp commonResp = new CommonResp();
        LOG.warn(&quot;参数校验失败：{}&quot;, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        commonResp.setSuccess(false);
        commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        return commonResp;
    }
}
//自定义异常
public class BusinessException extends RuntimeException {
    private BusinessExceptionEnum exceptionEnum;
    public BusinessException(BusinessExceptionEnum exceptionEnum) {
        super(exceptionEnum.getMessage());
        this.exceptionEnum = exceptionEnum;
    }
    public BusinessExceptionEnum getExceptionEnum() {
        return exceptionEnum;
    }
    public void setExceptionEnum(BusinessExceptionEnum exceptionEnum) {
        this.exceptionEnum = exceptionEnum;
    }
    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、全局返回值处理&lt;/h2&gt;
&lt;p&gt;实现返回值处理接口&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ApiResponseHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
    private final HandlerMethodReturnValueHandler handler;
    private static final HashSet&amp;#x3C;String&gt; URL_SET = new HashSet&amp;#x3C;&gt;() {{
        add(&quot;/alipay/pay&quot;);
        add(&quot;/wechat/pay&quot;);
    }};
    public ApiResponseHandlerMethodReturnValueHandler(HandlerMethodReturnValueHandler handler) {
        this.handler = handler;
    }
    @Override
    public boolean supportsReturnType(MethodParameter methodParameter) {
        return this.handler.supportsReturnType(methodParameter);
    }
    @Override
    public void handleReturnValue(Object returnValue,
                                  MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest) throws Exception {
        String servletPath = nativeWebRequest.getNativeRequest(HttpServletRequest.class).getServletPath();
        // 这里根据请求路径做一些处理，比如支付宝、微信支付等接口，对返回值有明确要求的接口路径。保持原返回格式
        //todo: 所有的支付接口，都需要
        if (URL_SET.contains(servletPath)) {
            this.handler.handleReturnValue(returnValue, methodParameter, modelAndViewContainer, nativeWebRequest);
            return;
        }
        // 已经被包装过的CommonResp返回值，直接返回
        if (returnValue instanceof CommonResp&amp;#x3C;?&gt; value) {
            this.handler.handleReturnValue(returnValue, methodParameter, modelAndViewContainer, nativeWebRequest);
            return;
        }
        // 其他接口返回值，包装成CommonResp返回值
        this.handleReturnValue(CommonResp.success(returnValue), methodParameter, modelAndViewContainer, nativeWebRequest);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;替换原有的返回值处理器&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration
public class WebMvcConfiguration {
    @Autowired
    public void resetRequestMappingHandlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
        var oldReturnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
        assert oldReturnValueHandlers != null;
        List&amp;#x3C;HandlerMethodReturnValueHandler&gt; list = new ArrayList&amp;#x3C;&gt;(oldReturnValueHandlers.size());
        for (HandlerMethodReturnValueHandler returnValueHandler : oldReturnValueHandlers) {
            // 替换原有的返回值处理器
            if (returnValueHandler instanceof RequestResponseBodyMethodProcessor) {
                list.add(new ApiResponseHandlerMethodReturnValueHandler(returnValueHandler));
            } else {
                list.add(returnValueHandler);
            }
        }
        requestMappingHandlerAdapter.setReturnValueHandlers(list);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3、序列化替换jackson为fastjson2&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;添加Fastjson2依赖&lt;/strong&gt;： 首先，在你的&lt;code&gt;pom.xml&lt;/code&gt;文件中添加Fastjson2的依赖。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;dependency&gt;
    &amp;#x3C;groupId&gt;com.alibaba&amp;#x3C;/groupId&gt;
    &amp;#x3C;artifactId&gt;fastjson2&amp;#x3C;/artifactId&gt;
    &amp;#x3C;version&gt;2.0.2&amp;#x3C;/version&gt;
&amp;#x3C;/dependency&gt;
&amp;#x3C;dependency&gt;
    &amp;#x3C;artifactId&gt;fastjson2-extension-spring6&amp;#x3C;/artifactId&gt;
    &amp;#x3C;groupId&gt;com.alibaba.fastjson2&amp;#x3C;/groupId&gt;
    &amp;#x3C;version&gt;2.0.2&amp;#x3C;/version&gt;
&amp;#x3C;/dependency&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置Fastjson2为默认的序列化工具&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;创建一个配置类，用于替换Spring Boot默认的Jackson为Fastjson2。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.alibaba.fastjson2.support.config.FastJsonConfig;
import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

@Configuration
public class FastJsonConfiguration {
    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 1. 需要定义一个convert转换消息的对象;
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 2. 添加fastJson的配置信息，比如：是否要格式化返回的json数据;
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;);
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 3. 在convert中添加配置信息;
        return new HttpMessageConverters(fastConverter);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;移除Jackson依赖（可选）&lt;/strong&gt;： 如果你不希望Spring Boot项目中包含Jackson依赖，防止用到了默认的jackson后与预期的序列化不一致，可以在&lt;code&gt;pom.xml&lt;/code&gt;中排除Jackson依赖。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;dependency&gt;
    &amp;#x3C;groupId&gt;org.springframework.boot&amp;#x3C;/groupId&gt;
    &amp;#x3C;artifactId&gt;spring-boot-starter-web&amp;#x3C;/artifactId&gt;
    &amp;#x3C;exclusions&gt;
        &amp;#x3C;exclusion&gt;
            &amp;#x3C;groupId&gt;com.fasterxml.jackson.core&amp;#x3C;/groupId&gt;
            &amp;#x3C;artifactId&gt;jackson-databind&amp;#x3C;/artifactId&gt;
        &amp;#x3C;/exclusion&gt;
    &amp;#x3C;/exclusions&gt;
&amp;#x3C;/dependency&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过以上步骤，你就可以将Spring Boot默认的Jackson序列化工具替换为Fastjson2。这样，Spring Boot应用在处理HTTP请求和响应时，将使用Fastjson2进行序列化和反序列化。&lt;/p&gt;</content:encoded><category>项目</category></item><item><title>Go1.18：新的切片扩容策略</title><link>https://leyou240.github.io/blog/go118-new-growslice</link><guid isPermaLink="true">https://leyou240.github.io/blog/go118-new-growslice</guid><description>Golang的切片扩容策略以及1.18中带来的优化</description><pubDate>Sun, 27 Mar 2022 06:04:09 GMT</pubDate><content:encoded>&lt;p&gt;随着go 1.18版本的发布，go社区终于迎来了期盼已久的正式泛型语法，然而我在浏览关于1.18的changelog时发现1.18对于slice的扩容策略也做了一些修改，刚好我最近正在看draven大佬的新书也讲到了slice底层的源码的一些运行逻辑，在好奇心的驱使下，想知道社区究竟改动了什么以及为什么作出这些改动，于是翻开1.18的源码，便有了这篇文章。&lt;/p&gt;
&lt;h2&gt;Growslice In Go 1.17&lt;/h2&gt;
&lt;p&gt;首先我们先简单回顾一下go 1.17及以前的切片扩容策略，这部分的主要逻辑在&lt;code&gt;src/runtime/slice.go&lt;/code&gt;中的&lt;code&gt;growslice&lt;/code&gt;函数（省略部分代码，下同）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func growslice(et *_type, old slice, cap int) slice {
  ...
  
  newcap := old.cap
  doublecap := newcap + newcap
  if cap &gt; doublecap {
    newcap = cap
  } else {
    if old.cap &amp;#x3C; 1024 {
      newcap = doublecap
    } else {
      // Check 0 &amp;#x3C; newcap to detect overflow
      // and prevent an infinite loop.
      for 0 &amp;#x3C; newcap &amp;#x26;&amp;#x26; newcap &amp;#x3C; cap {
        newcap += newcap / 4
      }
      // Set newcap to the requested cap when
      // the newcap calculation overflowed.
      if newcap &amp;#x3C;= 0 {
        newcap = cap
      }
    }
  }
  
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面代码的扩容策略可以简述为以下三个规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当期望容量 &gt; 两倍的旧容量时，直接使用期望容量作为新切片的容量&lt;/li&gt;
&lt;li&gt;如果旧容量 &amp;#x3C; 1024（注意这里单位是元素个数）,那么直接翻倍旧容量&lt;/li&gt;
&lt;li&gt;如果旧容量 &gt; 1024，那么会进入一个循环，每次增加25%直到大于期望容量&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以看到，原来的go对于切片扩容后的容量判断有一个明显的magic number：1024，在1024之前，增长的系数是2，而1024之后则变为1.25。关于为什么会这么设计，社区的相关讨论[^group-discussion]给出了几点理由：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果只选择翻倍的扩容策略，那么对于较大的切片来说，现有的方法可以更好的节省内存。&lt;/li&gt;
&lt;li&gt;如果只选择每次系数为1.25的扩容策略，那么对于较小的切片来说扩容会很低效。&lt;/li&gt;
&lt;li&gt;之所以选择一个小于2的系数，在扩容时被释放的内存块会在下一次扩容时更容易被重新利用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关于3的原理也很简单，对于一个2为公比的等比数列，那么其前（n-1）项和必然小于第n项：
$$
\sum(2^0,2^1,2^2,...,2^{(n-1)}) = 2^{n-1}-1 &amp;#x3C; 2^n
$$
也就是说，如果按2为系数进行扩容，那么&lt;strong&gt;每一次扩容所需要的空间都大于之前释放的所有空间之和&lt;/strong&gt;，那么也就谈不上重新利用了。&lt;/p&gt;
&lt;p&gt;可以看到当前方法也是作出了一些权衡，希望同时兼顾扩容效率和内存利用率，而以1024为分界点多半也是写代码人的个人喜好。&lt;/p&gt;
&lt;h2&gt;What&apos;s the problem？&lt;/h2&gt;
&lt;p&gt;上面的扩容策略一直使用了许多年，但它仍然存在一个问题：那就是扩容策略并&lt;strong&gt;不是一个单调函数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;对于扩容策略不是单调函数，我们用下面的代码来做验证：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func main() {
  for i := 0; i &amp;#x3C; 2000; i += 100 {
    fmt.Println(i, cap(append(make([]bool, i), true)))
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码通过申请一个长度为&lt;code&gt;i&lt;/code&gt;的切片，然后对其append一个元素来触发扩容。每次实验新增加100个元素，同时打印扩容前后的大小（单位byte）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/image-20220327154806590.CPTZ4bHW_Z11h7zm.webp&quot; alt=&quot;go1.17中扩容前后对比&quot;&gt;&lt;/p&gt;
&lt;p&gt;其中蓝色的线代表扩容前的每次容量大小变化，橙色线代表对应扩容后的大小变化，横坐标为实验序号，纵坐标为容量大小。&lt;/p&gt;
&lt;p&gt;可以看到在第11次与第12次扩容后，扩容后的容量反而出现了下降，由于存在内存对齐，所以最后的容量会向上取一个合理的数值。&lt;/p&gt;
&lt;p&gt;|            | 扩容前容量 | 扩容后容量 |
| ---------- | ---------- | ---------- |
| &lt;strong&gt;第11次&lt;/strong&gt; | 1000       | 2048       |
| &lt;strong&gt;第12次&lt;/strong&gt; | 1100       | 1408       |&lt;/p&gt;
&lt;h2&gt;Growslice In Go 1.18&lt;/h2&gt;
&lt;p&gt;接下来我们回到刚刚发布的go 1.18版本中，在1.18中，优化了切片扩容的策略[^github-commit]，让底层数组大小的增长更加平滑：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func growslice(et *_type, old slice, cap int) slice {
  ...
  
  newcap := old.cap
  doublecap := newcap + newcap
  if cap &gt; doublecap {
    newcap = cap
  } else {
    const threshold = 256
    if old.cap &amp;#x3C; threshold {
      newcap = doublecap
    } else {
      // Check 0 &amp;#x3C; newcap to detect overflow
      // and prevent an infinite loop.
      for 0 &amp;#x3C; newcap &amp;#x26;&amp;#x26; newcap &amp;#x3C; cap {
        // Transition from growing 2x for small slices
        // to growing 1.25x for large slices. This formula
        // gives a smooth-ish transition between the two.
        newcap += (newcap + 3*threshold) / 4
      }
      // Set newcap to the requested cap when
      // the newcap calculation overflowed.
      if newcap &amp;#x3C;= 0 {
        newcap = cap
      }
    }
  }
  
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重点关注第6行以后的代码，修改原来的1024为一个值为256的&lt;code&gt;threshold&lt;/code&gt;，大于阈值后的新容量的计算也由原来的
$$
newcap = oldcap + \frac{oldcap}{4} \space\space\space\space\space \text{if $oldcap \geq 1024$}
$$
变为了
$$
newcap = oldcap + \frac{oldcap + 3 \times threshold}{4} \space\space\space\space\space \text{if $oladcap \geq threshold$}
$$
通过减小阈值并固定增加一个常数，使得优化后的扩容的系数在阈值前后不再会出现从2到1.25的突变，该commit作者给出了几种原始容量下对应的“扩容系数”：&lt;/p&gt;
&lt;p&gt;| 原始容量 | 扩容系数 |
| -------- | -------- |
| 256      | 2.0      |
| 512      | 1.63     |
| 1024     | 1.44     |
| 2048     | 1.35     |
| 4096     | 1.30     |&lt;/p&gt;
&lt;p&gt;我们重新验证一下前一节中的代码，可以看到1.18中优化后的扩容策略可以保证是一个单调函数&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/image-20220327155204270.mjd2Lf87_JmKhv.webp&quot; alt=&quot;go1.18中扩容前后对比&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The End&lt;/h2&gt;
&lt;p&gt;这次问题源自于社区成员对于扩容机制中魔数的疑问，而对于整个runtime来说，slice的扩容只是其中最简单的冰山一角，即使如此，我们也看到社区对于细节问题的重视，通过一点一点的优化让golang能够不断进步。对于gopher来说，我们在使用golang的时候往往不会注意其背后的运行原理，因为go已经把一切都做好了，这也是我喜欢golang的原因，在“少即是多”的原则之下，go把许多复杂的运行机制很好的隐藏在runtime的源码之中，从而带给gopher最好的编程体验。而研究runtime的运行机制，便能够发现许多类似这种问题，通过研究社区的解决方法，也不失为一种乐趣。&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;[^github-commit]: &lt;a href=&quot;https://github.com/golang/go/commit/2dda92ff6f9f07eeb110ecbf0fc2d7a0ddd27f9d&quot;&gt;runtime: make slice growth formula a bit smoother · golang/go@2dda92f (github.com)&lt;/a&gt;
[^group-discussion]: &lt;a href=&quot;https://groups.google.com/g/golang-nuts/c/UaVlMQ8Nz3o&quot;&gt;slices grow at 25% after 1024 but why 1024? (google.com)&lt;/a&gt;&lt;/p&gt;</content:encoded><category>Golang</category></item><item><title>nacos客户端是如何进行服务发现的？</title><link>https://leyou240.github.io/blog/sca02</link><guid isPermaLink="true">https://leyou240.github.io/blog/sca02</guid><description>nacos客户端是如何进行服务发现的？</description><pubDate>Tue, 02 Jun 2020 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;本来自己写了一遍，但从网上找到的一个源码分析图。觉得比自己总结的好且更为细致，就直接放过来了。一图胜千言。&lt;/p&gt;
&lt;p&gt;https://xie.infoq.cn/article/d342a914b8754dd52f5709b43&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/nacos.DTpU4uxy_XJiYk.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;服务心跳、服务剔除  这些都与eureka类似。定时访问对应的接口即可&lt;/p&gt;
&lt;p&gt;与eureka不太一样的是，服务同步如果是ephemeral则异步更新，达到最终一致性 AP。如果是持久化的注册，则使用raft协议同步 CP。&lt;/p&gt;
&lt;p&gt;nacos1.x版本使用http进行通信、2.x版本做了重构改为使用grpc进行内部节点以及对外通信，目前为beta版。&lt;/p&gt;</content:encoded><category>spring cloud alibaba源码</category></item><item><title>nacos注册中心初始化</title><link>https://leyou240.github.io/blog/sca01</link><guid isPermaLink="true">https://leyou240.github.io/blog/sca01</guid><description>nacos注册中心初始化</description><pubDate>Mon, 01 Jun 2020 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;注册中心启动类就一个springboot应用，看不出什么东西&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@EnableScheduling
@SpringBootApplication(scanBasePackages = {&quot;com.alibaba.nacos.naming&quot;, &quot;com.alibaba.nacos.core&quot;})
public class NamingApp {
    
    public static void main(String[] args) {
        SpringApplication.run(NamingApp.class, args);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过官方文档已经一些分析，得知底层是用了raft协议来做集群同步&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;//PersistentConsistencyServiceDelegateImpl
private BasePersistentServiceProcessor createNewPersistentServiceProcessor(ProtocolManager protocolManager,
        ClusterVersionJudgement versionJudgement) throws Exception {
    //根据相关配置，判断到底是standlone还是raft模式
    final BasePersistentServiceProcessor processor =
            EnvUtil.getStandaloneMode() ? new StandalonePersistentServiceProcessor(versionJudgement)
                    : new PersistentServiceProcessor(protocolManager, versionJudgement);
    //调用基类的方法
    processor.afterConstruct();
    return processor;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体的代码逻辑&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;//PersistentServiceProcessor
@Override
public void afterConstruct() {
    super.afterConstruct();
    this.protocol.addRequestProcessors(Collections.singletonList(this));
    //监听元数据的状态
    this.protocol.protocolMetaData()
            .subscribe(Constants.NAMING_PERSISTENT_SERVICE_GROUP, MetadataKey.LEADER_META_DATA,
                    (o, arg) -&gt; hasLeader = StringUtils.isNotBlank(String.valueOf(arg)));
    // If you choose to use the new RAFT protocol directly, there will be no compatible logical execution
    if (EnvUtil.getProperty(Constants.NACOS_NAMING_USE_NEW_RAFT_FIRST, Boolean.class, false)) {
        NotifyCenter.registerSubscriber(notifier);
        waitLeader();
        startNotify = true;
    }
}
//等待leader选举完成
private void waitLeader() {
    while (!hasLeader &amp;#x26;&amp;#x26; !hasError) {
        Loggers.RAFT.info(&quot;Waiting Jraft leader vote ...&quot;);
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException ignored) {
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再看raft相关的代码&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@PostConstruct
public void init() throws Exception {
    Loggers.RAFT.info(&quot;initializing Raft sub-system&quot;);
    final long start = System.currentTimeMillis();

    raftStore.loadDatums(notifier, datums);

    setTerm(NumberUtils.toLong(raftStore.loadMeta().getProperty(&quot;term&quot;), 0L));

    Loggers.RAFT.info(&quot;cache loaded, datum count: {}, current term: {}&quot;, datums.size(), peers.getTerm());

    initialized = true;

    Loggers.RAFT.info(&quot;finish to load data from disk, cost: {} ms.&quot;, (System.currentTimeMillis() - start));
	//选举
    masterTask = GlobalExecutor.registerMasterElection(new MasterElection());
    //心跳
    heartbeatTask = GlobalExecutor.registerHeartbeat(new HeartBeat());

    versionJudgement.registerObserver(isAllNewVersion -&gt; {
        stopWork = isAllNewVersion;
        if (stopWork) {
            try {
                shutdown();
                raftListener.removeOldRaftMetadata();
            } catch (NacosException e) {
                throw new NacosRuntimeException(NacosException.SERVER_ERROR, e);
            }
        }
    }, 100);

    NotifyCenter.registerSubscriber(notifier);

    Loggers.RAFT.info(&quot;timer started: leader timeout ms: {}, heart-beat timeout ms: {}&quot;,
            GlobalExecutor.LEADER_TIMEOUT_MS, GlobalExecutor.HEARTBEAT_INTERVAL_MS);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从后置处理器触发选举，心跳等逻辑。达到大于集群半数节点即可选出leader。&lt;/p&gt;
&lt;p&gt;所有的选举都通过http协议提交，详见RaftController中的/vote接口&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public synchronized RaftPeer receivedVote(RaftPeer remote) {
    if (stopWork) {
        throw new IllegalStateException(&quot;old raft protocol already stop work&quot;);
    }
    if (!peers.contains(remote)) {
        throw new IllegalStateException(&quot;can not find peer: &quot; + remote.ip);
    }

    RaftPeer local = peers.get(NetUtils.localServer());
    //不符合条件的投票，此时投票给自己
    if (remote.term.get() &amp;#x3C;= local.term.get()) {
        String msg = &quot;received illegitimate vote&quot; + &quot;, voter-term:&quot; + remote.term + &quot;, votee-term:&quot; + local.term;

        Loggers.RAFT.info(msg);
        if (StringUtils.isEmpty(local.voteFor)) {
            local.voteFor = local.ip;
        }

        return local;
    }
	//修改自己的下次投票时间及其他信息，并投票给remote
    local.resetLeaderDue();
	
    local.state = RaftPeer.State.FOLLOWER;
    local.voteFor = remote.ip;
    local.term.set(remote.term.get());

    Loggers.RAFT.info(&quot;vote {} as leader, term: {}&quot;, remote.ip, remote.term);

    return local;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>spring cloud alibaba源码</category></item><item><title>SpringCloud源码| zuul源码</title><link>https://leyou240.github.io/blog/sc06</link><guid isPermaLink="true">https://leyou240.github.io/blog/sc06</guid><description>SpringCloud源码| zuul源码</description><pubDate>Fri, 13 Dec 2019 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;zuul主要使用责任链设计模式&lt;/p&gt;
&lt;p&gt;其中有如下过滤器&lt;/p&gt;
&lt;p&gt;pre过滤器&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-3：ServletDetectionFilter&lt;/li&gt;
&lt;li&gt;-2：Servlet30WrapperFilter&lt;/li&gt;
&lt;li&gt;-1：FromBodyWrapperFilter&lt;/li&gt;
&lt;li&gt;1：DebugFilter&lt;/li&gt;
&lt;li&gt;5：PreDecorationFilter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;routing过滤器&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;10：RibbonRoutingFilter&lt;/li&gt;
&lt;li&gt;100：SimpleHostRoutingFilter&lt;/li&gt;
&lt;li&gt;500：SendForwardFilter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;post过滤器&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0：SendErrorFilter&lt;/li&gt;
&lt;li&gt;900：LocationRewriteFilter&lt;/li&gt;
&lt;li&gt;1000：SendResponseFilter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;@EnableZuulProxy为spring cloud中zuul的入口&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在ZuulProxyAutoConfiguration进行相关类的注册&lt;/p&gt;
&lt;p&gt;经过断点调试，核心逻辑为此方法ZuulServlet#service&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
    try {
        init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

        // Marks this request as having passed through the &quot;Zuul engine&quot;, as opposed to servlets
        // explicitly bound in web.xml, for which requests will not have the same data attached
        //从threadlocal中获取线程独有的RequestContext
        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();
		
        //先执行pre过滤器
        try {
            preRoute();
        } catch (ZuulException e) {
            //若执行失败，则执行post过滤器 并返回
            error(e);
            postRoute();
            return;
        }
        //执行route过滤器
        try {
            route();
        } catch (ZuulException e) {
            //报错则执行post过滤器 并返回
            error(e);
            postRoute();
            return;
        }
        //若报错则再重试一遍
        try {
            postRoute();
        } catch (ZuulException e) {
            error(e);
            return;
        }

    } catch (Throwable e) {
        error(new ZuulException(e, 500, &quot;UNHANDLED_EXCEPTION_&quot; + e.getClass().getName()));
    } finally {
        //执行完毕之后，回收threadlocal中的context
        RequestContext.getCurrentContext().unset();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大致的逻辑就是如此，因为在项目中暂时不准备使用zuul。详细源码以后再进行分析&lt;/p&gt;</content:encoded><category>SpringCloud源码</category></item><item><title>dubbo源码浅析</title><link>https://leyou240.github.io/blog/dubbo01</link><guid isPermaLink="true">https://leyou240.github.io/blog/dubbo01</guid><description>dubbo源码浅析</description><pubDate>Tue, 20 Jun 2017 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Dubbo 为了更好地达到 OCP 原则（即“对扩展开放，对修改封闭”的原则），采用了“&lt;strong&gt;微内核+插件&lt;/strong&gt;”的架构。那什么是微内核架构呢？微内核架构也被称为插件化架构（Plug-in Architecture），这是一种面向功能进行拆分的可扩展性架构。内核功能是比较稳定的，只负责管理插件的生命周期，不会因为系统功能的扩展而不断进行修改。功能上的扩展全部封装到插件之中，插件模块是独立存在的模块，包含特定的功能，能拓展内核系统的功能。&lt;/p&gt;
&lt;p&gt;微内核常见的方式有SPI（Service Provider Interface）、Factory、IOC、OSGI等。dubbo采用的是SPI，参考了JDK原生的SPI，并进行了优化和功能增强。&lt;/p&gt;
&lt;h2&gt;JDK SPI&lt;/h2&gt;
&lt;p&gt;需要在Classpath 下的 META-INF/services/目录创建一个以接口名命名的文件。mysql的driver就是使用的此方式，spring中相关框架也有相关应用&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface Log { 
    void log(String info); 
}

public class Logback implements Log { 
    @Override 
    public void log(String info) { 
        System.out.println(&quot;Logback:&quot; + info); 
    } 
} 

public class Log4j implements Log { 
    @Override 
    public void log(String info) { 
        System.out.println(&quot;Log4j:&quot; + info); 
    } 
} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在项目的 resources/META-INF/services 目录下添加一个名为 com.xxx.Log 的文件，这是 JDK SPI 需要读取的配置文件，具体内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;com.xxx.impl.Log4j 
com.xxx.impl.Logback 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用方法如下&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main { 
    public static void main(String[] args) { 
        ServiceLoader&amp;#x3C;Log&gt; serviceLoader =  
                ServiceLoader.load(Log.class); 
        Iterator&amp;#x3C;Log&gt; iterator = serviceLoader.iterator(); 
        while (iterator.hasNext()) { 
            Log log = iterator.next(); 
            log.log(&quot;JDK SPI&quot;);  
        } 
    } 
} 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;JDK SPI缺点&lt;/h3&gt;
&lt;p&gt;所有的实现类都会被加载并初始化，如果没有使用就会有资源的浪费。&lt;/p&gt;
&lt;h2&gt;dubbo SPI&lt;/h2&gt;
&lt;p&gt;dubbo SPI将配置文件改为kv形式，在需要用到的时候再进行加载并初始化，并且使用的时候也不需要遍历SPI所有的接口。&lt;/p&gt;
&lt;h3&gt;@SPI注解&lt;/h3&gt;
&lt;p&gt;dubbo中某个接口被@SPI注解修饰时，就表示该接口是&lt;strong&gt;dubbo扩展接口&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@SPI(value = &quot;dubbo&quot;, scope = ExtensionScope.FRAMEWORK)
public interface Protocol {
//忽略其内容
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;@SPI 注解的 value 值指定了&lt;strong&gt;默认的扩展名称&lt;/strong&gt;，例如，在通过 Dubbo SPI 加载 Protocol 接口实现时，如果没有明确指定扩展名，则默认会将 @SPI 注解的 value 值作为扩展名，即加载 dubbo 这个扩展名对应的 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension();//与下面等价
//Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(&quot;dubbo&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ExtensionLoader.getExtension()方法。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private T createExtension(String name, boolean wrap) {
    //查找对应的class对象
    Class&amp;#x3C;?&gt; clazz = getExtensionClasses().get(name);
    if (clazz == null || unacceptableExceptions.contains(name)) {
        throw findException(name);
    }
    try {
        //尝试从缓存中获取
        T instance = (T) extensionInstances.get(clazz);
        if (instance == null) {
            //创建对象
            extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
            instance = (T) extensionInstances.get(clazz);
            //后置处理器
            instance = postProcessBeforeInitialization(instance, name);
            //依赖注入，查找所有的setter方法，有合适的就调用setter方法进行注入
            injectExtension(instance);
            //后置处理器
            instance = postProcessAfterInitialization(instance, name);
        }

        if (wrap) {
            List&amp;#x3C;Class&amp;#x3C;?&gt;&gt; wrapperClassesList = new ArrayList&amp;#x3C;&gt;();
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }

            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                for (Class&amp;#x3C;?&gt; wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    boolean match = (wrapper == null) ||
                        ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &amp;#x26;&amp;#x26;
                            !ArrayUtils.contains(wrapper.mismatches(), name));
                    if (match) {
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        instance = postProcessAfterInitialization(instance, name);
                    }
                }
            }
        }

        // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException(&quot;Extension instance (name: &quot; + name + &quot;, class: &quot; +
            type + &quot;) couldn&apos;t be instantiated: &quot; + t.getMessage(), t);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;@Adaptive 注解与适配器&lt;/h3&gt;
&lt;p&gt;@Adaptive 注解用来实现dubbo的适配器功能。&lt;/p&gt;
&lt;p&gt;Adaptive 注解在类上的情况很少，在 Dubbo 中，仅有两个类被 Adaptive 注解了，分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况，表示拓展的加载逻辑由人工编码完成。&lt;/p&gt;
&lt;p&gt;加到方法上，较常用。表示拓展的加载逻辑需由框架自动生成，dubbo借助了javaassist框架，进行相关代码生成。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;//以下代码，由于自己的强迫症(部分用了StringBuilder，部分没用)，对代码做了略微修改
public String generate(boolean sort) {
    // 如果接口中，没有方法加Adaptive注解，则直接抛异常
    if (!hasAdaptiveMethod()) {
        throw new IllegalStateException(&quot;No adaptive method exist on extension &quot; + type.getName() + &quot;, refuse to create the adaptive class!&quot;);
    }

    StringBuilder code = new StringBuilder();
    //包名
    generatePackageInfo(code);
    //import相关代码
    generateImports(code);
    //类名，以LoadBalance接口为例，生成的名字为LoadBalance$Adaptive
    generateClassDeclaration(code);

    Method[] methods = type.getMethods();
    if (sort) {
        Arrays.sort(methods, Comparator.comparing(Method::toString));
    }
    //生成所有的方法
    for (Method method : methods) {
        generateMethod(method,code);
    }
    code.append(&apos;}&apos;);

    String generatedCode = code.toString();
    if (logger.isDebugEnabled()) {
        logger.debug(generatedCode);
    }
    return generatedCode;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;@Activate注解与自动激活特性&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    /**
     * Activate the current extension when one of the groups matches. The group passed into
     * {@link ExtensionLoader#getActivateExtension(URL, String, String)} will be used for matching.
     *
     * @return group names to match
     * @see ExtensionLoader#getActivateExtension(URL, String, String)
     */
    //修饰实现类是在provider端还是在consumer端
    String[] group() default {};

    /**
     * Activate the current extension when the specified keys appear in the URL&apos;s parameters.
     * &amp;#x3C;p&gt;
     * For example, given &amp;#x3C;code&gt;@Activate(&quot;cache, validation&quot;)&amp;#x3C;/code&gt;, the current extension will be return only when
     * there&apos;s either &amp;#x3C;code&gt;cache&amp;#x3C;/code&gt; or &amp;#x3C;code&gt;validation&amp;#x3C;/code&gt; key appeared in the URL&apos;s parameters.
     * &amp;#x3C;/p&gt;
     *
     * @return URL parameter keys
     * @see ExtensionLoader#getActivateExtension(URL, String)
     * @see ExtensionLoader#getActivateExtension(URL, String, String)
     */
    //url参数中出现指定的key才被激活
    String[] value() default {};

    /**
     * Absolute ordering info, optional
     *
     * Ascending order, smaller values will be in the front o the list.
     *
     * @return absolute ordering info
     */
    //确定实现类的排序，
    int order() default 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用方法&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;URL url = URL.valueOf(&quot;test://localhost/test&quot;);
List&amp;#x3C;ActivateExt1&gt; list = getExtensionLoader(ActivateExt1.class)
    .getActivateExtension(url, new String[]{}, &quot;order&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;dubbo中无非就是用了以上SPI的功能，来实现相关的功能。理解了以上代码，再逐个去看dubbo中的相关代码。在需要扩展或者修改的时候，就可以通过SPI来实现。我之前实现了一个按id定向loadbalance的扩展，就是参考了LoadBalance接口源码，进行了相关扩展。&lt;/p&gt;
&lt;p&gt;在使用**@DubboReference&lt;strong&gt;和&lt;/strong&gt;@DubboService**，在相关的注解配置中，使用自己扩展的功能即可。如下&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@DubboReference(interfaceClass = Admin.class,loadbalance = &quot;myhash&quot;)
   private Admin admin;
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>dubbo</category></item><item><title>事务隔离级别，MVCC与国内主流分布式事务方案</title><link>https://leyou240.github.io/blog/sc07</link><guid isPermaLink="true">https://leyou240.github.io/blog/sc07</guid><description>事务隔离级别，MVCC与国内主流分布式事务方案</description><pubDate>Thu, 11 Jun 2015 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;ACID与隔离级别&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;事务具有以下四种特征：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;原子性（Atomicity）&lt;/p&gt;
&lt;p&gt;事务包含的所有操作要么全部成功，要么全部失败回滚。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一致性（Consistency）&lt;/p&gt;
&lt;p&gt;事务必须使数据库从一个一致性状态变换到另一个一致性状态，也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说，假设用户A和用户B两者的钱加起来一共是5000，那么不管A和B之间如何转账，转几次账，事务结束后两个用户的钱相加起来应该还得是5000，这就是事务的一致性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;隔离性（Isolation）&lt;/p&gt;
&lt;p&gt;不同事务并发执行操作相同数据时，每个事务都有各自完整的数据空间，即一个事务内所操作使用的数据与其他并发执行的各事务不互相干扰。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;持久性（Durability）&lt;/p&gt;
&lt;p&gt;一个事务一旦提交，对数据库中数据状态变更应该是永久保存下来，即使发生系统崩溃或宕机，也一定能恢复到事务成功结束时的状态。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;隔离级别&lt;/p&gt;
&lt;p&gt;| 隔离级别 | 脏读   | 不可重复读 | 幻读   |
| -------- | ------ | ---------- | ------ |
| 读未提交 | 存在   | 存在       | 存在   |
| 读已提交 | 不存在 | 存在       | 存在   |
| 可重复读 | 不存在 | 不存在     | 存在   |
| 串行化   | 不存在 | 不存在     | 不存在 |&lt;/p&gt;
&lt;p&gt;下面用口语化对各个隔离级别进行解释。&lt;/p&gt;
&lt;p&gt;假设，数据库中有这样一个数据，id=1 name=张三&lt;/p&gt;
&lt;h3&gt;读未提交&lt;/h3&gt;
&lt;p&gt;事务1中，将张三修改为&lt;strong&gt;李四&lt;/strong&gt;，但&lt;strong&gt;未提交&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;事务2中，就读到了id=1 name=&lt;strong&gt;李四&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;此为读未提交，存在&lt;strong&gt;脏读&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;读已提交&lt;/h3&gt;
&lt;p&gt;事务1中，将张三修改为&lt;strong&gt;李四&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;事务2中，第一次读到了id=1 name=&lt;strong&gt;张三&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;事务1，&lt;strong&gt;提交&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;事务2中，第一次读到了id=1 name=&lt;strong&gt;李四&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;此为读已提交&lt;/p&gt;
&lt;h3&gt;可重复读&lt;/h3&gt;
&lt;p&gt;事务1中，将张三修改为&lt;strong&gt;李四或删除&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;事务2中，第一次读到了id=1 name=&lt;strong&gt;张三&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;事务1，&lt;strong&gt;提交&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;事务2中，第2次读到了id=1 name=&lt;strong&gt;张三&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;此为可重复读，此为&lt;strong&gt;mysql的默认隔离级别&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;串行化&lt;/h3&gt;
&lt;p&gt;所有事务按顺序排队执行&lt;/p&gt;
&lt;h3&gt;幻读&lt;/h3&gt;
&lt;p&gt;事务1中，&lt;strong&gt;新增&lt;/strong&gt;一个数据，id=2 name=李四~~、或者&lt;strong&gt;删除&lt;/strong&gt;id=1的数据(此为不可重复读)~~&lt;/p&gt;
&lt;p&gt;事务2中，第一次select * 读到了一条数据。 id=1 name=张三&lt;/p&gt;
&lt;p&gt;事务1，&lt;strong&gt;提交&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;事务2中，&lt;strong&gt;若事务1新增时&lt;/strong&gt;第2次读到了两条数据~~，&lt;strong&gt;若事务1删除&lt;/strong&gt;第2次读到的结果为空(此为不可重复读)~~&lt;/p&gt;
&lt;h2&gt;MVCC&lt;/h2&gt;
&lt;p&gt;修改数据：事务未提交前，数据的新旧版本共存，但拥有不同的版本号；&lt;/p&gt;
&lt;p&gt;读取数据：先获取当前版本号V，再去查询&amp;#x3C;=V版本已提交的数据；&lt;/p&gt;
&lt;p&gt;写操作获取行锁，读操作不需要锁，有效避免读写锁竞争，提高读写并发度。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;分布式事务方案&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;1、TCC方案：try（对资源做锁定或者预留）、confirm（本地提交）、cancel（本地回滚）。基于bytetcc、seata等框架。基于TransactionManager来实现&lt;/p&gt;
&lt;p&gt;2、本地消息表：ebay提出的，国内少有人用&lt;/p&gt;
&lt;p&gt;3、可靠消息最终一致性方案：一般来说，基于MQ来实现&lt;/p&gt;
&lt;p&gt;4、最大努力通知方案：本地事务执行完以后，由后台任务发送消息到MQ，由后端系统去执行（需做幂等），执行完通知上游系统&lt;/p&gt;
&lt;p&gt;5、saga：这个只有华为的service comb实现了。&lt;/p&gt;</content:encoded><category>分布式事务</category></item><item><title>SpringCloud源码| ribbon源码</title><link>https://leyou240.github.io/blog/sc05</link><guid isPermaLink="true">https://leyou240.github.io/blog/sc05</guid><description>SpringCloud源码| ribbon源码</description><pubDate>Sat, 30 May 2015 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;ribbon的作用：一个服务部署多个实例时，负责负载均衡请求的组件&lt;/p&gt;
&lt;h3&gt;ribbon重要组件&lt;/h3&gt;
&lt;p&gt;ILoadBalancer：负载均衡器，其中包含了IRule和IPing&lt;/p&gt;
&lt;p&gt;IRule：负责负载均衡选择服务的规则&lt;/p&gt;
&lt;p&gt;IPing：定时ping服务器，判断其是否存活&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class MyConfiguration {
@Bean
public IRule getRule() {
return new MyRule();
}

@Bean
public IPing getPing() {
return new MyPing();
}
}

@RibbonClient(name = &quot;ServiceB&quot;, configuration = MyConfiguration.class)
public class ServiceBConfiguration {

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ribbon的入口&lt;/h3&gt;
&lt;p&gt;@LoadBalanced将将一个RestTemplate标志为底层采用LoadBalancerClient来执行实际的http请求，支持负载均衡。&lt;/p&gt;
&lt;p&gt;org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration中有相关的bean注册。LoadBalancerRequestFactory、LoadBalancerInterceptor、RestTemplateCustomizer等等&lt;/p&gt;
&lt;p&gt;使用RestTemplateCustomizer对每个restTemplate，使用LoadBalancerInterceptor进行定制。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass(&quot;org.springframework.retry.support.RetryTemplate&quot;)
	static class LoadBalancerInterceptorConfig {
		//配置拦截器
		@Bean
		public LoadBalancerInterceptor loadBalancerInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}
		//RestTemplate定制化，使用LoadBalancerInterceptor
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -&gt; {
				List&amp;#x3C;ClientHttpRequestInterceptor&gt; list = new ArrayList&amp;#x3C;&gt;(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;LoadBalancerClient是谁？&lt;/h4&gt;
&lt;p&gt;LoadBalancerClient在org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration中进行注册了，RibbonLoadBalancerClient&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需要注意的是，对于client来说，每个服务也可以说服务名对应一个独立的ApplicationContext。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient(final SpringClientFactory springClientFactory) {
   return new RibbonLoadBalancerClient(springClientFactory);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;那么LoadBalancerClient底层使用的ILoadBalancer是谁呢？&lt;/h4&gt;
&lt;p&gt;org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration中有如下配置&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;	@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList&amp;#x3C;Server&gt; serverList, ServerListFilter&amp;#x3C;Server&gt; 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&amp;#x3C;&gt;(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ZoneAwareLoadBalancer与eureka整合，如何获取到注册表呢？&lt;/h4&gt;
&lt;p&gt;其父类DynamicServerListLoadBalancer#initWithNiwsConfig方法&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
    try {
        super.initWithNiwsConfig(clientConfig);
        String niwsServerListClassName = clientConfig.getPropertyAsString(
                CommonClientConfigKey.NIWSServerListClassName,
                DefaultClientConfigImpl.DEFAULT_SEVER_LIST_CLASS);

        ServerList&amp;#x3C;T&gt; niwsServerListImpl = (ServerList&amp;#x3C;T&gt;) ClientFactory
                .instantiateInstanceWithClientConfig(niwsServerListClassName, clientConfig);
        this.serverListImpl = niwsServerListImpl;

        if (niwsServerListImpl instanceof AbstractServerList) {
            AbstractServerListFilter&amp;#x3C;T&gt; niwsFilter = ((AbstractServerList) niwsServerListImpl)
                    .getFilterImpl(clientConfig);
            niwsFilter.setLoadBalancerStats(getLoadBalancerStats());
            this.filter = niwsFilter;
        }

        String serverListUpdaterClassName = clientConfig.getPropertyAsString(
                CommonClientConfigKey.ServerListUpdaterClassName,
                DefaultClientConfigImpl.DEFAULT_SERVER_LIST_UPDATER_CLASS
        );

        this.serverListUpdater = (ServerListUpdater) ClientFactory
                .instantiateInstanceWithClientConfig(serverListUpdaterClassName, clientConfig);

        restOfInit(clientConfig);
    } catch (Exception e) {
        throw new RuntimeException(
                &quot;Exception while initializing NIWSDiscoveryLoadBalancer:&quot;
                        + clientConfig.getClientName()
                        + &quot;, niwsClientConfig:&quot; + clientConfig, e);
    }
}

void restOfInit(IClientConfig clientConfig) {
    boolean primeConnection = this.isEnablePrimingConnections();
    // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
    this.setEnablePrimingConnections(false);
    enableAndInitLearnNewServersFeature();

    updateListOfServers();
    if (primeConnection &amp;#x26;&amp;#x26; this.getPrimeConnections() != null) {
        this.getPrimeConnections()
                .primeConnections(getReachableServers());
    }
    this.setEnablePrimingConnections(primeConnection);
    LOGGER.info(&quot;DynamicServerListLoadBalancer for client {} initialized: {}&quot;, clientConfig.getClientName(), this.toString());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比较坑的是RibbonClientConfiguration和EurekaRibbonClientConfiguration都配置了serverlist&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;//RibbonClientConfiguration配置的
@Bean
@ConditionalOnMissingBean
@SuppressWarnings(&quot;unchecked&quot;)
public ServerList&amp;#x3C;Server&gt; ribbonServerList(IClientConfig config) {
   if (this.propertiesFactory.isSet(ServerList.class, name)) {
      return this.propertiesFactory.get(ServerList.class, config, name);
   }
   ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
   serverList.initWithNiwsConfig(config);
   return serverList;
}
//EurekaRibbonClientConfiguration
@Bean
	@ConditionalOnMissingBean
	public ServerList&amp;#x3C;?&gt; ribbonServerList(IClientConfig config,
			Provider&amp;#x3C;EurekaClient&gt; eurekaClientProvider) {
		if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
			return this.propertiesFactory.get(ServerList.class, config, serviceId);
		}
		DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
				config, eurekaClientProvider);
		DomainExtractingServerList serverList = new DomainExtractingServerList(
				discoveryServerList, config, this.approximateZoneFromHostname);
		return serverList;
	}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过调试发现，还是DiscoveryEnabledNIWSServerList的getUpdatedServerList()方法返回的server list。此处估计是根据配置，使用各自的server&lt;/p&gt;
&lt;h4&gt;那么server list怎么更新服务列表呢？&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;//RibbonClientConfiguration配置的
@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
   return new PollingServerListUpdater(config);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认一分钟过后第一次执行，然后每隔30秒执行一次，刷新注册表&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;//RibbonClientConfiguration配置的
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
   if (this.propertiesFactory.isSet(IRule.class, name)) {
      return this.propertiesFactory.get(IRule.class, config, name);
   }
   ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
   rule.initWithNiwsConfig(config);
   return rule;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认的负载均衡为ZoneAvoidanceRule，其基类PredicateBasedRule的choose方法。&lt;/p&gt;
&lt;p&gt;默认是轮询算法&lt;/p&gt;
&lt;p&gt;RibbonLoadBalancerClient调用excute执行方法调用。其中重要的调用&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;//方法调用
T returnVal = request.apply(serviceInstance);

public class AsyncLoadBalancerInterceptor implements AsyncClientHttpRequestInterceptor {
    private LoadBalancerClient loadBalancer;

    public AsyncLoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        this.loadBalancer = loadBalancer;
    }

    public ListenableFuture&amp;#x3C;ClientHttpResponse&gt; intercept(final HttpRequest request, final byte[] body, final AsyncClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        //底层实际调用
        return (ListenableFuture)this.loadBalancer.execute(serviceName, new LoadBalancerRequest&amp;#x3C;ListenableFuture&amp;#x3C;ClientHttpResponse&gt;&gt;() {
            public ListenableFuture&amp;#x3C;ClientHttpResponse&gt; apply(final ServiceInstance instance) throws Exception {
                HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
                return execution.executeAsync(serviceRequest, body);
            }
        });
    }
}
//ServiceRequestWrapper#getURI方法，将服务名替换为选择到的服务具体ip端口
public class ServiceRequestWrapper extends HttpRequestWrapper {

	private final ServiceInstance instance;

	private final LoadBalancerClient loadBalancer;

	public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance,
			LoadBalancerClient loadBalancer) {
		super(request);
		this.instance = instance;
		this.loadBalancer = loadBalancer;
	}

	@Override
	public URI getURI() {
		URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
		return uri;
	}

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/RibbonLoadBalancerClient%E5%AE%9E%E9%99%85%E6%89%A7%E8%A1%8Chttp%E8%AF%B7%E6%B1%82%E7%9A%84%E6%B5%81%E7%A8%8B.D1TUzEed_ZGXOcE.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;最后ribbon的健康检查。调用对应服务的健康检查接口，进行判断&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
   if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
      return this.propertiesFactory.get(IPing.class, config, serviceId);
   }
   NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
   ping.initWithNiwsConfig(config);
   return ping;
}
//NIWSDiscoveryPing#isAlive
public boolean isAlive(Server server) {
		    boolean isAlive = true;
		    if (server!=null &amp;#x26;&amp;#x26; server instanceof DiscoveryEnabledServer){
	            DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;	            
	            InstanceInfo instanceInfo = dServer.getInstanceInfo();
	            if (instanceInfo!=null){	                
	                InstanceStatus status = instanceInfo.getStatus();
	                if (status!=null){
	                    isAlive = status.equals(InstanceStatus.UP);
	                }
	            }
	        }
		    return isAlive;
		}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>SpringCloud源码</category></item><item><title>SpringCloud源码| hystrix源码</title><link>https://leyou240.github.io/blog/sc04</link><guid isPermaLink="true">https://leyou240.github.io/blog/sc04</guid><description>SpringCloud源码| hystrix源码</description><pubDate>Sat, 23 May 2015 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;hystrix在微服务中扮演重要作用，其实现了隔离、熔断、降级等重要组件&lt;/p&gt;
&lt;p&gt;但在spring cloud中，常与feign搭配起来使用。故本文仅包含其余feign整合后的相关源码。&lt;/p&gt;
&lt;h3&gt;hystrix资源隔离&lt;/h3&gt;
&lt;p&gt;共有两种：线程池的资源隔离，信号量的资源隔离&lt;/p&gt;
&lt;p&gt;线程池：适合绝大多数的场景，99%的，线程池，对依赖服务的网络请求的调用和访问，timeout这种问题&lt;/p&gt;
&lt;p&gt;信号量：适合对内部的一些比较复杂的业务逻辑的访问，但是像这种访问，系统内部的代码，其实不涉及任何的网络请求，那么只要做信号量的普通限流就可以了，因为不需要去捕获timeout类似的问题。但算法+数据结构的效率不是太高，并发量突然太高，因为这里稍微耗时一些，导致很多线程卡在这里的话，不太好，所以进行一个基本的资源隔离和访问，避免内部复杂的低效率的代码，导致大量的线程被hang住&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/feign%E4%B8%8Ehystrix%E6%95%B4%E5%90%88%E7%9A%84%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86.RMdOtLsG_1XnjWT.webp&quot; alt=&quot;feign与hystrix整合的核心原理&quot;&gt;&lt;/p&gt;
&lt;p&gt;feign集成hystrix以后，HystrixFeign.Builder则被替换为HystrixFeign.Builder。以支持hystrix相关组件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/%E5%90%AF%E7%94%A8hystrix%E4%B9%8B%E5%90%8E%E7%94%9F%E6%88%90feign%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E7%9A%84%E8%BF%87%E7%A8%8B.BeeX_A53_ZleuBG.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;//ReflectiveFeign 类中
public &amp;#x3C;T&gt; T newInstance(Target&amp;#x3C;T&gt; target) {
  Map&amp;#x3C;String, MethodHandler&gt; nameToHandler = targetToHandlersByName.apply(target);
  //对应FeignClient中的方法，及代理类
  Map&amp;#x3C;Method, MethodHandler&gt; methodToHandler = new LinkedHashMap&amp;#x3C;Method, MethodHandler&gt;();
  List&amp;#x3C;DefaultMethodHandler&gt; defaultMethodHandlers = new LinkedList&amp;#x3C;DefaultMethodHandler&gt;();

  for (Method method : target.type().getMethods()) {
    if (method.getDeclaringClass() == Object.class) {
      continue;
    } else if (Util.isDefault(method)) {
      DefaultMethodHandler handler = new DefaultMethodHandler(method);
      defaultMethodHandlers.add(handler);
      methodToHandler.put(method, handler);
    } else {
      methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
    }
  }
  //调用HystrixFeign.Build方法
  InvocationHandler handler = factory.create(target, methodToHandler);
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
      new Class&amp;#x3C;?&gt;[] {target.type()}, handler);

  for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}

//HystrixFeign.Build
 @Override
    public Feign build() {
      return build(null);
    }
Feign build(final FallbackFactory&amp;#x3C;?&gt; nullableFallbackFactory) {
    super.invocationHandlerFactory(new InvocationHandlerFactory() {
        @Override
        public InvocationHandler create(Target target,
                                        Map&amp;#x3C;Method, MethodHandler&gt; dispatch) {
            return new HystrixInvocationHandler(target, dispatch, setterFactory,
                                                nullableFallbackFactory);
        }
    });
    super.contract(new HystrixDelegatingContract(contract));
    return super.build();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HystrixInvocationHandler重要代码&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public HystrixCommand.Setter create(Target&amp;#x3C;?&gt; target, Method method) {
  String groupKey = target.name();
  String commandKey = Feign.configKey(target.type(), method);
  return HystrixCommand.Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
      .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个接口一个线程池即groupKey，一个方法一个线程即commandKey&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  HystrixInvocationHandler(Target&amp;#x3C;?&gt; target, Map&amp;#x3C;Method, MethodHandler&gt; dispatch,
      SetterFactory setterFactory, FallbackFactory&amp;#x3C;?&gt; fallbackFactory) {
    this.target = checkNotNull(target, &quot;target&quot;);
    this.dispatch = checkNotNull(dispatch, &quot;dispatch&quot;);
    this.fallbackFactory = fallbackFactory;
    this.fallbackMethodMap = toFallbackMethod(dispatch);
    this.setterMethodMap = toSetters(setterFactory, target, dispatch.keySet());
  }
  /**
   * Process all methods in the target so that appropriate setters are created.
   */
  static Map&amp;#x3C;Method, Setter&gt; toSetters(SetterFactory setterFactory,
                                       Target&amp;#x3C;?&gt; target,
                                       Set&amp;#x3C;Method&gt; methods) {
    Map&amp;#x3C;Method, Setter&gt; result = new LinkedHashMap&amp;#x3C;Method, Setter&gt;();
    for (Method method : methods) {
      method.setAccessible(true);
      result.put(method, setterFactory.create(target, method));
    }
    return result;
  }

@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
    throws Throwable {
  // early exit if the invoked method is from java.lang.Object
  // code is the same as ReflectiveFeign.FeignInvocationHandler
  if (&quot;equals&quot;.equals(method.getName())) {
    try {
      Object otherHandler =
          args.length &gt; 0 &amp;#x26;&amp;#x26; args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
      return equals(otherHandler);
    } catch (IllegalArgumentException e) {
      return false;
    }
  } else if (&quot;hashCode&quot;.equals(method.getName())) {
    return hashCode();
  } else if (&quot;toString&quot;.equals(method.getName())) {
    return toString();
  }

  HystrixCommand&amp;#x3C;Object&gt; hystrixCommand =
      new HystrixCommand&amp;#x3C;Object&gt;(setterMethodMap.get(method)) {
        @Override
        protected Object run() throws Exception {
          try {
            return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
          } catch (Exception e) {
            throw e;
          } catch (Throwable t) {
            throw (Error) t;
          }
        }

        @Override
        protected Object getFallback() {
          if (fallbackFactory == null) {
            return super.getFallback();
          }
          try {
            Object fallback = fallbackFactory.create(getExecutionException());
            Object result = fallbackMethodMap.get(method).invoke(fallback, args);
            if (isReturnsHystrixCommand(method)) {
              return ((HystrixCommand) result).execute();
            } else if (isReturnsObservable(method)) {
              // Create a cold Observable
              return ((Observable) result).toBlocking().first();
            } else if (isReturnsSingle(method)) {
              // Create a cold Observable as a Single
              return ((Single) result).toObservable().toBlocking().first();
            } else if (isReturnsCompletable(method)) {
              ((Completable) result).await();
              return null;
            } else if (isReturnsCompletableFuture(method)) {
              return ((Future) result).get();
            } else {
              return result;
            }
          } catch (IllegalAccessException e) {
            // shouldn&apos;t happen as method is public due to being an interface
            throw new AssertionError(e);
          } catch (InvocationTargetException | ExecutionException e) {
            // Exceptions on fallback are tossed by Hystrix
            throw new AssertionError(e.getCause());
          } catch (InterruptedException e) {
            // Exceptions on fallback are tossed by Hystrix
            Thread.currentThread().interrupt();
            throw new AssertionError(e.getCause());
          }
        }
      };

  if (Util.isDefault(method)) {
    return hystrixCommand.execute();
  } else if (isReturnsHystrixCommand(method)) {
    return hystrixCommand;
  } else if (isReturnsObservable(method)) {
    // Create a cold Observable
    return hystrixCommand.toObservable();
  } else if (isReturnsSingle(method)) {
    // Create a cold Observable as a Single
    return hystrixCommand.toObservable().toSingle();
  } else if (isReturnsCompletable(method)) {
    return hystrixCommand.toObservable().toCompletable();
  } else if (isReturnsCompletableFuture(method)) {
    return new ObservableCompletableFuture&amp;#x3C;&gt;(hystrixCommand);
  }
  return hystrixCommand.execute();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;hystrixCommand.execute()中源码大量用到了rxjava来实现，暂时不太熟悉rxjava。后续填坑~&lt;/p&gt;
&lt;p&gt;大致的逻辑是，hystrix内部实现了一个线程池来管理接口的调用，实现熔断限流降级等功能。&lt;/p&gt;
&lt;p&gt;hystrixCommand.execute() 底层返回一个future.get()的对象，实现了异步调用&lt;/p&gt;</content:encoded><category>SpringCloud源码</category></item><item><title>SpringCloud源码| feign源码</title><link>https://leyou240.github.io/blog/sc03</link><guid isPermaLink="true">https://leyou240.github.io/blog/sc03</guid><description>SpringCloud源码| feign源码</description><pubDate>Wed, 13 May 2015 06:09:48 GMT</pubDate><content:encoded>&lt;h3&gt;feign核心组件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Encoder和Decoder：编解码组件。SpringEncoder和ResponseEntityDecoder&lt;/li&gt;
&lt;li&gt;Contract：解析spring mvc相关组件。SpringMvcContract&lt;/li&gt;
&lt;li&gt;Feign.Builder：FeignClient的一个实例构造器，builder模式&lt;/li&gt;
&lt;li&gt;FeignClient：LoadBalancerFeignClient ，底层整合ribbon&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;feign的使用和自定义配置&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@FeignClient(name = “ServiceA”, configuration = MyConfiguration.class)
public interface ServiceAClient {

}

public class MyConfiguration {
@Bean
public RequestInterceptor requestInterceptor() {
return new MyRequestInterceptor();
}

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;feign的入口&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;@EnableFeignClients，使用FeignClientsRegistrar扫描包下面的所有FeignClient。&lt;/li&gt;
&lt;li&gt;使用SpringMvcContract解析相关spring mvc注解，生成LoadBalancerFeignClient动态代理&lt;/li&gt;
&lt;li&gt;FeignClient底层使用ribbon进行负载均衡，调用对应的远程服务&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/%E6%89%AB%E6%8F%8F@FeignClient%E6%B3%A8%E8%A7%A3%E7%9A%84%E5%85%A5%E5%8F%A3.D7mSMQCJ_Z27Wwoz.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    //省略
}
//FeignClientsRegistrar类的重要方法
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //注册bean
		registerDefaultConfiguration(metadata, registry);
        //扫描所有@FeignClient，解析注解中的所有属性。注册对应的FeignClient
		registerFeignClients(metadata, registry);
	}

	private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		Map&amp;#x3C;String, Object&gt; defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

		if (defaultAttrs != null &amp;#x26;&amp;#x26; defaultAttrs.containsKey(&quot;defaultConfiguration&quot;)) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = &quot;default.&quot; + metadata.getEnclosingClassName();
			}
			else {
				name = &quot;default.&quot; + metadata.getClassName();
			}
			registerClientConfiguration(registry, name,
					defaultAttrs.get(&quot;defaultConfiguration&quot;));
		}
	}

	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {

		LinkedHashSet&amp;#x3C;BeanDefinition&gt; candidateComponents = new LinkedHashSet&amp;#x3C;&gt;();
		Map&amp;#x3C;String, Object&gt; attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
//		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
//				FeignClient.class);
		final Class&amp;#x3C;?&gt;[] clients = attrs == null ? null
				: (Class&amp;#x3C;?&gt;[]) attrs.get(&quot;clients&quot;);
		if (clients == null || clients.length == 0) {
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
            //扫描FeignClient
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			Set&amp;#x3C;String&gt; basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
			for (Class&amp;#x3C;?&gt; clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}

		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				// verify annotated class is an interface
				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
				Assert.isTrue(annotationMetadata.isInterface(),
						&quot;@FeignClient can only be specified on an interface&quot;);

				Map&amp;#x3C;String, Object&gt; attributes = annotationMetadata
						.getAnnotationAttributes(
								FeignClient.class.getCanonicalName());

				String name = getClientName(attributes);
				registerClientConfiguration(registry, name,
						attributes.get(&quot;configuration&quot;));

				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}
	//解析FeignClient的所有属性，并注册FeignClientFactoryBean定义
	private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map&amp;#x3C;String, Object&gt; attributes) {
		String className = annotationMetadata.getClassName();
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		validate(attributes);
		definition.addPropertyValue(&quot;url&quot;, getUrl(attributes));
		definition.addPropertyValue(&quot;path&quot;, getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue(&quot;name&quot;, name);
		String contextId = getContextId(attributes);
		definition.addPropertyValue(&quot;contextId&quot;, contextId);
		definition.addPropertyValue(&quot;type&quot;, className);
		definition.addPropertyValue(&quot;decode404&quot;, attributes.get(&quot;decode404&quot;));
		definition.addPropertyValue(&quot;fallback&quot;, attributes.get(&quot;fallback&quot;));
		definition.addPropertyValue(&quot;fallbackFactory&quot;, attributes.get(&quot;fallbackFactory&quot;));
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = contextId + &quot;FeignClient&quot;;
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

		// has a default, won&apos;t be null
		boolean primary = (Boolean) attributes.get(&quot;primary&quot;);

		beanDefinition.setPrimary(primary);

		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;FeignClientFactoryBean就是创建FeignClient组件的工厂&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public Object getObject() throws Exception {
   return getTarget();
}

/**
 * @param &amp;#x3C;T&gt; the target type of the Feign client
 * @return a {@link Feign} client created with the specified data and the context
 * information
 */
&amp;#x3C;T&gt; T getTarget() {
   FeignContext context = applicationContext.getBean(FeignContext.class);
   Feign.Builder builder = feign(context);

   if (!StringUtils.hasText(url)) {
      if (!name.startsWith(&quot;http&quot;)) {
         url = &quot;http://&quot; + name;
      }
      else {
         url = name;
      }
      url += cleanPath();
      //生成动态代理
      return (T) loadBalance(builder, context,
            new HardCodedTarget&amp;#x3C;&gt;(type, name, url));
   }
   if (StringUtils.hasText(url) &amp;#x26;&amp;#x26; !url.startsWith(&quot;http&quot;)) {
      url = &quot;http://&quot; + url;
   }
   String url = this.url + cleanPath();
   Client client = getOptional(context, Client.class);
   if (client != null) {
      if (client instanceof LoadBalancerFeignClient) {
         // not load balancing because we have a url,
         // but ribbon is on the classpath, so unwrap
         client = ((LoadBalancerFeignClient) client).getDelegate();
      }
      if (client instanceof FeignBlockingLoadBalancerClient) {
         // not load balancing because we have a url,
         // but Spring Cloud LoadBalancer is on the classpath, so unwrap
         client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
      }
      builder.client(client);
   }
   Targeter targeter = get(context, Targeter.class);
   //生成动态代理
   return (T) targeter.target(this, builder, context,
         new HardCodedTarget&amp;#x3C;&gt;(type, name, url));
}
	protected &amp;#x3C;T&gt; T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget&amp;#x3C;T&gt; target) {
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			Targeter targeter = get(context, Targeter.class);
			return targeter.target(this, builder, context, target);
		}

		throw new IllegalStateException(
				&quot;No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?&quot;);
	}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此处获取的Targeter在FeignAutoConfiguration注册&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = &quot;feign.hystrix.HystrixFeign&quot;)
protected static class HystrixFeignTargeterConfiguration {

   @Bean
   @ConditionalOnMissingBean
   public Targeter feignTargeter() {
      return new HystrixTargeter();
   }

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass(&quot;feign.hystrix.HystrixFeign&quot;)
protected static class DefaultFeignTargeterConfiguration {

   @Bean
   @ConditionalOnMissingBean
   public Targeter feignTargeter() {
      return new DefaultTargeter();
   }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Feign.Builder最后调用build创建ReflectiveFeign实例，调用newInstance方法，使用动态代理创建FeignClient。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public &amp;#x3C;T&gt; T newInstance(Target&amp;#x3C;T&gt; target) {
  Map&amp;#x3C;String, MethodHandler&gt; nameToHandler = targetToHandlersByName.apply(target);
  Map&amp;#x3C;Method, MethodHandler&gt; methodToHandler = new LinkedHashMap&amp;#x3C;Method, MethodHandler&gt;();
  List&amp;#x3C;DefaultMethodHandler&gt; defaultMethodHandlers = new LinkedList&amp;#x3C;DefaultMethodHandler&gt;();

  for (Method method : target.type().getMethods()) {
      //过滤object的方法
    if (method.getDeclaringClass() == Object.class) {
      continue;
    } //是否默认方法
      else if (Util.isDefault(method)) {
      DefaultMethodHandler handler = new DefaultMethodHandler(method);
      defaultMethodHandlers.add(handler);
      methodToHandler.put(method, handler);
    } else {
      methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
    }
  }
  InvocationHandler handler = factory.create(target, methodToHandler);
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
      new Class&amp;#x3C;?&gt;[] {target.type()}, handler);

  for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>SpringCloud源码</category></item><item><title>SpringCloud源码| eureka源码</title><link>https://leyou240.github.io/blog/sc02</link><guid isPermaLink="true">https://leyou240.github.io/blog/sc02</guid><description>SpringCloud源码| eureka源码</description><pubDate>Fri, 13 Mar 2015 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;eureka server启动流程&lt;/h2&gt;
&lt;p&gt;EurekaBootStrap#contextInitialized(ServletContextEvent)  方法进行初始化&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public void contextInitialized(ServletContextEvent event) {
    try {
        //1、初始化eureka相关环境
        initEurekaEnvironment();
        //2、初始化eureka的serverContext
        initEurekaServerContext();

        ServletContext sc = event.getServletContext();
        sc.setAttribute(EurekaServerContext.class.getName(), serverContext);
    } catch (Throwable e) {
        logger.error(&quot;Cannot bootstrap eureka server :&quot;, e);
        throw new RuntimeException(&quot;Cannot bootstrap eureka server :&quot;, e);
    }
}

/**
     * Users can override to initialize the environment themselves.
     */
protected void initEurekaEnvironment() throws Exception {
    logger.info(&quot;Setting the eureka configuration..&quot;);

    AbstractConfiguration configInstance = ConfigurationManager.getConfigInstance();
    String dataCenter = configInstance.getString(EUREKA_DATACENTER);
    if (dataCenter == null) {
        logger.info(&quot;Eureka data center value eureka.datacenter is not set, defaulting to default&quot;);
        configInstance.setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
    } else {
        configInstance.setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
    }
    String environment = configInstance.getString(EUREKA_ENVIRONMENT);
    if (environment == null) {
        configInstance.setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
        logger.info(&quot;Eureka environment value eureka.environment is not set, defaulting to test&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ConfigurationManager是一个double check+volatile实现的单例模式，并且其中所有的相关配置通过接口来实现。具体的实现类中，硬编码配置项名称，默认值等&lt;/p&gt;
&lt;p&gt;初始化EurekaServerContext，包含大致7个步骤&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/**
 * init hook for server context. Override for custom logic.
 */
protected void initEurekaServerContext() throws Exception {
    // 1、加载eureka-server.properties的数据
    EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();

    // For backward compatibility
    JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
    XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);

    logger.info(&quot;Initializing the eureka client...&quot;);
    logger.info(eurekaServerConfig.getJsonCodecName());
    ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig);
    //2、初始化eureka server
    ApplicationInfoManager applicationInfoManager = null;

    if (eurekaClient == null) {
        EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
                ? new CloudInstanceConfig()
                : new MyDataCenterInstanceConfig();
        
        applicationInfoManager = new ApplicationInfoManager(
                instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
        
        EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
        eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
    } else {
        applicationInfoManager = eurekaClient.getApplicationInfoManager();
    }
    //3、构造注册相关的组件
    PeerAwareInstanceRegistry registry;
    if (isAws(applicationInfoManager.getInfo())) {
        registry = new AwsInstanceRegistry(
                eurekaServerConfig,
                eurekaClient.getEurekaClientConfig(),
                serverCodecs,
                eurekaClient
        );
        awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager);
        awsBinder.start();
    } else {
        registry = new PeerAwareInstanceRegistryImpl(
                eurekaServerConfig,
                eurekaClient.getEurekaClientConfig(),
                serverCodecs,
                eurekaClient
        );
    }
    //4、构造peer节点同步组件
    PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(
            registry,
            eurekaServerConfig,
            eurekaClient.getEurekaClientConfig(),
            serverCodecs,
            applicationInfoManager
    );
    //5、完成上下文的创建
    serverContext = new DefaultEurekaServerContext(
            eurekaServerConfig,
            serverCodecs,
            registry,
            peerEurekaNodes,
            applicationInfoManager
    );

    EurekaServerContextHolder.initialize(serverContext);

    serverContext.initialize();
    logger.info(&quot;Initialized server context&quot;);
    //6、同步相邻节点的信息，从相邻eureka节点拷贝注册信息
    // Copy registry from neighboring eureka node
    int registryCount = registry.syncUp();
    registry.openForTraffic(applicationInfoManager, registryCount);
    //7、注册所有的监控统计项
    // Register all monitoring statistics.
    EurekaMonitors.registerAllStats();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1、加载eureka-server.properties的数据，初始化EurekaServerConfig&lt;/p&gt;
&lt;p&gt;2、初始化ApplicationInfoManager及eurekaClient，其中用到的InstanceInfo使用了builder模式构造复杂的实例对象&lt;/p&gt;
&lt;p&gt;(1)读取相关配置EurekaInstanceConfig和InstanceInfo&lt;/p&gt;
&lt;p&gt;(2)根据配置，处理是否抓取注册表&lt;/p&gt;
&lt;p&gt;(3)初始化3个线程池：调度线程池、心跳线程池、缓存刷新线程池&lt;/p&gt;
&lt;p&gt;3、处理注册相关的事情&lt;/p&gt;
&lt;p&gt;(1)创建PeerAwareInstanceRegistry内部比较重要的是MeasuredRate，其lastBucket统计上一分钟的心跳，currentBucket统计当前一分钟心跳。此组件用做eureka保护模式的重要判断依据&lt;/p&gt;
&lt;p&gt;(2)构造peer节点同步组件，用以集群同步&lt;/p&gt;
&lt;p&gt;(3)DefaultEurekaServerContext创建，并且启动服务注册，从相邻节点同步，注册监控统计项&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/eureka%20server%E5%90%AF%E5%8A%A8%E7%9A%84%E6%B5%81%E7%A8%8B%E5%9B%BE.Wqa0n1LM_Z1HMmBg.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;再从eureka的example中例子&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ExampleEurekaClient {

    private static ApplicationInfoManager applicationInfoManager;
    private static EurekaClient eurekaClient;

    private static synchronized ApplicationInfoManager initializeApplicationInfoManager(EurekaInstanceConfig instanceConfig) {
        if (applicationInfoManager == null) {
            InstanceInfo instanceInfo = new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get();
            applicationInfoManager = new ApplicationInfoManager(instanceConfig, instanceInfo);
        }

        return applicationInfoManager;
    }

    private static synchronized EurekaClient initializeEurekaClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig clientConfig) {
        if (eurekaClient == null) {
            eurekaClient = new DiscoveryClient(applicationInfoManager, clientConfig);
        }

        return eurekaClient;
    }


    public void sendRequestToServiceUsingEureka(EurekaClient eurekaClient) {
        // initialize the client
        // this is the vip address for the example service to talk to as defined in conf/sample-eureka-service.properties
        String vipAddress = &quot;sampleservice.mydomain.net&quot;;

        InstanceInfo nextServerInfo = null;
        try {
            nextServerInfo = eurekaClient.getNextServerFromEureka(vipAddress, false);
        } catch (Exception e) {
            System.err.println(&quot;Cannot get an instance of example service to talk to from eureka&quot;);
            System.exit(-1);
        }

        System.out.println(&quot;Found an instance of example service to talk to from eureka: &quot;
                + nextServerInfo.getVIPAddress() + &quot;:&quot; + nextServerInfo.getPort());

        System.out.println(&quot;healthCheckUrl: &quot; + nextServerInfo.getHealthCheckUrl());
        System.out.println(&quot;override: &quot; + nextServerInfo.getOverriddenStatus());

        Socket s = new Socket();
        int serverPort = nextServerInfo.getPort();
        try {
            s.connect(new InetSocketAddress(nextServerInfo.getHostName(), serverPort));
        } catch (IOException e) {
            System.err.println(&quot;Could not connect to the server :&quot;
                    + nextServerInfo.getHostName() + &quot; at port &quot; + serverPort);
        } catch (Exception e) {
            System.err.println(&quot;Could not connect to the server :&quot;
                    + nextServerInfo.getHostName() + &quot; at port &quot; + serverPort + &quot;due to Exception &quot; + e);
        }
        try {
            String request = &quot;FOO &quot; + new Date();
            System.out.println(&quot;Connected to server. Sending a sample request: &quot; + request);

            PrintStream out = new PrintStream(s.getOutputStream());
            out.println(request);

            System.out.println(&quot;Waiting for server response..&quot;);
            BufferedReader rd = new BufferedReader(new InputStreamReader(s.getInputStream()));
            String str = rd.readLine();
            if (str != null) {
                System.out.println(&quot;Received response from server: &quot; + str);
                System.out.println(&quot;Exiting the client. Demo over..&quot;);
            }
            rd.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ExampleEurekaClient sampleClient = new ExampleEurekaClient();

        // create the client
        ApplicationInfoManager applicationInfoManager = initializeApplicationInfoManager(new MyDataCenterInstanceConfig());
        EurekaClient client = initializeEurekaClient(applicationInfoManager, new DefaultEurekaClientConfig());

        // use the client
        sampleClient.sendRequestToServiceUsingEureka(client);


        // shutdown the client
        eurekaClient.shutdown();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;client启动流程&lt;/p&gt;
&lt;p&gt;（1）读取eureka-client.properties配置文件，形成一个服务实例的配置，基于接口对外提供服务实例的配置项的读取&lt;/p&gt;
&lt;p&gt;（2）基于服务实例的配置，构造了一个服务实例（InstanceInfo）&lt;/p&gt;
&lt;p&gt;（3）基于服务实例的配置和服务实例，构造了一个服务实例管理器（ApplicationInfoManager）&lt;/p&gt;
&lt;p&gt;（4）读取eureka-client.properites配置文件，形成一个eureka client的配置，接口接口对外提供eureka client的配置项的读取&lt;/p&gt;
&lt;p&gt;（5）基于eureka client配置，和服务实例管理器，来构造了一个EurekaClient（DiscoveryClient），保存了一些配置，处理服务的注册和注册表的抓取，启动了几个线程池，启动了网络通信组件，启动了一些调度任务，注册了监控项&lt;/p&gt;
&lt;p&gt;（6）DiscoveryClient内部依赖了一个InstanceInfoReplicator，进行服务注册  服务注册表实际上就是一个ConcurrentHashMap&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class ExampleEurekaService {

    private static ApplicationInfoManager applicationInfoManager;
    private static EurekaClient eurekaClient;

    private static synchronized ApplicationInfoManager initializeApplicationInfoManager(EurekaInstanceConfig instanceConfig) {
        if (applicationInfoManager == null) {
            InstanceInfo instanceInfo = new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get();
            applicationInfoManager = new ApplicationInfoManager(instanceConfig, instanceInfo);
        }

        return applicationInfoManager;
    }

    private static synchronized EurekaClient initializeEurekaClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig clientConfig) {
        if (eurekaClient == null) {
            eurekaClient = new DiscoveryClient(applicationInfoManager, clientConfig);
        }

        return eurekaClient;
    }


    public static void main(String[] args) {

        DynamicPropertyFactory configInstance = com.netflix.config.DynamicPropertyFactory.getInstance();
        ApplicationInfoManager applicationInfoManager = initializeApplicationInfoManager(new MyDataCenterInstanceConfig());
        EurekaClient eurekaClient = initializeEurekaClient(applicationInfoManager, new DefaultEurekaClientConfig());

        ExampleServiceBase exampleServiceBase = new ExampleServiceBase(applicationInfoManager, eurekaClient, configInstance);
        try {
            exampleServiceBase.start();
        } finally {
            // the stop calls shutdown on eurekaClient
            exampleServiceBase.stop();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务端启动流程，仅仅增加服务端注册同步相关代码，其余与客户端启动一致&lt;/p&gt;
&lt;h2&gt;服务注册信息的获取接口ApplicationsResource#getContainers&lt;/h2&gt;
&lt;p&gt;其使用了多级缓存以提高并发性能&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/eureka-server%E7%9A%84%E5%A4%9A%E7%BA%A7%E7%BC%93%E5%AD%98%E8%BF%87%E6%9C%9F%E6%9C%BA%E5%88%B6.BPFS3EpM_ZKdHlv.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
    this.serverConfig = serverConfig;
    this.serverCodecs = serverCodecs;
    this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();
    this.registry = registry;

    long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
    this.readWriteCacheMap =
            CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache())
        	//过期时间
                    .expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
        	//移除region相关的监听器
                    .removalListener(new RemovalListener&amp;#x3C;Key, Value&gt;() {
                        @Override
                        public void onRemoval(RemovalNotification&amp;#x3C;Key, Value&gt; notification) {
                            Key removedKey = notification.getKey();
                            if (removedKey.hasRegions()) {
                                Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
                                regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
                            }
                        }
                    })
        	//从注册表中获取注册信息
                    .build(new CacheLoader&amp;#x3C;Key, Value&gt;() {
                        @Override
                        public Value load(Key key) throws Exception {
                            if (key.hasRegions()) {
                                Key cloneWithNoRegions = key.cloneWithoutRegions();
                                regionSpecificKeys.put(cloneWithNoRegions, key);
                            }
                            //
                            Value value = generatePayload(key);
                            return value;
                        }
                    });

    if (shouldUseReadOnlyResponseCache) {
        timer.schedule(getCacheUpdateTask(),
                new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
                        + responseCacheUpdateIntervalMs),
                responseCacheUpdateIntervalMs);
    }

    try {
        Monitors.registerObject(this);
    } catch (Throwable e) {
        logger.warn(&quot;Cannot register the JMX monitor for the InstanceRegistry&quot;, e);
    }
}
//定时从注册表更新readOnlyCacheMap
private TimerTask getCacheUpdateTask() {
    return new TimerTask() {
        @Override
        public void run() {
            logger.debug(&quot;Updating the client cache from response cache&quot;);
            for (Key key : readOnlyCacheMap.keySet()) {
                if (logger.isDebugEnabled()) {
                    logger.debug(&quot;Updating the client cache from response cache for key : {} {} {} {}&quot;,
                            key.getEntityType(), key.getName(), key.getVersion(), key.getType());
                }
                try {
                    CurrentRequestVersion.set(key.getVersion());
                    Value cacheValue = readWriteCacheMap.get(key);
                    Value currentCacheValue = readOnlyCacheMap.get(key);
                    //版本不一致时，更新
                    if (cacheValue != currentCacheValue) {
                        readOnlyCacheMap.put(key, cacheValue);
                    }
                } catch (Throwable th) {
                    logger.error(&quot;Error while updating the client cache from response cache for key {}&quot;, key.toStringCompact(), th);
                } finally {
                    CurrentRequestVersion.remove();
                }
            }
        }
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1、首先去readOnlyCacheMap中读，读到则返回。若没有则从readWriteCacheMap获取&lt;/p&gt;
&lt;p&gt;2、readWriteCacheMap没有，则从注册表中去获取。&lt;strong&gt;定时比较&lt;/strong&gt;readWriteCacheMap和readOnlyCacheMap，不一致时将最新的值更新到readOnlyCacheMap。&lt;/p&gt;
&lt;p&gt;3、注册表在状态改变的时候，同步&lt;strong&gt;将对应的key从readWriteCacheMap清空&lt;/strong&gt;。保证缓存最终一致性。此逻辑详见&lt;strong&gt;AbstractInstanceRegistry&lt;/strong&gt;中的各方法对其调用&lt;/p&gt;
&lt;h2&gt;增量注册表&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public abstract class AbstractInstanceRegistry implements InstanceRegistry {
    //底层由一个队列实现
    //在注册表变更的时候，写入队列
    private ConcurrentLinkedQueue&amp;#x3C;RecentlyChangedItem&gt; recentlyChangedQueue = new ConcurrentLinkedQueue&amp;#x3C;RecentlyChangedItem&gt;();
    //定时更新过期的增量注册表
    private TimerTask getDeltaRetentionTask() {
        return new TimerTask() {

            @Override
            public void run() {
                Iterator&amp;#x3C;RecentlyChangedItem&gt; it = recentlyChangedQueue.iterator();
                while (it.hasNext()) {
                    if (it.next().getLastUpdateTime() &amp;#x3C;
                            System.currentTimeMillis() - serverConfig.getRetentionTimeInMSInDeltaQueue()) {
                        it.remove();
                    } else {
                        break;
                    }
                }
            }

        };
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;故障摘除&lt;/h3&gt;
&lt;p&gt;AbstractInstanceRegistry#evict(long additionalLeaseMs)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/**
     * Evicts everything in the instance registry that has expired, if expiry is enabled.
     *
     * @see com.netflix.eureka.lease.LeaseManager#evict()
     */
    @Override
    public void evict() {
        evict(0l);
    }

    public void evict(long additionalLeaseMs) {
        logger.debug(&quot;Running the evict task&quot;);

        if (!isLeaseExpirationEnabled()) {
            logger.debug(&quot;DS: lease expiration is currently disabled.&quot;);
            return;
        }

        // We collect first all expired items, to evict them in random order. For large eviction sets,
        // if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
        // the impact should be evenly distributed across all applications.
        List&amp;#x3C;Lease&amp;#x3C;InstanceInfo&gt;&gt; expiredLeases = new ArrayList&amp;#x3C;&gt;();
        for (Entry&amp;#x3C;String, Map&amp;#x3C;String, Lease&amp;#x3C;InstanceInfo&gt;&gt;&gt; groupEntry : registry.entrySet()) {
            Map&amp;#x3C;String, Lease&amp;#x3C;InstanceInfo&gt;&gt; leaseMap = groupEntry.getValue();
            if (leaseMap != null) {
                for (Entry&amp;#x3C;String, Lease&amp;#x3C;InstanceInfo&gt;&gt; leaseEntry : leaseMap.entrySet()) {
                    Lease&amp;#x3C;InstanceInfo&gt; lease = leaseEntry.getValue();
                    if (lease.isExpired(additionalLeaseMs) &amp;#x26;&amp;#x26; lease.getHolder() != null) {
                        expiredLeases.add(lease);
                    }
                }
            }
        }

        // To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
        // triggering self-preservation. Without that we would wipe out full registry.
        int registrySize = (int) getLocalRegistrySize();
        int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
        int evictionLimit = registrySize - registrySizeThreshold;
		//每次最多摘除15%的实例，因为renewalPercentThreshold默认值是0.85
        int toEvict = Math.min(expiredLeases.size(), evictionLimit);
        if (toEvict &gt; 0) {
            logger.info(&quot;Evicting {} items (expired={}, evictionLimit={})&quot;, toEvict, expiredLeases.size(), evictionLimit);
			//使用随机算法，随机摘除故障实例
            Random random = new Random(System.currentTimeMillis());
            for (int i = 0; i &amp;#x3C; toEvict; i++) {
                // Pick a random item (Knuth shuffle algorithm)
                int next = i + random.nextInt(expiredLeases.size() - i);
                Collections.swap(expiredLeases, i, next);
                Lease&amp;#x3C;InstanceInfo&gt; lease = expiredLeases.get(i);

                String appName = lease.getHolder().getAppName();
                String id = lease.getHolder().getId();
                EXPIRED.increment();
                logger.warn(&quot;DS: Registry: expired lease for {}/{}&quot;, appName, id);
                internalCancel(appName, id, false);
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;eureka集群注册表同步机制&lt;/h3&gt;
&lt;p&gt;PeerEurekaNode中的batchingDispatcher和nonBatchingDispatcher  负责调度&lt;/p&gt;
&lt;p&gt;AcceptorExecutor负责从队列中拿出任务来执行&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class PeerEurekaNode {
    private final TaskDispatcher&amp;#x3C;String, ReplicationTask&gt; batchingDispatcher;
    private final TaskDispatcher&amp;#x3C;String, ReplicationTask&gt; nonBatchingDispatcher;
     public PeerEurekaNode(PeerAwareInstanceRegistry registry, String targetHost, String serviceUrl, HttpReplicationClient replicationClient, EurekaServerConfig config) {
        this(registry, targetHost, serviceUrl, replicationClient, config, BATCH_SIZE, MAX_BATCHING_DELAY_MS, RETRY_SLEEP_TIME_MS, SERVER_UNAVAILABLE_SLEEP_TIME_MS);
    }

    /* For testing */ PeerEurekaNode(PeerAwareInstanceRegistry registry, String targetHost, String serviceUrl,
                                     HttpReplicationClient replicationClient, EurekaServerConfig config,
                                     int batchSize, long maxBatchingDelayMs,
                                     long retrySleepTimeMs, long serverUnavailableSleepTimeMs) {
        this.registry = registry;
        this.targetHost = targetHost;
        this.replicationClient = replicationClient;

        this.serviceUrl = serviceUrl;
        this.config = config;
        this.maxProcessingDelayMs = config.getMaxTimeForReplication();

        String batcherName = getBatcherName();
        ReplicationTaskProcessor taskProcessor = new ReplicationTaskProcessor(targetHost, replicationClient);
        this.batchingDispatcher = TaskDispatchers.createBatchingTaskDispatcher(
                batcherName,
                config.getMaxElementsInPeerReplicationPool(),
                batchSize,
                config.getMaxThreadsForPeerReplication(),
                maxBatchingDelayMs,
                serverUnavailableSleepTimeMs,
                retrySleepTimeMs,
                taskProcessor
        );
        this.nonBatchingDispatcher = TaskDispatchers.createNonBatchingTaskDispatcher(
                targetHost,
                config.getMaxElementsInStatusReplicationPool(),
                config.getMaxThreadsForStatusReplication(),
                maxBatchingDelayMs,
                serverUnavailableSleepTimeMs,
                retrySleepTimeMs,
                taskProcessor
        );
    }

    /**
     * Sends the registration information of {@link InstanceInfo} receiving by
     * this node to the peer node represented by this class.
     *
     * @param info
     *            the instance information {@link InstanceInfo} of any instance
     *            that is send to this instance.
     * @throws Exception
     */
    public void register(final InstanceInfo info) throws Exception {
        long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
        batchingDispatcher.process(
                taskId(&quot;register&quot;, info),
                new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
                    @Override
                    public EurekaHttpResponse&amp;#x3C;Void&gt; execute() {
                        return replicationClient.register(info);
                    }
                },
                expiryTime
        );
    }
}

class AcceptorExecutor&amp;#x3C;ID, T&gt; {
    
    private final BlockingQueue&amp;#x3C;TaskHolder&amp;#x3C;ID, T&gt;&gt; acceptorQueue = new LinkedBlockingQueue&amp;#x3C;&gt;();
    private final BlockingDeque&amp;#x3C;TaskHolder&amp;#x3C;ID, T&gt;&gt; reprocessQueue = new LinkedBlockingDeque&amp;#x3C;&gt;();
    static class BatchWorkerRunnable&amp;#x3C;ID, T&gt; extends WorkerRunnable&amp;#x3C;ID, T&gt; {

        BatchWorkerRunnable(String workerName,
                            AtomicBoolean isShutdown,
                            TaskExecutorMetrics metrics,
                            TaskProcessor&amp;#x3C;T&gt; processor,
                            AcceptorExecutor&amp;#x3C;ID, T&gt; acceptorExecutor) {
            super(workerName, isShutdown, metrics, processor, acceptorExecutor);
        }

        @Override
        public void run() {
            try {
                while (!isShutdown.get()) {
                    List&amp;#x3C;TaskHolder&amp;#x3C;ID, T&gt;&gt; holders = getWork();
                    metrics.registerExpiryTimes(holders);

                    List&amp;#x3C;T&gt; tasks = getTasksOf(holders);
                    ProcessingResult result = processor.process(tasks);
                    switch (result) {
                        case Success:
                            break;
                        case Congestion:
                        case TransientError:
                            taskDispatcher.reprocess(holders, result);
                            break;
                        case PermanentError:
                            logger.warn(&quot;Discarding {} tasks of {} due to permanent error&quot;, holders.size(), workerName);
                    }
                    metrics.registerTaskResult(result, tasks.size());
                }
            } catch (InterruptedException e) {
                // Ignore
            } catch (Throwable e) {
                // Safe-guard, so we never exit this loop in an uncontrolled way.
                logger.warn(&quot;Discovery WorkerThread error&quot;, e);
            }
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/eureka%20server%E5%90%8C%E6%AD%A5%E4%BB%BB%E5%8A%A1%E6%89%B9%E5%A4%84%E7%90%86%E6%9C%BA%E5%88%B6.Cjv9f7Wc_qKmB9.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;spring-cloud-eureka-server注解式启动&lt;/h3&gt;
&lt;p&gt;@EnableEurekaServer注解，springboot启动以后，将eureka server启动起来。&lt;/p&gt;
&lt;p&gt;EurekaServerAutoConfiguration使用spring boot的auto configuration机制，触发EurekaServerAutoConfiguration的执行。将eureka server需要的类，统统交由spring bean来进行注入&lt;/p&gt;
&lt;p&gt;然后通过EurekaServerInitializerConfiguration来进行启动。&lt;/p&gt;
&lt;h3&gt;spring-cloud-eureka-client注解式启动&lt;/h3&gt;
&lt;p&gt;EurekaClientConfigBean配置文件读取配置&lt;/p&gt;
&lt;p&gt;EurekaClientAutoConfiguration注入相关的类到spring容器中&lt;/p&gt;
&lt;p&gt;EurekaAutoServiceRegistration重写了eureka client默认的注册逻辑，将原来InstanceInfoReplicator中的注册逻辑进行进一步的封装及定制。client启动以后就进行注册，不是eureka默认的40秒以后再注册。并且可以配置初始化注册状态&lt;/p&gt;
&lt;p&gt;总结：@EnableEurekaClient，触发了一个EurekaClientAutoConfiguration类的执行，完成从application.yml中读取配置，完成DiscoveryClient的初始化和启动，通过自己额外加的一些代码，一启动，直接触发一次register()服务注册，向eureka server完成一次注册。&lt;/p&gt;</content:encoded><category>SpringCloud源码</category></item><item><title>SpringCloud源码| 从单体到微服务</title><link>https://leyou240.github.io/blog/sc01</link><guid isPermaLink="true">https://leyou240.github.io/blog/sc01</guid><description>SpringCloud源码| 从单体到微服务</description><pubDate>Fri, 12 Dec 2014 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1、代码重复问题&lt;/h2&gt;
&lt;p&gt;拆分成多个服务，通过网络请求不同的服务。代码不再耦合重复&lt;/p&gt;
&lt;h2&gt;2、多人协作效率问题&lt;/h2&gt;
&lt;p&gt;各种专注于自己的小范围即可，代码不会与其他服务冲突。测试上线不依赖其他服务&lt;/p&gt;
&lt;h2&gt;3、扩容问题&lt;/h2&gt;
&lt;p&gt;之前其他服务(cpu/内存/网络/io)问题会导致服务宕机，现在各自独立部署，独立扩容。节省资源&lt;/p&gt;
&lt;h2&gt;4、可用性问题&lt;/h2&gt;
&lt;p&gt;依赖的其他服务宕机，是他自己的事。做好熔断降级就好了。&lt;/p&gt;</content:encoded><category>SpringCloud源码</category></item><item><title>并发源码|线程池原理</title><link>https://leyou240.github.io/blog/juc08</link><guid isPermaLink="true">https://leyou240.github.io/blog/juc08</guid><description>并发源码|线程池原理</description><pubDate>Fri, 13 Jun 2014 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;线程池&lt;/h2&gt;
&lt;p&gt;线程池的意义：频繁的创建和销毁线程，性能开销比较大。线程池创建一些线程，执行完任务后不立即销毁，可以等待去执行下一个任务&lt;/p&gt;
&lt;p&gt;线程池相关参数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;    /**
     * 用给定的初始参数创建一个新的ThreadPoolExecutor。
     */
    public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
                              int maximumPoolSize,//线程池的最大线程数
                              long keepAliveTime,//当线程数大于核心线程数时，多余的空闲线程存活的最长时间
                              TimeUnit unit,//时间单位
                              BlockingQueue&amp;#x3C;Runnable&gt; workQueue,//任务队列，用来储存等待执行任务的队列
                              ThreadFactory threadFactory,//线程工厂，用来创建线程，一般默认即可
                              RejectedExecutionHandler handler//拒绝策略，当提交的任务过多而不能及时处理时，我们可以定制策略来处理任务
                               )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;线程池支持5种，Executors静态方法创建：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;FixedThreadPool：固定数量的线程，其他线程放入无界等待队列&lt;/li&gt;
&lt;li&gt;CachedThreadPool：线程数量不固定，无论多少任务都会不停的创建线程。线程空闲一定时间，释放线程&lt;/li&gt;
&lt;li&gt;SingleThread：线程池里只有一个线程，其他线程放入无界等待队列&lt;/li&gt;
&lt;li&gt;ScheduledThread：提交的线程，会在等待的时间过后才会去执行&lt;/li&gt;
&lt;li&gt;WorkStealingPool：底层使用forkjoin来执行&lt;/li&gt;
&lt;li&gt;newVirtualThreadPerTaskExecutor：jdk21新增，底层使用虚拟线程。不推荐使用，因为虚拟线程比较轻量不推荐池化。仅适用于项目虚拟线程改造，将原来线程池做简单替换。代码改动较少&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;   // 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
   private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    /**
     * Set containing all worker threads in pool. Accessed only when
     * holding mainLock.
     */
    private final HashSet&amp;#x3C;Worker&gt; workers = new HashSet&amp;#x3C;Worker&gt;();

    private static int workerCountOf(int c) {
        return c &amp;#x26; CAPACITY;
    }

    private final BlockingQueue&amp;#x3C;Runnable&gt; workQueue;

    public void execute(Runnable command) {
        // 如果任务为null，则抛出异常。
        if (command == null)
            throw new NullPointerException();
        // ctl 中保存的线程池当前的一些状态信息
        int c = ctl.get();

        //  下面会涉及到 3 步 操作
        // 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize
        // 如果小于的话，通过addWorker(command, true)新建一个线程，并将任务(command)添加到该线程中；然后，启动该线程从而执行任务。
        if (workerCountOf(c) &amp;#x3C; corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里
        // 通过 isRunning 方法判断线程池状态，线程池处于 RUNNING 状态才会被并且队列可以加入任务，该任务才会被加入进去
        if (isRunning(c) &amp;#x26;&amp;#x26; workQueue.offer(command)) {
            int recheck = ctl.get();
            // 再次获取线程池状态，如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务，并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
            if (!isRunning(recheck) &amp;#x26;&amp;#x26; remove(command))
                reject(command);
                // 如果当前线程池为空就新创建一个线程并执行。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //3. 通过addWorker(command, false)新建一个线程，并将任务(command)添加到该线程中；然后，启动该线程从而执行任务。
        //如果addWorker(command, false)执行失败，则通过reject()执行相应的拒绝策略的内容。
        else if (!addWorker(command, false))
            reject(command);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/09_%E7%BA%BF%E7%A8%8B%E6%B1%A0.C-Xm_P_4_Z1DoqeM.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;工作队列&lt;/h2&gt;
&lt;p&gt;存放任务的工作队列有6种主要的实现，分别是 ArrayBlockingQueue、LinkedBlockingQueue、LinkedBlockingDeque、PriorityBlockingQueue、DelayQueue、SynchronousQueue。它们的区别如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ArrayBlockingQueue：一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。&lt;/li&gt;
&lt;li&gt;LinkedBlockingQueue：一个由链表结构组成的有界阻塞队列，在未指明容量时，&lt;strong&gt;容量默认为 Integer.MAX_VALUE&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;LinkedBlockingDeque：使用双向队列实现的双端阻塞队列，双端意味着可以像普通队列一样 FIFO(先进先出)，可以以像栈一样 FILO(先进后出)&lt;/li&gt;
&lt;li&gt;PriorityBlockingQueue：一个支持优先级排序的无界阻塞队列，对元素没有要求，可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较，跟时间没有任何关系，仅仅是按照优先级取任务。&lt;/li&gt;
&lt;li&gt;DelayQueue：同 PriorityBlockingQueue，也是二叉堆实现的优先级阻塞队列。要求元素都实现 Delayed 接口，通过执行时延从队列中提取任务，时间没到任务取不出来。&lt;/li&gt;
&lt;li&gt;SynchronousQueue：一个不存储元素的阻塞队列，消费者线程调用 take() 方法的时候就会发生阻塞，直到有一个生产者线程生产了一个元素，消费者线程就可以拿到这个元素并返回；生产者线程调用put()方法的时候就会发生阻塞，直到有一个消费者线程消费了一个元素，生产者才会返回。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;拒绝策略&lt;/h2&gt;
&lt;p&gt;内置的有4种拒绝策略&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AbortPolicy（默认）：丢弃任务并抛出 RejectedExecutionException 异常。&lt;/li&gt;
&lt;li&gt;CallerRunsPolicy：由调用线程处理该任务。(例如io操作，线程消费速度没有NIO快，可能导致阻塞队列一直增加，此时可以使用这个模式)。&lt;/li&gt;
&lt;li&gt;DiscardPolicy：丢弃任务，但是不抛出异常。（可以配合这种模式进行自定义的处理方式）。&lt;/li&gt;
&lt;li&gt;DiscardOldestPolicy：丢弃队列最早的未处理任务，然后重新尝试执行任务。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当然也可以根据需求自定义拒绝策略，实现RejectedExecutionHandler接口即可&lt;/p&gt;
&lt;h2&gt;如何关闭线程池&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;shutdown&lt;/strong&gt;：阻止新的任务提交，将线程池的状态改为shutdown，当再提交任务时，如果状态不为running，则执行拒绝策略。对于已提交的任务不会产生任何影响，如果还有任务未执行，线程将继续把任务执行完&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;shutdownNow&lt;/strong&gt;：会关闭正在执行任务的线程，任务可能并没有执行完毕，关闭线程不需要等待&lt;/p&gt;
&lt;h2&gt;线程池核心线程数经验配置&lt;/h2&gt;
&lt;p&gt;CPU密集型任务：尽量压榨CPU，参考值设置为CPU的个数+1。&lt;/p&gt;
&lt;p&gt;IO密集型任务：参考值可以设置为CPU的个数 ✖️ 2。&lt;/p&gt;
&lt;p&gt;虚拟线程（jdk21新增）：对于IO密集型任务，也可以改为使用虚拟线程。但需要代码中不使用synchronized关键字&lt;/p&gt;
&lt;h2&gt;线程池的好处&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;线程重用&lt;/strong&gt;：线程的创建和销毁开销是巨大的，而通过线程池的重用大大减少了这些不必要的开销，当然既然少了这么多开销，其线程执行速度也是突飞猛进的提升。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;控制线程池的并发数&lt;/strong&gt;：线程不是并发的越多，性能越高，反而在线程并发太多时，线程的切换会消耗系统大量的资源，可以通过设置线程池最大并发线程数目，维持系统高性能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程池可以对线程进行管理&lt;/strong&gt;：虽然线程提供了线程组操控线程，但是线程池拥有更多管理线程的API。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可以储存需要执行的任务&lt;/strong&gt;：当任务提交过多时，可以将任务储存起来，等待线程处理。&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>juc源码</category></item><item><title>并发源码|其他并发容器</title><link>https://leyou240.github.io/blog/juc07</link><guid isPermaLink="true">https://leyou240.github.io/blog/juc07</guid><description>并发源码|其他并发容器</description><pubDate>Sun, 27 Apr 2014 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;CopyOnWriteArrayList&lt;/h2&gt;
&lt;p&gt;写时复制的并发Arraylist容器，底层使用锁来实现&lt;/p&gt;
&lt;p&gt;底层使用volatile标记array，保证并发修改后能立即可见。&lt;/p&gt;
&lt;p&gt;另外使用ReentrantLock在进行写操作时加锁&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有的读操作（get,迭代器,contains)都是读的array快照，弱一致性模型&lt;/p&gt;
&lt;p&gt;优点：线程安全，读写不互斥&lt;/p&gt;
&lt;p&gt;缺点：若一致性，每次修改都要新建一个数组。空间浪费大，增加gc压力&lt;/p&gt;
&lt;h2&gt;ConcurrentLinkedQueue&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;无界队列&lt;/strong&gt;，底层使用&lt;strong&gt;单向链表&lt;/strong&gt;实现，无大小限制。所有的指针操作都是基于CAS，没成功则进入下次循环直到成功。&lt;/p&gt;
&lt;p&gt;不停的往此队列插入数据，可能会导致内存溢出。&lt;/p&gt;
&lt;p&gt;此容器使用CAS保证并发安全，但size是弱一致性的&lt;/p&gt;
&lt;h2&gt;LinkedBlockingQueue&lt;/h2&gt;
&lt;p&gt;有界队列，底层使用&lt;strong&gt;单向链表&lt;/strong&gt;实现，有大小限制，超过了限制，往队列里插入数据就会阻塞住。可以限制内存队列的大小，避免内存无限制的增长，最后撑爆内存&lt;/p&gt;
&lt;p&gt;底层使用2个锁提高并发能力：take锁与put锁，以及各自的Condition。如下图所示&lt;/p&gt;
&lt;p&gt;take的时候，若为空则阻塞等待，容器非空时再take数据&lt;/p&gt;
&lt;p&gt;put的时候，若满则阻塞等待，容器不满再put数据&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/08_LinkedBlockingQueue.XrdgZUDM_Z16lTDS.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;	/** Current number of elements */
    private final AtomicInteger count = new AtomicInteger();
    /** Lock held by take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    private final Condition notEmpty = takeLock.newCondition();

    /** Lock held by put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();

    /** Wait queue for waiting puts */
    private final Condition notFull = putLock.newCondition();
    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c &gt; 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
    /**
     * Inserts the specified element at the tail of this queue, waiting if
     * necessary for space to become available.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        Node&amp;#x3C;E&gt; node = new Node&amp;#x3C;E&gt;(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            /*
             * Note that count is used in wait guard even though it is
             * not protected by lock. This works because count can
             * only decrease at this point (all other puts are shut
             * out by lock), and we (or some other waiting put) are
             * signalled if it ever changes from capacity. Similarly
             * for all other uses of count in other wait guards.
             */
            while (count.get() == capacity) {
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 &amp;#x3C; capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ArrayBlockingQueue&lt;/h2&gt;
&lt;p&gt;基于&lt;strong&gt;环形数组&lt;/strong&gt;实现的&lt;strong&gt;有界队列&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;底层使用一个锁，2个condition。其余与LinkedBlockingQueue的原理大致一致&lt;/p&gt;</content:encoded><category>juc源码</category></item><item><title>并发源码|Hashmap死循环问题与ConcurrentHashmap</title><link>https://leyou240.github.io/blog/juc06</link><guid isPermaLink="true">https://leyou240.github.io/blog/juc06</guid><description>并发源码|Hashmap死循环问题与ConcurrentHashmap</description><pubDate>Fri, 18 Apr 2014 03:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;jdk7hashmap死循环问题&lt;/h2&gt;
&lt;p&gt;在多线程并发环境下。不应该用此容器，而应使用线程安全的容器如ConcurrentHashmap&lt;/p&gt;
&lt;p&gt;其死循环主要是&lt;strong&gt;扩容的时候resize&lt;/strong&gt;方法内调用的&lt;strong&gt;transfer&lt;/strong&gt;方法导致。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;e.next = newTable[i];
newTable[i] = e;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果两个线程正在处理同一个节点e，那么第一个线程正常执行，但是第二个线程设置e.next=e，因为第一个线程已经将newTable[i]设置为e。节点e现在指向它自己，当调用get（Object）时，它将进入一个无限循环。无限循环则会导致&lt;strong&gt;cpu100%&lt;/strong&gt;，另外因为节点形成了环形，后面的元素无法访问到导致&lt;strong&gt;数据丢失&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而java8中，resize的时候保持了节点的顺序。但同样会有数据丢失的问题&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;void transfer(Entry[] newTable) {
    Entry[] src = table;
    int newCapacity = newTable.length;
    for (int j = 0; j &amp;#x3C; src.length; j++) {
        Entry&amp;#x3C;K,V&gt; e = src[j];
        if (e != null) {
            src[j] = null;
            do {
                Entry&amp;#x3C;K,V&gt; next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;jdk7与jdk8关于Hashmap的主要区别&lt;/p&gt;
&lt;p&gt;1、jdk7中数据结构为：数组+链表 jdk8中数据结构为：数组+链表/红黑树 当链表数量大于8时，链表转化为红黑树&lt;/p&gt;
&lt;p&gt;2、hash值的计算：jdk8采用高低16位异或来进行hashcode计算，可以尽量减少hash碰撞&lt;/p&gt;
&lt;p&gt;3、链表的插入：jdk7采用头插法，扩容后元素位置相反。jdk8中采用尾插法，扩容后元素位置一致&lt;/p&gt;
&lt;h2&gt;ConcurrentHashmap&lt;/h2&gt;
&lt;p&gt;put源码中，&lt;strong&gt;Unsafe，CAS的操作，都是线程安全的&lt;/strong&gt;，保证了只有一个线程可以在这里成功的将一个key-value对方在数组的一个地方里。如果多个线程并发来执行put的操作，都走到这里，可能就会有其他的线程，CAS往数组里赋值操作就会失败，如果CAS成功了，此时就直接break掉，put操作就成功了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/**
 * Maps the specified key to the specified value in this table.
 * Neither the key nor the value can be null.
 *
 * &amp;#x3C;p&gt;The value can be retrieved by calling the {@code get} method
 * with a key that is equal to the original key.
 *
 * @param key key with which the specified value is to be associated
 * @param value value to be associated with the specified key
 * @return the previous value associated with {@code key}, or
 *         {@code null} if there was no mapping for {@code key}
 * @throws NullPointerException if the specified key or value is null
 */
public V put(K key, V value) {
    return putVal(key, value, false);
}

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node&amp;#x3C;K,V&gt;[] tab = table;;) {
        Node&amp;#x3C;K,V&gt; f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) &amp;#x26; hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node&amp;#x3C;K,V&gt;(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            //此处有hash冲突，只对有hash冲突的节点进行加锁
            //体现了分段加锁的思想，jdk7中使用segment锁冲突会更多，jdk8锁粒度更细
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh &gt;= 0) {
                        binCount = 1;
                        for (Node&amp;#x3C;K,V&gt; e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &amp;#x26;&amp;#x26;
                                ((ek = e.key) == key ||
                                 (ek != null &amp;#x26;&amp;#x26; key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node&amp;#x3C;K,V&gt; pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node&amp;#x3C;K,V&gt;(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        Node&amp;#x3C;K,V&gt; p;
                        binCount = 2;
                        if ((p = ((TreeBin&amp;#x3C;K,V&gt;)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount &gt;= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;get类似，使用Unsafe保证线程安全。Node的val是volatile的，保持了多线程可见性&lt;/p&gt;
&lt;p&gt;扩容，扩容时使用&lt;/p&gt;
&lt;p&gt;对于get、size、遍历 这些操作，都是弱一致性的&lt;/p&gt;</content:encoded><category>juc源码</category></item><item><title>并发源码|并发常用组件</title><link>https://leyou240.github.io/blog/juc05</link><guid isPermaLink="true">https://leyou240.github.io/blog/juc05</guid><description>并发源码|并发常用组件</description><pubDate>Fri, 11 Apr 2014 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;CountDownLatch：同步等待多个线程完成任务的并发组件&lt;/p&gt;
&lt;p&gt;CyclicBarrier：将工作任务给多线程分而治之的并发组件，此组件比CountDownLatch可重复使用&lt;/p&gt;
&lt;p&gt;案例实战：api服务中对多个接口并发调用后统一合并返回数据，可使用CountDownLatch或者CyclicBarrier并行请求多个接口，全部完成后再统一返回给调用方。用此方法可极大降低请求时间。&lt;/p&gt;
&lt;p&gt;Semaphore：等待指定数量的线程完成任务的并发组件。此组件设置一个指定的数量i，可以有&gt;=i个线程来并发执行。一旦完成了i个任务后，就可以执行接下来的方法了。&lt;/p&gt;</content:encoded><category>juc源码</category></item><item><title>Hashmap源码</title><link>https://leyou240.github.io/blog/jdk2</link><guid isPermaLink="true">https://leyou240.github.io/blog/jdk2</guid><description>Hashmap源码</description><pubDate>Mon, 31 Mar 2014 13:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Hashmap&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/03_hashmap%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.DgnWdHbO_Z1vxmbh.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;对key进行hash，放到数组对应的位置 p = tab[i = (n - 1) &amp;#x26; hash] 。i的位置为i = (n - 1) &amp;#x26; hash&lt;/p&gt;
&lt;p&gt;从JDK8以后结构为：数组 + 链表 + 红黑树，从原来的hash冲突时由纯链表变为，当链表长度大于8以后变为红黑树。&lt;/p&gt;
&lt;p&gt;fail-fast机制是什么东西，这个在开发的时候是一个比较常见的异常，ConcurrentModificationException&lt;/p&gt;
&lt;h2&gt;核心变量&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;static final int DEFAULT_INITIAL_CAPACITY = 1 &amp;#x3C;&amp;#x3C; 4; // aka 16
//应该是数组的默认的初始大小，是16，这个跟ArrayList是不一样的，初始的默认大小是10
//这个数组的大小，一般会自己手动指定一下，就跟你用ArrayList一样，你需要去预估一下你的这个数据结构里会放多少key-value对，指定的大一些，避免频繁的扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//默认的负载因子，如果你在数组里的元素的个数达到了数组大小（16） * 负载因子（0.75f），默认是达到12个元素，就会进行数组的扩容
static class Node&amp;#x3C;K,V&gt; implements Map.Entry&amp;#x3C;K,V&gt; {
        final int hash;
        final K key;
        V value;
        Node&amp;#x3C;K,V&gt; next;
}
//这是一个很关键的内部类，他其实是代表了一个key-value对，里面包含了key的hash值，key，value，还有就是可以有一个next的指针，指向下一个Node，也就是指向单向链表中的下一个节点
//通过这个next指针，就可以形成一个链表
transient Node&amp;#x3C;K,V&gt;[] table;
//这个是什么东东？Node&amp;#x3C;K, V&gt;[]，这个数组就是所谓的map里的核心数据结构的数组，数组的元素就可以看到是Node类型的，天然就可以挂成一个链表，单向链表，Node里面只有一个next指针
transient int size;
//这个size代表的是就是当前hashmap中有多少个key-value对，如果这个数量达到了指定大小 * 负载因子，那么就会进行数组的扩容
int threshold;
//这个值，其实就是说capacity（就是默认的数组的大小），就是说capacity * loadFactory，就是threshold，如果size达到了threshold，那么就会进行数组的扩容，如果面试这么回答，你就是在打自己的脸。扩容的时机是size&gt;threshold

//负载因子，默认值，threshold，扩容，扩容，rehash的算法，JDK 1.7以前的rehash是怎么做的，性能有多差，JDK 1.8以后是如何优化的rehash的算法，东西很多的

final float loadFactor;
//默认就是负载因子，默认的值是0.75f，你也可以自己指定，如果你指定的越大，一般就越是拖慢扩容的速度，一般不要修改
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.8优化&lt;/h2&gt;
&lt;h3&gt;降低冲突概率的算法&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h &gt;&gt;&gt; 16);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;h &gt;&gt;&gt; 16，对h进行无符号右移，即将高位16hash值移到低位。&lt;/p&gt;
&lt;p&gt;然后使用^进行异或操作。将高低位异或，计算出来的值减少hash冲突&lt;/p&gt;
&lt;h3&gt;寻址算法优化&lt;/h3&gt;
&lt;p&gt;(length - 1) &amp;#x26; hash等价于hash%n（当length为2的倍数时，jdk强制length为2的倍数）。但按位与&amp;#x26;比取模算法效率高。&lt;/p&gt;
&lt;h3&gt;哈希冲突时，链表处理方式&lt;/h3&gt;
&lt;p&gt;使用拉链法，在冲突的地方链接一个双向链表。若还有冲突，则继续在链表中插入。&lt;/p&gt;
&lt;p&gt;**jdk7使用头插法，jdk8使用尾插法。**优化原因，头插法resize的时候会有并发问题，尾插法则不会。虽然并发访问不适合用此类，但jdk8源码开发者还是改了此处。原因是防止指令重排后，resize和插入元素会有重复XX问题？  待确认&lt;/p&gt;
&lt;h3&gt;链表大于8时，将链表转换为红黑树&lt;/h3&gt;
&lt;p&gt;优化原因，大量哈希冲突，会导致get操作性能急剧下降。链表查询为O(n)，转换为红黑树后查询时间变为O(logn)&lt;/p&gt;
&lt;h3&gt;扩容原理&lt;/h3&gt;
&lt;p&gt;2倍扩容，并且rehash。有hash冲突的元素，rehash后的位置变为index+oldCap或位置不变。2倍扩容的方式同样避免了低效的取模运算，使用按位与提高效率&lt;/p&gt;
&lt;p&gt;源码：newTab[e.hash &amp;#x26; (newCap - 1)] = e;&lt;/p&gt;
&lt;h2&gt;LinkedHashMap&lt;/h2&gt;
&lt;p&gt;插入的时候调用了linkNodeLast方法，插入到了双链表尾部。维护了插入的顺序，遍历的时候会以插入顺序进行输出&lt;/p&gt;
&lt;h2&gt;TreeMap&lt;/h2&gt;
&lt;p&gt;底层将hashmap的数组改为使用红黑树来存放数据，默认使用key的自然顺序来排序，当然也可以指定自定义的排序规则。使用key的排序规则来进行迭代输出&lt;/p&gt;
&lt;h3&gt;迭代器的fail fast机制&lt;/h3&gt;
&lt;p&gt;ConcurrentModificationException，并发修改的异常，这个机制就叫做fail fast.&lt;/p&gt;
&lt;p&gt;使用modCount来记录对数据的修改操作，当在迭代的时候修改此迭代器。则会抛出此异常。&lt;/p&gt;
&lt;p&gt;因为集合包中的类，都是非线程安全的。所以设计了针对并发修改集合的问题，都有fail fast机制，使用modCount来实现。一旦expectedModCount!=modCount，抛出异常。&lt;/p&gt;</content:encoded><category>jdk源码</category></item><item><title>并发源码|AQS、ReentratLock原理</title><link>https://leyou240.github.io/blog/juc04</link><guid isPermaLink="true">https://leyou240.github.io/blog/juc04</guid><description>并发源码|AQS、ReentratLock原理</description><pubDate>Wed, 19 Mar 2014 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;AQS原理&lt;/h2&gt;
&lt;p&gt;底层使用如下组件&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private volatile int state;
//标识同步状态，第一次加锁时设置为1，若重入加锁，则state递增。释放锁时state递减，若state=0时将owner设置为null，并且使用LockSupport.unpark(s.thread);方法唤醒等待队列中的第一个线程
//其他线程来获取锁时，若失败，则加入等待队列(双向链表)，并且使用LockSupport.park(this);休眠此线程

private transient Thread exclusiveOwnerThread;
//标识当前线程所有者

//一个双向链表作为一个队列来使用，存放等待队列(保存待加锁的线程)。

//非公平锁：新来一个线程直接判断能不能加锁，若能加锁则直接加锁，不能加锁才放入等待队列
//公平锁：等待队列中的线程依次加锁，体现的“公平”的特点
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ReentratLock&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/05_AQS%E5%8E%9F%E7%90%86.DoFXzino_26Eqii.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;多个线程争抢锁&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/06_%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%BA%89%E6%8A%A2ReentrantLock.B5AWZNb6_Z1aGF1t.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;尝试加锁，public boolean tryLock(long timeout, TimeUnit unit) 如果加锁不成功，则等待一段时间不成功则设置当前线程waitstate为SIGNAL状态，并持续重试，若最终超过了等待时间则从队列中移除&lt;/p&gt;
&lt;h2&gt;ReentrantReadWriteLock读写锁&lt;/h2&gt;
&lt;p&gt;使用state的高低16位(因为int是32位)来标识&lt;strong&gt;读锁(高16位)&lt;strong&gt;和&lt;/strong&gt;写锁(低16位)&lt;/strong&gt;，非0表示已经加过锁了&lt;/p&gt;
&lt;p&gt;读锁，可以同时被多个线程同时持有。读请求可以并发起来&lt;/p&gt;
&lt;p&gt;写锁，则是互斥的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/07_%E5%86%99%E9%94%81%E5%8E%9F%E7%90%86.PQINSFJJ_Z1Ts3iI.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Condition&lt;/h2&gt;
&lt;p&gt;可重入锁可以创建Condition，主要有2个方法await与notify/notifyAll。 await等待唤醒，await与notify/notifyAll唤醒对应conditionawait的线程。&lt;/p&gt;
&lt;p&gt;Condition.await()原理：将自己加入condition等待队列、释放锁、挂起自己&lt;/p&gt;
&lt;p&gt;如果在加锁等待队列里有人阻塞，会有unpark的过程，唤醒加锁等待队列中的队头元素的那个过程&lt;/p&gt;
&lt;p&gt;signal唤醒的过程，大概的意思，就是把condition等待队列中的元素，转化为一个加锁等待队列中的元素&lt;/p&gt;</content:encoded><category>juc源码</category></item><item><title>ArrayList和LinkedList集合源码</title><link>https://leyou240.github.io/blog/jdk01</link><guid isPermaLink="true">https://leyou240.github.io/blog/jdk01</guid><description>ArrayList和LinkedList集合源码</description><pubDate>Thu, 13 Mar 2014 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;ArrayList&lt;/h2&gt;
&lt;p&gt;底层使用数组实现，适合插入较少&lt;/p&gt;
&lt;p&gt;适合随机查找O(1)，原理：地址连续&lt;/p&gt;
&lt;p&gt;扩容O(n) 源码中int newCapacity = oldCapacity + (oldCapacity &gt;&gt; 1);即扩容为原来的1.5倍&lt;/p&gt;
&lt;p&gt;插入O(n)，尾插为O(1)，最坏O(n)，平均即为 O(n)&lt;/p&gt;
&lt;h2&gt;LinkedList&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/01_LinkedList%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.COwSRC56_1IYcVd.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;底层使用(双向)链表，适合插入较多&lt;/p&gt;
&lt;p&gt;随机查找O(n)&lt;/p&gt;
&lt;p&gt;头尾插入O(1)&lt;/p&gt;
&lt;h2&gt;使用场景&lt;/h2&gt;
&lt;p&gt;ArrayList：一般场景，都是用ArrayList来代表一个集合，只要别频繁的往里面插入和灌入大量的元素就可以了，遍历，或者随机查，都可以。&lt;/p&gt;
&lt;p&gt;LinkedList：适合，频繁的在list中插入和删除某个元素，然后尤其是LinkedList他其实是可以当做队列来用的，这个东西的话呢，我们后面看源码的时候，可以来看一下，先进先出，在list尾部怼进去一个元素，从头部拿出来一个元素。如果要在内存里实现一个基本的队列的话，可以用LinkedList&lt;/p&gt;</content:encoded><category>jdk源码</category></item><item><title>并发源码|Atomicxxx原理</title><link>https://leyou240.github.io/blog/juc03</link><guid isPermaLink="true">https://leyou240.github.io/blog/juc03</guid><description>并发源码|Atomicxxx原理</description><pubDate>Tue, 11 Mar 2014 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;AtomicXxx原子类底层的一些原理&lt;/h2&gt;
&lt;p&gt;AtomicLong、AtomicBoolean、AtomicReference、LongAdder等&lt;/p&gt;
&lt;p&gt;都是无锁化或者叫乐观锁，判断此时此刻是否是某个值，如果是则修改，不是则重新查询一个最新的值，再次执行判断。这个操作叫CAS，compare and set(swap jdk8及以后)&lt;/p&gt;
&lt;p&gt;Atomic原子类底层核心的原理就是CAS，无锁化，乐观锁，每次尝试修改的时候，就对比一下，有没有人修改过这个值，没有人修改，自己就修改，如果有人修改过，就重新查出来最新的值，再次重复那个过程&lt;/p&gt;
&lt;p&gt;源码：&lt;/p&gt;
&lt;p&gt;（1）变量的offset，volatile value 使其值对其他线程可见&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt; private static final long valueOffset; 

  static {

    try {

      valueOffset = unsafe.objectFieldOffset

        (AtomicInteger.class.getDeclaredField(&quot;value&quot;));

    } catch (Exception ex) { throw new Error(ex); }

  } 

private volatile int value;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（2）Unsafe：核心类，负责执行CAS操作&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（3）API接口：Atomic原子类的各种使用方式&lt;/p&gt;
&lt;p&gt;最底层使用c代码，发送cpu指令，确保CAS操作绝对原子(通过指令来锁掉某一小块内存，并且保证只有一个线程在同一时间可以对此块内存数据做CAS操作)。&lt;/p&gt;
&lt;h2&gt;CAS的问题&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;ABA问题&lt;/p&gt;
&lt;p&gt;如果某个值一开始是A，后来变成了B，然后又变成了A，你本来期望的是值如果是第一个A才会设置新值，结果第二个A一比较也ok，也设置了新值，跟期望是不符合的。所以atomic包里有AtomicStampedReference类，就是会比较两个值的引用是否一致，如果一致，才会设置新值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无限循环(自旋)&lt;/p&gt;
&lt;p&gt;大家看源码就知道Atomic类设置值的时候会进入一个无限循环，只要不成功，就不停循环再次尝试，这个在高并发修改一个值的时候其实挺常见的，比如你用AtomicInteger在内存里搞一个原子变量，然后高并发下，多线程频繁修改，其实可能会导致这个compareAndSet()里要循环N次才设置成功，所以还是要考虑到的。JDK 1.8引入的LongAdder来解决，是一个重点，分段CAS思路&lt;/p&gt;
&lt;p&gt;LongAdder&lt;/p&gt;
&lt;p&gt;大量线程并发更新一个原子类的时候，天然的一个问题就是自旋，会导致并发性能还是有待提升，比synchronized当然好很多了&lt;/p&gt;
&lt;p&gt;分段迁移，某一个线程如果对一个Cell更新的时候，发现说出现了很难更新他的值，出现了多次自旋的一个问题，如果他CAS失败了，自动迁移段，他会去尝试更新别的Cell的值，这样的话就可以让一个线程不会盲目的等待一个cell的值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多原子变量问题&lt;/p&gt;
&lt;p&gt;AtomicXXX类，只能保证一个变量的原子性，但是如果多个变量呢？你可以用AtomicReference，这个是封装自定义对象的，多个变量可以放一个自定义对象里，然后他会检查这个对象的引用是不是一个。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded><category>juc源码</category></item><item><title>并发源码|synchronized、wait与notify</title><link>https://leyou240.github.io/blog/juc02</link><guid isPermaLink="true">https://leyou240.github.io/blog/juc02</guid><description>并发源码|synchronized、wait与notify</description><pubDate>Sat, 08 Mar 2014 13:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;synchronized的底层原理&lt;/h2&gt;
&lt;p&gt;跟jvm及monitor有关。&lt;/p&gt;
&lt;p&gt;如果用到了synchronized关键字，在底层编译后的jvm指令中，会有monitorenter和monitorexit两个指令&lt;/p&gt;
&lt;p&gt;每个对象都是有个关联的monitor对象，即对象的Mark Word。里面有个计数器i，一个线程获取到锁以后i++，并且将锁对象设置为当前线程。释放锁时候i--，若i为0 表示完全释放锁，将monitor的锁对象设为null&lt;/p&gt;
&lt;p&gt;这个monitor的锁是支持重入加锁的，什么意思呢，好比下面的代码片段&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;synchronized(myObject) {
    // 一大堆的代码
    synchronized(myObject) {
    // 一大堆的代码
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果一个线程第一次synchronized那里，获取到了myObject对象的monitor的锁，计数器加1，然后第二次synchronized那里，会再次获取myObject对象的monitor的锁，这个就是重入加锁了，然后计数器会再次加1，变成2&lt;/p&gt;
&lt;p&gt;这个时候，其他的线程在第一次synchronized那里，会发现说myObject对象的monitor锁的计数器是大于0的，意味着被别人加锁了，然后此时线程就会进入block阻塞状态，什么都干不了，就是等着获取锁&lt;/p&gt;
&lt;p&gt;接着如果出了synchronized修饰的代码片段的范围，就会有一个monitorexit的指令，在底层。此时获取锁的线程就会对那个对象的monitor的计数器减1，如果有多次重入加锁就会对应多次减1，直到最后，计数器是0&lt;/p&gt;
&lt;p&gt;然后后面block住阻塞的线程，会再次尝试获取锁，但是只有一个线程可以获取到锁&lt;/p&gt;
&lt;p&gt;block以后，将block的线程加入wait set等待其他线程完成执行&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/04_synchronized%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86.BAZNiu7l_26ezU3.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;wait与sleep的区别&lt;/h2&gt;
&lt;p&gt;前者释放锁，后者不释放锁&lt;/p&gt;
&lt;p&gt;wait()，必须是有人notify唤醒他&lt;/p&gt;
&lt;p&gt;wait(timeout)，阻塞一段时间，然后自己唤醒，继续争抢锁&lt;/p&gt;
&lt;p&gt;wait与notify，必须在synchronized代码块中使用，因为必须是拥有monitor lock的线程才可以执行wait与notify操作&lt;/p&gt;
&lt;p&gt;因此wait与notify，必须与synchornized一起，对同一个对象进行使用，这样他们对应的monitor才是一样的&lt;/p&gt;
&lt;p&gt;notify()与notifyall()：前者就唤醒block状态的一个线程，后者唤醒block状态的所有线程&lt;/p&gt;</content:encoded><category>juc源码</category></item><item><title>并发源码| JMM与volatile</title><link>https://leyou240.github.io/blog/juc01</link><guid isPermaLink="true">https://leyou240.github.io/blog/juc01</guid><description>并发源码| JMM与volatile</description><pubDate>Sat, 01 Mar 2014 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;主内存的数据会被加载到cpu本地缓存里去，cpu后面会读写自己的缓存&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/02_cpu%E7%BC%93%E5%AD%98%E6%A8%A1%E5%9E%8B.oJzg_qit_Z25jYz9.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;缓存模型下的并发问题&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/03_cpu%E7%BC%93%E5%AD%98%E6%A8%A1%E5%9E%8B%E4%B8%8B%E7%9A%84%E5%B9%B6%E5%8F%91%E9%97%AE%E9%A2%98.LFJHJ19__Z238DF2.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;java内存模型&lt;/p&gt;
&lt;p&gt;read（从主存读取），load（将主存读取到的值写入工作内存），use（从工作内存读取数据来计算），assign（将计算好的值重新赋值到工作内存中），store（将工作内存数据写入主存），write（将store过去的变量值赋值给主存中的变量）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://leyou240.github.io/_astro/03_java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.CQHRKq1v_Z53NvX.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;volatile保持内存可见的原理&lt;/h2&gt;
&lt;p&gt;就是说一定会强制保证说assign之后，就立马执行store + write，刷回到主内存里去。并且将其他工作内存中的状态改为过期&lt;/p&gt;
&lt;h2&gt;volatile无法保持原子性的原理&lt;/h2&gt;
&lt;p&gt;2个线程同时use后，线程1assign了以后，刷回了主内存。但线程2已经use了，不需要在从内存中加载。此时使用的旧的值。&lt;/p&gt;
&lt;p&gt;底层使用cpu的MESI缓存一致性协议，强制刷主内存，过期其他线程中的工作内存&lt;/p&gt;
&lt;h2&gt;volatile内存屏障及happen before原则&lt;/h2&gt;
&lt;p&gt;在被volatile标记的字段读写前后都设置内存屏障，防止指令重排。&lt;/p&gt;
&lt;p&gt;指令重排的happen before原则，是给开发JVM的人看的。&lt;/p&gt;
&lt;p&gt;大概有几个原则&lt;/p&gt;
&lt;p&gt;程序次序规则：一个线程内，按照代码顺序，书写在前面的操作先行发生于书写在后面的操作&lt;/p&gt;
&lt;p&gt;lock规则：一个unLock操作先行发生于后面对同一个锁的lock操作&lt;/p&gt;
&lt;p&gt;volatile变量规则：必须保证是先写，再读&lt;/p&gt;
&lt;h2&gt;volatile的实际用途和场景&lt;/h2&gt;
&lt;p&gt;double check的单例模式，使用volatile防止指令重排&lt;/p&gt;
&lt;p&gt;instance=new Singleton() 分为3步&lt;/p&gt;
&lt;p&gt;1、volatile保证并发可见性&lt;/p&gt;
&lt;p&gt;2、synchronized保证有序性&lt;/p&gt;
&lt;p&gt;3、double check保证对象被正常创建（类加载创建过程多线程问题）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {
    }
    public Singleton getInstance(){
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance=new Singleton();
                }
            }
        }
        return instance;
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>juc源码</category></item></channel></rss>