<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          常見的負(fù)載均衡算法的實(shí)現(xiàn)與應(yīng)用

          共 9675字,需瀏覽 20分鐘

           ·

          2023-08-19 03:23

          走過路過不要錯(cuò)過

          點(diǎn)擊 藍(lán)字 關(guān)注我們


          所謂負(fù)載均衡就是將外部發(fā)送過來的請(qǐng)求均勻或者根據(jù)某種算法分配到對(duì)稱結(jié)構(gòu)中的某一臺(tái)服務(wù)器中。負(fù)載均衡可以分為硬件負(fù)載均衡和軟件負(fù)載均衡,常見的硬件負(fù)載均衡有F5、Array等,但是這些設(shè)備都比較昂貴。相比之下,利用軟件來實(shí)現(xiàn)負(fù)載均衡就比較簡(jiǎn)單了,常見的像是 Nginx 的反向代理負(fù)載均衡。

          這篇文章并不去細(xì)說 Nginx 這類軟件的具體配置,只是著重來了解幾種常見的負(fù)載均衡算法的實(shí)現(xiàn)(本文使用Java描述)與應(yīng)用。對(duì)于我們來講,所了解的最基本的負(fù)載均衡算法包括了:隨機(jī)、輪詢、一致性Hash等幾種方式,接下來就來詳細(xì)介紹這幾種算法:

          一、隨機(jī)#

          1. 完全隨機(jī)

          所謂完全隨機(jī)就是完全沒有規(guī)律可言,每次隨機(jī)種IP列表中選取一個(gè),將請(qǐng)求打到該服務(wù)器上:

              
          Copy
          public class ServerIps {

          public static final List<String> LIST = Arrays.asList(
          "192.168.0.1",
          "192.168.0.2",
          "192.168.0.3",
          "192.168.0.4",
          "192.168.0.5"
          );
          }
              
          Copy
          public class Random {

          public static String getServer() {
          java.util.Random = new java.util.Random();
          int rand = random.nextInt(ServerIps.LIST.size());

          return ServerIps.LIST.get(rand);
          }

          public static void main(String[] args) {
          for(int i = 0; i < 10; i++) {
          System.out.println(getServer());
          }
          }
          }

          完全隨機(jī)是最簡(jiǎn)單的負(fù)載均衡算法了,實(shí)現(xiàn)起來也很簡(jiǎn)單。但是我們?cè)谌粘Ia(chǎn)過程中,機(jī)器的處理效率肯定是不同的,有些機(jī)器處理得快,有些機(jī)器處理得慢,完全隨機(jī)的缺點(diǎn)在此刻就很明顯了,無法將更多的請(qǐng)求分配到更好的服務(wù)器上,由此就有了加權(quán)隨機(jī)的思想了。

          2. 加權(quán)隨機(jī)#

          加權(quán)隨機(jī)常見的有兩種實(shí)現(xiàn)方式,現(xiàn)在先來了解第一種方式,先構(gòu)建一個(gè)ServerIps類先,如下:

              
          Copy
          public class ServerIps {

          public static final Map<String, Integer> WEIGHT_LIST = new LinkedHashMap<String, Integer>();

          static {
          WEIGHT_LIST.put("192.168.0.1", 1);
          WEIGHT_LIST.put("192.168.0.2", 8);
          WEIGHT_LIST.put("192.168.0.3", 3);
          WEIGHT_LIST.put("192.168.0.4", 6);
          WEIGHT_LIST.put("192.168.0.5", 5);
          }
          }

          首先這種實(shí)現(xiàn)方式的想法很簡(jiǎn)單,就是通過上面的服務(wù)器IP的List,再去構(gòu)建一次List:如果一個(gè)IP服務(wù)器的權(quán)重為8,那么就往List里面添加8次該對(duì)應(yīng)的IP地址,如果權(quán)重為3,那么就添加3次,以此類推。這樣的話,結(jié)合前面的完全隨機(jī)實(shí)現(xiàn),那么這樣權(quán)重越大的,在List中出現(xiàn)的比例也越高,被選中的幾率當(dāng)然也會(huì)更大:

              
          Copy
          public class WeightRandom {

          public static String getServer() {
          // 構(gòu)建一個(gè)新的List
          List<String> ips = new ArrayList<>();

          for(String ip : ServerIps.WEIGHT_LIST.keySet()) {
          Integer weight = ServerIps.WEIGHT_LIST.get(ip);

          for(int i = 0; i < weight; i++) {
          ips.add(ip);
          }
          }

          java.util.Random random = new java.util.Random();
          int randomPos = random.nextInt(ips.size());

          return ips.get(randomPos);
          }

          public static void main(String[] args) {
          // 連續(xù)調(diào)用10次
          for(int i = 0; i < 10; i++) {
          System.out.println(getServer());
          }
          }
          }

          上面代碼可以自行測(cè)試一下,最后結(jié)構(gòu)應(yīng)該就是權(quán)重高的服務(wù)器被選中的幾率更高。但是這也有一種很明顯的缺點(diǎn),如果現(xiàn)在的權(quán)重是像下面這樣配置呢?

              
          Copy
          WEIGHT_LIST.put("192.168.0.1", 998);
          WEIGHT_LIST.put("192.168.0.2", 13);
          // ...

          這樣很明顯就會(huì)帶來一個(gè)新的問題,服務(wù)器中的新建的List動(dòng)不動(dòng)就會(huì)有上萬條記錄,那么此時(shí)服務(wù)器豈不是要白白浪費(fèi)掉一部分內(nèi)存去維護(hù)一個(gè)數(shù)組,所以此時(shí)我們就有了下面更優(yōu)的實(shí)現(xiàn)。

          3. 加權(quán)隨機(jī)優(yōu)化

          假設(shè)我們現(xiàn)在有3臺(tái)服務(wù)器,A服務(wù)器的權(quán)重為3,B服務(wù)器的權(quán)重為5,C服務(wù)器的權(quán)重為1。然后我們隨機(jī)生成一個(gè)數(shù):

          • 如果生成的隨機(jī)數(shù)為1,1 ≤ 3,那么此時(shí)請(qǐng)求在落在A的區(qū)間段,請(qǐng)求應(yīng)該交由A來處理;

          • 如果生成的隨機(jī)數(shù)為5,那么此時(shí) 3 < 5,所以不在A區(qū)間,而 5 - 3 = 2 < 5,那么此時(shí)應(yīng)該落在B區(qū)間;

          • 如果生成的隨機(jī)數(shù)為9,那么 9 > 3 + 5,所以不可能落在A區(qū)間或者B區(qū)間,而剛好后面C的權(quán)重為,落在C區(qū)間。

          用圖展示會(huì)更直觀些,假設(shè)下面數(shù)組下標(biāo)從1開始:

          b4eb22c72e99ac06a763a0def47d2105.webp

          當(dāng)然我們不可能真的像圖中那樣再去維護(hù)一個(gè)數(shù)組了,這樣不也和剛剛那樣白占內(nèi)存么。所以每次但一個(gè)隨機(jī)數(shù)過來時(shí),我們會(huì)先去判斷它是否小于A的權(quán)重,如果小于,那么就是請(qǐng)求發(fā)到A,如果不小于,注意,這里就得將隨機(jī)數(shù)減去A的權(quán)重,然后下一次循環(huán)再和B的權(quán)重比,如果小于B的權(quán)重,那么就是請(qǐng)求到B,具體我們可以看下代碼實(shí)現(xiàn):

              
          Copy
          public class WeightRandomV2 {

          public static String getServer() {
          int totalWeight = 0;
          for(Integer weight : ServerIps.WEIGHT_LIST.values()) {
          totalWeight += weight;
          }

          java.util.Random random = new java.util.Random();
          int pos = random.nextInt(totalWeight);

          for(String ip : ServerIps.WEIGHT_LIST.keySet()) {
          Integer weight = ServerIps.WEIGHT_LIST.get(ip);

          if(pos < weight) {
          return ip;
          }

          pos = pos - weight;
          }

          return "";
          }
          }

          二、輪詢

          1. 完全輪詢#

          完全輪詢的思想也十分簡(jiǎn)單,大家輪著來嘛,這次請(qǐng)求打到A上,下次就到B,再下次就是C了,這種實(shí)現(xiàn)的方式也很簡(jiǎn)單,代碼實(shí)現(xiàn)如下:

              
          Copy
          public clsss RoundRobin {

          private static Integer pos = 0;

          public String String getServer() {
          if (pos >= ServerIps.LIST.size()) {
          pos = 0;
          }
          String ip = ServerIps.LIST.get(pos);
          pos++;

          return ip;
          }
          }

          2. 加權(quán)輪詢

          完全輪詢?nèi)秉c(diǎn)和完全隨機(jī)基本無異,當(dāng)遇見性能較好的機(jī)器時(shí),它的性能無法展示出來,當(dāng)遇見性能較差的機(jī)器時(shí),它的弊端倒是很容易顯現(xiàn),由此我們也需要來實(shí)現(xiàn)加權(quán)輪詢。同樣的,加權(quán)輪詢也像加權(quán)隨機(jī)那樣有兩種實(shí)現(xiàn)方式,一種靠著擴(kuò)展列表,以外一種靠著偏移量,這里就簡(jiǎn)單演示下后者,畢竟后者的實(shí)現(xiàn)更優(yōu)。

          這里我們加上些簡(jiǎn)單的優(yōu)化,請(qǐng)求是從前面發(fā)過來的,帶有一個(gè)RequestID,那么我們就利用這個(gè)ID來幫助我們的輪詢:

          我們先創(chuàng)建一個(gè)RequestId模仿請(qǐng)求ID,代碼如下:

              
          Copy
          public class RequestId {

          public static Integer num = 0;

          public static Integer getAndIncrement() {
          return num++;
          }
          }

          接下來我們就利用這個(gè)ID,然后進(jìn)行輪詢,代碼如下:

              
          Copy
          public class WeightRoundRobin {

          public static String getServer() {
          int totalWeight = 0;
          for (Integet weight : ServerIps.WEIGHT_LIST.values()) {
          totalWeight += weight;
          }

          Integer requestId = Request.getAndIncrement();

          Integer pos = requestId % totalWeight;

          for(String ip : ServerIps.WEIGHT_LIST.keySet()) {
          Integer weight = ServerIps.WEIGHT_LIST.get(ip);

          if(pos < weight) {
          return ip;
          }

          pos = pos - weight;
          }

          return "";
          }
          }

          通過上面方式我們確實(shí)能實(shí)現(xiàn)加權(quán)輪詢,但是這種輪詢是連著來到,意思也就說,如果有請(qǐng)求過來,那么如果A服務(wù)器的權(quán)重為3,就會(huì)有連續(xù)3個(gè)請(qǐng)求打到A上,那么B此時(shí)卻沒有請(qǐng)求來訪問,等到A服務(wù)器經(jīng)歷3個(gè)請(qǐng)求后,接下來又有連續(xù)的5個(gè)請(qǐng)求打到B上,依次類推。

              
          Copy
          A A A B B B B B C

          這種實(shí)現(xiàn)也會(huì)來帶很大的弊端,所以我們實(shí)際上是既要有輪詢,輪詢中間最好還是要有交叉,由此有了平滑加權(quán)輪詢的思想。

          3. 平滑加權(quán)輪詢#

          首先我們需要先來了解靜態(tài)權(quán)重與動(dòng)態(tài)權(quán)重的概念:

          • 靜態(tài)權(quán)重:權(quán)重由用戶自己設(shè)定,在輪詢過程中一直不變化,像上面幾種加權(quán)方式就是靜態(tài)權(quán)重;

          • 動(dòng)態(tài)權(quán)重:動(dòng)態(tài)權(quán)重的思想與靜態(tài)權(quán)重剛好相反,在輪詢的過程中動(dòng)態(tài)地變換各機(jī)器的權(quán)重,但是最終的加權(quán)效果不能與靜態(tài)權(quán)重不同,只是用來實(shí)現(xiàn)交叉輪詢的效果。

          接下來就講解一種動(dòng)態(tài)權(quán)重實(shí)現(xiàn)的算法,假設(shè)用戶給A、B、C三臺(tái)服務(wù)器配置的權(quán)重為3、5、1,我們將3、5、1稱為固定權(quán)重,在算法過程中變化的權(quán)重稱為動(dòng)態(tài)權(quán)重:

          • 請(qǐng)求第一次過來,此時(shí)A、B、C的權(quán)重分別是3、5、1,在這里面 5 是大的,所對(duì)應(yīng)的就是B服務(wù)器,將請(qǐng)求打到B服務(wù)器,接下來將我們剛剛所選中的服務(wù)器的權(quán)重減去所有機(jī)器的權(quán)重加起來的總值,作為接下來的動(dòng)態(tài)權(quán)重,沒被選中的服務(wù)器權(quán)重不變。也就是B服務(wù)器:5 - (3 + 5 + 1) = -4;那么第一次請(qǐng)求結(jié)束后三臺(tái)服務(wù)器的動(dòng)態(tài)權(quán)重為:3、-4、1;

          • 請(qǐng)求第二次過來,此時(shí)A、B、C的動(dòng)態(tài)權(quán)重為3、-4、1,固定權(quán)重3、5、1,我們需要將這兩個(gè)權(quán)重相加,得出此時(shí)三臺(tái)服務(wù)器的動(dòng)態(tài)權(quán)重為6、1、2。那么此時(shí)A的權(quán)重最大,那么就將請(qǐng)求打到A上。接下來再將剛剛所選中的服務(wù)器的動(dòng)態(tài)權(quán)重減去所有服務(wù)器的權(quán)重加起來的總值,也就是A服務(wù)器:6 - (3 + 5 + 1 ) = -3;那么第二次請(qǐng)求結(jié)束后三臺(tái)服務(wù)器的動(dòng)態(tài)權(quán)重為:-3、1、2;

          • 請(qǐng)求第三次過來,此時(shí)A、B、C的動(dòng)態(tài)權(quán)重為-3、1、2,固定權(quán)重3、5、1,將這兩個(gè)權(quán)重相加,得出此時(shí)三臺(tái)服務(wù)器的動(dòng)態(tài)權(quán)重為0、6、3。那么此時(shí)B的權(quán)重最大,那么就將請(qǐng)求打到B服務(wù)器上。接下來再將剛剛所選中的服務(wù)器的動(dòng)態(tài)權(quán)重減去所有的服務(wù)器的權(quán)重加起來的總值,也就是B服務(wù)器:6 - (3 + 5 + 1) = -3;那么第三次請(qǐng)求結(jié)束后三臺(tái)服務(wù)器的動(dòng)態(tài)權(quán)重為:0、-3、3;

          • ......

          dfa33009f5ba72342694705fc1e72b0b.webp

          到這里應(yīng)該能有一個(gè)基本的了解,那么我們現(xiàn)在就用代碼實(shí)現(xiàn)一遍:

          首先實(shí)現(xiàn)一個(gè)權(quán)重類:

              
          Copy
          public class Weight {

          private String ip;
          private Integer weight; // 固定權(quán)重
          private Integer currentWeight; // 動(dòng)態(tài)權(quán)重

          public Weight(String ip, Integer weight, Integer currentWeight) {
          this.ip = up;
          this.weight = weight;
          this.currentWeight = currentWeight;
          }

          // get & set methods...
          }

          接下來我們就來實(shí)現(xiàn)這個(gè)平滑加權(quán)輪詢算法:

              
          Copy
          public class WeightRoundRobinV3 {

          private static Map<String, Weight> weights = new HashMap<String, Weight>();

          public static String getServer() {
          if (weights.isEmpty()) {
          ServerIps.WEIGHT_LIST.forEach((ip, weight) -> {
          weights.put(ip, new Weight(ip, weight, 0));
          });
          }

          for (Weight weight : weight.values()) {
          weight.setCurrentWeight(weight.getCurrentWeight() + weight.getWeight());
          }

          // 尋找本次請(qǐng)求中權(quán)重最高的服務(wù)器IP
          Weight maxCurrentWeight = null;
          for (Weight weight : weights.values()) {
          if (maxCurrentWeight == null || weight.getCurrentWeight() > maxCurrentWeight.getCurrentWeight()) {
          maxCurrentWeight = weight;
          }
          }

          return maxCurrentWeight.getIp();
          }

          public static void main(String[] args) {
          // 連續(xù)調(diào)用20次
          for (int i = 0; i < 20; i++) {
          System.out.print(getServer() + " ");
          }
          }
          }

          三、一致性Hash

          其實(shí)我們可以發(fā)現(xiàn),隨機(jī)和輪詢的思想其實(shí)差不多,都是盡可能將請(qǐng)求均衡地派發(fā)到服務(wù)器中,最多加權(quán)實(shí)現(xiàn)。如果我們現(xiàn)在想要讓某個(gè)用于發(fā)過來的請(qǐng)求一直打到相同的服務(wù)器上去處理,那該怎么辦呢?常見的實(shí)現(xiàn)就是一致性Hash算法了,這一算法在Nginx中配置負(fù)載均衡時(shí)也有體現(xiàn):

          98aab2096cee7b849ec35263314afd3e.webp

          如上圖,當(dāng)有相同用戶的連接過來的時(shí)候,可以通過Hash映射的方式打到同一臺(tái)服務(wù)器上,這也是解決分布式Session的一種思路。

          現(xiàn)在這里就涉及到一些問題了,我們一步一步來解決。首先用戶的請(qǐng)求過來,根據(jù)Hash算法計(jì)算出對(duì)應(yīng)的值后,如何找到對(duì)應(yīng)的服務(wù)器IP呢?

          這很簡(jiǎn)單,將所有可能的HashCode值列出來,造一個(gè)Map,將HashCode與IP一一對(duì)應(yīng),大概多個(gè)HashCode會(huì)對(duì)應(yīng)到同一臺(tái)機(jī)器。

          那如果HashCode的可能值有很多個(gè)呢?那豈不是需要去造一個(gè)占用內(nèi)存很大的Map?

          嗯,客戶端發(fā)起的請(qǐng)求情況是無窮無盡的(客戶端地址不同,請(qǐng)求參數(shù)不同等等),所以對(duì)于哈希值也是無窮大的,所以我們不可能把所有的哈希值都進(jìn)行映射到服務(wù)端IP上,這里就需要用到哈希環(huán)了,如下圖:

          3339cb0d2346df6b437acfe23a955f20.webp

          • 哈希值如果在ip1和ip2之間,則應(yīng)該選擇ip2作為響應(yīng)服務(wù)器;

          • 哈希值如果在ip2和ip3之間,則應(yīng)該選擇ip3作為響應(yīng)服務(wù)器;

          • 哈希值如果在ip3和ip4之間,則應(yīng)該選擇ip4作為響應(yīng)服務(wù)器;

          • 哈希值如果在ip4和ip1之間,則應(yīng)該選擇ip1作為響應(yīng)服務(wù)器;

          如果多臺(tái)服務(wù)器所“控制”的區(qū)域很小,或者有時(shí)候其中一臺(tái)服務(wù)器宕機(jī)了,像下面這種出現(xiàn)哈希傾斜的情況,怎么辦?

          9657ec188a930c8092d89cb9eebefca0.webp

          嗯,像這種情況可以加入虛擬節(jié)點(diǎn),如下圖所示:

          168c1ff5b279062cafd9017ca9fbe00d.webp

          其中ip1-2、ip2-1等節(jié)點(diǎn)其實(shí)是虛擬的,等同于ip1和ip2服務(wù)器本身。實(shí)際上,這只是處理這種不平衡性的一種思路,實(shí)際上就算哈希環(huán)本身是平衡的,你也可以加入如更多的虛擬節(jié)點(diǎn)來使這個(gè)環(huán)更加平滑。

          那么我們?nèi)绾蝸韺?shí)現(xiàn)這個(gè)算法呢?接下來我們就來看代碼實(shí)現(xiàn):

              
          Copy
          public class ConsitentHash {

          // 用紅黑樹,有序
          private static TreeMap<Integer, String> virtualNodes = new TreeMap<>();
          private static final int VIRTUAL_NODES = 160;

          static {
          // 對(duì)每個(gè)真實(shí)的節(jié)點(diǎn)添加虛擬節(jié)點(diǎn),虛擬節(jié)點(diǎn)會(huì)根據(jù)哈希算法進(jìn)行散列
          for (String ip : ServerIps.LIST) {
          for (int i = 0; i < VIRTUAL_NODES; i++) {
          int hash = getHash(ip + "VN" + i);
          virtualNodes.put(hash, ip);
          }
          }
          }

          public staitc String getServer(String clientInfo) {
          int hash = getHash(client);

          // 得到大于該Hash值的子紅黑樹
          SortedMap<Integer, String> subMap = virtualNode.tailMap(hash);

          // 獲取該樹的第一個(gè)元素,也就是最小的元素
          Integer nodeIndex = subMap.firstKey();

          // 如果沒有大于該元素的子樹了,則取整棵樹的第一個(gè)元素,相當(dāng)于取哈希環(huán)中的最小的那個(gè)元素
          if (nodeIndex == null) {
          nodeIndex = virtualNodes.firstKey();
          }

          // 返回對(duì)應(yīng)的虛擬節(jié)點(diǎn)名稱
          return virtualNodes.get(nodeIndex);
          }

          // 計(jì)算hash值的算法
          private static int getHash(String str) {
          final int p = 16777619;
          int hash = (int) 216613621L;
          for (int i = 0; i < str.length(); i++)
          hash = (hash ^ str.charAt(i)) * p;
          hash += hash << 13;
          hsah ^= hash >> 7;
          return hashl
          }

          }

          四、最小連接數(shù)法

          前面幾種方法費(fèi)盡心思來實(shí)現(xiàn)服務(wù)消費(fèi)者請(qǐng)求次數(shù)分配的均衡,當(dāng)然這么做是沒錯(cuò)的,可以為后端的多臺(tái)服務(wù)器平均分配工作量,最大程度地提高服務(wù)器的利用率,但是實(shí)際情況是否真的如此?實(shí)際情況中,請(qǐng)求次數(shù)的均衡真的能代表負(fù)載的均衡嗎?這是一個(gè)值得思考的問題。

          上面的問題,再換一個(gè)角度來說就是:以后端服務(wù)器的視角來觀察系統(tǒng)的負(fù)載,而非請(qǐng)求發(fā)起方來觀察。最小連接數(shù)法便屬于此類。

          最小連接數(shù)算法比較靈活和智能,由于后端服務(wù)器的配置不盡相同,對(duì)于請(qǐng)求的處理有快有慢,它正是根據(jù)后端服務(wù)器當(dāng)前的連接情況,動(dòng)態(tài)地選取其中當(dāng)前積壓連接數(shù)最少的一臺(tái)服務(wù)器來處理當(dāng)前請(qǐng)求,盡可能地提高后端服務(wù)器的利用效率,將負(fù)載合理地分流到每一臺(tái)機(jī)器。由于最小連接數(shù)設(shè)計(jì)服務(wù)器連接數(shù)的匯總和感知,設(shè)計(jì)與實(shí)現(xiàn)較為繁瑣,此處就不說它的實(shí)現(xiàn)了。


          想進(jìn)大廠的小伙伴請(qǐng)注意,

          大廠面試的套路很神奇,

          早做準(zhǔn)備對(duì)大家更有好處,

          埋頭刷題效率低,

          看面經(jīng)會(huì)更有效率!

          小編準(zhǔn)備了一份 大廠 常問面經(jīng) 匯總集

          大致內(nèi)容包括了: Java 集合、JVM、多線程、并發(fā)編程、設(shè)計(jì)模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat等大廠面試題等、等技術(shù)棧!

          6786a3d099e49d294ce205bccc26d0f6.webp

          剩下的就不會(huì)給大家一展出來了,以上資料按照一下操作即可獲得

          ——將文章進(jìn)行 轉(zhuǎn)發(fā) 評(píng)論 , 關(guān)注公眾號(hào)【Java烤豬皮】 ,關(guān)注后繼續(xù)后臺(tái)回復(fù)領(lǐng)取口令“  666  ”即可免費(fèi)領(lǐng)文章取中所提供的資料。




          1cb8f1b7aa52d1b233a5d63842da2b6a.webp往期精品推薦


          騰訊、阿里、滴滴后臺(tái)試題匯集總結(jié) — (含答案)

          面試:史上最全多線程序面試題!

          最新阿里內(nèi)推Java后端試題

          JVM難學(xué)?那是因?yàn)槟銢]有真正看完整這篇文章


          c4c9771a6418f6d77de370ca7130f8fa.webp 結(jié)束


          關(guān)注作者微信公眾號(hào) —  《JAVA烤豬皮》


          了解了更多java后端架構(gòu)知識(shí)以及最新面試寶典



          看完本文記得給作者點(diǎn)贊+在看哦~~~大家的支持,是作者來源不斷出文的動(dòng)力~

          瀏覽 60
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www.三级网站 | 日日日日人人人夜夜2022 | 一级片黑人 | 亚洲欧美专区 | 黄色爱爱 |