第一章 SpringCloud简介

一、架构的演变过程

我们最先接触的单体架构,整个系统就只有一个工程,打包往往是打成了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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值