標籤: 網頁設計公司

  • Nginx 的變量究竟是怎麼一回事?

    之前說了很多關於 Nginx 模塊的內容,還有一部分非常重要的內容,那就是 Nginx 的變量。變量在 Nginx 中可以說無處不在,認識了解這些變量的作用和原理同樣是必要的,下面幾乎囊括了關於 Nginx 的所有變量,單獨看起來可能比較枯燥,放心,後面依然有實戰內容。

    Nginx 變量的運行原理

    圍繞 Nginx 中的變量模塊可以分為兩類,一類是提供變量的模塊,另外一類是使用變量的模塊。

    • 提供變量的模塊
      • 在 Preconfiguration 源代碼中定義變量名以及可以解析出變量的方法
    • 使用變量的模塊
      • 解析 nginx.conf 時定義變量的使用方式

    也就是在 Nginx 啟動時,已經定義了變量,而只有當真正處理請求的時候,才會根據 nginx.conf 解析出來的變量使用方式調用 Preconfiguration 中定義的方法來實際獲取值。

    這也是變量的兩個特性:

    • 惰性求值:只有使用的時候才會去調方法解析
    • 變量值可以時刻變化,其值為使用的那一時刻的值。例如發送響應包體字節數,實際在發送的過程中是一直在變化的。

    除了 Nginx 的模塊之外,Nginx 框架也包含許多的變量,這些變量不需要通過編譯模塊來引入,而且,Nginx 框架所提供的變量往往反映了處理請求的細節,因此,了解 Nginx 框架所提供的變量是十分有必要的。

    HTTP 請求相關的變量

    先來看一下關於 HTTP 請求的相關變量。

    • arg_參數名:URL 中某個具體參數的值

    • query_string:與 args 變量完全相同

    • args:全部 URL 參數

    • is_args:如果請求 URL 中有參數則返回 ?,否則返回空

    • content_length:HTTP 請求中標識包體長度的 Content-Length 頭部的值。如果請求中沒有攜帶這個參數,那麼就取不到對應的值。

    • content_type:標識請求包體類型的 Content-Type 頭部的值。同樣需要用戶請求中攜帶對應的參數。

    • uri:請求的 URI(不同於 URL,不包括 ? 后的參數)

    • document_uri:與 uri 完全相同。由於歷史原因而存在的。

    • request_uri:請求的 URL(包括 URI 以及完整的參數)

    • scheme:協議名,例如 HTTP 或者 HTTPS

    • request_method:請求方法,例如 GET 或者 POST

    • request_length:所有請求內容的大小,包括請求行、頭部、包體等

    • remote_user:由 HTTP Basic Authentication 協議傳入的用戶名

    • request_body_file:很多時候會將用戶請求的包體存放到文件中,這個變量就是臨時存放請求包體的文件

      • 如果包體非常小則不會存文件
      • client_body_in_file_only 指令強制所有包體存入文件,且可決定是否刪除
    • request_body:請求中的包體,這個變量當且僅當使用反向代理,且設定用內存暫存包體時才有效

    • request:原始的 URL 請求,含有方法與協議版本,例如 GET /?a=1&b=22 HTTP/1.1

    • host

      • 先從請求行中獲取
      • 如果含有 Host 頭部,則用其值替換掉請求行中的主機名
      • 如果前兩者都取不到,則使用匹配上的 server_name
    • http_頭部名字:返回一個具體請求頭部的值

      特殊變量,這些變量會做一些處理。

      • http_host
      • http_user_agent
      • http_referer
      • http_via
      • http_x_forwarded_for
      • http_cookie

      通用變量,除了以上的變量,都可以取到對應的值。

    TCP 連接相關的變量

    下面是關於 TCP 連接的變量。

    • binary_remote_addr:客戶端地址的整形格式,對於 IPv4 是 4 字節,對於 IPv6 是 16 字節,所以在 limit_req 和 limit_conn 中通常可以用作 key (詳見:Nginx 處理 HTTP 請求的 11 個階段 中的 preaccess 階段)
    • connection:遞增的連接序號
    • connection_requests:當前連接上執行過的請求數,對 keepalive 連接有意義
    • remote_addr:客戶端地址
    • remote_port:客戶端端口
    • proxy_protocol_addr:若使用了 proxy_protocol 協議,則返回協議中的地址,否則返回空
    • proxy_protocol_port:若使用了 proxy_protocol 協議則返回協議中的端口,否則返回空
    • server_addr:服務端地址
    • server_port:服務器端端口
    • TCP_INFO:TCP 內核層參數,包括 $tcpinfo_rtt, ​$tcpinfo_rttvar,​$tcpinfo_snd_cwnd, $tcpinfo_rcv_space
    • server_protocol:服務器端協議,例如 HTTP/1.1

    Nginx 處理請求過程中產生的變量

    Nginx 處理 HTTP 請求的過程中也會產生很多變量。

    • request_time:請求處理到現在的耗時,單位為秒,精確到毫秒
    • server_name:匹配上請求的 server_name 值
    • https:如果開啟了 TLS/SSL 則返回 on,否則返回空
    • request_completion:若請求處理完則返回 OK,否則返回空
    • request_id:以 16 進制輸出的請求表示 id,該 id 共含有 16 個字節,是隨機生成的
    • request_filename:待訪問文件的完整路徑
    • document_root:由 URI 和 root、alias 規則生成的文件夾路徑
    • realpath_root:將 document_root 中的軟鏈接等換成真實路徑
    • limit_rate:返回客戶端響應時的速度上限,單位為每秒字節數。可以通過 set 指令修改對請求產生的效果

    發送 HTTP 響應時相關的變量

    • body_bytes_sent:響應中 body 包體的長度

    • bytes_sent:全部 http 響應的長度

    • status:http 響應中的返回碼

    • sent_trailer_名字:把響應結尾內容里的值返回

    • sent_http_頭部名字:響應中某個具體頭部的值

      特殊處理,下面這些變量需要經過特殊處理:

      • sent_http_content_type
      • sent_http_content_length
      • sent_http_location
      • sent_http_last_modified
      • sent_http_connection
      • sent_http_keep_alive
      • sent_http_transfer_encoding
      • sent_http_cache_control
      • sent_http_link

      通用:除了上面這些頭部,其他的頭部都是通用型的,也就是可以直接拿來用。

    Nginx 系統變量

    • time_local:以本地時間標準輸出的當前時間,例如 14/Nov/2018:15:55:37 +0800
    • time_iso8601:使用 ISO8601 標準輸出的當前時間,例如 2018-11-14T15:55:37+08:00
    • nginx_version:Nginx 版本號
    • pid:所屬 worker 進程的進程 id
    • pipe:使用了管道則返回 p,否則返回 .
    • hostname:所在服務器的主機名,與 hostname 命令輸出一致
    • msec:1970 年 1 月 1 日到現在的時間,單位為秒,小數點后精確到毫秒

    實戰

    配置文件:

    log_format  vartest  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status bytes_sent=$bytes_sent body_bytes_sent=$body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$sent_http_abc"';
    
    server {
    	server_name var.ziyang.com localhost;
    	#error_log logs/myerror.log debug;
    	access_log logs/vartest.log vartest;
    	listen 9090;
    	
    	location / {
    		set $limit_rate 10k;
            # return 200; tcpinfo: $tcpinfo_rtt,$tcpinfo_rttvar, $tcpinfo_snd_cwnd, $tcpinfo_rcv_space 
    		return 200 '
    arg_a: $arg_a,arg_b: $arg_b,args: $args
    connection: $connection,connection_requests: $connection_requests
    cookie_a: $cookie_a
    uri: $uri,document_uri: $document_uri, request_uri: $request_uri
    request: $request
    request_id: $request_id
    server: $server_addr,$server_name,$server_port,$server_protocol
                
    host: $host,server_name: $server_name,http_host: $http_host
    limit_rate: $limit_rate
    hostname: $hostname
    content_length: $content_length
    status: $status
    body_bytes_sent: $body_bytes_sent,bytes_sent: $bytes_sent
    time: $request_time,$msec,$time_iso8601,$time_local
    ';
    	}	
    }
    

    從上面這個配置文件中,我們可以看出來,返回的響應裡面包含了一系列的變量,實際驗證一下:

      test_nginx curl -H 'Content-Length: 0' -H 'Cookie: a=c1' 'localhost:9090?a=1&b=22'
    
    arg_a: 1,arg_b: 22,args: a=1&b=22
    connection: 2,connection_requests: 1
    cookie_a: c1
    uri: /,document_uri: /, request_uri: /?a=1&b=22
    request: GET /?a=1&b=22 HTTP/1.1
    request_id: 5d40b1ff29d2b87d5db5c4f95ebf5e4d
    server: 127.0.0.1,var.ziyang.com,9090,HTTP/1.1
    host: localhost,server_name: var.ziyang.com,http_host: localhost:9090
    limit_rate: 10240
    hostname: yuanzizhen.local
    content_length: 0
    status: 200
    body_bytes_sent: 0,bytes_sent: 0
    time: 0.000,1590842354.866,2020-05-30T20:39:14+08:00,30/May/2020:20:39:14 +0800
    

    大家可以對比一下響應和配置文件中的值是不是一一對應的,更加深刻的理解一下變量的含義。

    好了,這一節咱們學習了。關於 Nginx 的變量就講完了,下一節講一下實際應用變量的兩個模塊,大家會有更深刻的理解。

    本文首發於我的個人博客:iziyang.github.io,所有配置文件我已經放在了 Nginx 配置文件,大家可以自取。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • .Net Core微服務入門全紀錄(二)——Consul-服務註冊與發現(上)

    .Net Core微服務入門全紀錄(二)——Consul-服務註冊與發現(上)

    前言

    上一篇【.Net Core微服務入門全紀錄(一)——項目搭建】講到要做到服務的靈活伸縮,那麼需要有一種機制來實現它,這個機制就是服務註冊與發現。當然這也並不是必要的,如果你的服務實例很少,並且很穩定,那麼就沒有必要使用服務註冊與發現。

    服務註冊與發現

    • 服務註冊:簡單理解,就是有一個註冊中心,我們的每個服務實例啟動時,都去註冊中心註冊一下,告訴註冊中心我的地址,端口等信息。同樣的服務實例要刪除時,去註冊中心刪除一下,註冊中心負責維護這些服務實例的信息。
    • 服務發現:既然註冊中心維護了各個服務實例的信息,那麼客戶端通過註冊中心就很容易發現服務的變化了。

    有了服務註冊與發現,客戶端就不用再去配置各個服務實例的地址,改為從註冊中心統一獲取。
    那註冊中心又是怎麼保證每個地址的可用狀態呢,假如某個實例掛了怎麼辦呢?原則上掛掉的實例不應該被客戶端獲取到,所以就要提到:健康檢查 。

    • 健康檢查:每個服務都需要提供一個用於健康檢查的接口,該接口不具備業務功能。服務註冊時把這個接口的地址也告訴註冊中心,註冊中心會定時調用這個接口來檢測服務是否正常,如果不正常,則將它移除,這樣就保證了服務的可用性。

    常見註冊中心有 Consul、ZooKeeper、etcd、Eureka。

    Consul

    Consul官網:https://www.consul.io/
    Consul的主要功能有服務註冊與發現、健康檢查、K-V存儲、多數據中心等。

    • Consul安裝:很簡單,直接在官網下載解壓即可。
    • Consul運行:在consul.exe目錄下打開命令行執行 consul.exe agent -dev
    • 瀏覽器訪問:http://localhost:8500/

      Consul已成功運行。

    服務註冊

    • 首先Nuget安裝一下Consul:

      這個類庫里封裝了Consul的api操作,方便我們直接使用。當然自己去寫http調用Consul的接口也不是不行。。。接口說明:https://www.consul.io/api-docs

    • 改造一下訂單服務的代碼:

    ConsulHelper.cs:

        public static class ConsulHelper
        {
            /// <summary>
            /// 服務註冊到consul
            /// </summary>
            /// <param name="app"></param>
            /// <param name="lifetime"></param>
            public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app, IConfiguration configuration, IHostApplicationLifetime lifetime) 
            {
                var consulClient = new ConsulClient(c =>
                {
                    //consul地址
                    c.Address = new Uri(configuration["ConsulSetting:ConsulAddress"]);
                });
    
                var registration = new AgentServiceRegistration()
                {
                    ID = Guid.NewGuid().ToString(),//服務實例唯一標識
                    Name = configuration["ConsulSetting:ServiceName"],//服務名
                    Address = configuration["ConsulSetting:ServiceIP"], //服務IP
                    Port = int.Parse(configuration["ConsulSetting:ServicePort"]),//服務端口 因為要運行多個實例,端口不能在appsettings.json里配置,在docker容器運行時傳入
                    Check = new AgentServiceCheck()
                    {
                        DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服務啟動多久后註冊
                        Interval = TimeSpan.FromSeconds(10),//健康檢查時間間隔
                        HTTP = $"http://{configuration["ConsulSetting:ServiceIP"]}:{configuration["ConsulSetting:ServicePort"]}{configuration["ConsulSetting:ServiceHealthCheck"]}",//健康檢查地址
                        Timeout = TimeSpan.FromSeconds(5)//超時時間
                    }
                };
    
                //服務註冊
                consulClient.Agent.ServiceRegister(registration).Wait();
    
                //應用程序終止時,取消註冊
                lifetime.ApplicationStopping.Register(() =>
                {
                    consulClient.Agent.ServiceDeregister(registration.ID).Wait();
                });
    
                return app;
            }
        }
    

    appsettings.json:

    {
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft": "Warning",
          "Microsoft.Hosting.Lifetime": "Information"
        }
      },
      "AllowedHosts": "*",
      "ConsulSetting": {
        "ServiceName": "OrderService",
        "ServiceIP": "localhost",
        "ServiceHealthCheck": "/healthcheck",
        "ConsulAddress": "http://host.docker.internal:8500"//注意,docker容器內部無法使用localhost訪問宿主機器,如果是控制台啟動的話就用localhost
      }
    }
    

    Startup.cs:

        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllers();
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseRouting();
    
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
    
                //服務註冊
                app.RegisterConsul(Configuration, lifetime);
            }
        }
    

    OrdersController.cs:

        [Route("[controller]")]
        [ApiController]
        public class OrdersController : ControllerBase
        {
            private readonly ILogger<OrdersController> _logger;
            private readonly IConfiguration _configuration;
    
            public OrdersController(ILogger<OrdersController> logger, IConfiguration configuration)
            {
                _logger = logger;
                _configuration = configuration;
            }
    
            [HttpGet]
            public IActionResult Get()
            {
                string result = $"【訂單服務】{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}——" +
                    $"{Request.HttpContext.Connection.LocalIpAddress}:{_configuration["ConsulSetting:ServicePort"]}";
                return Ok(result);
            }
        }
    

    HealthCheckController.cs:

        [Route("[controller]")]
        [ApiController]
        public class HealthCheckController : ControllerBase
        {
            /// <summary>
            /// 健康檢查接口
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            public IActionResult Get()
            {
                return Ok();
            }
        }
    

    至此就完成了服務註冊,取消註冊,健康檢查等功能的代碼編寫。

    • 同樣的改造一下產品服務,代碼差不多一樣,就不貼了。

    運行服務

    繼續在docker中運行服務實例,不習慣docker的話用控制台啟動也行。–ConsulSetting:ServicePort參數就是傳入容器的端口信息。

    docker build -t orderapi:1.0 -f ./Order.API/Dockerfile .
    docker run -d -p 9060:80 --name orderservice orderapi:1.0 --ConsulSetting:ServicePort="9060"
    docker run -d -p 9061:80 --name orderservice1 orderapi:1.0 --ConsulSetting:ServicePort="9061"
    docker run -d -p 9062:80 --name orderservice2 orderapi:1.0 --ConsulSetting:ServicePort="9062"
    
    docker build -t productapi:1.0 -f ./Product.API/Dockerfile .
    docker run -d -p 9050:80 --name productservice productapi:1.0 --ConsulSetting:ServicePort="9050"
    docker run -d -p 9051:80 --name productservice1 productapi:1.0 --ConsulSetting:ServicePort="9051"
    docker run -d -p 9052:80 --name productservice2 productapi:1.0 --ConsulSetting:ServicePort="9052"
    

    至此,6個服務器實例都已運行,並且成功註冊到Consul。

    隨便停止2個服務:

    可以看到停止的服務已經在Consul中被移除。注意,這個是我們停止程序時主動調用Consul移除的。

    //應用程序終止時,取消註冊
    lifetime.ApplicationStopping.Register(() =>
    {
        consulClient.Agent.ServiceDeregister(registration.ID).Wait();
    });
    

    當然程序發生異常,健康檢查不能正確響應的話,Consul也會移除,有一點區別。

    那麼註冊,發現,健康檢查功能都完成了,下一步就該考慮客戶端如何拿到這些服務實例的地址了。

    代碼放在:https://github.com/xiajingren/NetCoreMicroserviceDemo

    未完待續…

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • Vue —— 精講 VueRouter(1)

    最近被Boos調去給新人做培訓去了,目前把自己整理的一些東西分享出來,希望對大家有所幫助

    demo源代碼地址:https://github.com/BM-laoli/BMlaoli-learn-VueRouter

    本章節為VueRouter前端 路由的章節部分

    大綱

    一、基本概念

    路由就是通過網絡把訊息從源地址傳輸到目的地的活動
    需要一些映射表

    1. 做路由
    2. 做信息的轉發(核心就是:轉發)

    後端路由還有前端路由,後端渲染和前端渲染

    前端渲染(前後端分離API生態),後端渲染(view嵌套一起)

    前端路由的核心概念
    地址變化的時候改變url的時候,不進行整體頁面刷新

    改變url但是不刷新頁面,的解決方式

    我們有這樣的一個需求,改變url跳轉地址,我們獲取新的頁面,但是不希望頁面發生刷新

    解決方案1:locaion.hash = ‘/’

    這個是vueRouter的底層實現

    監聽hash的變化,從而改變網頁數據的獲取機制,渲染對應的組件,

    解決方案2:H5的histroray模式

    1. pushState
      history.pushState({},”,’home’),第三個參數就是url

    這裏的push實際上就是一個棧結構(先進后出),

    假設我們這裏需要回去,使用back()彈棧

    history.pushState({},'','home'),
    history.pushState({},'','about'),
    history.pushState({},'','user'),
    
    //執行這個之後就能進行back()出棧了
    history.back(),
    // 此時的url是 /about
    
    
    1. repalceState

    這裡有一個方法和push方法很像,但是不會back()不能點擊後腿按鈕

    history.repalceState({},'','user'),
    
    1. go

    這裏的go是對棧的一個操作,
    go(-1)彈出一個
    go(-2)彈出二個

    go(1)壓入一個
    go(2)壓入二個

    go(-1)
    

    以上就是我們的基本的前端路由原理

    二、v-router基本使用

    前端三大框架都有自己的router,可以用來構建SPA應用

    使用小提示,還是非常非常的簡單的:

    1. 如果你沒有安裝就需要 npm install vue-router去安裝
      • 導入路由對象,並且調用Vue.use(VueRouter)安裝這個路由插件
      • 創建路由實例,傳入映射配置wxain
      • 在vue實例中掛載創建好了的路由

    1.導入路由對象,並且配置optionn給路由

    /router/index.js

    
    /**
     * 配置路由相關的信息
     */
    // 1. 導入
     import Router from 'vue-router'
     
     // 2.1 導入vue實例
    import Vue from 'vue'
    
    // 導入組件
    import Home from '../components/Home.vue'
    import About from '../components/About.vue'
    
    
    // 2.2使用路由(插件),安裝插件,vue的插件,都是這樣安裝,Vue.use
    Vue.use(Router)
    
    // 3. 創建路路由對象,這個就是在Router裏面配置映射和對象等東西
    
    // 4. 抽離配置項出來
    const routes = []
    
    const router = new Router({routes})
    
    //4. 導出
    export default router 
     
    
    

    2.配置路由映射

    /router/index.js

    const routes = [
     
     {path:'/home',component:Home},
     {path:'/about',component:About},
    
    ] 
    

    3.在實例中使用路由

    /main.js

    import Vue from 'vue'
    import App from './App'
    import router from './router'//注意啊模塊查找規則index.js
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,// 主要是在這裏掛載進去就好了
      render: h => h(App)
    }) 
    

    4.小心,我們的路由入口還有連接link

    /App.vue

    
    <template>
      <div id="app">
        <!-- //這兩個是一個全局祖冊過着個組件,這個就是一個a標籤 -->
        <router-link to='/home'>首頁</router-link>
        <router-link to='/about'>關於</router-link>
        <!-- 路由出口,既:渲染的出口,這個就是一個佔位符號 -->
        <router-view></router-view>
      </div>
    </template>
    
    

    以下是我們的兩個組件

    /Home.vue

    <template>
        <div>
            <h2>我是首頁</h2>
            <p>我是首頁內容哈哈哈</p>
        </div>
    </template>
    
    <script>
        export default {
            
        }
    </script>
    
    <style scoped>
    
    </style>
    

    /About.vue

    <template>
        <div>
            <h2>我是關於頁面</h2>
            <p>我是首關於內容哈哈哈</p>
        </div>
    </template>
    
    <script>
        export default {
            
        }
    </script>
    
    <style>
    
    </style>
    

    以上就是我們非常簡單的使用

    三、其它的知識點補充

    路由的默認值,並且修改成mode=>hisary模式

    我們希望默認显示的就是一個首頁
    解決方式,映射一個’/’,然後進行重定向
    /index.js

      {
        path:'/',
        redirect:'/home'
      },
    

    我們為什麼要去做這調整成一個history,因為我們希望去掉#這個標識

    只需要在new 的時候指定一下就好了
    /index,js

    const router = new Router({
      routes,
      mode:"history"//就是這裏的這個更改路由方式
    })
    

    router-link的屬性

    1. tage
      to是一個屬性 ,默認是渲染成一個a鏈接,假設我現在需要默認渲染成一個buttmm怎麼辦呢?
      加一個tag就好了
        <router-link to='/home' tag='button'  >首頁</router-link>
    
    1. 更改模式replceStats 不允許瀏覽器回退
      replace加上去就好了
    <router-link to='/about' tag='button' replace >關於</router-link>
    
    1. 我們可以利用一些默認的東西去非常方便的做到想要的效果
    <style>
    .router-link-active{
      color: blue;
    }
    </style>
    

    替換值:我們希望不要怎麼長,我們希望.active就能改樣式怎麼搞?
    加一個active-calss就好了,這個直接就是acitve做為類就好了

     <router-link to='/home' tag='button'  active-class  >首頁</router-link>
     <style>
        .active{
            bgc:red
        }
     </style>
    

    代碼路由跳轉,意思就是重定向

    注意啊!route != router
    在我們學習路由的時候,this.$router是一個非常重要的對象

    這個東西在開中經常的使用

    // this.$router.push('重定向的值就好了')。
    // this.$router.push('/home')
    // 如果你不想有出現回退按鈕,這樣來做就好了
    this.$router.replace('/home')
    

    四、動態路由參數

    這裏只是簡單的介紹了理由傳參的地址欄拼接模式,但是還有更多更奇奇怪怪的傳值方式,詳見官方Router文檔,

    this.$router.parmas
    // 這個parmas裏面就是我們的路由參數存放點
    

    這裏我們有這樣的一個需求,我們希望點擊user頁面的時候可以,得到任意的路由參數

    比如我們現在/user/zhnsang,的時候可以獲取zhangshang,/user/lishi的時候可以獲取lishi>

    1. 首先我們需要在路由裏面加:
      /router/index.js
       {
            path: "/user/:usermsg",
            component: User
        }
    ]
    
    1. 頁面傳遞數據
      /App.vue
    router-link :to="'/user/'+username">用戶相關</router-link>
    <!-- 路由出口,既:渲染的出口,這個就是一個佔位符號 -->
    <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App',
      data() {
        return {
          username: 'lisi'
        }
      },
    
    
    1. 頁面獲取數據

    一定要注意了,一定是rouer裏面定義的才能從另一路由拿出來

    /User.vue

    
    <template>
        <div>
            <h2>我是用戶相關專業</h2>
            <p>我是用戶訊息相關頁面,嘿嘿嘿嘿嘿</p>
            <h1>{{ $route.params.usermsg }}44</h1>
            <hr>
            <h2>{{username}}</h2>
        </div>
    </template>
    
    <script>
        export default {    
            computed: {
                username() {
                    return this.$route.params.usermsg
                }
            },
        }
    </script>
    
    <style scpoe>
    
    </style>
    

    五、細節詳解

    注意啊!再說一遍route != router

    注意啊,這裏的$route實際上是我們在main裏面new的一個Router得到的,
    並且 這個route對象是隨着請求的地方不一樣,而改變的。也就是說,這個的route是當前頁面中的route對象,而且在vue只能只有一個route實例存在

    六、 Vue的webpack打包詳解 + 路由懶加載

    一個vue項目的簡單打包目錄結構分析

    我們來看看,在一個vue項目中,簡單的三個文件是怎麼打包的

    假設目前有這樣的三個文件 ,我們需要對他們進行打包,mian是入口,有一個add業務,有一個math依賴模塊。那麼我們webpack打包成的三個文件到底是如何運行的呢?

    在vue中 使用webpack打包的時候,會把一些東西給分模塊的打包出來,它打包的東西的目錄結構如下
    裏面我們實際打包的時候會把css,js都給分開,各自有各自的作用

    | dist
    | ---static
    | ---css
    | ---js
    | -----app.XXXX.js         (這個是項目的業務邏輯所在)
    | -----manifest.xxxx.js    (這個是底層打包的依賴文件所在)
    | -----vendor.xxxx.js      (這個是依賴所在)
    | idnex.html
    

    路由懶加載

    1. 概念的理解

    目前呢,我們打包的情況是這樣的:我們所有的代碼都是集中放在了以一個app.xxx.js文件中,這樣其實不利於後期的維護和開發,因為如果我們有很多很多的大量的代碼的時候,我們的這個文件就會變得非常非常的大,於是呢,我們就需要路由懶加載,所謂懶加載就是:‘在需要的時候,才去加載某個資源文件’,路由懶加載,就是把每一個路由對應的業務邏輯代碼,在打包的時候分割到不同的js文件中,如何在需要用的時候再去請求它

    經過這樣的打包的懶加載之後,我們的目錄會變成這個樣子

    | dist
    | ---static
    | ---css
    | ---js
    | -----0.xxx.js            (假設是路由home的業務邏輯代碼)
    | -----1.xxx.js             (假設是路由about的業務邏輯代碼)
    | -----app.XXXX.js         (這個是項目的業務邏輯所在)
    | -----manifest.xxxx.js    (這個是底層打包的依賴文件所在)
    | -----vendor.xxxx.js      (這個是依賴所在)
    | idnex.html
    
    1. 如何使用

    使用非常的簡單,主要有如下的三種方式去使用,但是我最喜歡的還是最後一種方式
    /rouetr/index.js

    - 使用vue的異步組價和webpack的寫法,早期的時候
    const Home = resolve =>{ require.ensure(['../compenet/Home.vue'],()=>{
       resolve (require('../compenet/Home.vue'))
    })}
    
    - AMD規範的寫法
    const About = resolve =>{ require(['../compenent/About.vue'],resolve) }
    
    
    - ES6的結合異步組件的方式(最常用)
    const Home = () => import('../compenet/Home.vue')
    

    實際的使用
    /router/index.js

    /**
     * 配置路由相關的信息
     */
    // 1. 導入
    import Router from 'vue-router'
    
    // 2.1 導入vue實例
    import Vue from 'vue'
    
    // 導入組件
    // import Home from '../components/Home.vue'
    // import About from '../components/About.vue'
    // import User from '../components/User'
    const Home = () =>
        import ('../components/Home.vue')
    const About = () =>
        import ('../components/About.vue')
    const User = () =>
        import ('../components/User')
    
    
    // 2.2使用路由(插件),安裝插件,vue的插件,都是這樣安裝,Vue.use
    Vue.use(Router)
    
    // 3. 創建路路由對象,這個就是在Router裏面配置映射和對象等東西
    
    // 4. 抽離配置項出來
    const routes = [{
            path: '/',
            redirect: '/home'
        },
        {
            path: '/home',
            component: Home
        },
        {
            path: '/about',
            component: About
        },
        {
            path: "/user/:usermsg",
            component: User
        }
    ]
    
    const router = new Router({
        routes,
        mode: "history"
    })
    
    //4. 導出
    export default router
    
    //6. 去main裏面掛載
    

    七、 路由嵌套

    我們目前有這樣的一個需求:我們希望我們在hone下,可以/home/new去到home下的一個子組件,/home/message去到另一個子組件

    1. 首先 我們需要有組件
      /components/HomeMessage.vue
    <template>
        <div>
          <ul>
              <li1>我是消息1</li1>
              <li2>我是消息2</li2>
              <li3>我是消息3</li3>
              <li4>我是消息4</li4>
          </ul>
        </div>
    </template>
    
    <script>
        export default {
            name:"HomeMessage"
        }   
    </script>
    
    <style>
    
    </style>
    
    

    /components/HomeNews

    <template>
        <div>
        <ul>
            <li1>新1</li1>
            <li2>新2</li2>
            <li3>新3</li3>
            <li4>新4</li4>
            <li5>新5</li5>
        </ul>
        </div>
    </template>
    
    <script>
        export default {
            name:"HomeNews"
        }
    </script>
    
    <style>
    
    </style>
    
    1. 在路由裏面去配置
    const HomeNews = () =>
        import ('../components/HomeNews')
    const HomeMessage = () =>
        import ('../components/HomeNews')
    
    
    // 2.2使用路由(插件),安裝插件,vue的插件,都是這樣安裝,Vue.use
    Vue.use(Router)
    
    // 3. 創建路路由對象,這個就是在Router裏面配置映射和對象等東西
    
    // 4. 抽離配置項出來
    const routes = [{
            path: '/',
            redirect: '/home'
        },
        {
            path: '/home',
            component: Home,
            children: [{
                    path: '',
                    redirect: 'news'
                },
                {
                    path: 'news',// 這裏寫路由實際上應該是/home/news,這裏只是一個相對路由地址,
                    component: HomeNews
                },
                {
                    path: 'message',
                    component: HomeMessage
                },
    
            ]
        },
        {
    
    1. 打入口router-view(瞎起的名字實際上就是路由的佔位符)
      /Home.vue
    <template>
        <div>
            <h2>我是首頁</h2>
            <p>我是首頁內容哈哈哈</p>
         <router-link to="/home/news">news</router-link>
         <router-link to="/home/message">message</router-link>
        <router-view></router-view>
        </div>
    </template>
    
    <script>
        export default {
            
        }
    </script>
    
    <style scoped>
    
    </style>
    

    這裏如果是有關狀態的保持,我們需要使用key-alive,後面我們再做詳細的講解

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    ※教你寫出一流的銷售文案?

  • 小師妹學JavaIO之:用Selector來發好人卡

    小師妹學JavaIO之:用Selector來發好人卡

    目錄

    • 簡介
    • Selector介紹
    • 創建Selector
    • 註冊Selector到Channel中
    • SelectionKey
    • selector 和 SelectionKey
    • 總的例子
    • 總結

    簡介

    NIO有三寶:Buffer,Channel,Selector少不了。本文將會介紹NIO三件套中的最後一套Selector,並在理解Selector的基礎上,協助小師妹發一張好人卡。我們開始吧。

    Selector介紹

    小師妹:F師兄,最近我的桃花有點旺,好幾個師兄莫名其妙的跟我打招呼,可是我一心向著工作,不想談論這些事情。畢竟先有事業才有家嘛。我又不好直接拒絕,有沒有什麼比較隱晦的方法來讓他們放棄這個想法?

    更多內容請訪問www.flydean.com

    這個問題,我沉思了大約0.001秒,於是給出了答案:給他們發張好人卡吧,應該就不會再來糾纏你了。

    小師妹:F師兄,如果給他們發完好人卡還沒有用呢?

    那就只能切斷跟他們的聯繫了,來個一刀兩斷。哈哈。

    這樣吧,小師妹你最近不是在學NIO嗎?剛好我們可以用Selector來模擬一下發好人卡的過程。

    假如你的志偉師兄和子丹師兄想跟你建立聯繫,每個人都想跟你建立一個溝通通道,那麼你就需要創建兩個channel。

    兩個channel其實還好,如果有多個人都想同時跟你建立聯繫通道,那麼要維持這些通道就需要保持連接,從而浪費了資源。

    但是建立的這些連接並不是時時刻刻都有消息在傳輸,所以其實大多數時間這些建立聯繫的通道其實是浪費的。

    如果使用Selector就可以只啟用一個線程來監聽通道的消息變動,這就是Selector。

    從上面的圖可以看出,Selector監聽三個不同的channel,然後交給一個processor來處理,從而節約了資源。

    創建Selector

    先看下selector的定義:

    public abstract class Selector implements Closeable
    

    Selector是一個abstract類,並且實現了Closeable,表示Selector是可以被關閉的。

    雖然Selector是一個abstract類,但是可以通過open來簡單的創建:

    Selector selector = Selector.open();
    

    如果細看open的實現可以發現一個很有趣的現象:

    public static Selector open() throws IOException {
            return SelectorProvider.provider().openSelector();
        }
    

    open方法調用的是SelectorProvider中的openSelector方法。

    再看下provider的實現:

     public SelectorProvider run() {
       if (loadProviderFromProperty())
            return provider;
        if (loadProviderAsService())
            return provider;
          provider = sun.nio.ch.DefaultSelectorProvider.create();
          return provider;
        }
     });
    

    有三種情況可以加載一個SelectorProvider,如果系統屬性指定了java.nio.channels.spi.SelectorProvider,那麼從指定的屬性加載。

    如果沒有直接指定屬性,則從ServiceLoader來加載。

    最後如果都找不到的情況下,使用默認的DefaultSelectorProvider。

    關於ServiceLoader的用法,我們後面會有專門的文章來講述。這裏先不做多的解釋。

    註冊Selector到Channel中

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress("localhost", 9527));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    

    如果是在服務器端,我們需要先創建一個ServerSocketChannel,綁定Server的地址和端口,然後將Blocking設置為false。因為我們使用了Selector,它實際上是一個非阻塞的IO。

    注意FileChannels是不能使用Selector的,因為它是一個阻塞型IO。

    小師妹:F師兄,為啥FileChannel是阻塞型的呀?做成非阻塞型的不是更快?

    小師妹,我們使用FileChannel的目的是什麼?就是為了讀文件呀,讀取文件肯定是一直讀一直讀,沒有可能讀一會這個channel再讀另外一個channel吧,因為對於每個channel自己來講,在文件沒讀取完之前,都是繁忙狀態,沒有必要在channel中切換。

    最後我們將創建好的Selector註冊到channel中去。

    SelectionKey

    SelectionKey表示的是我們希望監聽到的事件。

    總的來說,有4種Event:

    • SelectionKey.OP_READ 表示服務器準備好,可以從channel中讀取數據。
    • SelectionKey.OP_WRITE 表示服務器準備好,可以向channel中寫入數據。
    • SelectionKey.OP_CONNECT 表示客戶端嘗試去連接服務端
    • SelectionKey.OP_ACCEPT 表示服務器accept一個客戶端的請求
    public static final int OP_READ = 1 << 0;
    public static final int OP_WRITE = 1 << 2;
    public static final int OP_CONNECT = 1 << 3;
    public static final int OP_ACCEPT = 1 << 4;
    

    我們可以看到上面的4個Event是用位運算來定義的,如果將這個四個event使用或運算合併起來,就得到了SelectionKey中的interestOps。

    和interestOps類似,SelectionKey還有一個readyOps。

    一個表示感興趣的操作,一個表示ready的操作。

    最後,SelectionKey在註冊的時候,還可以attach一個Object,比如我們可以在這個對象中保存這個channel的id:

    SelectionKey key = channel.register(
      selector, SelectionKey.OP_ACCEPT, object);
    key.attach(Object);
    Object object = key.attachment();
    

    object可以在register的時候傳入,也可以調用attach方法。

    最後,我們可以通過key的attachment方法,獲得該對象。

    selector 和 SelectionKey

    我們通過selector.select()這個一個blocking操作,來獲取一個ready的channel。

    然後我們通過調用selector.selectedKeys()來獲取到SelectionKey對象。

    在SelectionKey對象中,我們通過判斷ready的event來處理相應的消息。

    總的例子

    接下來,我們把之前將的串聯起來,先建立一個小師妹的ChatServer:

    public class ChatServer {
    
        private static String BYE_BYE="再見";
    
        public static void main(String[] args) throws IOException, InterruptedException {
            Selector selector = Selector.open();
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress("localhost", 9527));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            ByteBuffer byteBuffer = ByteBuffer.allocate(512);
    
            while (true) {
                selector.select();
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> iter = selectedKeys.iterator();
                while (iter.hasNext()) {
                    SelectionKey selectionKey = iter.next();
                    if (selectionKey.isAcceptable()) {
                        register(selector, serverSocketChannel);
                    }
                    if (selectionKey.isReadable()) {
                        serverResonse(byteBuffer, selectionKey);
                    }
                    iter.remove();
                }
                Thread.sleep(1000);
            }
        }
    
        private static void serverResonse(ByteBuffer byteBuffer, SelectionKey selectionKey)
                throws IOException {
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            socketChannel.read(byteBuffer);
            byteBuffer.flip();
            byte[] bytes= new byte[byteBuffer.limit()];
            byteBuffer.get(bytes);
            log.info(new String(bytes).trim());
            if(new String(bytes).trim().equals(BYE_BYE)){
                log.info("說再見不如不見!");
                socketChannel.write(ByteBuffer.wrap("再見".getBytes()));
                socketChannel.close();
            }else {
                socketChannel.write(ByteBuffer.wrap("你是個好人".getBytes()));
            }
            byteBuffer.clear();
        }
    
        private static void register(Selector selector, ServerSocketChannel serverSocketChannel)
                throws IOException {
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
        }
    }
    

    上面例子有兩點需要注意,我們在循環遍歷中,當selectionKey.isAcceptable時,表示服務器收到了一個新的客戶端連接,這個時候我們需要調用register方法,再註冊一個OP_READ事件到這個新的SocketChannel中,然後繼續遍歷。

    第二,我們定義了一個stop word,當收到這個stop word的時候,會直接關閉這個client channel。

    再看看客戶端的代碼:

    public class ChatClient {
    
        private static SocketChannel socketChannel;
        private static ByteBuffer byteBuffer;
    
        public static void main(String[] args) throws IOException {
    
            ChatClient chatClient = new ChatClient();
            String response = chatClient.sendMessage("hello 小師妹!");
            log.info("response is {}", response);
            response = chatClient.sendMessage("能不能?");
            log.info("response is {}", response);
            chatClient.stop();
    
        }
    
        public void stop() throws IOException {
            socketChannel.close();
            byteBuffer = null;
        }
    
        public ChatClient() throws IOException {
            socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9527));
            byteBuffer = ByteBuffer.allocate(512);
        }
    
        public String sendMessage(String msg) throws IOException {
            byteBuffer = ByteBuffer.wrap(msg.getBytes());
            String response = null;
            socketChannel.write(byteBuffer);
            byteBuffer.clear();
            socketChannel.read(byteBuffer);
            byteBuffer.flip();
            byte[] bytes= new byte[byteBuffer.limit()];
            byteBuffer.get(bytes);
            response =new String(bytes).trim();
            byteBuffer.clear();
            return response;
    
        }
    }
    

    客戶端代碼沒什麼特別的,需要注意的是Buffer的讀取。

    最後輸出結果:

    server收到: INFO com.flydean.ChatServer - hello 小師妹!
    client收到: INFO com.flydean.ChatClient - response is 你是個好人
    server收到: INFO com.flydean.ChatServer - 能不能?
    client收到: INFO com.flydean.ChatClient - response is 再見
    

    解釋一下整個流程:志偉跟小師妹建立了一個連接,志偉向小師妹打了一個招呼,小師妹給志偉發了一張好人卡。志偉不死心,想繼續糾纏,小師妹回復再見,然後自己關閉了通道。

    總結

    本文介紹了Selector和channel在發好人卡的過程中的作用。

    • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
    • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
    • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
    • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

    本文作者:flydean程序那些事

    本文鏈接:http://www.flydean.com/java-io-nio-selector/

    本文來源:flydean的博客

    歡迎關注我的公眾號:程序那些事,更多精彩等着您!

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 手把手教你學Numpy,這些api不容錯過

    手把手教你學Numpy,這些api不容錯過

    本文始發於個人公眾號:TechFlow,原創不易,求個關注

    今天是Numpy專題的第5篇文章,我們來繼續學習Numpy當中一些常用的數學和統計函數。

    基本統計方法

    在日常的工作當中,我們經常需要通過一系列值來了解特徵的分佈情況。比較常用的有均值、方差、標準差、百分位數等等。前面幾個都比較好理解,簡單介紹一下這個百分位數,它是指將元素從小到大排列之後,排在第x%位上的值。我們一般常用的是25%,50%和75%這三個值,通過這幾個值,我們很容易對於整個特徵的分佈有一個大概的了解。

    前面三個指標:均值、方差、標準差都很好理解,我們直接看代碼就行。

    median和percentile分別是求中位數與百分位數,它們不是Numpy當中array的函數,而是numpy的庫函數。所以我們需要把array當做參數傳入。percentile這個函數還需要額外傳入一個int,表示我們想要得到的百分位數,比如我們想要知道50%位置上的數,則輸入50。

    除了這些之外,我們還會經常用到sum,min,max,argmin,argmax這幾個函數。sum,min,max很好理解,argmin和argmax的意思是獲取最小值和最大值的索引

    這裏返回的索引有點奇怪,和我們想的不同,居然不是一個二維的索引而是一維的。實際上numpy的內部會將高維數組轉化成一維之後再進行這個操作,我們可以reshape一下數組來進行驗證:

    這些只是api的基本用法,numpy當中支持的功能不僅如此。我們觀察一下這些函數會發現,它們的作用域都是一組數據,返回的是一組數據通過某種運算得到的結果。舉個例子,比如sum,是對一組數據的價格。std計算的是一組數據的標準差,這樣的函數我們稱為聚合函數

    numpy當中的聚合函數在使用的時候允許傳入軸這個參數,限制它聚合的範圍。我們通過axis這個參數來控制,axis=0表示對列聚合,axis=1表示對行聚合。我們死記的話總是會搞混淆,實際上axis傳入的也是一個索引,表示第幾個索引的索引。我們的二維數組的shape是[行, 列],其中的第0位是行,第1位是列,可以認為axis是這個索引向量的一個索引。

    我們可以來驗證一下:

    可以看到axis=0和axis=1返回的向量的長度是不同的,因為以列為單位聚合只有4列,所以得到的是一個1 x 4的結果。而以行為單位聚合有5行,所以是一個1 x 5的向量。

    除了上面介紹的這些函數之外,還有cumsum和cumprod這兩個api。其中cumsum是用來對數組進行累加運算,而cumprod是進行的累乘運算。只是在實際工作當中,很少用到,我就不展開細講了,感興趣的同學可以查閱api文檔了解一下。

    bool數組的方法

    我們之前在Python的入門文章當中曾經提到過,在Python中True和False完全等價於1和0。那麼在上面這些計算的方法當中,如果存在bool類型的值,都會被轉化成1和0進行的計算。

    我們靈活運用這點會非常方便,舉個例子,假設我們要統計一批數據當中有多少條大於0。我們利用sum會非常方便:

    bool數組除了可以應用上面這些基本的運算api之外,還有專門的兩個api,也非常方便。一個叫做any,一個叫做all。any的意思是只要數組當中有一個是True,那麼結果就是True。可以認為是Is there any True in the array的意思,同樣,all就是說只有數組當中都是True,結果才是True。對應的英文自然是Are the values in the array all True。

    這個只要理解了,基本上很難忘記。

    排序

    Python原生的數組可以排序,numpy當中的數組自然也不例外。我們只需要調用sort方法就可以排序了,不過有一點需要注意,numpy中的sort默認是一個inplace的方法。也就是說我們調用完了sort之後,原數組的值就自動變化了。

    如果寫成了arr = arr.sort()會得到一個None,千萬要注意。

    同樣,我們也可以通過傳入軸這個參數來控制它的排序範圍,可以做到對每一列排序或者是對每一行排序,我們來看個例子:

    這個是對列排序,如果傳入0則是對行排序,這個應該不難理解。

    集合api

    numpy當中還提供了一些面向集合的api,相比於針對各種計算的api,這些方法用到的情況比較少。常用的一般只有unique和in1d

    unique顧名思義就是去重的api,可以返回一維array去重且排序之後的結果。我們來看個例子:

    它等價於:

    set(sorted(arr))
    

    in1d是用來判斷集合內的元素是否在另外一個集合當中,函數會返回一個bool型的數組。我們也可以來看個例子:

    除了這兩個api之外,還有像是計算並集並排序的union1d,計算差集的setdiff1d,計算兩個集合交集並排序的intersect1d等等。這些api的使用頻率實在是不高,所以就不贅述了。用到的時候再去查閱即可。

    總結

    今天我們聊了numpy當中很多常用的計算api,這些api在我們日常做機器學習和數據分析的時候經常用到。比如分析特徵分佈的時候,如果數據量很大是不適合作圖或者是可視化觀察的。這個時候可以從中位數、均值、方差和幾個關鍵百分位點入手,再比如在我們使用softmax多分類的時候,也會用到argmax來獲取分類的結果。

    總之,今天的內容非常關鍵,在numpy整體的應用當中佔比很高,希望大家都能熟悉它們的基本用法。這樣即使以後忘記,用到的時候再查閱也還來得及。

    今天的文章就是這些,如果喜歡本文,可以的話請點個關注,給我一點鼓勵,也方便獲取更多文章。

    本文使用 mdnice 排版

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※教你寫出一流的銷售文案?

  • Redis的持久化設計

    Redis的持久化設計

    Redis 持久化設計

    持久化的功能:Redis是內存數據庫,數據都是存儲在內存中的,為了避免進程退出導致數據的永久丟失,要定期將Redis中的數據以某種形式從內存保存到硬盤,當下次Reids重啟時,利用持久化文件實現數據恢復。

    RDB:將當前數據保存到硬盤

    AOF:將每次執行的寫命令保存到硬盤(類似MySQL的binlog)

    1. RDB持久化

    RDB持久化是將當前進程中的數據生成快照保存到硬盤(因此也稱作快照持久化),保存的文件後綴是rdb;當Redis重新啟動時,可以讀取快照文件恢複數據。

    1. 觸發條件

      • 手動觸發 save 命令和bgsave命令都可以生成RDB文件, save命令會阻塞Redis服務進程,知道RDB文件創建完畢,bgsave命令則是創建一個子進程,由子進程來負責創建RDB文件,父進程繼續處理請求,bgsave命令執行過程中,只有fork子進程時會阻塞服務器,而對於save命令,整個過程都會阻塞服務器,因此save已基本被廢棄,線上環境要杜絕save的使用;後文中也將只介紹bgsave命令。此外,在自動觸發RDB持久化時,Redis也會選擇bgsave而不是save來進行持久化

      SAVE 執行期間,AOF 寫入可以在後台線程進行,BGREWRITEAOF 可以在子進程進行,所以這三種操作可以同時進行 ,為了避免性能問題,BGSAVE 和 BGREWRITEAOF 不能同時執行

    2. 自動觸發

    save m n

    在配置文件中通過 save m n 命令,指定當前m秒內發生n次變化時,觸發bgsave。

    ​ 其中save 900 1的含義是:當時間到900秒時,如果redis數據發生了至少1次變化,則執行bgsave;save 300 10和save 60 10000同理。當三個save條件滿足任意一個時,都會引起bgsave的調用.

    Redis的save m n,是通過serverCron函數、dirty計數器、和lastsave時間戳來實現的-

    • serverCron函數,是Redis服務器的周期性操作函數,默認每隔100ms執行一次,該函數對服務器的狀態進行維護,其中一項工作就是檢測save m n 配置是否滿足條件,如果滿足就執行bgsave.
    • dirty計數器 記錄服務器進行了多少起操作,修改,不是客戶端執行了多少修改數據的命令
    • lastsave時間戳也是Reids服務器維持的一個狀態,記錄上一次成功執行bgsave的時間

    save m n的原理如下:每隔100ms,執行serverCron函數;在serverCron函數中,遍歷save m n配置的保存條件,只要有一個條件滿足,就進行bgsave。對於每一個save m n條件,只有下面兩條同時滿足時才算滿足:

    • 當前時間-lastsave > m

    • dirty >= n

    在主從複製場景下,如果從節點執行全量複製操作,則主節點會執行bgsave命令,並將rdb文件發送給從節點。

    在執行shutdown命令時,自動執行rdb持久化

    1.2 RDB文件

    設置存儲路徑

    - 配置文件:dir配置指定目錄,dbfilename指定文件名。默認是Redis根目錄下的dump.rdb文件
    - 動態設置: 
    

    config set dir {newdir} /// config set dbfilename {newFileName}

    RDB文件 是經過壓縮的二進制文件,默認採用LZF算法對RDB文件進行壓縮,雖然壓縮耗時,但是可以大大減小文件體積,默認是開啟的,可以通過命令關閉:

    config set rdbcompression no

    RDB文件的壓縮並不是針對整個文件進行的,而是對數據庫中的字符串進行的,且只有在字符串達到一定長度(20字節)時才會進行

    格式:

    字段說明:

    1. REDIS常量,保存‘REDIS’5個字符

    2. db_version RDB文件的版本號

    3. SELECTDB 表示一個完整的數據庫(0號數據庫),同理SELECTDB 3 pairs表示完整的3號數據庫;只有當數據庫中有鍵值對時,RDB文件中才會有該數據庫的信息(上圖所示的Redis中只有0號和3號數據庫有鍵值對);如果Redis中所有的數據庫都沒有鍵值對,則這一部分直接省略。其中:SELECTDB是一個常量,代表後面跟着的是數據庫號碼;0和3是數據庫號碼;

    4. KEY-VALUE-PAIRS: pairs則存儲了具體的鍵值對信息,包括key、value值,及其數據類型、內部編碼、過期時間、壓縮信息等等

    1. EOF 標志著數據庫內容的結尾(不是文件的結尾),值為 rdb.h/EDIS_RDB_OPCODE_EOF (255)

    2. CHECK-SUM RDB 文件所有內容的校驗和,一個 uint_64t 類型值, REDIS 在寫入 RDB 文件時將校驗和保存在 RDB 文件的末尾,當讀取時,根據它的值對內容進行校驗

    。如果這個域的值為 0 ,那麼表示 Redis 關閉了校驗和功能。

    1.3 啟動時加載

    ​ RDB文件的載入工作是在服務器啟動時自動執行的,並沒有專門的命令。但是由於AOF的優先級更高,因此當AOF開啟時,Redis會優先載入AOF文件來恢複數據;只有當AOF關閉時,才會在Redis服務器啟動時檢測RDB文件,並自動載入。服務器載入RDB文件期間處於阻塞狀態,直到載入完成為止

    2. AOF持久化

    AOF(Append Only File) 則以協議文本的方式,將所有對數據庫進行過寫入的命令(及其參數)記錄到 AOF
    文件,以此達到記錄數據庫狀態的目的

    2.1 開啟AOF

    Redis服務器默認開啟RDB,關閉AOF;要開啟AOF,需要在配置文件中配置:

    appendonly yes

    2.2 執行流程

    2.2.1 命令寫入緩衝區

    //緩衝區的定義 是一個SDS, 可以兼容C語言的字符串
    struct redisServer {
        // AOF緩衝區, 在進入事件loop之前寫入
        sds aof_buf;
    };
    
    1. 命令傳播: Redis將執行完的命令、命令的參數、命令的參數個數等信息發送到 AOF 程序中

    2. 緩存追加: AOF程序根據接收到的命令命令數據,將命令轉換為網絡通訊協議的格式,然後將協議內容追加到服務器的 AOF 緩存中。

      • 將命令以文本協議格式保存在緩存中
      • 為什麼使用文本協議格式?兼容性,避免二次開銷,可讀性
      • 為什麼寫入緩存?這樣不會受制於磁盤的IO性能,避免每次有寫命令都直接寫入硬盤,導致硬盤IO成為Redis負載的瓶頸
    3. 文件寫入和保存:AOF 緩存中的內容被寫入到 AOF 文件末尾,如果設定的 AOF 保存
      條件被滿足的話,fsync 函數或者 fdatasync 函數會被調用,將寫入的內容真正地保存到磁盤中。

      為了提高文件寫入效率,在現代操作系統中,當用戶調用write函數將數據寫入文件時,操作系統通常會將數據暫存到一個內存緩衝區里,當緩衝區被填滿或超過了指定時限后,才真正將緩衝區的數據寫入到硬盤裡。這樣的操作雖然提高了效率,但也帶來了安全問題:如果計算機停機,內存緩衝區中的數據會丟失;因此系統同時提供了fsync、fdatasync等同步函數,可以強制操作系統立刻將緩衝區中的數據寫入到硬盤裡,從而確保數據的安全性。

      AOF保存模式:

      • AOF_FSYNC_ALWAYS: 命令寫入aof-buf后立即調用系統的fsync操作同步到AOF文件。因為 SAVE 是由 Redis 主進程執行的,所以在 SAVE 執行期間,主進程會被阻塞,不能接受命令請求。這種情況下,每次有寫命令都要同步到AOF文件,硬盤IO成為性能瓶頸,Redis只能支持大約幾百TPS寫入,嚴重降低了Redis的性能;即便是使用固態硬盤(SSD),每秒大約也只能處理幾萬個命令,而且會大大降低SSD的壽命。
      • AOF_FSYNC_NO: 命令寫入aof_buf后調用系統write操作,不對AOF文件做fsync同步;同步由操作系統負責,通常同步周期為30秒。這種情況下,文件同步的時間不可控,且緩衝區中堆積的數據會很多,數據安全性無法保證。
      • AOF_FSYNC_EVERYSEC: 每一秒鐘保存一次,命令寫入aof_buf后調用系統write操作, write完成后線程返回, fsync同步文件操作由專門線程每秒調用一次

    2.2.2. 文件重寫

    隨着命令不斷寫入AOF,文件會越來越大,為了解決這個問題,Redis引入AOF重寫機制壓縮文件體積,AOF文件重寫是把Redis進程內的數據轉化為寫命令同步到新AOF文件的過程。

    重寫后的AOF文件為什麼可以變小?

    1. 進程內已經超時的數據不再寫入文件
    2. 舊的AOF文件含有無效命令 ,如有些數據被重複設值(set mykey v1, set mykey v2)、有些數據被刪除了(sadd myset v1, del myset)等等, 新的AOF文件只保留最終的數據寫入命令
    3. 多條寫入命令可以合併為一個,如:lpush list a、lpush list b可以轉化為:lpush list a b。為了防止單條命令過大造成客戶端緩衝區溢出,對於list、set、hash等類型操作,以64個元素為邊界拆分為多條

    AOF重寫可以手動觸發也可以自動觸發:

    • 手動觸發: 直接調用bgrewriteaof命令
    • 自動觸發:根據auto-aof-rewrite-min-size和auto-aof-rewrite-percentage參數確定自動觸發時機。
      • auto-aof-rewrite-min-size:表示運行AOF重寫時文件最小體積,默認為64MB
      • auto-aof-rewrite-percentage:代表當前AOF文件空間(aof_current_size)和上一次重寫后AOF文件空間(aof_base_size)的比值

    流程說明:

    1)執行AOF重寫請求。

    如果當前進程正在執行AOF重寫,請求不執行。

    如果當前進程正在執行bgsave操作,重寫命令延遲到bgsave完成之後再執行。

    2)父進程執行fork創建子進程,開銷等同於bgsave過程。

    3.1)主進程fork操作完成后,繼續響應其它命令。

      所有修改命令依然寫入AOF文件緩衝區並根據appendfsync策略同步到磁盤,保證原有AOF機制正確性。

    3.2)由於fork操作運用寫時複製技術,子進程只能共享fork操作時的內存數據

      由於父進程依然響應命令,Redis使用“AOF”重寫緩衝區保存這部分新數據,防止新的AOF文件生成期間丟失這部分數據。

    4)子進程依據內存快照,按照命令合併規則寫入到新的AOF文件。

      每次批量寫入硬盤數據量由配置aof-rewrite-incremental-fsync控制,默認為32MB,防止單次刷盤數據過多造成硬盤阻塞。

    5.1)新AOF文件寫入完成后,子進程發送信號給父進程,父進程調用一個信號處理函數,並執行以前操作更新統計信息。

    5.2)父進程把AOF重寫緩衝區的數據寫入到新的AOF文件。這時新 AOF 文件所保存的數據庫狀態將和服務器當前的數據庫狀態一致。

    5.3)對新的AOF文件進行改名,原子地(atomic)覆蓋現有的AOF文件,完成新舊文件的替換。

    在整個 AOF 後台重寫過程中,只有信號處理函數執行時會對服務器進程(父進程)造成阻塞,其他時候,AOF 後台重寫都不會阻塞父進程,這將 AOF 重寫對服務器性能造成的影響降到了最低

    參考《Redis-設計與實現:AOF-持久化》

    2.2.3 重啟加載

    流程說明:

    1)AOF持久化開啟且存在AOF文件時,優先加載AOF文件。

    2)AOF關閉或者AOF文件不存在時,加載RDB文件。

    3)加載AOF/RDB文件成功后,Redis啟動成功。

    4)AOF/RDB文件存在錯誤時,Redis啟動失敗並打印錯誤信息。

    數據還原的詳細步驟:

    1. 創建一個不帶網絡連接的偽客戶端(fake client): 因為 Redis 的命令只能在客戶端上下文中執行,而載入 AOF 文件時所使用的命令直接來源於 AOF 文件而不是網絡連接,所以服務器使用了一個沒有網絡連接的偽客戶端來執行 AOF 文件保存的寫命令,偽客戶端執行命令的效果和帶網絡連接的客戶端執行命令的效果完全一樣。
    2. 從AOF文件中分析並讀取出一條寫命令,使用偽客戶端執行被讀出的寫命令,重複此操作,直到AOF文件中的所有寫命令都被處理完畢為止。

    2.2.4 文件校驗

    加載損壞的AOF文件會拒絕啟動,並打印錯誤信息。

    注意:對於錯誤格式的AOF文件,先進性備份,然後採用redis-check-aof --fix命令進行修復,修復后使用diff -u對比數據差異,找到丟失的數據,有些可以進行人工補全。

    AOF文件可能存在結尾不完整的情況,比如機器突然掉電導致AOF尾部文件命令寫入不全。

    Redis為我們提高了aof-load-truncated配置來兼容這種情況,默認開啟

    3. 了解MySQL中的binlog

    mysql binlog應用場景與原理深度剖析

    參考博文與書籍:

    1. 《redis設計與實現》
    2. Redis持久化
    3. [徐劉根-Redis實戰和核心原理詳解(8)使用快照RDB和AOF將Redis數據持久化到硬盤中](https://blog.csdn.net/xlgen157387/article/details/61925524

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※推薦評價好的iphone維修中心

  • 一文入門Kafka,必知必會的概念通通搞定

    一文入門Kafka,必知必會的概念通通搞定

    Kakfa在大數據消息引擎領域,絕對是沒有爭議的國民老公。

    這是kafka系列的第一篇文章。預計共出20篇系列文章,全部原創,從0到1,跟你一起死磕kafka。

    本文盤點了 Kafka 的各種術語並且進行解讀,術語可能比較枯燥,但真的是精髓中的精髓!

    了解Kafka之前我們必須先掌握它的相關概念和術語,這對於後面深入學習 Kafka 各種功能將大有裨益。所以,枯燥你也得給我看完!

    大概是有這麼些東西要掌握,不多不多,預計20分鐘可以吃透:

    主題層

    主題層有三個兒子,分別叫做:Topic、Partition、Replica。既然我說是三個兒子,那你懂了,是不可分割的整體。

    Topic(主題)

    Kafka 是分佈式的消息引擎系統,它的主要功能是提供一套完備的消息(Message)發布與訂閱解決方案。

    在 Kafka 中,發布訂閱的對象是主題(Topic),你可以為每個業務、每個應用甚至是每類數據都創建專屬的主題。

    一個Topic是對一組消息的歸納。也可以理解成傳統數據庫里的表,或者文件系統里的一個目錄。

    Partition(分區)

    一個Topic通常都是由多個partition組成的,創建topic時候可以指定partition數量。

    分區優勢

    為什麼需要將Topic分區呢?如果你了解其他分佈式系統,你可能聽說過分片、分區域等說法,比如 MongoDB 和 Elasticsearch 中的 Sharding、HBase 中的 Region,其實它們都是相同的原理。

    試想,如果一個Topic積累了太多的數據以至於單台 Broker 機器都無法容納了,此時應該怎麼辦呢?

    一個很自然的想法就是,能否把數據分割成多份保存在不同的機器上?這不就是分區的作用嗎?其實就是解決伸縮性的問題,每個partition都可以放在獨立的服務器上。

    當然優勢不僅於此,也可以提高吞吐量。kafka只允許單個partition的數據被一個consumer線程消費。因此,在consumer端,consumer并行度完全依賴於被消費的分區數量。綜上所述,通常情況下,在一個Kafka集群中,partition的數量越多,意味着可以到達的吞吐量越大。

    partition結構

    每個partition對應於一個文件夾,該文件夾下存儲該partition的數據和索引文件。

    如圖所示,可以看到兩個文件夾,都對應着一個叫做asd的topic,在該台服務器上有兩個分區,0和2,那麼1呢?在其他服務器上啦!畢竟是分佈式分佈的!

    我們進去asd-0目錄中看看是什麼?有後綴為.index和.log的文件,他們就是該partition的數據和索引文件:

    現在先不管它們是何方神聖,因為我會在【分區機制原理】這篇文章中詳細描述。

    partition順序性

    現在,我需要你睜大眼睛看看關於分區非常重要的一點:

    【每個partition內部保證消息的順序。但是分區之間是不保證順序的】

    這一點很重要,例如kafka中的消息是某個業務庫的數據,mysql binlog是有先後順序的,10:01分我沒有付款,所以pay_date為null,而10:02分我付款了,pay_date被更新了。

    但到了kafka那,由於是分佈式的,多分區的,可就不一定能保證順序了,也許10:02分那條先來,這樣可就會引發嚴重生產問題了。因此,一般我們需要按表+主鍵來分區。保證同一主鍵的數據發送到同一個分區中。

    如果你想要 kafka 中的所有數據都按照時間的先後順序進行存儲,那麼可以設置分區數為 1。

    Replica (副本)

    每個partition可以配置若干個副本。Kafka 定義了兩類副本:領導者副本(Leader Replica)和追隨者副本(Follower Replica)。只能有 1 個領導者副本和 N-1 個追隨者副本。

    為啥要用副本?也很好理解,反問下自己為什麼重要的文件需要備份多份呢?備份機制(Replication)是實現高可用的一個手段。

    需要注意的是:僅Leader Replica對外提供服務,與客戶端程序進行交互,生產者總是向領導者副本寫消息,而消費者總是從領導者副本讀消息。而Follower Replica不能與外界進行交互,它只做一件事:向領導者副本發送請求,請求領導者把最新生產的消息發給它,保持與領導者的同步。

    如果對於剛剛所說的主題、分區、副本還有疑惑,那麼結合下面這張圖再思考一下,我相信你就可以玩轉它了:

    下圖所示,TopicA,具有三個partition,每個partion都有1 個leader副本和 1 個follower者副本。為了保證高可用性,一台機器宕機不會有影響,因此leader副本和follower副本必然分佈在不同的機器上。

    消息層

    Kafka的官方定義是message system,由此我們可以知道Kafka 中最基本的數據單元無疑是消息message,它可理解成數據庫里的一條行或者一條記錄。消息是由字符數組組成。關於消息你必須知道這幾件事:

    消息key

    發送消息的時候指定 key,這個 key 也是個字符數組。key 用來確定消息寫入分區時,進入哪一個分區。你可以用有明確業務含義的字段作為key,比如用戶號,這樣就可以保證同一個用戶號進入同一個分區。

    批量寫入

    為了提高效率, Kafka 以批量batch的方式寫入。

    一個 batch 就是一組消息的集合, 這一組的數據都會進入同一個 topic 和 partition(這個是根據 producer 的配置來定的) 。

    每一個消息都進行一次網絡傳輸會很消耗性能,因此把消息收集到一起再同時處理就高效的多。

    當然,這樣會引入更高的延遲以及吞吐量:batch 越大,同一時間處理的消息就越多。batch 通常都會進行壓縮,這樣在傳輸以及存儲的時候效率都更高一些。

    位移
    生產者向分區寫入消息,每條消息在分區中的位置信息由一個叫位移(Offset)的數據來表徵。分區位移總是從 0 開始,假設一個生產者向一個空分區寫入了 10 條消息,那麼這 10 條消息的位移依次是 0、1、2、…、9。

    服務端

    Kafka 的服務器端由被稱為 Broker 的服務進程構成,即一個 Kafka 集群由多個 Broker 組成,Kafka支持水平擴展,broker數量越多,集群吞吐量越高。在集群中每個broker都有一個唯一brokerid,不得重複。Broker 負責接收和處理客戶端發送過來的請求,以及對消息進行持久化。

    一般會將不同的 Broker 分散運行在不同的機器上,這樣如果集群中某一台機器宕機,kafka可以自動選舉出其他機器上的 Broker 繼續對外提供服務。這其實就是 Kafka 提供高可用的手段之一。

    controller

    Kafka集群中會有一個或者多個broker,其中有且僅有一個broker會被選舉為控制器(Kafka Controller),它負責管理整個集群中所有分區和副本的狀態。

    當某個分區的leader副本出現故障時,由控制器負責為該分區選舉新的leader副本。當檢測到某個分區的ISR集合發生變化時,由控制器負責通知所有broker更新其元數據信息。當為某個topic增加分區數量時,同樣還是由控制器負責分區的重新分配。

    這幾句話可能會讓你覺得困惑不要方 只是突出下控制器的職能很多,而這些功能的具體細節會在後面的文章中做具體的介紹。

    Kafka中的控制器選舉的工作依賴於Zookeeper,成功競選為控制器的broker會在Zookeeper中創建/controller這個臨時(EPHEMERAL)節點,此臨時節點的內容參考如下:

    其中version在目前版本中固定為1,brokerid表示稱為控制器的broker的id編號,timestamp表示競選稱為控制器時的時間戳。

    兩種客戶端

    Kafka有兩種客戶端。生產者和消費者。我們把生產者和消費者統稱為客戶端(Clients)。

    向主題Topic發布消息Message的客戶端應用程序稱為生產者(Producer),生產者程序通常持續不斷地向一個或多個主題發送消息。

    而訂閱這些主題消息的客戶端應用程序就被稱為消費者(Consumer)。和生產者類似,消費者也能夠同時訂閱多個主題的消息。

    Producer

    Producer 用來創建Message。在發布訂閱系統中,他們也被叫做 Publisher 發布者或 writer 寫作者。

    通常情況下,會發布到特定的Topic,並負責決定發布到哪個分區(通常簡單的由負載均衡機制隨機選擇,或者通過key,或者通過特定的分區函數選擇分區。)
    Producer分為Sync Producer 和 Aync Producer。

    Sync Producer同步的生產者,即一定要某條消息成功才會發送下一條。所以它是低吞吐率、一般不會出現數據丟失。

    Aync Producer異步的生產者,有個隊列的概念,是直接發送到隊列裏面,批量發送。高吞吐率、可能有數據丟失的。

    Consumer 和 Consumer Group

    消費者

    Consumer 讀取消息。在發布訂閱系統中,也叫做 subscriber 訂閱者或者 reader 閱讀者。消費者訂閱一個或者多個主題,然後按照順序讀取主題中的數據。

    消費位移

    消費者需要記錄消費進度,即消費到了哪個分區的哪個位置上,這是消費者位移(Consumer Offset)。注意,這和上面所說的消息在分區上的位移完全不是一個概念。上面的“位移”表徵的是分區內的消息位置,它是不變的,即一旦消息被成功寫入到一個分區上,它的位移值就是固定的了。

    而消費者位移則不同,它可能是隨時變化的,畢竟它是消費者消費進度的指示器嘛。通過存儲最後消費的 Offset,消費者應用在重啟或者停止之後,還可以繼續從之前的位置讀取。保存的機制可以是 zookeeper,或者 kafka 自己。

    消費者組

    ConsumerGroup:消費者組,指的是多個消費者實例組成一個組來消費一組主題,分區只能被消費者組中的其中一個消費者去消費,組員之間不能重複消費。

    為什麼要引入消費者組呢?主要是為了提升消費者端的吞吐量。多個消費者實例同時消費,加速整個消費端的吞吐量(TPS)。

    當然它的作用不僅僅是瓜分訂閱主題的數據,加速消費。它們還能彼此協助。假設組內某個實例掛掉了,Kafka 能夠自動檢測到,然後把這個 Failed 實例之前負責的分區轉移給其他活着的消費者,這個過程稱之為重平衡(Rebalance)。

    你務必先把這個詞記住,它是kafka大名鼎鼎的重平衡機制,生產出現的異常問題很多都是由於它導致的。後續我會在【kafka大名鼎鼎又臭名昭著的重平衡】文章中詳細分析。

    Zookeeper

    zookeeper目前在kafka中扮演着舉重輕重的角色和作用~是kafka不可缺少的一個組件。

    目前,Apache Kafka 使用 Apache ZooKeeper 來存儲它的元數據,比如brokers信息、分區的位置和主題的配置等數據就是存儲在 ZooKeeper 集群中。

    注意我的用詞,我只說是目前。why?在 2019 年社區提出了一個計劃,以打破這種依賴關係,並將元數據管理引入 Kafka 本身。因為擁有兩個系統會導致大量的重複。

    在之前的設計中,我們至少需要運行三個額外的 Java 進程,有時甚至更多。事實上,我們經常看到具有與 Kafka 節點一樣多的 ZooKeeper 節點的 Kafka 集群!此外,ZooKeeper 中的數據還需要緩存在 Kafka 控制器上,這導致了雙重緩存。

    更糟糕的是,在外部存儲元數據限制了 Kafka 的可伸縮性。當 Kafka 集群啟動時,或者一個新的控制器被選中時,控制器必須從 ZooKeeper 加載集群的完整狀態。隨着元數據數量的增加,加載過程需要的時間也會增加,這限制了 Kafka 可以存儲的分區數量。

    最後,將元數據存儲在外部會增加控制器的內存狀態與外部狀態不同步的可能性。

    因此,未來,Kafka 的元數據將存儲在 Kafka 本身中,而不是存儲在 ZooKeeper 之類的外部系統中。可以持續關注kafka社區動態哦!

    總結

    一個典型的kafka集群包含若干個producer(向主題發布新消息),若干consumer(從主題訂閱新消息,用Consumer Offset表徵消費者消費進度),cousumergroup(多個消費者實例共同組成的一個組,共同消費多個分區),若干broker(服務器端進程)。還有zookeeper。

    kafka發布訂閱的對象叫主題,每個Topic下可以有多個Partition,Partition中每條消息的位置信息又叫做消息位移(Offset),Partition有副本機制,使得同一條消息能夠被拷貝到多個地方以提供數據冗餘,副本分為領導者副本和追隨者副本。

    可以用下面這張圖來形象表達kafka的組成:

    另外,再po一張思維導圖助你回顧本文所述的術語。

    重要!!關注【胖滾豬學編程】公眾號發送”kafka”。獲取本文所有架構圖以及Kafka全系列思維導圖!

    本文來源於公眾號:【胖滾豬學編程】。一枚集顏值與才華於一身,不算聰明卻足夠努力的女程序媛。用漫畫形式讓編程so easy and interesting!求關注!

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 滅絕時代將來臨? WWF:50年來全球野生動物數量驟減2/3

    摘錄自2020年9月16日民視新聞報導

    全球最大的非政府環境保護組織「世界自然基金會」,最近發布了地球生命力報告2020,顯示近半世紀來,全球野生動物種群數量,已平均銳減68%,生物多樣性消失,人類難辭其咎,像是巴西中西部大沼澤的林火,疑似是人為的火災演變成的森林大火,目前就燒毀了70%美洲虎的主要棲息地。

    中南美洲的物種及全球淡水棲息地受到的衝擊尤其嚴重,平均分別下降了94%和84%,世界自然基金會總幹事藍柏堤尼表示,「不到50年我們就看到銳減2/3的野生動物,相較於這些動物棲息在地球上數百萬年,這不過是一眨眼的工夫,第二個原因必須擔憂的是,我們看到過去20年來加速惡化,上次地球生命力報告發布時說的是近6成,但現在是7成。」

    生物多樣性
    國際新聞

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※教你寫出一流的銷售文案?

  • 8分之1的歐洲人口死於環境污染 空污、噪音通通上榜

    摘錄自2020年9月15日自由時報報導

    根據歐洲環境署的一項調查研究報告,歐盟人口大約有8分之1的死與環境污染有關;對此,歐洲環境署負責人建議,優先保護社會上最受威脅的族群,解決貧困問題。

    歐洲環境署補充,化學物質、過量使用抗生素後對病原體造成的抗藥性以及被污染的飲用水,上述也是導致人們過早死亡的因素,而在許多東歐國家,環境因素導致的過早死亡率比西歐高得多。

    根據世衛組織的數據,歐盟國家過早死亡的比例大約為13%,相當於每年有63萬人過早死亡,不幸的是,環境因素(例如:癌症、心臟病和中風)造成的死亡原本是可以避免。

    歐洲環境署負責人漢斯.布魯尼克斯(Hans Bruyninckx)建議,必須採取措施來保護社會上最受威脅的族群,而貧困通常是主要問題,因為它會帶來惡劣的環境和影響健康狀況,因此為來歐洲未來可以在此多著墨,如此一來才能真正解決環境與健康的問題。

    污染治理
    國際新聞
    歐洲
    空污
    噪音

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※推薦評價好的iphone維修中心

  • 聯合國生物多樣性報告:10年愛知目標 沒有一項完全達成

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案