springcloud入门(一)之基本概念、服务提供者及消费者
springcloud入门(二)之eureka服务发现与注册
springcloud入门(四)之feign负载均衡与rest调用
springcloud入门(五)之Hystrix熔断、降级及监控
springcloud入门(六)之Zuul路由网关及过滤
springcloud入门(七)之config配置中心
1 Ribbon负载均衡
1.1 ribbon是什么
- springcloud ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡的工具
- 简单的说ribbon是netfix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将netflix的中间层服务连接在一起。
- ribbon的客户端组件提供一些列完整的配置项:连接超时,重试等等。
- 在配置文件中列出LoadBalance(简称LB:负载均衡)后面所有的机器,ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等等)去连接这些机器。
- 我们也很容易使用ribbon实现自定义的负载均衡算法
1.2 ribbon能干什么
- LB在微服务活分布式集群中经常用的一种应用
- 负载均衡简单来说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)
- 常见的负载均衡软件有nginx,lvs等等
- dubbo、springcloud中均给我们提供了负载均衡,springcloud的负载均衡算法可以自定义
- 负载均衡简单分类:
集中式LB
即在服务的消费方和提供方之间使用独立的LB设施,如Nginx,由该设施负责把访问请求通过某种策略转发至服务的提供方
进程式LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器
ribbon就属于进程式LB,它只是一个类库,集成于消费式进程,消费方通过它来获取到服务提供方的地址
1.3 ribbon实现负载均衡
以下是针对8080消费者工程的调整
pom
在8080消费者工程中新添加两个依赖
<!--导入Ribbon的同时要导入erueka,因为它要发现服务从那里来-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
RestTemplateConfig
目前使用的RestTemplate调用,需要添加注解
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced //Ribbon 只需要加了这个注解,这个RestTemplate就变成了负载均衡
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
yml
添加eureka配置
server:
port: 8080
# eureka配置
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka0.com:8000/eureka/,http://eureka1.com:8100/eureka/,http://eureka2.com:8200/eureka/
启动类
@SpringBootApplication
@EnableEurekaClient //服务启动后注册在eureka上
public class ConsumerDept8080 {
public static void main(String[] args) {
SpringApplication.run(ConsumerDept8080.class, args);
}
}
DeptController
将调用地址该为服务名
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private RestTemplate restTemplate;
//用Ribbon做负载均衡的时候不应该写死地址,地址应该是一个变量,通过服务名来访问
private static final String REST_URL_PREFIX = "http://PROVIDER-DEPT/dept/";
//private static final String REST_URL_PREFIX = "http://localhost:8001/dept/";
@GetMapping("/findAll")
public List<Dept> findAll() {
return restTemplate.getForObject(REST_URL_PREFIX + "findAllDept", List.class);
}
@GetMapping("/findById/{id}")
public Dept findById(@PathVariable("id") int id) {
return restTemplate.getForObject(REST_URL_PREFIX + "findById/" + id, Dept.class);
}
@PostMapping("/addDept")
public int addDept(@RequestBody Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX + "addDept", dept, Integer.class);
}
}
自此 我们的负载均衡已经实现了,但是由于提供者只有一个,那么现实的结果也一样;那我们在分布创建8002和8003两个服务提供者
模拟数据库
整体架构图
为了便于对应这儿将8001的数据库对应为demo1
创建三个数据库demo1、demo2及demo3 数据一样只是dept_source为数据库名以便区分
创建另两个服务提供者
额外在新建两个模块 8002及8003 代码直接copy即可
只需要将配置对应调整
- 三个服务提供者代码一致(启动类根据自己的端口命名)
- pom导包一样
- yml的端口对应数据库 instance-id对应(应用名称不能修改)
8001
server:
port: 8001
mybatis:
mapper-locations: classspath:mapper/*.xml
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo1?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
application:
name: provider-dept
eureka:
client:
service-url:
# 将原本注册在一台的 注册到三台上
#defaultZone: http://localhost:8000/eureka/
defaultZone: http://eureka1.com:8100/eureka/,http://eureka2.com:8200/eureka/,http://eureka0.com:8000/eureka/
instance:
instance-id: springcloud-provider-8001 #修改在Eureka上默认的状态名字
info:
app.name: Damon-springcloud
company.name: www.ccct.com
8002
server:
port: 8002
mybatis:
mapper-locations: classspath:mapper/*.xml
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo2?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
application:
name: provider-dept
eureka:
client:
service-url:
# 将原本注册在一台的 注册到三台上
#defaultZone: http://localhost:8000/eureka/
defaultZone: http://eureka1.com:8100/eureka/,http://eureka2.com:8200/eureka/,http://eureka0.com:8000/eureka/
instance:
instance-id: springcloud-provider-8002 #修改在Eureka上默认的状态名字
info:
app.name: Damon-springcloud
company.name: www.ccct.com
8003
server:
port: 8003
mybatis:
mapper-locations: classspath:mapper/*.xml
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo3?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
application:
name: provider-dept
eureka:
client:
service-url:
# 将原本注册在一台的 注册到三台上
#defaultZone: http://localhost:8000/eureka/
defaultZone: http://eureka1.com:8100/eureka/,http://eureka2.com:8200/eureka/,http://eureka0.com:8000/eureka/
instance:
instance-id: springcloud-provider-8003 #修改在Eureka上默认的状态名字
info:
app.name: Damon-springcloud
company.name: www.ccct.com
项目结构
然后先启动三个注册中心集群;在启动三个提供者者;最后启动消费者
访问打印
访问地址:http://localhost:8080/dept/findAll;多访问几次你会发现是轮询,三个提供者轮流提供数据
从上述的截图信息中可以发现数据来源于不同的数据库
2 Ribbon负载均衡探究
2.1 IRule接口
Ribbon中有一个非常重要的接口IRule接口;该接口基本上实现了所有常用的负载均衡算法
里面有很多负载均衡算法,默认为轮询;如果需要别的算法,只需要要修改配置类中注册的Bean即可
RestTemplateConfig
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced //Ribbon 只需要加了这个注解,这个RestTemplate就变成了负载均衡
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Bean
public IRule myRule(){
//return new RoundRobinRule(); //默认轮询访问
return new RandomRule(); //随机访问
}
}
修改为上述配置后重启8080消费者项目,访问接口多访问几次;就会发现没有规律可循
2.2 自定义负载均衡算法
在配置包中新建负载均衡算法类MyRule
MyRule
复制一个实现算法,修改算法核心代码
public class MyRule extends AbstractLoadBalancerRule {
//每个机器访问3次换下一个 总共3个
private int total = 0;//被调用的次数
private int curIndex = 0;//当前是谁在提供服务
public Server choose(ILoadBalancer lb, Object key){
if (lb == null) {
return null;
} else {
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
// 获得还可用的服务
List<Server> upList = lb.getReachableServers();
// 获得全部的服务
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//-------核心代码------------
if(total<=3){
total++;
}else {
total = 0;
curIndex++;
if(curIndex>=serverCount){
curIndex=0;
}
}
server = upList.get(curIndex);
//---------核心代码----------
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(),key);
}
}
RestTemplateConfig
修改注册bean
@Bean
public IRule myRule(){
//return new RoundRobinRule(); //默认轮询访问
//return new RandomRule(); //随机访问
return new MyRule();
}
启动类
添加注解,让项目启动时加载我们自定义的规则即可
@SpringBootApplication
@EnableEurekaClient //服务启动后注册在eureka上
//项目启动时添加我们自定义的规则
@RibbonClient(name = "PROVIDER-DEPT",configuration= MyRule.class)
public class ConsumerDept8080 {
public static void main(String[] args) {
SpringApplication.run(ConsumerDept8080.class, args);
}
}
重启消费者8080项目,访问http://localhost:8080/dept/findAll;你会发现每3次就会变化数据来源。