1. 程式人生 > 其它 >Mall商城的高階篇的開發(一)

Mall商城的高階篇的開發(一)

Mall商城的高階篇的開發

專案的整體架構圖

實現全文檢索和日誌分析

在本專案中,全文檢索使用ElasticSearch來做全文檢索。

做日誌儲存和日誌檢索(日誌的快速定位)使用ELK(ElasticSearch+Kibana+LogStash).

比如,騰訊雲的ES伺服器利用LogStash來進行資料傳輸匯聚到我們的ES中進行儲存和異常定位與監控。

他會收集我們專案中日誌,包括前臺服務和後臺服務,還有第三方的中介軟體

實現全文檢索

流程是這樣:在我們後臺管理系統中對應著有SPU管理,有一個商品上架的功能。

通過點選商品的上架功能,我們的後臺伺服器將改商品的所有屬性傳給我們的ES伺服器並在ES伺服器儲存(index),之後在實現我們的全文檢索的功能。

其中儲存商品的資料,不是說將商品的所有資訊都儲存在ES伺服器,主要的原因是ES伺服器儲存的資料都在記憶體中,記憶體相對硬碟,價效比還是相差很大。

SKU在ES中的儲存模型分析

當然在ES伺服器中儲存的是JSON資料,基於此,我們約定好需要檢索的內容。

{
  "skuId":"1",
  "spuId":"11"
  "skuTitle":"Apple xx",
  "price":"99",
  "seleCount":"990"
  "attrs":[
    {"尺寸":"xx"},
    {"CPU":"xx"},
    {"解析度":""},    
  ]
}

這樣進行儲存,很容易產生冗餘欄位。但是呢,這樣檢索起來方便。這個是利用空間換時間。

其實呢,為了減少冗餘欄位,因為每個商品的屬性有的就是一樣。

sku索引{
  "skuId":"1",
  "skuTitle":"Apple xx",
  "price":"99",
  "seleCount":"990"
}

attr索引{
  "spuId":"11",
  "attrs":[
   	{"尺寸":"xx"},
   {"CPU":"xx"},
   {"解析度":""}  
  ]
}

這個使用時間換空間。

最後我們還是選擇浪費時間少的儲存模型。

PUT product
{
    "mappings":{
        "properties": {
            "skuId":{ "type": "long" },
            "spuId":{ "type": "keyword" },  # 不可分詞
            "skuTitle": {
                "type": "text",
                "analyzer": "ik_smart"  # 中文分詞器
            },
            "skuPrice": { "type": "keyword" },  # 保證精度問題
            "skuImg"  : {
      						"type": "keyword",
      						"index":false,
      						"doc_value"
    							},  
            "saleCount":{ "type":"long" },
            "hasStock": { "type": "boolean" },
            "hotScore": { "type": "long"  },
            "brandId":  { "type": "long" },
            "catalogId": { "type": "long"  },
            "brandName": {"type": "keyword"}, # 視訊中有false
            "brandImg":{
                "type": "keyword",
                "index": false,  # 不可被檢索,不生成index,只用做頁面使用
                "doc_values": false # 不可被聚合,預設為true
            },
            "catalogName": {"type": "keyword" }, # 視訊裡有false
            "attrs": {
                "type": "nested",
                "properties": {
                    "attrId": {"type": "long"  },
                    "attrName": {
                        "type": "keyword",
                        "index": false,
                        "doc_values": false
                    },
                    "attrValue": {"type": "keyword" }
                }
            }
        }
    }
}

使用Nginx做動靜分離和反向代理

動靜分離

使用者在請求的時候,如果只是簡單訪問圖片、html等靜態資源的時候,Nginx直接返回。

如果使用者傳送動態請求,Nginx就會把動態請求傳送給閘道器,閘道器在路由給響應的後臺服務。

Nginx的動靜分離簡單來說就是把靜態請求和動態請求分開來處理,不能簡單理解成只是單純的把動態頁面和靜態頁面物理分離。

嚴格意義上來說應該是動態請求和靜態請求分開,可以理解成使用Nginx處理靜態請求,使用Tomcat處理動態請求。

動靜分離從目前的實現角度,大致分為兩種:

  • 一種是單純的把靜態檔案獨立成單獨的域名,放在獨立的伺服器上,也是目前主流的實現方案。
  • 另外一種是就是動態和靜態檔案混合在一起釋出,通過Nginx來分開。

頁面開發

引入模版引擎thymeleaf

官方文件:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#what-kind-of-templates-can-thymeleaf-process

配置的步驟:

  • 關閉thymeleaf的快取。目的是:為了在我們調式專案方便。

  • 靜態資源放在static目錄下

  • html頁面放在templates目錄下。

  • 頁面修改實時更新(不重啟伺服器的情況下)

    • 匯入devtools依賴
    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-devtools</artifactId>
         <optional>true</optional>
    </dependency>
    
    • 重新build

    以上功能可以實現,都是因為SpringBoot的WebMvcAutoConfiguration

反向代理

在此和幾個很常見的面試題有關聯:

1、在瀏覽器位址列輸入一個URL後回車,背後發生了什麼

2、同時關於TCP三次握手和四次揮手。

後面會有專門的部落格,對此內容在此講解。

要實現的邏輯:本機瀏覽器請求gulimall.com,通過配置hosts檔案之後,那麼當你在瀏覽器中輸入gulimall.com的時候,相當於域名解析DNS服務解析得到ip 192.168.56.10,也就是並不是訪問java服務,而是先去找nginx。

什麼意思呢?是說如果某一天專案上線了,gulimall.com應該是nginx的ip,使用者訪問的都是nginx

請求到了nginx之後,

  • 如果是靜態資源/static/* 直接在nginx伺服器中找到靜態資源直接返回。
  • 如果不是靜態資源 /(他配置在/static/*的後面所以才優先順序低),nginx把他upstream轉交給另外一個ip 192.168.56.1:88這個ip埠是閘道器gateway。
    • (在upstream的過程中要注意配置proxy_set_header Host $host;)

到達閘道器之後,通過url資訊斷言判斷應該轉發給nacos中的哪個微服務(在給nacos之前也可以重寫url),這樣就得到了響應。

而對於openFeign,因為在服務中註冊了nacos的 ip,所以他並不經過nginx。

實現負載均衡的步驟和解釋

  1. Nginx+Mac搭建訪問域名的訪問的環境(也就是講我們的虛擬機器的IP地址配置到我們的本地域名伺服器中)

Mac 的本地域名檔案:

/etc/hosts
  1. 測試結果
  1. 實現的第一個效果

訪問gulimall.com通過Nginx的反向代理將請求轉到我們的商品服務(product)的index.html

此時需要我們來對Nginx進行修改它的配置檔案:

需要大概瞭解一下它的配置檔案:

nginx.conf的配置檔案的分佈圖:

//======全域性塊=====start======
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

//======全域性塊=====end======

//======events塊=====start======
events {
    worker_connections  1024;//每個程序的最大連線數
}
//======events塊=====end======

//======http塊=====start======
http {
  	//====http全域性塊=====start=====
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf; //包含其他的配置
    //====http全域性塊=====end=====
}

nginx.conf中我們不能發現它是沒有server塊,但是我們可以看到的是include /etc/nginx/conf.d/*.conf;

server {
listen       80; //監聽我們的80埠
listen  [::]:80; 
server_name  localhost; //域名

#access_log  /var/log/nginx/host.access.log  main;

location / {
  root   /usr/share/nginx/html;
  index  index.html index.htm;
}

#error_page  404              /404.html;

# redirect server error pages to the static page /50x.html
#
error_page   500 502 503 504  /50x.html;
location = /50x.html {
  root   /usr/share/nginx/html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
#    proxy_pass   http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
#    root           html;
#    fastcgi_pass   127.0.0.1:9000;
#    fastcgi_index  index.php;
#    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
#    include        fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
#    deny  all;
#}
}

我們需要在這一塊配置,來達到我們一開始的需求:訪問gulimall.com通過Nginx的反向代理將請求轉到我們的商品服務(product)的index.html

我們先通過訪問我們自定義的域名,可以看到它跳到我們的Nginx的80埠的index頁面。

現在我們是希望它可以幫我們轉到我們的商品服務。也就是8081/

所以此時我們需要在server塊,進行反向代理的配置。為了安全起見,我們可以選擇複製一份我們的default.conf

cp default.conf gulimall.conf

所以我們針對我們的需求,對Nginx的server塊,進行一個配置:

最後呢,我們需要重啟我們的Nginx服務。

docker restart nginx

測試頁面,發現訪問失敗,開始對失敗定位。

# 看一下nginx是否啟動
docker ps nginx

# 看一下nginx的日誌 
docker logs nginx

說我們的第10行又問題。

通過這個命令,可以看到我們的行數。

其實就是少了個分號。

最後還有一個錯誤就是格式錯誤:

  1. 測試成功

![image-20220507165956313](/Users/wanglufei/Library/Application Support/typora-user-images/image-20220507165956313.png)

  1. 改動(由於我們的後臺服務後多,如果每一個服務都配置,顯然是不友好的,此時我們只需要將請求帶給我們的閘道器,讓我們的閘道器自己去在服務的註冊中心去找),也就是負載均衡到我們的閘道器

要實現的效果:

使用Nginx做負載均衡配置官方文件的地址:http://nginx.org/en/docs/http/load_balancing.html

Introduction

Load balancing across multiple application instances is a commonly used technique for optimizing resource utilization, maximizing throughput, reducing latency, and ensuring fault-tolerant configurations.

在多個應用例項之間進行負載均衡是一種常用的技術,可以優化資源利用,最大限度地提高吞吐量,減少延遲,並確保容錯配置。

It is possible to use nginx as a very efficient HTTP load balancer to distribute traffic to several application servers and to improve performance, scalability and reliability of web applications with nginx.

可以使用nginx作為一個非常有效的HTTP負載均衡器,將流量分配到幾個應用伺服器上,用nginx提高網路應用的效能、可擴充套件性和可靠性。

Load balancing methods(負載平衡方法)

The following load balancing mechanisms (or methods) are supported in nginx:(nginx支援下列負載平衡機制(或方法)。)

  • round-robin — requests to the application servers are distributed in a round-robin fashion,

    round-robin - 對應用伺服器的請求是以輪流方式分配的。

  • least-connected — next request is assigned to the server with the least number of active connections,

    最少連線 - 將下一個請求分配給活動連線數最少的伺服器。

  • ip-hash — a hash-function is used to determine what server should be selected for the next request (based on the client’s IP address).

    ip-hash - 用一個雜湊函式來決定下一個請求應該選擇哪個伺服器(基於客戶端的IP地址)。

官方提出的負載均衡最簡單的例項:

http {
    upstream myapp1 { //上有伺服器
        server srv1.example.com;
        server srv2.example.com;
        server srv3.example.com;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://myapp1;
        }
    }
}

官方對他作出的解釋:

In the example above, there are 3 instances of the same application running on srv1-srv3. When the load balancing method is not specifically configured, it defaults to round-robin. All requests are proxied to the server group myapp1, and nginx applies HTTP load balancing to distribute the requests.

在上面的例子中,有3個相同應用程式的例項在srv1-srv3上執行。當沒有特別配置負載均衡方法時,它預設為輪循。所有請求都被代理到伺服器組myapp1,nginx應用HTTP負載均衡來分配請求。

Reverse proxy implementation in nginx includes load balancing for HTTP, HTTPS, FastCGI, uwsgi, SCGI, memcached, and gRPC.

nginx的反向代理實現包括HTTP、HTTPS、FastCGI、uwsgi、SCGI、memcached和gRPC的負載均衡。

To configure load balancing for HTTPS instead of HTTP, just use “https” as the protocol.

要為HTTPS而不是HTTP配置負載均衡,只需使用 "https "作為協議。

When setting up load balancing for FastCGI, uwsgi, SCGI, memcached, or gRPC, use fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, and grpc_pass directives respectively.

當為FastCGI、uwsgi、SCGI、memcached或gRPC設定負載均衡時,分別使用fastcgi_pass、uwsgi_pass、scgi_pass、memcached_pass和grpc_pass指令

進入到我們的nginx.conf配置http塊:

進入到/nginx/conf/conf.d修改我們的代理伺服器:

同時我們還需要配置閘道器,使用host路由斷言:

- id: gulimall_host_route
          uri: lb://product
          predicates:
            - Host=**.gulimall.com

重啟我們的閘道器服務,測試

也就是出現了:Nginx代理到閘道器丟失了請求頭的資訊(Host)

解決辦法:

需要到我們的nginx的server塊,進行設定一個代理的請求頭的資訊:

location / {
        # 設定請求頭的host 給Host設定$host(當前請求的host的值 )
        proxy_set_header Host $host;
        proxy_pass  http://gulimall;
    }

測試:

梳理一下流程

我們通過在本機的host檔案中進行了域名對映,

192.168.1.115 gulimall.com

所以當我們在瀏覽器中輸入gulimall.com的時候,由於我們在本機配置了域名的對映,所以它會訪問到我們對映的192.168.1.115這個IP地址。

為了實現我們在瀏覽器中輸入gulimall.com