Load Balancing,即負載均衡, 是一種計算機技術,用來在多個計算機(計算機集群)、網絡連接、CPU、磁盤驅動器或其他資源中分配負載,以達到最優化資源使用、最大化吞吐率、最小化響應時間、同時避免過載的目的。
實現負載均衡的兩大方式:
一、服務端負載均衡:Nginx實現負載均衡
負載均衡是Nginx常用的一個功能,是在服務端通過的負載均衡算法實現的,Nginx也要具有很多不同的負載均衡策略。負載均衡的意思是將請求分攤到不同的服務器上執行,例如:web服務器、企業內部服務器等等,這樣可以提高系統的吞吐量和請求的響應速度。
常見的負載均衡算法有三策略(這里只挑選三種來說明):
- 輪詢(默認)
#每個請求按時間順序逐一分配到不同的后端服務器,如果后端服務器down掉,能自動剔除
upstream test {
server localhost:8080;
server localhost:8081;
}
server {
listen 8081;
server_name localhost;
client_max_body_size 1024M;
location / {
proxy_pass http://test;
proxy_set_header Host $host:$server_port;
}
}
這里模擬了兩台tomcat服務器,采用不同的端口8080、8081,當兩台服務器之間有一個服務器處於不能訪問的狀態(服務器掛了),請求就不會打到該服務器,所有避免了一台服務器掛了而直接影響到整個系統業務不能正常使用的情況,由於Nginx默認是采用RR策略,所有不需要替他更多的配置。
2. 權重
#指定輪詢幾率,weight和訪問比率成正比,用於后端服務器性能不均的情況upstream test {
server localhost:8080 weight=8;
server localhost:8081 weight=2;
}
- ip_hash
upstream test {
ip_hash;
server localhost:8080;
server localhost:8081;
}
二、客戶端負載均衡:手寫負載均衡算法實現負載均衡
假設現在有用戶中心服務 user-center 和內容中心 content-center 服務,內容中心需要調用用戶中心的端口服務,由於在並發較高的環境下,這是要在內容中心(客戶端)實現一個負載均衡算法來實現調用user-center(服務端)服務,如何手寫一個負載均衡算法來實現呢?
ShareController
@RestController
@RequestMapping("/shares")
public class ShareController {
@Autowired
private ShareService shareService;
@GetMapping("/{id}")
public ShareDTO findById(@PathVariable Integer id) {
return this.shareService.findById(id);
}
}
ShareServiceImpl
@Slf4j
@Service
public class ShareServiceImpl implements ShareService {
@Autowired
private ShareMapper shareMapper;
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@Override
public ShareDTO findById(Integer id) {
//1.獲取分享詳情
Share share = shareMapper.selectByPrimaryKey(id);
//2.發布人的id
Integer userId = share.getUserId();
//拿到用戶中心所有實例的信息
List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
//獲取所有用戶中心實例請求地址
List<String> targetUrls = instances.stream()
.map(instance -> instance.getUri().toString() + "/users/1")
.collect(Collectors.toList());
//設計隨機算法,隨機生成i
int i = ThreadLocalRandom.current().nextInt(targetUrls.size());
String targetUrl = targetUrls.get(i);
log.info("請求目標地址:{}", targetUrl);
//調用用戶微服務的/users/{userId}?使用RestTemplate,RestTemplate會幫我們自動轉換成UserDTO
UserDTO userDTO = this.restTemplate.getForObject(
"http://user-center/users/1",
UserDTO.class);
//3.消息的裝配
ShareDTO shareDTO = new ShareDTO();
BeanUtils.copyProperties(share, shareDTO);
shareDTO.setWxNickname(userDTO.getWxNickname());
return shareDTO;
}
}
我們通過log打印出請求目標地址如下,我們可以清楚的看到分別打到8080、8081端口,實現了客戶端的負載均衡:
三、使用Spring Cloud Ribbon實現負載均衡
Ribbon組成
Ribbon是比較靈活的,它對所有的組件值都提供了接口,如果你對這些值不滿意,你可以基於Ribbon的接口自定義來實現。
接口 | 作用 | 默認值 |
IClientConfig | 服務配置 | DefaultClientConfigImpl |
IRule | 負載均衡規則,選擇實例 | ZoneAvoidanceRule |
IPing | 篩選掉ping不通的實例 | DumyPing (該類什么不干,認為每個實例都可用,都能ping通) |
ServerList | 交給Ribbon的實例列表 | Ribbon:ConfigurationBasedServerList Spring Cloud Alibaba: NacosServerList |
ServerListFilter | 過濾掉不符合條件的實例 | ZonePreferenceServerListFilter |
ILoadBalancer | Ribbon負載均衡的入口 | ZoneAwareLoadBalancer |
ServerListUpdater | 更新交給Ribbon的List的策略 | PollingServerListUpdater |
Ribbon八大負載均衡規則介紹
規則名稱 | 特點 |
---|---|
AvailabilityFilteringRule |
過濾掉一直連接失敗的被標記為circuit tripped(電路跳閘)的后端Service,並過濾掉那些高並發的后端Server或者使用一個AvailabilityPredicate來包含過濾Server的邏輯,其實就是檢查status的記錄的各個Server的運行狀態 |
BestAvailableRule |
選擇一個最小的並發請求的Server,逐個考察Server,如果Server被tripped了,則跳過 |
RandomRule |
隨機選擇一個Server |
ResponseTimeWeightedRule |
已廢棄,作用同WeightedResponseTimeRule |
RetryRule |
對選定的負責均衡策略機上充值機制,在一個配置時間段內當選擇Server不成功,則一直嘗試使用subRule的方式選擇一個可用的Server |
RoundRobinRule |
輪詢選擇,輪詢index,選擇index對應位置Server |
WeightedResponseTimeRule |
根據相應時間加權,相應時間越長,權重越小,被選中的可能性越低 |
ZoneAvoidanceRule |
(默認是這個)負責判斷Server所Zone的性能和Server的可用性選擇Server,在沒有Zone的環境下,類似於輪詢(RoundRobinRule ) |
引入Ribbon
配置在RestTemplate添加@LoadBalanced負載均衡注解,這時候會為RestTemplate整合Ribbon:
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
使用Ribbon我們不需要關注負載均衡算法的實現,因為Ribbon已經涵蓋了豐富的負載均衡策略,我們只需要給Ribbon注冊一個實例列表,Ribbon會自動幫我們選擇具體的某個實例,引進Ribbon代碼演進如下:
@Override
public ShareDTO findById(Integer id) {
//1.獲取分享詳情
Share share = shareMapper.selectByPrimaryKey(id);
//2.發布人的id
Integer userId = share.getUserId();
//調用用戶微服務的/users/{userId}?使用RestTemplate,RestTemplate會幫我們自動轉換成UserDTO
UserDTO userDTO = this.restTemplate.getForObject(
"http://user-center/users/1",
UserDTO.class);
//3.消息的裝配
ShareDTO shareDTO = new ShareDTO();
BeanUtils.copyProperties(share, shareDTO);
shareDTO.setWxNickname(userDTO.getWxNickname());
return shareDTO;
}
引入Ribbon之后我們的代碼簡化了很多,這是后訪問 http://localhost:8888/shares/1 也能得到結果,表示我們使用Ribbon實現實現了負載均衡: