整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          我自定義的攔截器為什么會(huì)靠后執(zhí)行?

          項(xiàng)目中自定義了攔截器Filter,項(xiàng)目中使用了spring security,它也有對(duì)應(yīng)的攔截器,我想讓我自定義的Filter在spring security的攔截器前執(zhí)行。

          因?yàn)槲易远x的攔截器,需要提前做一些邏輯處理;然后spring security的攔截器需要用到這部分的處理結(jié)果;所以我必須要想辦法讓我自定義的攔截器靠前執(zhí)行。

          那就一起來看看spring security設(shè)置的攔截器的默認(rèn)優(yōu)先級(jí)等級(jí)是多少吧。

          模擬場(chǎng)景

          自定義攔截器如下:

          @Slf4j
          public class MyOncePerRequestFilter extends OncePerRequestFilter {
              @Override
              protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                      throws ServletException, IOException {
                  log.info("======== MyOncePerRequestFilter ========");
                  filterChain.doFilter(request, response);
              }
          }
          
          @Configuration
          public class Config {
              @Bean
              public FilterRegistrationBean<MyOncePerRequestFilter> i18nFilterRegistrationBean() {
                  FilterRegistrationBean<MyOncePerRequestFilter> registrationBean = new FilterRegistrationBean();
                  MyOncePerRequestFilter myOncePerRequestFilter = new MyOncePerRequestFilter();
                  registrationBean.setFilter(myOncePerRequestFilter);
                  registrationBean.addUrlPatterns("/*");
                  registrationBean.setOrder(-1);
                  return registrationBean;
              }
          }
          

          spring security的簡(jiǎn)單配置如下:

          @Slf4j
          public class MyTokenStore implements TokenStore {
              @Override
              public OAuth2AccessToken readAccessToken(String tokenValue) {
                  log.info("======== readAccessToken ========");
                  return new DefaultOAuth2AccessToken(tokenValue);
              }
          
              @Override
              public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
                  Authentication authentication = new AbstractAuthenticationToken(Sets.newHashSet()) {
                      {
                          super.setAuthenticated(true);
                      }
                      @Override
                      public Object getCredentials() {
                          return null;
                      }
                      @Override
                      public Object getPrincipal() {
                          return StringUtils.EMPTY;
                      }
                  };
                  OAuth2Request request =
                          new OAuth2Request(null, null, null, true,
                                  Sets.newHashSet(), Sets.newHashSet(), null, null, null);
                  return new OAuth2Authentication(request, authentication);
              }
          
              @Override public OAuth2Authentication readAuthentication(String token) {
                  return null;
              }
          
              @Override
              public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
              }
          
              @Override public void removeAccessToken(OAuth2AccessToken token) {
          
              }
          
              @Override public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
              }
          
              @Override public OAuth2RefreshToken readRefreshToken(String tokenValue) {
                  return null;
              }
          
              @Override public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
                  return null;
              }
          
              @Override public void removeRefreshToken(OAuth2RefreshToken token) {
              }
          
              @Override public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
              }
          
              @Override public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
                  return null;
              }
          
              @Override public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
                  return null;
              }
          
              @Override public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
                  return null;
              }
          }
          
          @Configuration
          @EnableResourceServer
          @EnableWebSecurity
          public class MyResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {
          
              @Override
              public void configure(ResourceServerSecurityConfigurer resources) {
                  MyTokenStore tokenStore = new MyTokenStore();
                  resources.tokenStore(tokenStore);
              }
          
              @Override
              public void configure(HttpSecurity http) throws Exception {
                  http.csrf().disable()
                          .authorizeRequests().anyRequest().authenticated()
                          .and().anonymous().key("anonymousUser")
                          .and().httpBasic();
              }
          }
          

          啟動(dòng)類如下:

          @RestController
          public class MyController {
              @GetMapping("/hello")
              public String hello() {
                  return "hello,world!";
              }
          }
          
          @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
          public class Starter {
              public static void main(String[] args) {
                  SpringApplication.run(Starter.class, args);
              }
          }
          

          啟動(dòng)后,訪問 http://127.0.0.1:8080/hello?access_token=123

          日志打印如下:

          102149 [http-nio-8080-exec-1] INFO  c.e.l.s.mvc.security.MyTokenStore - ======== readAccessToken ======== 
          102149 [http-nio-8080-exec-1] INFO  c.e.l.s.m.s.MyOncePerRequestFilter - ======== MyOncePerRequestFilter ======== 
          

          從結(jié)果可以看出,spring security的攔截器是比我們自定義的攔截器先執(zhí)行的,而我們自定義的攔截器的優(yōu)先級(jí)是registrationBean.setOrder(-1)

          我猜應(yīng)該是這個(gè)值決定了執(zhí)行順序,那就帶著這個(gè)猜想往下看一下吧。

          是不是因?yàn)閛rder的值

          在之前的配置中,我們將自定義的攔截器順序置為-1

          我們先在MyOncePerRequestFilter.doFilterInternal打個(gè)斷點(diǎn),看一下執(zhí)行鏈的順序:

          從這條鏈中,我們猜測(cè)springSecurityFilterChain的order是-100,我們自定義的攔截器是在它后面的

          那我們直接把我們的攔截器設(shè)置成-101,registrationBean.setOrder(-101);,再來嘗試一下:

          從斷點(diǎn)結(jié)果可以看出,我們的設(shè)置是有效的,并且起到了作用,而且打印日志也說明了結(jié)果,如下:

          11956 [http-nio-8080-exec-1] INFO  c.e.l.s.m.s.MyOncePerRequestFilter - ======== MyOncePerRequestFilter ======== 
          98419 [http-nio-8080-exec-1] INFO  c.e.l.s.mvc.security.MyTokenStore - ======== readAccessToken ======== 
          

          找出在哪里賦予的order值

          這個(gè)過程是極其枯燥的,所以就先給結(jié)果了,如下:

          spring security的攔截器鏈?zhǔn)窃谙旅孢@部分創(chuàng)建的:

          @Configuration(proxyBeanMethods = false)
          @ConditionalOnWebApplication(type = Type.SERVLET)
          @EnableConfigurationProperties(SecurityProperties.class)
          @ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
          @AutoConfigureAfter(SecurityAutoConfiguration.class)
          public class SecurityFilterAutoConfiguration {
          	private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
          	@Bean
          	@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
          	public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
          			SecurityProperties securityProperties) {
          		DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
          				DEFAULT_FILTER_NAME);
          		registration.setOrder(securityProperties.getFilter().getOrder()); // 這里
          		registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
          		return registration;
          	}
          }
          
          public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {
              public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
          }
          
          @ConfigurationProperties(prefix = "spring.security")
          public class SecurityProperties {
          	public static final int DEFAULT_FILTER_ORDER = OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100; // 這里
          
          	private final Filter filter = new Filter();
          	public Filter getFilter() {
          		return this.filter;
          	}
          	public static class Filter {
          		private int order = DEFAULT_FILTER_ORDER; // 這里
          		public int getOrder() {
          			return this.order;
          		}
          		public void setOrder(int order) {
          			this.order = order;
          		}
          	}
          }
          
          public interface OrderedFilter extends Filter, Ordered {
          	int REQUEST_WRAPPER_FILTER_MAX_ORDER = 0; // 這里
          }
          

          從上面的代碼可以看出,默認(rèn)值是-100,同樣也可以使用spring.security.filter.order來自定義值。

          下面是尋找此過程的歷程:

          繼續(xù)從這里開始,ApplicationFilterChain.internalDoFilter如下:

          可以看出所有的攔截器都是在filters中,我們可以看這個(gè)值是怎么來的,通過調(diào)試,是在ApplicationFilterChain.addFilter這個(gè)地方,如下:

          它是被ApplicationFilterFactory.createFilterChain調(diào)用的,如下:

          所以filters是根據(jù)filterMaps來添加的,我們?cè)賮砜匆幌耭ilterMaps是怎么來的,一共涉及到兩個(gè)地方,如下:

          StandardContext.addFilterMap和StandardContext.addFilterMapBefore如下:

          看一下調(diào)用鏈:

          原來是被ServletWebServerApplicationContext.selfInitialize調(diào)用的,如下:

          ServletWebServerApplicationContext.getServletContextInitializerBeans如下:

          ServletContextInitializerBeans構(gòu)造函數(shù)如下:

          所有的攔截器都是通過addServletContextInitializerBeans(beanFactory);和addAdaptableBeans(beanFactory);來把bean加進(jìn)來的

          經(jīng)過一番調(diào)試,終于找到spring security這個(gè)攔截器定義順序的位置,SecurityFilterAutoConfiguration.securityFilterChainRegistration如下:

          可以看到SecurityProperties securityProperties是注入進(jìn)來的,找到這個(gè)類看一下,securityProperties.filter.order如下:

          @ConfigurationProperties(prefix = "spring.security")
          public class SecurityProperties {
          	public static final int DEFAULT_FILTER_ORDER = OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100;
          	private final Filter filter = new Filter();
          	private final User user = new User();
          	public static class Filter {
          		/**
          		 * Security filter chain order.
          		 */
          		private int order = DEFAULT_FILTER_ORDER;
          		public int getOrder() {
          			return this.order;
          		}
          		public void setOrder(int order) {
          			this.order = order;
          		}
          	}
          }
          public interface OrderedFilter extends Filter, Ordered {
          	/**
          	 * Filters that wrap the servlet request should be ordered less than or equal to this.
          	 */
          	int REQUEST_WRAPPER_FILTER_MAX_ORDER = 0;
          }
          

          到此我們也找到了這個(gè)默認(rèn)值,是根據(jù)spring.security.filter.order來決定的,默認(rèn)值是-100

          解決辦法

          第一種就是修改自己的順序:

          @Configuration
          public class Config {
              @Bean
              public FilterRegistrationBean<MyOncePerRequestFilter> i18nFilterRegistrationBean() {
                  FilterRegistrationBean<MyOncePerRequestFilter> registrationBean = new FilterRegistrationBean();
                  MyOncePerRequestFilter myOncePerRequestFilter = new MyOncePerRequestFilter();
          
                  registrationBean.setFilter(myOncePerRequestFilter);
                  registrationBean.addUrlPatterns("/*");
                  registrationBean.setOrder(-101); // 這里
                  return registrationBean;
              }
          }
          

          第二種就是修改spring security攔截器的順序:

          spring:
            security:
              filter:
                order: 0
          

          大家可以自己跑跑試試看,完結(jié)撒花~~~~~~

          原文地址:https://www.cnblogs.com/eaglelihh/p/15009562.html

          述:本文將討論如何用最簡(jiǎn)單的術(shù)語在網(wǎng)站上運(yùn)行 C# 代碼。半技術(shù)講座我使用了 wasm-tools-net7,這是一個(gè)基于 wasm-tools 的工作負(fù)載,沒有包含任何額外的包。我的重點(diǎn)是簡(jiǎn)單性和主要主題。徹底了解該主題可提供完成所有其他任務(wù)所需的信息。如何工作?WebAssembly 工作原理:序列圖創(chuàng)建演示創(chuàng)建項(xiàng)目我用的是net7,但這取決于你。Dotnet new console -o WASM_Demo cd WASM_Demo Dotnet workload install wasm-tools-net7此時(shí),需要對(duì) csproj 文件進(jìn)行修改。Project Sdk=Mi

          本文將討論如何用最簡(jiǎn)單的術(shù)語在網(wǎng)站上運(yùn)行 C# 代碼。

          半技術(shù)講座

          我使用了 wasm-tools-net7,這是一個(gè)基于 wasm-tools 的工作負(fù)載,沒有包含任何額外的包。我的重點(diǎn)是簡(jiǎn)單性和主要主題。徹底了解該主題可提供完成所有其他任務(wù)所需的信息。

          如何工作?

          WebAssembly 工作原理:序列圖

          創(chuàng)建演示

          創(chuàng)建項(xiàng)目

          • 我用的是net7,但這取決于你。
          Dotnet new console -o WASM_Demo  
            
          cd WASM_Demo  
            
          Dotnet workload install wasm-tools-net7
          

          此時(shí),需要對(duì) csproj 文件進(jìn)行修改。

          <Project Sdk="Microsoft.NET.Sdk">
          
              <PropertyGroup>
                  <OutputType>Exe</OutputType>
                  <TargetFramework>net7.0</TargetFramework>
                  <ImplicitUsings>enable</ImplicitUsings>
                  <Nullable>enable</Nullable>
          
                  <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
                  <WasmMainJSPath>main.js</WasmMainJSPath>
                  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
              </PropertyGroup>
          
              <ItemGroup>
                  <WasmExtraFilesToDeploy Include="index.html" />
                  <WasmExtraFilesToDeploy Include="main.js" />
              </ItemGroup>
          
          </Project>

          我們添加了什么:

          • RuntimeIdentifier (wasm-tools 需要)
          • WasmMainJSPath (wasm-tools 需要)
          • AllowUnsafeBlocks(JSExportAttribute 需要不安全的代碼)
          • ItemGroup (Include as resource)導(dǎo)入 index.html導(dǎo)入main.js

          返回到program.cs文件,需要考慮某些規(guī)則。

          • 類必須是公共的和部分的。
          • 函數(shù)必須是公共的和靜態(tài)的,并且必須使用 [JSExport] 進(jìn)行屬性化。

          讓我們舉個(gè)例子。

          using System.Runtime.InteropServices.JavaScript;
          
          namespace WASM_Demo;
          
          public partial class Program
          {
              static void Main(string[] args) { }
          
              [JSExport]
              public static string Response()
              {
                  return """
                         <h1>
                             Hello World
                         </h1>
                         """;
              }
          }

          沒關(guān)系,但是我們?nèi)绾卧跒g覽器中運(yùn)行此代碼?

          運(yùn)行這個(gè)程序的代碼是dotnet.js的,它自帶了wasm-tools,所以沒有必要擔(dān)心它。要使用此dotnet.js,我們只需使用一個(gè)名為 main.js 的文件。

          import { dotnet } from './dotnet.js'
          
          const is_browser = typeof window != "undefined";
          if (!is_browser) throw new Error(`Expected to be running in a browser`);
          
          const { setModuleImports, getAssemblyExports, getConfig, runMainAndExit } = await dotnet
              .withDiagnosticTracing(false)
              .withApplicationArgumentsFromQuery()
              .create();
          
          const config = getConfig();
          const exports = await getAssemblyExports(config.mainAssemblyName);
          
          const html = 
              exports
                  .WASM_Demo    // Namespace
                  .Program      // Class Name
                  .Response();  // Function Name
          
          // Regular javascript code
          document.getElementById("app").innerHTML = `${html}`;
          
          await runMainAndExit(config.mainAssemblyName, [] /* Console App Args */);

          index.html頁面的模板已經(jīng)準(zhǔn)備完畢。

          <!DOCTYPE html>
          <html lang="en">
              <head>
                  <title>WASM Demo</title>
                  <meta charset="UTF-8" />
                  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                  <link rel="modulepreload" href="./dotnet.js" />
              </head>
              
              <body>
                  <main id="app"></main>
                  <script type="module" src="./main.js"></script>
              </body>
          </html>

          現(xiàn)在,讓我們?cè)倏匆槐檫@個(gè)過程,

          • HTTP 請(qǐng)求傳入
          • WASM-Tools 處理此問題并發(fā)送index.html文件。
          • index.html文件請(qǐng)求dotnet.js和main.js文件,dotnet.js由 WASM-Tools 發(fā)送,main.js是我們的自定義代碼。
          • 通過在 main.js 中使用 dotnet.js,我們可以進(jìn)入 C# 代碼中的 Program 類,調(diào)用 Response 函數(shù)并在 main.js 中進(jìn)行我們想要的任何客戶端更改。

          我們還有一件事要做,你需要打開一個(gè)名為 runtimeconfig.template.json 的文件,并將以下 JSON 數(shù)據(jù)放入其中。

          {
            "wasmHostProperties": {
              "perHostConfig": [
                {
                  "name": "browser",
                  "html-path": "index.html",
                  "Host": "browser"
                }
              ]
            }
          }

          我們已經(jīng)到了盡頭,程序現(xiàn)在可以運(yùn)行了。唯一需要的命令是:

          Dotnet run -c Release
          

          常見問題

          我可以托管所有文件而不是 wasm-tools 嗎?又是如何做到的呢?

          當(dāng)然,但它可能會(huì)變得有點(diǎn)復(fù)雜,你用 wasm-tools 制作的項(xiàng)目不能用于任何其他目的,即控制臺(tái)應(yīng)用程序不起作用,wasm-tools 可以工作。因?yàn)槲覀冞x擇 browser-wasm 作為 RuntimeIdentifier,并且多個(gè) RuntimeIdentifiers 在 .NET 中不可用。作為替代方法,您可以打開兩個(gè)項(xiàng)目,將第一個(gè)項(xiàng)目設(shè)置為 WASM 項(xiàng)目,然后在第二個(gè)項(xiàng)目中將其設(shè)置為控制臺(tái)應(yīng)用程序,然后生成第一個(gè)項(xiàng)目并托管輸出文件夾,所有 DLL 和文件都將在那里。

          這個(gè)演示只是索引文件,我可以做多頁嗎?又是如何做到的呢?

          當(dāng)然,但這比你想象的要難得多,因?yàn)檫@樣做的方法是一種叫做SPA(單頁應(yīng)用程序)的方法,用戶總是在同一頁面上,只是內(nèi)容發(fā)生了變化。有多種方法可以做到這一點(diǎn)。所以它可以用你的創(chuàng)造力來完成。

          我可以像計(jì)數(shù)器一樣做動(dòng)態(tài)代碼嗎?又是如何做到的呢?

          _是的,我也這樣做了,你可以一遍又一遍地調(diào)用 C# 函數(shù),如果你只是將導(dǎo)出綁定到 window 對(duì)象,你可以從每個(gè) JavaScript 代碼中調(diào)用它。

          Q簡(jiǎn)介

          • 什么是MQ
          • 跨進(jìn)程的消息隊(duì)列,主要角色包括生產(chǎn)者與消費(fèi)者。
          • 生產(chǎn)者只負(fù)責(zé)生產(chǎn)信息,無法感知消費(fèi)者是誰,消息怎么處理,處理結(jié)果是什么。
          • 消費(fèi)者負(fù)責(zé)接收及處理消息,無法感知生產(chǎn)者是誰,怎么產(chǎn)生的。
          • Mq能做什么?
          • MQ 特性一般有異步,吞吐量大 ,延時(shí)低;
          • 適合做:
          1. 投遞異步通知。
          2. 限流,削峰谷。
          3. 可靠事件,處理數(shù)據(jù)一致性。
          4. 利用一些特性,可以做定時(shí)任務(wù)。
          5. 等….

          由于MQ是異步處理消息的,所以MQ不適合做同步處理操作,如果需要及時(shí)的返回處理結(jié)果請(qǐng)不要用MQ;

          • MQ 個(gè)系統(tǒng)帶來了什么?
          • 缺點(diǎn):增加了系統(tǒng)的復(fù)雜性,除了代碼組件接入以外還需要考慮,高可用,集群,消息的可靠性等問題!
          • 生產(chǎn)者:消息發(fā)送怎么保證可靠性,怎么保證不重復(fù)!
          • 消費(fèi)者:怎么保證冪等性,接收到重復(fù)消息怎么處理!
          • 還有會(huì)帶來的處理延時(shí)等問題!

          優(yōu)點(diǎn): 解耦,利用MQ我們可以很好的給我們系統(tǒng)解耦,特別是分布式/微服系統(tǒng)!

          原來的同步操作,可以用異步處理,也可以帶來更快的響應(yīng)速度;

          • 哪些場(chǎng)景可以使用MQ

          場(chǎng)景 (1)

          系統(tǒng)解耦,用戶系統(tǒng)或者其他系統(tǒng)需要發(fā)送短信可以通過 MQ 執(zhí)行;很好的將 用戶系統(tǒng) 和 短信系統(tǒng)進(jìn)行解耦;

          場(chǎng)景(2)

          順序執(zhí)行的任務(wù)場(chǎng)景,假設(shè) A B C 三個(gè)任務(wù),B需要等待 A完成才去執(zhí)行,C需要等待B完成才去執(zhí)行;

          我見過一些同學(xué)的做法是 ,用 三個(gè)定時(shí)器 錯(cuò)開時(shí)間去執(zhí)行的,假設(shè) A定時(shí)器 9 點(diǎn)執(zhí)行, B 定時(shí)器 10 點(diǎn)執(zhí)行 , C 11 點(diǎn)執(zhí)行 , 類似這樣子;

          這樣做其實(shí)是 不安全的, 因?yàn)?后一個(gè)任務(wù) 無法知道 前一個(gè)任務(wù)是否 真的執(zhí)行了! 假設(shè) A 宕機(jī)了, 到 10 點(diǎn) B 定時(shí)去 執(zhí)行,這時(shí)候 數(shù)據(jù)就會(huì)產(chǎn)生異常!

          當(dāng)我們 引入 MQ 后 可以這么做, A執(zhí)行完了 發(fā)送 消息給 B ,B收到消息后 執(zhí)行,C 類似,收到 B消息后執(zhí)行;

          場(chǎng)景(3)

          支付網(wǎng)關(guān)的通知,我們的系統(tǒng)常常需要接入支付功能,微信或者支付寶通常會(huì)以回調(diào)的形式通知我們系統(tǒng)支付結(jié)果。

          我們可以將我們的支付網(wǎng)關(guān)獨(dú)立出來,通過MQ通知我們業(yè)務(wù)系統(tǒng)進(jìn)行處理,這樣處理有利于系統(tǒng)的解耦和擴(kuò)展!

          假設(shè)我們還有一個(gè)積分系統(tǒng),用戶支付成功,給用戶添加積分。只需要積分系統(tǒng)監(jiān)聽這個(gè)消息,并處理積分就好,無需去修改再去修改網(wǎng)關(guān)層代碼!

          如果沒有使用MQ ,我是不是還得去修改網(wǎng)關(guān)系統(tǒng)的代碼,遠(yuǎn)程調(diào)用增加積分的接口?

          這就是使用了MQ的好處,解耦和擴(kuò)展!

          當(dāng)然我們的轉(zhuǎn)發(fā)規(guī)則也要保證每個(gè)感興趣的隊(duì)列能獲取到消息!



          場(chǎng)景(4)

          微服/分布式系統(tǒng),分布式事務(wù) - 最終一致性 處理方案!

          詳情: 分布式事務(wù)處理方案,微服事務(wù)處理方案

          場(chǎng)景(5)

          • 消息延時(shí)隊(duì)列,可做些定時(shí)任務(wù),不固定時(shí)間執(zhí)行的定時(shí)任務(wù)。
          • 例如:用戶下單后如果24小時(shí)未支付訂單取消;
          • 確認(rèn)收貨后2天后沒有評(píng)價(jià)自動(dòng)好評(píng);
          • 等...

          我們以前的做法是 通常啟用一個(gè)定時(shí)器,每分鐘或者每小時(shí),去跑一次取出需要處理的訂單或其他數(shù)據(jù)進(jìn)行處理。

          這種做法一個(gè)是 效率比較低,如果數(shù)據(jù)量大的話,每次都要掃庫,非常要命!

          再者時(shí)效性不是很高,最差的時(shí)候可能需要等待一輪時(shí)長(zhǎng)!

          還有可能出現(xiàn)重復(fù)執(zhí)行的結(jié)果,時(shí)效和輪詢的頻率難以平衡!

          利用MQ(Rabbitmq),DLX (Dead Letter Exchanges)和 消息的 TTL (Time-To-Live Extensions)特性。我們可以高效的完成這個(gè)任務(wù)場(chǎng)景!不需要掃庫,時(shí)效性更好!

          DLX:http://www.rabbitmq.com/dlx.html,

          TTL:http://www.rabbitmq.com/ttl.html#per-message-ttl

          原理:

          發(fā)送到隊(duì)列的消息,可以設(shè)置一個(gè)存活時(shí)間 TTL,在存活時(shí)間內(nèi)沒有被消費(fèi),可以設(shè)置這個(gè)消息轉(zhuǎn)發(fā)到其他隊(duì)列里面去;然后我們從這個(gè)其他隊(duì)列里面消費(fèi)執(zhí)行我們的任務(wù),這樣就可以達(dá)到一個(gè)消息延時(shí)的效果!



          設(shè)置過期時(shí)間:

          過期時(shí)間可以統(tǒng)一設(shè)置到消息隊(duì)列里面,也可以單獨(dú)設(shè)置到某個(gè)消息!

          PS 如果消息設(shè)置了過期時(shí)間,發(fā)生到了設(shè)置有過期時(shí)間的隊(duì)列,已隊(duì)列設(shè)置的過期時(shí)間為準(zhǔn)!

          已 SpringBoot 為例:

          配置轉(zhuǎn)發(fā)隊(duì)列和被轉(zhuǎn)發(fā)隊(duì)列:

          @Component
          @Configuration
          public class RabbitMqConfig {
           @Bean
           public Queue curQueue() {
           Map<String, Object> args = new HashMap<String, Object>();
           //超時(shí)后的轉(zhuǎn)發(fā)器 過期轉(zhuǎn)發(fā)到 delay_queue_exchange
           args.put("x-dead-letter-exchange", "delay_queue_exchange");
           //routingKey 轉(zhuǎn)發(fā)規(guī)則
           args.put("x-dead-letter-routing-key", "user.#");
           //過期時(shí)間 20 秒
           args.put("x-message-ttl", 20000);
           return new Queue("cur_queue", false, false, false, args);
           }
           @Bean
           public Queue delayQueue() {
           return new Queue("delay_queue");
           }
           @Bean
           TopicExchange exchange() {
           //當(dāng)前隊(duì)列
           return new TopicExchange("cur_queue_exchange");
           }
           @Bean
           TopicExchange exchange2() {
           //被轉(zhuǎn)發(fā)的隊(duì)列
           return new TopicExchange("delay_queue_exchange");
           }
           @Bean
           Binding bindingHelloQueue(Queue curQueue, TopicExchange exchange) {
           //綁定隊(duì)列到轉(zhuǎn)發(fā)器
           return BindingBuilder.bind(curQueue).to(exchange).with("user.#");
           }
           @Bean
           Binding bindingHelloQueue2(Queue delayQueue, TopicExchange exchange2) {
           return BindingBuilder.bind(delayQueue).to(exchange2).with("user.#");
           }
          }
          

          發(fā)生消息:

          @Component
          public class MqEventSender {
           Logger logger = LoggerFactory.getLogger(MqEventSender.class);
           @Autowired
           private RabbitTemplate rabbitTemplate;
           /**
           * 消息沒有設(shè)置 時(shí)間
           * 發(fā)生到隊(duì)列 cur_queue_exchange
           * @param msg
           */
           public void sendMsg(String msg) {
           logger.info("發(fā)送消息: " + msg);
           rabbitTemplate.convertAndSend("cur_queue_exchange", "user.ss", msg);
           }
           /**
           * 消息設(shè)置時(shí)間
           * 發(fā)生到隊(duì)列 cur_queue_exchange
           * @param msg
           */
           public void sendMsgWithTime(String msg) {
           logger.info("發(fā)送消息: " + msg);
           MessageProperties messageProperties = new MessageProperties();
           //過期時(shí)間設(shè)置 10 秒
           messageProperties.setExpiration("10000");
           Message message = rabbitTemplate.getMessageConverter().toMessage(msg, messageProperties);
           rabbitTemplate.convertAndSend("cur_queue_exchange", "user.ss", message);
           }
          }
          

          消息監(jiān)聽:

          監(jiān)聽 的隊(duì)列是 delay_queue 而不是 cur_queue;

          PS cur_queue 不應(yīng)該有監(jiān)聽者,否則消息被消費(fèi)達(dá)不到想要的延時(shí)消息效果!

          /**
           * Created by linli on 2017/8/21.
           * 監(jiān)聽 被丟到 超時(shí)隊(duì)列內(nèi)容
           */
          @Component
          @RabbitListener(queues = "delay_queue")
          public class DelayQueueListener {
           public static Logger logger = LoggerFactory.getLogger(AddCommentsEventListener.class);
           @RabbitHandler
           public void process(@Payload String msg) {
           logger.info("收到消息 "+msg);
           }
          }
          

          測(cè)試:

          /**
           * Created by linli on 2017/8/21.
           */
          @RestController
          @RequestMapping("/test")
          public class TestContorller {
           @Autowired
           MqEventSender sender;
           @RequestMapping("/mq/delay")
           public String test() {
           sender.sendMsg("隊(duì)列延時(shí)消息!");
           sender.sendMsgWithTime("消息延時(shí)消息!");
           return "";
           }
          }
          

          結(jié)果:



          觀察結(jié)果發(fā)現(xiàn):發(fā)送時(shí)間 和 收到時(shí)間 間隔 20秒 ;

          我們給消息設(shè)置的 10 秒 TTL 時(shí)間沒有生效!驗(yàn)證了 : 如果消息設(shè)置了過期時(shí)間,發(fā)生到了設(shè)置有過期時(shí)間的隊(duì)列,已隊(duì)列設(shè)置的過期時(shí)間為準(zhǔn)!

          如果希望每個(gè)消息都要自己的存活時(shí)間,發(fā)送到隊(duì)列 不要設(shè)置

          args.put(“x-message-ttl”, 20000);

          消息的過期時(shí)間 設(shè)置在隊(duì)列還是消息,根據(jù)自己的業(yè)務(wù)場(chǎng)景去定!

          • 總結(jié)

          MQ 是一個(gè)跨進(jìn)程的消息隊(duì)列,我們可以很好的利用他進(jìn)行系統(tǒng)的解耦;

          引入MQ會(huì)給系統(tǒng)帶來一定的復(fù)雜度,需要評(píng)估!

          MQ 適合做異步任務(wù),不適合做同步任務(wù)!


          主站蜘蛛池模板: 精品视频一区二区三区四区| 天美传媒一区二区三区| 亚洲国产精品一区二区第一页| 人妻无码一区二区三区| 波多野结衣精品一区二区三区| 日本精品视频一区二区| 久久国产精品免费一区二区三区| 国产精品亚洲产品一区二区三区| 亚洲AV无码一区二区二三区入口| 国产精品视频一区二区猎奇| 风流老熟女一区二区三区| 精品aⅴ一区二区三区| 亚洲福利视频一区二区三区| 日韩三级一区二区三区| 亚洲国产精品乱码一区二区| 国产精品99精品一区二区三区 | 日本视频一区二区三区| 国产一区视频在线| 欧洲精品免费一区二区三区| 国产免费一区二区三区不卡| 日韩视频在线一区| 日本内射精品一区二区视频| 一区三区三区不卡| 日韩精品电影一区| 在线成人一区二区| 久久国产精品无码一区二区三区 | www亚洲精品少妇裸乳一区二区 | 日韩高清国产一区在线| 精品一区二区三区高清免费观看 | 美女免费视频一区二区| 久久精品成人一区二区三区| 久久精品免费一区二区| 99精品一区二区免费视频| 91在线视频一区| 99精品国产高清一区二区麻豆 | 无码国产精品一区二区高潮| 久久精品免费一区二区喷潮| 国产麻豆精品一区二区三区| 人妖在线精品一区二区三区| 丝袜美腿一区二区三区| 国产乱子伦一区二区三区|