問題引入
我們一個服務可能會進行多機部署,也就說多臺服務器組成的集群共同對外提供一致的服務,那么我們的微服務的代碼就需要拷貝多份,部署到不同的機器上。
我們使用 IDEA 來開啟多個相同的服務
這里以 product-service 為例:
找到 services 按鍵,點開來:
找到我們需要多機部署的服務,右鍵然后點擊 Copy Configuration ,復制這個服務的所有配置。
之后就是給我們新的服務命名,然后點擊 Modify options 修改配置信息。
點擊 Add VM options
在 VM options 添加端口信息:
-Dserver.port=端口號
注意由于我們是在本機部署多個服務,所以端口號需要修改,避免端口的沖突
最后點擊 Apply ,然后 OK,就可以創建成功了。
然后我們啟動所有的服務,在 Eureka 界面可以看到我們的 product-service 有多個注冊信息
這里使用 order-service 來注冊發現 product-service:
@Slf4j
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient;public OrderInfo getOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectById(orderId);
// String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();//從 eureka 中獲取服務信息List<ServiceInstance> instances = discoveryClient.getInstances("product-service");String uri = instances.get(0).getUri().toString();String url = uri + "/product/" + orderInfo.getProductId();log.info("訪問的資源路徑:" + url);ProductDetailInfo productDetailInfo = restTemplate.getForObject(url, ProductDetailInfo.class);orderInfo.setProductDetailInfo(productDetailInfo);return orderInfo;}
}
但是我們發現多個請求,即使 product-service 有三個一樣的服務,但是使用的都是 9092,如果我們的請求類一旦上升,就可能會導致 9092這個服務器崩潰,我們應該要做到均衡地將這些請求發送給 product-service 的三個不同的服務器中,這就是我們本章要提到的負載均衡
負載均衡
概念
負載均衡(Load Balance,簡稱LB),是高并發, 高可用系統必不可少的關鍵組件.
當服務流量增大時,通常會采用增加機器的方式進行擴容,負載均衡就是用來在多個機器或者其他資源中,按照一定的規則合理分配負載.
分類
負載均衡分為服務端負載均衡和客戶端負載均衡
服務端負載均衡,主要使用的是負載均衡器 Nginx,請求先到達負載均衡器,然后通過負載均衡算法在多個服務器之間選擇一個進行訪問
客戶端負載均衡:
將負載均衡的功能以庫的方式集成到客戶端中,而不是由一臺負載均衡設備集中提供
模擬實現
這里我們使用原子類,避免發生線程不安全,通過原子類的數值和我們獲取到的服務注冊列表的長度進行取余獲取下標,以輪詢的方式來訪問 product-service 服務端。
@Slf4j
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient;private static final AtomicInteger count = new AtomicInteger(1);private List<ServiceInstance> instances;@PostConstructpublic void init() {instances = instances = discoveryClient.getInstances("product-service");}public OrderInfo getOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectById(orderId);
// String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();int index = count.getAndIncrement() % instances.size();String uri = instances.get(index).getUri().toString();String url = uri + "/product/" + orderInfo.getProductId();log.info("訪問的資源路徑:" + url);ProductDetailInfo productDetailInfo = restTemplate.getForObject(url, ProductDetailInfo.class);orderInfo.setProductDetailInfo(productDetailInfo);return orderInfo;}
}
但是這個實現方式也有缺陷,就是如果后續有新的服務注冊或者舊的服務崩潰的話,我們的 order-service 就不會獲得到最新的注冊列表,導致后續出現 bug
即使你采用下面的方式,每次 order-service 處理請求都要進行重新獲取服務列表,也還是會出現 bug ,那就是如果舊的服務崩潰,可能無法即使獲取,導致出現ConnectException
java.net.ConnectException: Connection refused: connect
@Slf4j
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient;private static final AtomicInteger count = new AtomicInteger(1);// private List<ServiceInstance> instances;
//
// @PostConstruct
// public void init() {
// instances = instances = discoveryClient.getInstances("product-service");
// }public OrderInfo getOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectById(orderId);
// String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();List<ServiceInstance> instances = discoveryClient.getInstances("product-service");int index = count.getAndIncrement() % instances.size();String uri = instances.get(index).getUri().toString();String url = uri + "/product/" + orderInfo.getProductId();log.info("訪問的資源路徑:" + url);ProductDetailInfo productDetailInfo = restTemplate.getForObject(url, ProductDetailInfo.class);orderInfo.setProductDetailInfo(productDetailInfo);return orderInfo;}
}
LoadBalance
SpringCloud從2020.0.1版本開始,移除了Ribbon組件,使用Spring Cloud LoadBalancer組件來代替Ribbon實現客戶端負載均衡
使用
添加注解 @LoadBalanced
在 RestTemplate 上添加 @LoadBalanced
將 RestTemplate 交給 LoadBalance 管理
@Configuration
public class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}
}
修改遠程調用的代碼
修改 String url = “http://product-service/product/” + orderInfo.getProductId();
將 ip 和端口號修改為 服務名稱【product-service】,這樣 LoadBalance會自動為我們提供服務端
@Slf4j
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient;public OrderInfo getOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectById(orderId);String url = "http://product-service/product/" + orderInfo.getProductId();log.info("訪問的資源路徑:" + url);ProductDetailInfo productDetailInfo = restTemplate.getForObject(url, ProductDetailInfo.class);orderInfo.setProductDetailInfo(productDetailInfo);return orderInfo;}
}
負載均衡策略
負載均衡策略是一種思想,無論是哪種負載均衡器,它們的負載均衡策略都是相似的 SpringCloud
LoadBalancer僅支持兩種負載均衡策略:輪詢策略和隨機策略
1.輪詢(RoundRobin):輪詢策略是指服務器輪流處理用戶的請求.這是一種實現最簡單,也最常用的策略.生活中也有類似的場景,比如學校輪流值日,或者輪流打掃衛生.
2.隨機選擇(Random):隨機選擇策略是指隨機選擇一個后端服務器來處理新的請求
自定義負載均衡策略
SpringCloud LoadBalancer默認負載均衡策略是輪詢策略,實現是RoundRobinLoadBalancer,如果服務的消費者如果想采用隨機的負載均衡策略,也非常簡單。
官方鏈接:SpringCloud LoadBalancer
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);}
}
CustomLoadBalancerConfiguration 這個類不用加類注解【@Configuration】
因為這個類是在組件的掃描范圍內
@LoadBalancerClient(name = "product-service", configuration = CustomLoadBalancerConfiguration.class)
@Configuration
public class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}
}
我們在 RestTemplate 上添加 @LoadBalancerClient 注解,將服務名稱和負載策略填寫進去