“ 技不压身,come on gays。” —— 23号老板
近年来,所谓“容器化”、“微服务”的关键字眼在互联网的业内出现频次颇多,也渐渐成为了企业级开发项目的必备良药。所以,作为一位后端程序猿,就不得不跟紧潮流,着手了解,进行开发和深入(主要是为了不被拉入“裁员”大军
)。因此,我们有必要对上述的开发技术有所了解。今天,我们就以一个简单的Demo对Springcloud中的服务注册、负载均衡做一个介绍。
一、概念引入
什么是微服务?
首先微服务并没有一个官方的定义,想要直接描述微服务比较困难。从对比传统的Web应用对比来看,微服务的架构手段可以简单理解为将传统的大型Web项目根据不同的业务或者模块划分,拆分成多个小型应用,进而通过拆分后的多个子系统共同组建运行,组合成为一个大的整体系统对外提供服务。微服务概念产生之后,解决的一大首要难题,就是服务与服务之间的解耦。
多子系统组成的租车应用
选用SpringCloud的原因
开箱即用,提供各种默认配置来简化项目配置。对于中小企业来讲,使用门槛较低。Spring Cloud来源于Spring,向上兼容,有其高效开发、稳定运行的保证。其次,SpringCloud社区非常活跃,从16年1.x版本到如今的2.x系列,相比于其它框架,Spring Cloud对微服务周边环境的支持力度最大。因此,综合考虑时间和成本而言,大部分企业和公司,乐于接收这样的开源脚手架进行开发。
Spring Cloud 组织架构图
Spring Cloud Eureka
Eureka是Spring Cloud Netflix项目下的服务治理模块。而Spring Cloud Netflix项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。
Eureka的功能特点:实现服务治理
二、开始撸代码
0.项目结构
1、新建Maven项目,编写父级pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bboyhan.cloud</groupId>
<artifactId>spring-cloud-project</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>provider-user</module>
<module>discovery-eureka</module>
<module>consumer-user</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、编写MVC服务
这里使用的是Jpa、H2,方便直接读取,小伙伴可以根据自己的项目情况进行简单更改。
(1)table.sql,自行插入table数据。
drop table user_login if exists;
CREATE TABLE `user_login` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`login` varchar(100) NOT NULL,
`pwd` varchar(100) NOT NULL,
`reg_date` datetime DEFAULT NULL,
`date` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `login_UNIQUE` (`login`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;
(2)provider-user模块的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-project</artifactId>
<groupId>com.bboyhan.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>provider-user</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
(3)controller,提供一个查询user的接口(/user/{id}),其它代码略。
package com.bboyhan.cloud.controller;
import com.bboyhan.cloud.entity.UserLogin;
import com.bboyhan.cloud.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: bboyHan
* @Date: 2019/1/13 17:53
*/
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/{id}")
public UserLogin getUser(@PathVariable Long id) {
return userService.getUser(id);
}
}
(4)provider-user模块的application.yml。
server:
port: 7901
spring:
jpa:
generate-ddl: false
show-sql: true
hibernate:
ddl-auto: none
datasource:
platform: h2
schema: classpath:schema.sql
data: classpath:data.sql
application:
name: provider-user
logging:
level:
root: INFO
org.hibernate: INFO
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.hibernate.type.descriptor.sql.BasicExtractor: TRACE
com.itmuch: DEBUG
eureka:
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://root:root@localhost:8761/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
3、编写eureka服务
(1)discovery-eureka模块的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-project</artifactId>
<groupId>com.bboyhan.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>discovery-eureka</artifactId>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
</project>
(2)discovery-eureka中的application.yml
在这里添加了账户权限(security,对应的依赖是spring-boot-starter-security),用于登陆eureka限制使用(可以不用添加)。
security:
basic:
enabled: true
user:
name: root
password: root
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://root:root@localhost:8761/eureka
(3)启动类增加注解@EnableEurekaServer
4、消费调用方
(1)pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-project</artifactId>
<groupId>com.bboyhan.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer-user</artifactId>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
(2)application.yml
spring:
application:
name: consumer-user
server:
port: 8010
eureka:
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://root:root@localhost:8761/eureka
instance:
prefer-ip-address: true
provider-user:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
(3)controller
package com.bboyhan.cloud.controller;
import com.bboyhan.cloud.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @Author: bboyHan
* @Date: 2019/1/13 18:12
*/
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/rest/{id}")
public User r(@PathVariable Long id) {
ServiceInstance serviceInstance = loadBalancerClient.choose("provider-user");
System.out.println("loadbalance" + " : " + serviceInstance.getServiceId() + ":" + serviceInstance.getHost() + ":" + serviceInstance.getPort());
return restTemplate.getForObject("http://provider-user/user/" + id, User.class);
}
}
(4)User实例
package com.bboyhan.cloud.entity;
import java.util.Date;
/**
* @Author: bboyHan
* @Date: 2019/1/13 18:12
*/
public class User {
private Long id;
private String login;
private String pwd;
private Date regDate;
private Date date;
//setter、getter方法
}
在消费调用方中使用了Ribbon的负载均衡,对此做一个简单的说明。
相信大家会了解一些负载均衡的硬件或者软件,比如F5(硬件负载均衡)、Nginx(轻量级的服务端负载均衡)。而Ribbon是一个客户端的负载均衡器,它提供对大量的HTTP和TCP客户端的访问控制。
Ribbon的核心组件
1、ServerList: 用于获取地址列表。它既可以是静态的(提供一组固定的地址),也可以是动态的(从注册中心中定期查询地址列表)。
2、ServerListFilter: 仅当使用动态ServerList时使用,用于在原始的服务列表中使用一定策略过虑掉一部分地址。
3、IRule: 选择一个最终的服务地址作为LoadBalance的结果。选择策略有轮询、根据响应时间加权、断路器(当Hystrix可用时)等。
4、Ribbon在工作时首选会通过ServerList来获取所有可用的服务列表,然后通过ServerListFilter过虑掉一部分地址,最后在剩下的地址中通过IRule选择出一台服务器作为最终结果。
Ribbon提供的负载均衡策略
1、简单轮询负载均衡(RoundRobin):以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。
2、随机负载均衡 (Random): 随机选择状态为UP的Server
3、加权响应时间负载均衡 (WeightedResponseTime):根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。
4、区域感知轮询负载均衡(ZoneAvoidanceRule):复合判断server所在区域的性能和server的可用性选择server。Ribbon自带负载均衡策略比较。
三、测试结果
1、在浏览器输入:http://localhost:8761;
2、测试provider-user服务:http://localhost:7900/user/1;
3、测试consumer-user服务:http://localhost:8010/rest/1;