Web進階 | 構建高可靠、高性能的Web應用

功能 -> 性能? -> 智能

可靠性:可擴展性、服務降級、負載均衡、灰度
性能:緩存、并發、池化、異步

一、可靠性

1、應用擴展
  • 垂直擴展(scala up)
    方式:提升機器硬件
    缺點:成本昂貴,擴展能力有限
垂直擴展:增加一臺機器硬件數量(并不一定是單機的)
  • 水平擴展(scala out)
    方式:增加節點(增加機器數量)
    優點:升級過程平滑透明,硬件成本低,理論上可無限擴展
    缺點:會增加系統的復雜度,維護成本高,服務須無狀態(比如多臺機器無法使用Session),可分布式的
水平擴展:增加多臺機器
2、數據庫擴展
  • 垂直拆分
    原先:一個庫數據量太大,將業務緊密,表間關聯密切的表劃分在一起
    分庫:將數據表拆分,可以提高性能、隔離故障
  • 水平拆分
    一個表的數據量太大,一表拆多表,根據查詢使用情況確定拆分規則;
    MySQL單表最大記錄數不要超過5000W;
    帶來的問題
    (1)如何從多張表中定位到數據所在的表?
    自定義映射規則
    為某個字段計算hash從而對應到相應的表中

    例子:
    用戶聊天信息表拆分為message_00,message_01,message_02..........message_98,message_99,
    然后根據用戶的ID來判斷這個用戶的聊天信息放到哪張表里面,可以用hash的方式來獲得,
    可以用求余的方式來獲得,方法很多
    

(2)如何使代碼優雅(代碼層不牽扯數據庫分表)
Spring動態數據源 -->> 連接不同的庫
分表組件:如TSharding,是一個簡易 sharding 組件,也是一個 Mybatis 分庫分表組件。

Mybatis 分庫分表組件 TSharding-Client

  • 數據庫擴展考慮點
  • 數據量:現有/未來數據量會有多大?
  • 增長速度:增長速度影響數據量,提前考慮分表(如何評估?周期不要超過半年,不可靠)
  • QPS:每秒查詢量
  • 切分規則:根據具體業務、表內容、需求等進行合理分片
3、負載均衡
  • 方案

  • Nginx反向代理服務器(推薦): 用戶請求到達Nginx服務器,按照一定策略http轉發到具體的機器

  • HTTP重定向: 302

  • DNS輪詢解析

  • LVS: 網絡4層,內核協議棧

  • HAProxy: 4/7層

  • 分發策略

  • Random:隨機分發(請求次數公平)

  • RoundRobin:RR,輪詢分發(請求次數公平)

  • LeastActive:最少活躍,根據服務器當前處理請求的能力(處理能力公平)

  • Hash:如根據ip做hash、根據內容(請求內容)做hash

  • 健康檢查
    將空的健康檢查文件HealthyCheck.html放在各個服務器上,以供Nginx等服務器檢查應用服務器的運行狀態。

4、服務降級
  • 服務分級
    對提供的服務進行分級,核心服務具有更高的優先級,頻率低、不重要的服務級別低
  • 功能開關
  • 全流程開關
  • config

二、性能

1、并發
Servlet非線程安全
每個Servlet線程都有自己的存儲區域,計算完畢后再將數據寫回寄存器,線程不安全

多線程環境下,需要使用線程安全的集合、共享資源加鎖等。

并發鎖
  • synchronized/ReentrantLock

  • 可重入鎖:

  • 互斥:同一時刻只能有一個線程持有(相對的是共享鎖)

  • 讀寫鎖

  • ReadWriterLock:有timeout機制,超時不等待

  • 讀鎖:是共享鎖

  • 寫鎖:是排它鎖,互斥

  • 鎖不能升級(讀鎖->寫鎖),只能降級(寫鎖->讀鎖);
    寫鎖要等待所有的讀鎖釋放

  • 樂觀鎖

  • 樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖假設認為數據一般情況下不會造成沖突,所以在數據進行提交更新的時候,才會正式對數據的沖突與否進行檢測,如果發現沖突了,則讓返回用戶錯誤的信息,讓用戶決定如何去做。

  • 悲觀鎖

  • 正如其名,它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處于鎖定狀態。悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系 統不會修改數據)。

2、緩存
緩存:將請求頻繁的數據從DB / 磁盤加載到內存
  • 本地緩存
  • HashMap

  • ConcurrentHashMap(并發環境)

  • Guava Cache

  • memcached
  • Key-Value
    username_zhangsan -> {"username":zhangsan,"nickname":"張全蛋"}

  • LRU
    Least Recently Used近期最少使用

  • 分布式
    一致性Hash
    client實現

  • Client
    xmemcached
    spymemcached


    [memcached](https://www.baidu.com/link?url=h3veG-DxMyBf86uSyo-ASq3Yg81BQXgBZ7q4cXQS4omKnRCBpEwaTXQYW-gNQsEsQuDKk7PpNkF4OkNmZL1Dc0d2Bp61s4eGZUMipAYrSpq&wd=&eqid=891a485f0000a2be0000000358d4d91e) 是一個高性能的分布式內存對象緩存系統,用于動態Web應用以減輕數據庫負載。它通過在內存中緩存數據和對象來減少讀取數據庫的次數,從而提高動態、數據庫驅動網站的速度。Memcached基于一個存儲鍵/值對的[hashmap](http://baike.baidu.com/item/hashmap)。其[守護進程](http://baike.baidu.com/item/%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B)(daemon )是用[C](http://baike.baidu.com/item/C/7252092)寫的,但是客戶端可以用任何語言來編寫,并通過memcached協議與守護進程通信。
  • redis
  • 數據結構豐富
    Hash
    List
    Set
    ...

  • 操作豐富

  • 可持久化:定期將內存數據保存到磁盤,關機后可load到內存

  • 單線程、多實例

redis提供了豐富的數據結構
3、序列化
概念

將對象的狀態信息轉換為可以存儲或傳輸形式的過程

方式
  • Json:將對象信息轉換為json數據
  • Java serialization

java序列化一定應該注意**的6個事項
1、如果子類實現Serializable接口而父類未實現時,父類不會被序列化,但此時父類必須有個無參構造方法,否則會拋InvalidClassException異常。
2、靜態變量不會被序列化,那是類的“菜”,不是對象的。
3、transient關鍵字修飾變量可以限制序列化。
4、虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化ID是否一致,就是 private static final long serialVersionUID = 1L。
5、Java 序列化機制為了節省磁盤空間,具有特定的存儲規則,當寫入文件的為同一對象時,并不會再將對象的內容進行存儲,而只是再次存儲一份引用。反序列化時,恢復引用關系。
6、序列化到同一個文件時,如第二次修改了相同對象屬性值再次保存時候,虛擬機根據引用關系知道已經有一個相同對象已經寫入文件,因此只保存第二次寫的引用,所以讀取時,都是第一次保存的對象。讀者在使用一個文件多次 writeObject 需要特別注意這個問題(基于第5點)。

  • Hessian
  • Protobuf
  • Kryo
4、池化技術
場景
  • 可復用資源
  • 創建代價大
類型
  • 線程池
  • Executor
  • 連接池
  • tomcat-jdbc
  • dbpc
  • c3p0
  • 對象池
  • Spring Bean 管理
5、異步
前端輪詢、后端異步
  • Futrue/CountDownLatch
消息隊列
  • QMQ/Kafka/AMQ/rabbitmq
HTTP
  • async-http-client
  • Apache HttpComponents
Dubbo
  • 異步調用、參數回調
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容