客户端负载均衡架构
- Ribbon在工作时分成两步 :
- 第一步先选择EurekaServer,它优先选择在同一个区域内负载均衡较少的Server。
- 第二步在根据用户指定的策略,在从server去到的服务注册列表中选择一个地址。
- 其中Ribbon提供了多种策略,比如轮询(默认),随机和根据响应时间加权重…等等
测试
- 3个数据库
- 3个Eureka注册中心
- 3个服务提供者
- 1个服务消费者
3个数据库 db01、db02、db03
CREATE TABLE dept(
deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
dname VARCHAR(60),
db_source VARCHAR(60)
);
INSERT INTO dept(dname,db_source) VALUES('开发部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('人事部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('财务部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('市场部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('运维
3个Eureka注册中心
- 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
- 配置文件
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
- 启动服务类
@SpringBootApplication
@EnableEurekaServer
public class Eureka_7001 {
public static void main(String[] args) {
SpringApplication.run(Eureka_7001.class,args);
}
}
- 其他两个Eureka注册中心一样,只需要改动
- 端口
- 与另外两个Eureka注册中心互相注册的defaultZone
- 启动类的名字
3个服务提供者
- 因为服务提供者与数据库直接连接,所以配置比较复杂
- 导入依赖
<dependencies>
<!--引入自定义的模块,我们就可以使用这个模块中的类了-->
<dependency>
<groupId>com.qk</groupId>
<artifactId>springcloud-01-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
- SSM三层
- mapper层
@Mapper
@Repository
public interface DeptMapper {
public boolean addDept(Dept dept); //添加一个部门
public Dept queryById(Long id); //根据id查询部门
public List<Dept> queryAll(); //查询所有部门
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qk.springcloud.mapper.DeptMapper">
<insert id="addDept" parameterType="Dept">
insert into dept (dname,db_source) values (#{dname},DATABASE());
</insert>
<select id="queryById" resultType="Dept" parameterType="Long">
select deptno,dname,db_source from dept where deptno = #{deptno};
</select>
<select id="queryAll" resultType="Dept">
select deptno,dname,db_source from dept;
</select>
</mapper>
- service层
public interface DeptService {
public boolean addDept(Dept dept); //添加一个部门
public Dept queryById(Long id); //根据id查询部门
public List<Dept> queryAll(); //查询所有部门
}
@Service
public class DeptServiceImpl implements DeptService{
//自动注入
@Autowired
private DeptMapper deptMapper;
@Override
public boolean addDept(Dept dept) {
return deptMapper.addDept(dept);
}
@Override
public Dept queryById(Long id) {
return deptMapper.queryById(id);
}
@Override
public List<Dept> queryAll() {
return deptMapper.queryAll();
}
}
- controller层
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private DeptService service;
@Autowired
private DiscoveryClient client;
// @RequestBody
// 如果参数是放在请求体中,传入后台的话,那么后台要用@RequestBody才能接收到
@PostMapping("/add")
public boolean addDept(Dept dept) {
return service.addDept(dept);
}
@GetMapping("/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return service.queryById(id);
}
@GetMapping("/list")
public List<Dept> queryAll() {
return service.queryAll();
}
@GetMapping("/discovery")
public Object discovery(){
//获得微服务列表清单
List<String> list = client.getServices();
System.out.println("client.getServices()==>"+list);
//得到一个具体的微服务!
List<ServiceInstance> serviceInstanceList =
client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance serviceInstance : serviceInstanceList) {
System.out.println(
serviceInstance.getServiceId()+"\t"+
serviceInstance.getHost()+"\t"+
serviceInstance.getPort()+"\t"+
serviceInstance.getUri()
);
}
return this.client;
}
}
- 配置文件
server:
port: 8001
mybatis:
type-aliases-package: com.qk.springcloud.pojo #扫描api Moudle 下的实体类
mapper-locations: classpath:mybatis/mapper/*.xml #扫描provider Moudle 下的实体类映射文件
config-location: classpath:mybatis/mybatis-config.xml #扫描provider Moudle 下的mybatis配置文件
spring:
application:
name: springcloud-provider-dept #给项目起个名字
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8
username: root
password: root
# 将服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: springcloud-8001
prefer-ip-address: true # true访问路径可以显示IP地址
info:
app.name: qk-springcloud
company.name: www.qk.com
- 启动类
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
- 另外两个服务提供者与上面的代码一样,只需要改动
- 配置文件中连接数据库的url里的数据库,3个分别为db01、db02、db03
- 配置文件中Eureka配置的instance-id,3个分别为springcloud-800、springcloud-8002、springcloud-8003
1个服务消费者
- 导入依赖
<dependencies>
<dependency>
<groupId>com.qk</groupId>
<artifactId>springcloud-01-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--ribbon-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
<!--消费方从服务注册中心获知有哪些地址可用:因此也需要Eureka-->
<!--Eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
</dependencies>
- 消费者的接口
@RestController
@RequestMapping("/consumer")
public class DeptConsumerController {
//理解:消费者,不应该有service层
// 远程调用 调用http://localhost:8001下的数据
// 使用RestTemplate访问restful接口非常的简单粗暴且无脑,提供多种便捷访问远程http服务的方法,简单的restful服务模板
// getForObject (请求地址 , 实体 ,响应类型)
// (url,requestMap,ResponseBean.class) 这三个参数分别代表
// REST请求地址,请求参数,Http响应转换 被 转换成的对象类型
@Autowired
private RestTemplate restTemplate;
//private static final String REST_URL_PREFIX = "http://localhost:8001";
//ribbon 通过服务名来访问
private static final String REST_URL_PREFIX = "http://springcloud-provider-dept";
@RequestMapping("/dept/add")
public boolean add(Dept dept){
return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
}
@RequestMapping("/dept/get/{id}")
public Dept get(@PathVariable("id") Long id){
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class);
}
@RequestMapping("/dept/list")
public List<Dept> list(){
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
}
/**
* 以前,消费者访问生产者,访问的是固定的生产者的url,
* 在使用负载均衡后,url应该作为一个变量,通过服务名(生产者的名字)来访问,
* 身为消费者,自身的defaultZone,设置了3个消费路径(注册中心),想要消费服务可以通过三个服务注册中心,消费
* 一个生产者服务也在三个服务注册中心注册
* 所以消费者想要消费,先通过负载均衡选择一个注册中心,然后根据服务名消费对应的服务
*/
}
- 接口配置文件
@Configuration
public class RestTemplateBean {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
/**
* 配置负载均衡实现RestTemplate。
* 原本是通过RestTemplate远程访问指定url的接口,现在只需要在RestTemplate上添加@LoadBalanced就可以实现Ribbon负载均衡
*/
}
- 配置文件
server:
port: 80
eureka:
client:
register-with-eureka: false # 不向注册中心注册自己,作为消费者只需要去注册中心消费服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ # 可以从哪几个注册中心去取
- 启动类
//Eureka和Ribbon整合以后,客户端可以根据服务名称直接调用,不用关心IP地址和端口号
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class,args);
}
}
搭建完毕
测试结果
tips
- 服务的名字在Eureka注册中心会被自动转换为大写,在消费者里,服务名无论大小写都不影响获取指定服务