一、架构的演变过程
我们最先接触的单体架构,整个系统就只有一个工程,打包往往是打成了war包,然后部署到单一tomcat上面,这种就是单体架构。
假如系统按照功能划分了,商品模块,购物车模块,订单模块,物流模块等等模块。那么所有模块都会在一个工程里面,这就是单体架构。
1、单体架构的优缺点
优点:
1)结构简单,部署简单。
2)所需的硬件资源少。
3)节省成本。
缺点:
1)版本迭代慢,往往改动一个代码会影响全局。
2)不能满足一定并发的访问。
3)代码维护困难,所有代码在一个工程里面,存在被其他人修改的风险。
随着业务的拓展,公司的发展,单体架构慢慢的不能满足我们的需求,我们需要对架构进行变动,我们能够想到的最简单的办法就是加机器,对应用横向扩展。 如图:
这种架构貌似暂时解决了我们的问题,但是用户量慢慢增加后,我们只能通过横向加机器来解决,还是会存在版本迭代慢,代码维护困难的问题。而且用户请求往往是读多写少的情况,所以可能真正需要扩容的只是商品模块而已,而现在是整个工程都扩容了,这无形中是一种资源的浪费,因为其他模块可能根本不需要扩容就可以满足需求。所以我们有必要对整个工程按照模块进行拆分,拆分后的架构图如下:
模块拆分后,模块和模块之间是需要通过接口调用的方式进行通信,模块和模块之间通过分流软件进行负载均衡。这个架构解决前面的资源浪费问题和代码管理问题,因为我们是对系统拆分了,各个模块都有单独的工程,比如我修改商品模块,就不需要担心会不会影响购物车模块。但是这种架构扩展非常麻烦,一旦需要横向加机器,或者减机器都需要修改nginx配置,一旦机器变多了以后,nginx的配置量就是一个不能完成的工作。这时候SOA服务治理框架就应运而生,架构图如下:
2、微服务的发展史
2.1、WEB集群:
考虑多用户并发访问处理速速度。
对于分布式的项目开发按照历史的发展经历过如下的一些技术:
CORBA公共对象请求代理架构:
他是一种开发的标准,而且也是许多语言支持的一个开发标准(效率低下)。
RMI远程方法调用:
该技术是SUN公司提出的,出现的最大特征是希望可以与CORBA进行市场的竞争,于是出现了一个问题很多公司不认可这个技术。但是RMI带来一个技术概念,在Java里面引出了远程接口的概念。
2.2、RMI实现方案:
后来SUN的设计师发现RMI很好用,但是不如COBBA广泛,于是就产生了一个新的协议:RMI-IIOP协议。他用在了EJB技术上。
2.3、EJB技术
WEB容器、EJB容器:Websphere、WebLogic、jboss
EJB留给世界只是它优秀的理论和它糟糕的实现,并且这个理论被一些开元框架无限制的扩充与实现。.NET这个时候出现了,后来又因为J#的问题,微软和SUN决裂,后来行业就乱了,企业面临两种技术选:后来由于XML作为数据交换的基础的出现,著名的软件机构:WebService诞生了(web服务)。
2.4、WebService技术
WebService产生的问题:
- 速度太慢,处理速度不行。
- 如果要采用远程接口的方式调用,则要利用工具生生一堆的工具类代码。
后来2005年的时候随着WebService继续发展,形成了有一大核心神器SOA(面向服务架构),它提出了一个面向服务总线的概念:ESB服务总线。
3、SOA框架介绍
基于注册中心的SOA框架,扩展是非常方便的,因为不需要维护分流工具,但我们启动应用的时候就会把服务通过http的方式注册到注册中心。
在SOA框架中一般会有三种角色:注册中心、服务提供方、服务消费方。
1)注册中心:在注册中心维护了服务列表。
2)服务提供方:服务提供方启动的时候会把自己注册到注册中心。
3)服务消费方:服务消费方启动的时候,把获取注册中心的服务列表,然后调用的时候从这个服务列表中选择某一个去调用。
SOA技术:可以整合不同技术写的服务接口(例如: Basic、 C、 C++)
这ESB类似于设计模式里面适配器模式,不管服务提供方是使用什么语音实现的,在他注册到消息总线里面,用过使用同样的一套规范,这样客户端调用就省事了很多。
所有的服务由服务总线通过管理,但这个消息总线其实只是一种思想,具体要要通过具体的RPC框架实现,说到RPC,其中最有代表意义的就是前面以及学习过的dubbo开发技术,他可以使用dubbo协议,或者RMI协议,在传输层是用netty。
4、Restful介绍
除开耳熟能详的dubbo,成长比较快的是Rest协议,这其实就是因为JSON的广泛应用,如果说是XML技术成就了WebService,那么JSON就造就了Rest服务。
不过Rest还是一个未成型的标准,SpringCloud就真正将Rest作为了RPC的实现技术,SpringCloud这技术以及成为了这行业的趋势,而且SpringCloud也依照于SpringBoot开发技术,可以实现项目的打包发布以及单独运行。
5、微服务工程的特点:
5.1、微服务架构的优点:
- 易于开发和维护
- 一个微服务可以专注于某一个业务场景,业务清晰、代码量比较少。
- 单个微服务启动较快
- 局部修改容易部署
- 技术栈不受限
- 按需伸缩
5.2、微服务架构的挑战:
- 运维要求比较高
- 分布式固有的复杂性
- 借口调整成本高
- 重复劳动
5.3、Spring cloud的特点:
- 约定优于配置
- 开箱即用、快速启动
- 适用于各种环境
- 轻量级的组件——服务发现——Eureka
- 组件的支持很丰富,功能很齐全(配置中心、注册中心、智能路由)
- 选型中立——服务发现(Eureka、Zookeeper、Consul)
二、Restful项目演练
1、微服务项目搭建
Git地址:https://gitee.com/hankin_chj/springcloud-micro-service.git
既然是微服务,整个项目必然会参加成一个个微模块:
microcloud-api模块,作为公共的信息导入配置模块;
microcloud-provider-product:作为服务提供者;
microcloud-consumer:作为微服务调用的客户端使用;
新建立一个maven项目:springcloud-micro-service,其中pom文件如下:
<?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.chj</groupId>
<artifactId>springcloud-micro-service</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>springcloud-micro-api</module>
<module>springcloud-micro-product</module>
<module>springcloud-micro-consumer</module>
</modules>
<name>springcloud-micro-service</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<jdk.version>1.8</jdk.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency> <!-- 进行SpringCloud依赖包的导入处理 -->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency> <!-- SpringCloud离不开SpringBoot,所以必须要配置此依赖包 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>com.chj</groupId>
<artifactId>springcloud-micro-api</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>springcloud-micro-service</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${jdk.version}</source><!-- 源代码使用的开发版本 -->
<target>${jdk.version}</target><!-- 需要生成的目标class文件的编译版本 -->
</configuration>
</plugin>
</plugins>
</build>
</project>
注意:
spingcloud中针对依赖包的版本并不像传统项目一样使用的是数字形式定义,反而是使用了一系列英国的地铁或者城市名字来定义,springcloud使用了springboot,其中对于的版本如下
Release Train Boot Version
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x
2、springcloud-micro-api模块
建立一个公共模板,这模块的主要功能是提供公共处理的工具类,实体,接口等。
pom文件如下:
<?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>springcloud-micro-service</artifactId>
<groupId>com.chj</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-micro-api</artifactId>
<version>1.0.0</version>
<name>springcloud-micro-api</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
由于实体对象不管是服务提供放还是消费者都需要用到,实体对象先创建到api模块中,创建一个Product实体(代码略)。
3、服务提供方
创建一个Product Rest提供者的项目模块:springcloud-micro-product
1)对应的数据库脚本如下:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`prodcutId` bigint(20) NOT NULL AUTO_INCREMENT,
`productName` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`productDesc` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`prodcutId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `product` VALUES (1, 'java编程', 'springcloud');
INSERT INTO `product` VALUES (2, 'Springboot', 'springcloud');
INSERT INTO `product` VALUES (3, '西游记', 'springcloud');
INSERT INTO `product` VALUES (4, '水浒传', 'springcloud');
INSERT INTO `product` VALUES (5, '西厢记', 'springcloud');
SET FOREIGN_KEY_CHECKS = 1;
2)product模块继续使用mybaits对数据库进行操作,pom文件如下:
<?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>springcloud-micro-service</artifactId>
<groupId>com.chj</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-micro-product</artifactId>
<name>springcloud-micro-product</name>
<version>1.0.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.chj</groupId>
<artifactId>springcloud-micro-api</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
3)创建一个ProductMapper对数据库的操作接口,这个接口方法特别简单
public interface ProductMapper {
boolean create(Product product);
public Product findById(Long id);
public List<Product> findAll();
}
4)新增修改application.yml文件,追加对mybatis以及数据库的支持
server:
port: 8080
mybatis:
mapper-locations: # 所有的mapper映射文件
- classpath:mapping/*.xml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 配置当前要使用的数据源的操作类型
driver-class-name: com.mysql.cj.jdbc.Driver # 配置MySQL的驱动程序类
url: jdbc:mysql://localhost:3306/springcloud?serverTimezone=GMT%2B8 # 数据库连接地址
username: root # 数据库用户名
password: root # 数据库连接密码
logging:
level:
com.chj.mapper: debug
5)创建修改ProductMapper.xml文件:
<?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.chj.mapper.ProductMapper">
<select id="findById" resultType="com.chj.vo.Product" parameterType="long">
select productId,productName,productDesc from product WHERE productId=#{id} ;
</select>
<select id="findAll" resultType="com.chj.vo.Product">
SELECT productId,productName,productDesc from product;
</select>
<insert id="create" parameterType="com.chj.vo.Product">
INSERT INTO product(productName,productDesc) VALUES (#{productName},database()) ;
</insert>
</mapper>
6)建立IProductService接口,并创建相关实现类:ProductServiceImpl.java
@Service
public class ProductServiceImpl implements IProductService {
@Resource
private ProductMapper productMapper;
@Override
public Product get(long id) {
return productMapper.findById(id);
}
@Override
public boolean add(Product product) {
return productMapper.create(product);
}
@Override
public List<Product> list() {
return productMapper.findAll();
}
}
7)定义主程序类,并定义好mapper扫描包:
@SpringBootApplication
@MapperScan("com.chj.mapper")
public class ProductApp {
public static void main(String[] args) {
SpringApplication.run(ProductApp.class,args);
}
}
8)编写单元测试
@SpringBootTest(classes = ProductApp.class)
@RunWith(SpringRunner.class)
public class ProductServiceTest {
@Resource
private IProductService iProductService;
@Test
public void testGet() {
System.out.println(iProductService.get(1));
}
@Test
public void testAdd() {
Product dept = new Product() ;
dept.setProductName("lison-" + System.currentTimeMillis());
System.out.println(iProductService.add(dept));
}
@Test
public void testList() {
System.out.println(iProductService.list());
}
}
测试完成,确认所有服务的方法都正确
9)建立ProductController建立一个Rest服务类
@RestController
@RequestMapping("/prodcut")
public class ProductController {
@Resource
private IProductService iProductService;
@RequestMapping(value="/get/{id}")
public Object get(@PathVariable("id") long id) {
return this.iProductService.get(id) ;
}
@RequestMapping(value="/add")
public Object add(@RequestBody Product product) {
return this.iProductService.add(product) ;
}
@RequestMapping(value="/list")
public Object list() {
return this.iProductService.list() ;
}
}
启动服务,浏览器访问:
调用get请求:localhost:8080/prodcut/get/1
调用list请求:localhost:8080/prodcut/list
4、服务消费方
创建一个maven新模块:springcloud-micro-consumer,这个模块作为服务的消费方,调用前面的product服务,添加内容如下:
4.1、修改pom文件
<dependency>
<groupId>com.chj</groupId>
<artifactId>springcloud-micro-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
4.2、修改application.yml配置文件
server:
port: 80
4.3、创建Rest配置类
在这需要调用Rest服务,一般需要用到RestTemplate类对象
@Configuration
public class RestConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
4.4、新建一个controller
负责使用RestTemplate调用远程的product服务
@RestController
@RequestMapping("/consumer")
public class ConsumerProductController {
public static final String PRODUCT_GET_URL = "http://localhost:8080/prodcut/get/";
public static final String PRODUCT_LIST_URL="http://localhost:8080/prodcut/list/";
public static final String PRODUCT_ADD_URL = "http://localhost:8080/prodcut/add/";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/product/get")
public Object getProduct(long id) {
Product product = restTemplate.getForObject(PRODUCT_GET_URL + id, Product.class);
return product;
}
@RequestMapping("/product/list")
public Object listProduct() {
List<Product> list = restTemplate.getForObject(PRODUCT_LIST_URL, List.class);
return list;
}
@RequestMapping("/product/add")
public Object addPorduct(Product product) {
Boolean result = restTemplate.postForObject(PRODUCT_ADD_URL, product, Boolean.class);
return result;
}
}
4.5、编写启动类
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
4.6、调用测试
新增:http://localhost/consumer/product/add?productName=apple
列表查询:http://localhost/consumer/product/list
获得单个数据:http://localhost/consumer/product/get?id=1
5、SpringSecurity
服务提供方配置安全验证,前面使用了RestTemplate进行远程接口调用,但要注意,这些Rest服务最终都可能暴露在公网的,任何人都可能调用,如果你的Rest服务属于一些私密信息,这样会导致信息的泄露。如果想进行安全方面的处理,首先要在服务的提供方上进行处理。
1)修改pom文件,追加SpringSecurity相关依赖信息
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2)修改application.yml配置文件,进行安全的用户名配置
security:
user:
name: admin # 认证用户名
password: admin # 认证密码
roles:
- USER # 授权角色
在项目中访问rest接口,localhost:8080/prodcut/list,这个时候会要求先输入用户名以及密码才能允许访问:输入用户名密码就可以调用前面的接口了。
6、服务消费方处理
服务提供方目前已经使用了密码验证,这个时候服务的消费方如果想直接访问就不可能了,这个时候一个以头的信息进行处理,然后使用Base64进行加密处理后才能得到正确的访问路径
6.1、consumer修改RestConfig配置类
在里面添加 HttpHeaders 的配置信息:
@Configuration
public class RestConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public HttpHeaders getHeaders() { // 要进行一个Http头信息配置
HttpHeaders headers = new HttpHeaders(); // 定义一个HTTP的头信息
String auth = "admin:admin"; // 认证的原始信息
// 进行一个加密的处理
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + new String(encodedAuth);
headers.set("Authorization", authHeader);
return headers;
}
}
6.2、修改Controller,在进行服务端调用的时候加上这个头信息:
@RestController
@RequestMapping("/consumer")
public class ConsumerProductSecurityController {
public static final String PRODUCT_GET_URL = "http://localhost:8080/prodcut/get/";
public static final String PRODUCT_LIST_URL="http://localhost:8080/prodcut/list/";
public static final String PRODUCT_ADD_URL = "http://localhost:8080/prodcut/add/";
@Resource
private RestTemplate restTemplate;
@Resource
private HttpHeaders httpHeaders;
/**
* 添加了spring security以后,访问接口需要密码验证,需要在请求头HttpHeaders中添加验证信息
*/
@RequestMapping("/product/get2")
public Object getProduct2(long id) {
Product product = restTemplate.exchange(PRODUCT_GET_URL + id,HttpMethod.GET,
new HttpEntity<Object>(httpHeaders), Product.class).getBody();
return product;
}
@RequestMapping("/product/list2")
public Object listProduct2() {
List<Product> list = restTemplate.exchange(PRODUCT_LIST_URL,HttpMethod.GET,
new HttpEntity<Object>(httpHeaders), List.class).getBody();
return list;
}
@RequestMapping("/product/add2")
public Object addPorduct2(Product product) {
Boolean result = restTemplate.exchange(PRODUCT_ADD_URL, HttpMethod.POST,
new HttpEntity<Object>(product,httpHeaders), Boolean.class).getBody();
return result;
}
}
6.3、调用测试:
列表查询:http://localhost/consumer/product/list2
获得单个数据:http://localhost/consumer/product/get2?id=1
注意:如果使用没加security之前的接口访问会报错
7、添加springcloud-micro-security模块
现在服务提供方只有一个Product服务,但真实的项目开发中必然有多个服务提供方,绝大多数情况下,这些服务都会用到安全验证,而且密码也会一样,如果每个服务都单独维护,每次密码变动改动都会很大,所以应该单独建立一个安全验证的模块。
7.1、创建一个springcloud-micro-security模块,修改其pom文件如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
7.2、修改父工程pom文件,把相应的版本依赖加到里面
<!-- 系统api模块 -->
<dependency>
<groupId>com.chj</groupId>
<artifactId>springcloud-micro-api</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 系统security模块 -->
<dependency>
<groupId>com.chj</groupId>
<artifactId>springcloud-micro-security</artifactId>
<version>1.0.0</version>
</dependency>
7.3、建立一个统一的安全配置类,这个类负责用户以及密码相关的配置
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(
new BCryptPasswordEncoder()).withUser("root")
.password(new BCryptPasswordEncoder().encode("admin"))
.roles("USER").and().withUser("admin")
.password(new BCryptPasswordEncoder().encode("admin"))
.roles("USER", "ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().anyRequest().fullyAuthenticated();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
7.4、springcloud-micro-product修改pom文件
删除spring-boot-starter-security的依赖信息,并加入自己定义的springcloud-micro-security依赖:
<!-- spring security -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-security</artifactId>-->
<!-- </dependency>-->
<!-- 删除spring security依赖,引入自定义的security依赖 -->
<dependency>
<groupId>com.chj</groupId>
<artifactId>springcloud-micro-security</artifactId>
</dependency>
7.5、product修改application.yml,删除与安全相关的配置项:
# security: # 使用自定义的security模块
# user:
# name: admin # 认证用户名
# password: admin # 认证密码
# roles:
# - USER # 授权角色
7.6、调用测试
列表查询:http://localhost/consumer/product/list2
获得单个数据:http://localhost/consumer/product/get2?id=1
三、Eureka服务注册与发现
在学习zookeeper的时候,重点就讲了服务注册与发现的流程,dubbo就是基于zookeeper来实现服务注册与发现的。基于zookeeper的服务注册与发现大致流程如下:
而在SpringCloud中,大量使用了Netflix的开源项目,其中Eureka就属于Netflix 提供的发现服务组件,所有的微服务都注册到Eureka中,它在其中扮演的就是注册中心的角色,后面所有的客户端直接从注册中心获取所需要的服务。
1、Eureka服务端
1.1、新建一个microcloud-eureka模块
这模块做的事情非常简单,既启动Eureka的服务端,pom文件如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
注意:如果是Edgware或之前的版本,用的是springboot 1.5.或者更低的版本,eureka版本不一样:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
1.2、修改application.yml文件,在里面配置eureka相关信息
server:
port: 7001
eureka:
instance: # eureak实例定义
hostname: localhost # 定义 Eureka 实例所在的主机名称
1.3、新增Eureka启动类,增加Eureka服务端注解
@SpringBootApplication
@EnableEurekaServer // 添加eureka服务端注解
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class,args);
}
}
1.4、运行main方法
会有一些出错信息,先不用管,后面再来处理。
com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: connect
Caused by: java.net.ConnectException: Connection refused: connect
com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
在浏览器上执行:http://localhost:7001/
2、服务提供方注册到Eureka
现在Eureka虽然有点小瑕疵,但现在已经能正常访问了,那么接下来就需要将用的微服务注册到Eureka服务当中,为后面客户端的使用做铺垫。
2.1、product服务修改pom文件,增加eureka客户端相关信息:
<!-- eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
注意:如果是Edgware或之前的版本,用的是springboot 1.5.或者更低的版本
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
2.2、product服务修改application.yml配置文件
在者个文件中定义要注册的eureka服务的地址:
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
2.3、product服务修改启动类
在这个类上增加eureka客户端的注解信息@EnableEurekaClient:
@SpringBootApplication
@MapperScan("com.chj.mapper")
@EnableEurekaClient
public class ProductApp {
public static void main(String[] args) {
SpringApplication.run(ProductApp.class,args);
}
}
2.4、加上这注解后,启动:
1)发现Application的名字是UNKNOWN,为此应该为这单独取一个名字,继续修改application.yml配置文件,为这个微服务起一个名字springcloud-micro-product:
spring:
application:
name: springcloud-micro-product
重新启动后刷新eureka管理页面:
2)现在虽然成功的实现了微服务注册,但是现在看下status,这个时候名称还有点乱,我们现在开源自定义一个路径名称:springcloud-micro-product,修改application.yml配置文件,追加主机名称的显示:
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
instance-id: springcloud-micro-product
重启继续查看如下:
3)另外一般情况下,当鼠标点击查看的时候应该以IP作为链接项:
例如本机当前提示地址为:http://hankin:8080/actuator/info
product服务修改application.yml配置文件
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
instance-id: springcloud-micro-product
prefer-ip-address: true
修改后地址栏变为:http://192.168.239.1:8080/actuator/info,点击状态栏:
2.5、增加actuator模块
如果想看状态信息需要增加actuator模块,这一块的内容已经在讲springboot的时候讲过,修改pom文件,增加引用:
<!-- actuator健康检查 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
product服务修改application.yml文件,追加info相关配置:
info: # 增加服务信息
app.name: microcloud-provider-product
company.name: hankin
build.artifactId: $project.artifactId$
build.modelVersion: $project.modelVersion$
重新启动访问结果如下:
{"app":{"name":"microcloud-provider-product"},"company":{"name":"hankin"},
"build":{"artifactId":"$project.artifactId$","modelVersion":"$project.modelVersion$"}}
注意:由于在yml文件中使用了$,这个时候启动是会报错的,因此还需要一个maven-resources-plugin插件的支持。在父工程增加插件,修改pom文件如下标红所示:
<build>
<finalName>springcloud-micro-service</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimiter>$</delimiter>
</delimiters>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${jdk.version}</source><!-- 源代码使用的开发版本 -->
<target>${jdk.version}</target><!-- 需要生成的目标class文件的编译版本 -->
</configuration>
</plugin>
</plugins>
</build>
重新启动后访问,结果如下:
{"app":{"name":"microcloud-provider-product"},"company":{"name":"hankin"},
"build":{"artifactId":"springcloud-micro-product","modelVersion":"4.0.0"}}
3、其他配置
在前面启动eureka中,会发现启动会报错,虽然这些错误不影响使用;另外在关闭product项目后,刷新eureka发现项目还在,隔一段时间后会发现:
这其实就是触发了安全模式。
3.1、eureka设置服务的清理间隔时间,修改application.yml文件
server:
port: 7001
eureka:
instance: # eureak实例定义
hostname: localhost # 定义 Eureka 实例所在的主机名称
server:
eviction-interval-timer-in-ms: 1000 #设置清理的间隔时间,而后这个时间使用的是毫秒单位(默认是60秒)
enable-self-preservation: false #设置为false表示关闭保护模式
client:
fetch-registry: false
register-with-eureka: false
这个重新测试,服务提供方注册后,关闭服务发现服务实例依然还在。
3.2、修改product服务的application.yml配置,增加心跳间隔时间:
eureka: # eureka客户端配置
client:
service-url:
defaultZone: http://localhost:7001/eureka
instance:
instance-id: springcloud-micro-product
prefer-ip-address: true
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
由于所有的服务都注册到了Eureka之中,这样如果配置了“lease-expiration-duration-in-seconds”此选项,表示距离上一次发送心跳之后等待下一次发送心跳的间隔时间,如果超过了此间隔时间,则认为该微服务已经宕机了。
3.3、通过发现服务来获取一些服务信息
product服务对于注册到Eureka上的服务,可以通过发现服务来获取一些服务信息,修改ProductController,增加一个方法:discover():
@RestController
@RequestMapping("/prodcut")
public class ProductController {
@Resource
private IProductService iProductService;
// import com.netflix.discovery.DiscoveryClient; // 注意这个包引用方法是旧版本1.x使用的
// import org.springframework.cloud.client.discovery.DiscoveryClient; //2.x版本使用
@Resource
private DiscoveryClient client ; // 进行Eureka的发现服务
@RequestMapping(value="/get/{id}")
public Object get(@PathVariable("id") long id) {
return this.iProductService.get(id) ;
}
@RequestMapping(value="/add")
public Object add(@RequestBody Product product) {
return this.iProductService.add(product) ;
}
@RequestMapping(value="/list")
public Object list() {
return this.iProductService.list() ;
}
/**
* product服务对于注册到Eureka上的服务,可以通过发现服务来获取一些服务信息
*/
@RequestMapping("/discover")
public Object discover() { // 直接返回发现服务信息
return this.client ;
}
}
3.4、product服务修改ProductApp,在主程序中启用发现服务项:
@SpringBootApplication
@MapperScan("com.chj.mapper")
@EnableEurekaClient
@EnableDiscoveryClient // 添加服务发现注解
public class ProductApp {
public static void main(String[] args) {
SpringApplication.run(ProductApp.class,args);
}
}
启动访问:localhost:8080/prodcut/discover,返回结果信息如下:
{"discoveryClients":[{"services":["springcloud-micro-product"]},
{"services":[]}],"services":["springcloud-micro-product"]}
Debug可以发现DiscoveryClient包含的很多信息:
可以看到在eureka里面显示的信息都可以在这里获取得到。
4、Eureka安全机制
一般情况下Eureka和服务的提供注册者都会在一个内网环境中,但免不了在某些项目中需要让其他外网的服务注册到Eureka,这个时候就有必要让Eureka增加一套安全认证机制了,让所有服务提供者通过安全认证后才能注册进来。
4.1、修改pom文件,引入SpringSecurity的依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
eureka注册服务修改application.yml文件,增加用户、密码验证,defaultZone增加用户校验:
server:
port: 7001
eureka:
instance: # eureak实例定义
hostname: localhost # 定义 Eureka 实例所在的主机名称
server:
eviction-interval-timer-in-ms: 1000 #设置清理的间隔时间,而后这个时间使用的是毫秒单位(默认是60秒)
enable-self-preservation: false #设置为false表示关闭保护模式
service-url:
defaultZone: http://admin:admin@localhost:7001/eureka
client:
fetch-registry: false
register-with-eureka: false
spring:
security:
user:
name: admin
password: admin
4.2、product服务修改application.yml文件,增加验证信息:
eureka: # eureka客户端配置
client:
service-url:
defaultZone: http://admin:admin@localhost:7001/eureka
instance:
instance-id: springcloud-micro-product
prefer-ip-address: true
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
重新启动,注意:如果是Edgware或之前的版本,做到这一步就行了,但使用现在版本,你会发现启动springcloud-micro-product后服务注册不上去。
4.3、注册中心增加安全校验配置代码
springcloud-micro-eureka新增配置类EurekaSecurityConfig,重写configure方法,把csrf劫持关闭
@Configuration
@EnableWebSecurity
public class EurekaSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
super.configure(http);
}
}
5、HA高可用
学习Zookepper服务注册的时候,注册中心是能实现高可用的,但现在的Eureka还是单节点的情况,如果Eureka出现了错误,将会导致整个集群无法继续使用,这个时候就需要考虑Eureka的高可用了。
现在需要3个eureka,每个eureka都需要配置hostname,所有先修改hosts文件内容如下:
127.0.0.1 eureka1 、127.0.0.1 eureka2、127.0.0.1 eureka3
5.1、复制两个注册中心
为了方便操作,将springcloud-micro-eureka项目复制两份,分别复制为springcloud-micro-eureka2、 springcloud-micro-eureka3。
1)父pom文件添加新模块eureka2、eureka3
<module>springcloud-micro-eureka</module>
<module>springcloud-micro-eureka2</module>
<module>springcloud-micro-eureka3</module>
2)两个复制节点pom文件注意修改artifactId
<artifactId>springcloud-micro-eureka2</artifactId>
<name>springcloud-micro-eureka2</name>
5.2、修改application.yml配置文件端口以及hostname
3)springcloud-micro-eureka修改application.yml配置文件,修改端口以及注册位置:
server:
port: 7001
eureka:
instance: # eureak实例定义
hostname: eureka1 # 定义 Eureka 实例所在的主机名称
server:
eviction-interval-timer-in-ms: 1000 #设置清理的间隔时间,而后这个时间使用的是毫秒单位(默认是60秒)
enable-self-preservation: false #设置为false表示关闭保护模式
service-url:
defaultZone: http://admin:admin@eureka1:7001/eureka,http://admin:admin@eureka2:7002/eureka,http://admin:admin@eureka3:7003/eureka
client:
fetch-registry: false
register-with-eureka: false
spring:
security:
user:
name: admin
password: admin
4)eureka2修改application.yml配置文件(port与hostname):
server:
port: 7002
eureka:
instance: # eureak实例定义
hostname: eureka2 # 定义 Eureka 实例所在的主机名称
server:
eviction-interval-timer-in-ms: 1000 #设置清理的间隔时间,而后这个时间使用的是毫秒单位(默认是60秒)
enable-self-preservation: false #设置为false表示关闭保护模式
service-url:
defaultZone: http://admin:admin@eureka1:7001/eureka,http://admin:admin@eureka2:7002/eureka,http://admin:admin@eureka3:7003/eureka
client:
fetch-registry: false
register-with-eureka: false
spring:
security:
user:
name: admin
password: admin
3)Eureka3修改application.yml配置文件:
server:
port: 7003
eureka:
instance: # eureak实例定义
hostname: eureka3 # 定义 Eureka 实例所在的主机名称
server:
eviction-interval-timer-in-ms: 1000 #设置清理的间隔时间,而后这个时间使用的是毫秒单位(默认是60秒)
enable-self-preservation: false #设置为false表示关闭保护模式
service-url:
defaultZone: http://admin:admin@eureka1:7001/eureka,http://admin:admin@eureka2:7002/eureka,http://admin:admin@eureka3:7003/eureka
client:
fetch-registry: false
register-with-eureka: false
spring:
security:
user:
name: admin
password: admin
5.3、修改启动类名称(不能一样)
分别启动eureka、eureka2、eureka3,进入服务的后台查看副本,登陆http://localhost:7001/
5.4、product服务修改application.yml配置文件
修改defaultZone配置多台enreka的注册:
server:
port: 8080
mybatis:
mapper-locations: # 所有的mapper映射文件
- classpath:mapping/*.xml
spring:
application:
name: springcloud-micro-product
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 配置当前要使用的数据源的操作类型
driver-class-name: com.mysql.cj.jdbc.Driver # 配置MySQL的驱动程序类
url: jdbc:mysql://localhost:3306/springcloud?serverTimezone=GMT%2B8 # 数据库连接地址
username: root # 数据库用户名
password: root # 数据库连接密码
eureka: # eureka客户端配置
client:
service-url:
defaultZone: http://admin:admin@eureka1:7001/eureka,http://admin:admin@eureka2:7002/eureka,http://admin:admin@eureka3:7003/eureka
# defaultZone: http://admin:admin@localhost:7001/eureka
instance:
instance-id: springcloud-micro-product
prefer-ip-address: true
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
info: # 增加服务信息
app.name: microcloud-provider-product
company.name: hankin
build.artifactId: $project.artifactId$
build.modelVersion: $project.modelVersion$
# security: # 使用自定义的security模块
# user:
# name: admin # 认证用户名
# password: admin # 认证密码
# roles:
# - USER # 授权角色
6、打包发布
在真实项目中,需要讲Eureka发布到具体服务器上进行执行,打包部署其实和springboot里面讲的大同小异和properties文件稍微有点不同,对于properties文件,不同的环境会有不同的配置文件比如application-dev.properties,application-test.properties,application-pro.properties等,但如果是yml文件,所有的的配置都再同一个yml文件中。
6.1、eureka修改application.yml文件
spring:
profiles: dev-7001
spring:
profiles: dev-7002
spring:
profiles: dev-7003
6.2、eureka服务添加一个打包插件,修改pom文件:
<plugin> <!-- 该插件的主要功能是进行项目的打包发布处理 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration> <!-- 设置程序执行的主类 -->
<mainClass>com.chj.EurekaApp</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
在pom文件所在目录:
mvn clean install package,接下来就可以在项目的编译目录发现:springcloud-micro-service.jar
采用默认的方式执行:springcloud-micro-service.jar
那么此时将运行在 7003 端口上:java -jar springcloud-micro-service.jar
运行其它的两个 profile 配置:
- 运行“dev-7001”profile:java -jar eureka-server.jar --spring.profiles.active=dev-7002;
- 运行“dev-7002”profile:java -jar eureka-server.jar --spring.profiles.active=dev-7003