目錄
負載均衡
問題
步驟
現象?
什么是負載均衡?
負載均衡的一些實現
服務端負載均衡
客戶端負載均衡
使用Spring Cloud LoadBalance實現負載均衡
負載均衡策略
?編輯??編輯LoadBalancer原理
服務部署
準備環境和數據
服務構建打包
啟動服務
上傳Jar包到云服務器
啟動服務
遠程調用訪問?
負載均衡
問題
上面是我們之前的代碼,是根據應用名稱獲取了服務實例列表,并從列表中選擇了一個服務實例。
那如果一個服務對應多個實例呢?流量是否可以合理的分配到多個實例呢?
我們再啟動兩個product-service示例。
步驟
打開View->Tool Windows->Services
選中ProductServiceApplication,然后右鍵,選擇Copy Configuration
然后改名,并點擊Modify options
然后點擊Add VM options
然后添加-Dserver.port=9091,然后Apply,OK
然后再重復上述步驟,再添加一個服務實例。
現象?
啟動上述所有實例后,可以在Eureka網站頁面看到:
此時多次訪問"http://127.0.0.1:8080/order/1",然后查看IDEA上的日志,可以看到,我們剛剛的多次訪問,都訪問到了同一臺機器上,即第一個注冊到Eureka的服務實例端口號為9092的機器。
這肯定不是我們想要的結果,我們啟動多個服務實例,是希望可以分擔其它機器的負荷,那么如何實現呢?
我們可以修改一下之前的order-service中的OrderService代碼,把只請求服務列表第一臺機器修改為輪詢請求服務列表中的機器。
修改后的order-service中的OrderService代碼如下:
package order.service;import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import order.mapper.OrderMapper;
import order.model.OrderInfo;
import order.model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;@Slf4j
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient;//計數器private AtomicInteger count = new AtomicInteger(1);private List<ServiceInstance> instances;@PostConstructpublic void init(){//從Eureka中獲取服務列表instances = discoveryClient.getInstances("product-service");}public OrderInfo selectOrderById(Integer orderId){OrderInfo orderInfo = orderMapper.selectOrderById(orderId);//計算輪流的實例idnexint index= count.getAndIncrement() % instances.size();//獲取實例String uri = instances.get(index).getUri().toString();//拼接urlString url = uri+"/product/"+orderInfo.getProductId();log.info("遠程調用url:{}", url);ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}}
重啟order-service,再次多次訪問"127.0.0.1:8080/order/1",可以看到每個服務實例都有被請求到:
通過?志可以看到, 請求被均衡的分配在了不同的實例上, 這就是負載均衡.
什么是負載均衡?
負載均衡(Load Balance,簡稱 LB) , 是?并發, ?可?系統必不可少的關鍵組件.
當服務流量增?時, 通常會采?增加機器的?式進?擴容, 負載均衡就是?來在多個機器或者其他資源中, 按照?定的規則合理分配負載.
負載均衡的一些實現
上?的例?中, 我們只是簡單的對實例進?了輪詢, 但真實的業務場景會更加復雜. ?如根據機器的配置進?負載分配, 配置?的分配的流量?, 配置低的分配流量低等.
服務多機部署時, 開發?員都需要考慮負載均衡的實現, 所以也出現了?些負載均衡器, 來幫助我們實現負載均衡.
負載均衡分為服務端負載均衡和客?端負載均衡.
服務端負載均衡
在服務端進?負載均衡的算法分配.
?較有名的服務端負載均衡器是Nginx. 請求先到達Nginx負載均衡器, 然后通過負載均衡算法, 在多個服務器之間選擇?個進?訪問.
客戶端負載均衡
在客?端進?負載均衡的算法分配.
把負載均衡的功能以庫的?式集成到客?端, ?不再是由?臺指定的負載均衡設備集中提供.
?如Spring Cloud的Ribbon, 請求發送到客?端, 客?端從注冊中?(?如Eureka)獲取服務列表, 在發送請求前通過負載均衡算法選擇?個服務器,然后進?訪問.
Ribbon是Spring Cloud早期的默認實現, 由于不維護了, 所以最新版本的Spring Cloud負載均衡集成的是Spring Cloud LoadBalancer(Spring Cloud官?維護)。
客?端負載均衡和服務端負載均衡最?的區別在于服務清單所存儲的位置。
Spring Cloud LoadBalance
SpringCloud 從 2020.0.1 版本開始, 移除了Ribbon 組件,使?Spring Cloud LoadBalancer 組件來代替 Ribbon 實現客?端負載均衡。
使用Spring Cloud LoadBalance實現負載均衡
1. 給 RestTemplate 這個Bean添加 @LoadBalanced 注解就可以
package order.config;import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
public class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}
}
2.修改后的order-service中的OrderService代碼如下:
修改IP為服務端名稱。
package order.service;import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import order.mapper.OrderMapper;
import order.model.OrderInfo;
import order.model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;@Slf4j
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;public OrderInfo selectOrderById(Integer orderId){OrderInfo orderInfo = orderMapper.selectOrderById(orderId);String url = "http://product-service/product/"+orderInfo.getProductId();log.info("遠程調用url:{}", url);ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}
}
此時再次多次訪問"127.0.0.1:8080/order/1",可以看到每個服務實例都有被請求到,且比例差不多:
負載均衡策略
負載均衡策略是?種思想, ?論是哪種負載均衡器, 它們的負載均衡策略都是相似的. Spring Cloud
LoadBalancer 僅?持兩種負載均衡策略: 輪詢策略 和 隨機策略。
1. 輪詢(Round Robin): 輪詢策略是指服務器輪流處理??的請求. 這是?種實現最簡單, 也最常?的策略. ?活中也有類似的場景, ?如學校輪流值?, 或者輪流打掃衛?.
2. 隨機選擇(Random): 隨機選擇策略是指隨機選擇?個后端服務器來處理新的請求.
官方介紹
翻譯:
Spring Cloud提供了自己的客戶端負載均衡器抽象和實現。對于負載平衡機制,添加了ReactiveLoadBalancer接口,并為其提供了基于輪轉和隨機的實現。為了讓實例從反應式ServiceInstanceListSupplier中進行選擇,使用了該接口。目前,我們支持ServiceInstanceListSupplier的基于服務發現的實現,該實現使用類路徑中可用的發現客戶端從服務發現中檢索可用實例。通過將Spring.Cloud.LoadBalancer.enabled的值設置為false,可以禁用Spring Cloud LoadBalancer。
1. 定義隨機算法對象, 通過 @Bean 將其加載到 Spring 容器中
此處使?Spring Cloud LoadBalancer提供的 RandomLoadBalancer
package order.config;import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;public class CustomLoadBalancerConfiguration {@BeanReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);}
}
注意: 該類需要滿?:
1. 不? @Configuration 注釋
2. 在組件掃描范圍內?
2. 使? @LoadBalancerClient 或者 @LoadBalancerClients 注解
在 RestTemplate 配置類上?, 使? @LoadBalancerClient 或 @LoadBalancerClients 注解, 可以對不同的服務提供?配置不同的客?端負載均衡算法策略.
由于我們只有?個客戶端服務提供者, 所以使?@LoadBalancerClient。
package order.config;import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@LoadBalancerClient(name = "product-service", configuration = CustomLoadBalancerConfiguration.class)
@Configuration
public class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}
}
@LoadBalancerClient 注解說明
1. name: 該負載均衡策略對哪個服務?效(服務提供?)
2. configuration : 該負載均衡策略 ?哪個負載均衡策略實現.?
此時再次多次訪問"127.0.0.1:8080/order/1",可以看到每個服務實例都有被請求到,且比例隨機:
?
LoadBalancer原理
LoadBalancer 的實現, 主要是 LoadBalancerInterceptor , 這個類會對 RestTemplate 的請
求進?攔截, 然后從Eureka根據服務id獲取服務列表,隨后利?負載均衡算法得到真實的服務地址信息,替換服務id。
我們來看看源碼實現:
可以看到這?的intercept?法, 攔截了??的HttpRequest請求,然后做了?件事:
1. request.getURI() 從請求中獲取uri, 也就是 http://product-service
2. service/product/1001 originalUri.getHost() 從uri中獲取路徑的主機名, 也就是服務id, product-service
3. loadBalancer.execute 根據服務id, 進?負載均衡, 并處理請求?
根據serviceId,和負載均衡策略, 選擇處理的服務:?
?根據serviceId,和負載均衡策略, 選擇處理的服務:
服務部署
準備環境和數據
安裝好JDK17和MySQL,并在MySQL中建表且存放好數據信息。
修改配置文件中的數據庫密碼。
服務構建打包
采?Maven打包, 需要對3個服務分別打包:
eureka-server, order-service, product-service
啟動服務
上傳Jar包到云服務器
第一次上傳需要安裝 lrzsz
Centos:
yum install lrzsz
Ubantu:
apt install lrzsz?
直接拖動文件到xshell窗口,上傳成功。
啟動服務
#后臺啟動eureka-server, 并設置輸出?志到logs/eureka.log
nohup java -jar eureka-server.jar >logs/eureka.log &
#后臺啟動order-service, 并設置輸出?志到logs/order.log
nohup java -jar order-service.jar >logs/order.log &
#后臺啟動product-service, 并設置輸出?志到logs/order.log
nohup java -jar product-service.jar >logs/product-9090.log &
再多啟動兩臺product-service實例
#啟動實例, 指定端?號為9091
nohup java -jar product-service.jar --server.port=9091 >logs/product-9091.log &
#啟動實例, 指定端?號為9092
nohup java -jar product-service.jar --server.port=9092 >logs/product-9092.log &?
遠程調用訪問?
可以看到,能夠正常訪問并響應。