客户端与服务端负载均衡
添加负载均衡功能
Ribbon-Consumer的Controller层
@RestController
public class Controller {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/sayHi")
public String sayHi()
{
return restTemplate.getForObject("http://eureka-client/sayHi",String.class);
}
}
启动类
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonConsumerApplication {
//添加@loadBalance使RestTemplate实现负载均衡
@Bean
@LoadBalanced
public RestTemplate template()
{
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(RibbonConsumerApplication.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
}
启动两个Eureka-Client端口分别为30000和30001,再启动一个Ribbon-Consumer验证负载均衡。
负载均衡策略配置
负载均衡配置
第一种
@Configuration
public class RibbonConfiguration {
@Bean
public IRule defaultLBStragety()
{
return new RandomRule();
}
}
第二种(优先于第三种)
@Configuration
@RibbonClient(name = "eureka-client",configuration = com.netflix.loadbalancer.RandomRule.class)
public class RibbonConfiguration {
}
第三种
spring.application.name=ribbon-consumer
server.port=30001
eureka.client.serviceUrl.defaultZone=http://localhost:20000/eureka/
eureka-client.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
自定义负载均衡策略
自定义MyRule类,实现了哈希一致性算法
package com.imooc.springcloud.rules;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;
import lombok.NoArgsConstructor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Created by 半仙.
*/
@NoArgsConstructor
public class MyRule extends AbstractLoadBalancerRule implements IRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes())
.getRequest();
String uri = request.getServletPath() + "?" + request.getQueryString();
return route(uri.hashCode(), getLoadBalancer().getAllServers());
}
public Server route(int hashId, List<Server> addressList) {
if (CollectionUtils.isEmpty(addressList)) {
return null;
}
TreeMap<Long, Server> address = new TreeMap<>();
addressList.stream().forEach(e -> {
// 虚化若干个服务节点,到环上
for (int i = 0; i < 8; i++) {
long hash = hash(e.getId() + i);
address.put(hash, e);
}
});
long hash = hash(String.valueOf(hashId));
SortedMap<Long, Server> last = address.tailMap(hash);
// 当request URL的hash值大于任意一个服务器对应的hashKey,
// 取address中的第一个节点
if (last.isEmpty()) {
address.firstEntry().getValue();
}
return last.get(last.firstKey());
}
public long hash(String key) {
MessageDigest md5;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] keyByte = null;
try {
keyByte = key.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
md5.update(keyByte);
byte[] digest = md5.digest();
long hashCode = ((long) (digest[2] & 0xFF << 16))
| ((long) (digest[1] & 0xFF << 8))
| ((long) (digest[0] & 0xFF));
return hashCode & 0xffffffffL;
}
}
切换我们自定义的Rule规则
package com.imooc.springcloud;
import com.imooc.springcloud.rules.MyRule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by 半仙.
*/
@Configuration
@RibbonClient(name = "eureka-client", configuration = MyRule.class)
public class RibbonConfiguration {
@Bean
public IRule defaultLBStrategy() {
return new RandomRule();
}
}
Feign组件
Eureka调用方式:http://ip:port/path
ribbon调用方式:http://serviceName/path
使用Feighn的目的:简化远程调用。
使用FeignDemo
启动注册中心和一个Eureka客户端,端口为30000
application.properties
spring.application.name=feign-consumer
server.port=40001
eureka.client.serviceUrl.defaultZone=http://localhost:20000/eureka/
Contoller
package com.imooc.springcloud;
import com.netflix.discovery.converters.Auto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Controller {
@Autowired
private IService service;
@GetMapping("/sayHi")
public String sayHi(){
return service.sayHi();
}
}
启动类
package com.imooc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignConsumerApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(FeignConsumerApplication.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
}
IService接口
package com.imooc.springcloud;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient("eureka-client")
public interface IService {
@GetMapping("/sayHi")
String sayHi();
}
Feign结构改造
feign-client模块
Controller
package com.imooc.springcloud;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class Controller implements IService{
@Value("${server.port}")
private String port;
@Override
public String sayHi() {
return "This is " + port;
}
@Override
public Friend sayHiPost(Friend friend) {
log.info("You are " + friend.getName());
friend.setPort(port);
return friend;
}
}
application.properties
spring.application.name=feign-client
server.port=40002
eureka.client.serviceUrl.defaultZone=http://localhost:20000/eureka/
feign-client-intf模块
IService
package com.imooc.springcloud;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient("feign-client")
public interface IService {
@GetMapping("/sayHi")
public String sayHi();
@PostMapping("/sayHi")
public Friend sayHiPost(@RequestBody Friend friend);
}
fegin-consumer-advance模块
package com.imooc.springcloud;
import com.netflix.discovery.converters.Auto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class Controller {
@Autowired
private IService service;
@GetMapping("sayHi")
public String sayHi()
{
return service.sayHi();
}
@PostMapping("sayHi")
public Friend sayHi2(Friend friend)
{
return service.sayHiPost(friend);
}
}
application.properties
spring.application.name=feign-consumer-advanced
server.port=40003
eureka.client.serviceUrl.defaultZone=http://localhost:20000/eureka/
改造模块最大的区别就是创建一个特定的模块用来存放请求接口,并在在此接口声明需要访问的Application name,并在每个方法上进行声明路由规则,客户端直接使用实现此接口的各个方法,消费端直接使用这个接口访问里面的方法即可。
配置重试和超时策略
先打开注册中心和三个客户端。
消费端配置文件application.properties
spring.application.name=feign-consumer-advanced
server.port=40001
spring.main.allow-bean-definition-overriding=true
eureka.client.serviceUrl.defaultZone=http://localhost:20000/eureka/
# 每台机器最大重试次数
feign-client.ribbon.MaxAutoRetries=2
# 可以再重试几台机器
feign-client.ribbon.MaxAutoRetriesNextServer=2
# 连接超时
feign-client.ribbon.ConnectTimeout=1000
# 业务处理超时
feign-client.ribbon.ReadTimeout=2000
# 在所有HTTP Method进行重试
feign-client.ribbon.OkToRetryOnAllOperations=true
当我们的请求的方法超过了我们所设置的是时间,比如连接时间超过一秒,执行业务的时间超过2秒,我们都会重新跳到另一台机器进行重试,每台 重试次数为2,重试的机器数为2。