AngeldayAngelday
Home
  • Java
  • MySQL
  • Maven
  • Spring
  • SpringMVC
  • SpringBoot
  • Mybatis-Plus
  • SpringCloud
  • Redis
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • VBA
  • CentOS
  • ApachePOI
  • 摄影
  • Photoshop
  • Premiere
  • Lightroom
  • Game
Home
  • Java
  • MySQL
  • Maven
  • Spring
  • SpringMVC
  • SpringBoot
  • Mybatis-Plus
  • SpringCloud
  • Redis
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • VBA
  • CentOS
  • ApachePOI
  • 摄影
  • Photoshop
  • Premiere
  • Lightroom
  • Game
  • SpringCloud

SpringCloud

基本概念

系统架构

我们之前做的所有的项目都属于单体架构,下面我们将要学习更适合大型项目的分布式架构

单体架构: 将业务的所有功能几种在一个项目中开发,打成一个包部署。

优点:架构简单、部署成本低 缺点:耦合度高

分布式架构: 根据业务功能对系统进行拆分、每个业务模块作为独立项目开发,称为一个服务。

优点:降低服务耦合、有利于服务升级拓展。 缺点:架构复杂、运维、监控、部署难度高。

微服务

微服务是一种经过良好的架构设计的分布式架构方案。

微服务架构特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责。
  • 面向服务:微服务对外暴露业务接口
  • 自治:团队独立,技术独立,数据独立,部署独立。
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题。

级联问题常涉及数据的关联性操作引发的一系列连锁变化。

微服务结构:

最知名的两种技术架构:SpringCloud、阿里巴巴Dubbo

SpringCloud集成了各种微服务功能组件。

并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的体验。

服务的拆分总结:

  1. 不同微服务,不能重复开发相同业务。
  2. 微服务数据独立,不能访问其他微服务的数据库。
  3. 微服务将自己的业务暴露为接口,供其他微服务使用。

远程调用

案例:有两个服务分别是用户服务和订单服务,要求根据订单ID查询订单的同时,把订单所属的用户信息一起返回。

前面知道不同服务之间数据库互相独立,所以我们就只能在后端再发一次http请求,去调用其他服务的接口。

这就需要在Java代码中,发起HTTP请求,此处使用 RestTemplate

RestTemplate 是 Spring 框架提供的一个同步的 HTTP 客户端工具,用于在 Java 应用程序中发送 HTTP 请求并处理响应。

// 我们需要在配置类中声明一个Bean,启动类也属于配置类,所以此方法也可写入启动类中。

/**
 * 创建RestTemplate并注入Spring容器
 * @return
 */
@Bean
public RestTemplate restTemplate(){
    return new RestTemplate();
}

然后就可以在Java代码中发送请求了

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RestTemplate restTemplate;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);

        String url = "http://localhost:8081/user/" + order.getUserId();
        //使用RestTemplate发送请求,第一个参数为请求地址,第二个参数为序列化返回对象
        User user = restTemplate.getForObject(url, User.class);
        order.setUser(user);
        // 4.返回
        return order;
    }
}

提供者与消费者

  • 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
  • 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)

服务调用关系:

  • 服务提供者:暴露接口给其他微服务调用
  • 服务消费者:调用其他微服务提供的接口
  • 提供者与消费者角色是相对的
  • 一个服务可以同时是服务提供者和服务消费者

Eureka注册中心

前面书写存在一些问题,有硬编码的部分,请求地址是写死了。

Eureka作用

消费者该如何获取服务提供者具体信息?

  • 服务提供者启动时向eureka注册自己的信息
  • eureka保存这些信息
  • 消费者根据服务名称向eureka拉取提供者信息

如果有多个服务提供者,消费者该如何选择?

  • 服务消费者利用负载均衡算法,从服务列表中挑选一个

消费者如何感知服务提供者健康状态?

  • 服务提供者会每隔30秒向 EurekaServer 发送心跳请求,报告健康状态eureka会
  • 更新记录服务列表信息,心跳不正常会被剔除
  • 消费者就可以拉取到最新的信息

在Eureka架构中,微服务角色有两类:

EurekaServer : 服务端,注册中心

  • 记录服务信息
  • 心跳监控

EurekaClient : 客户端

  • Provider:服务提供者,例如案例中的user-service
    • 注册自己的信息到 Eureka Server
    • 每隔30秒向Eureka Server发送心跳
  • consumer:服务消费者,例如案例中的order-service
    • 根据服务名称从Eureka Server拉取服务列表
    • 基于服务列表做负载均衡,选中一个微服务后发起远程调用

搭建Eureka Server服务端

第一步:创建新的Maven模块,引入eureka-server依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

第二步:为启动类添加启动注解

@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class,args);
    }
}

第三步:添加配置信息

server:
  port: 10086
spring:
  application:
    name: eurekaserver

eureka:
  client:
    service-url: #eureka地址信息
      defaultZone: http://127.0.0.1:10086/eureka

注册user-service

第一步:在需要注册的服务中引入eureka-client依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

第二步:在配置文件中,添加配置

spring:
  application:
    name: userserver

eureka:
  client:
    service-url: #eureka地址信息
      defaultZone: http://127.0.0.1:10086/eureka

同理也可以注册order-service

复制服务,右键要copy的服务,点击Copy Configuration 起一个名称,然后在VM options中配置一个新的端口 -Dserver.port=8082

服务拉取

服务拉取是基于服务器名称获取服务列表,然后在对服务列表做负载均衡

  1. 修改OrderService的代码,修改访问的url路径,用服务器名代替ip、端口
String url = "http://userservice/user/" + order.getUserId();
  1. 在RestTemplate的Bean中,添加负载均衡注解
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
    return new RestTemplate();
}

Ribbon负载均衡

Nacos注册中心

启动方式:

startup.cmd -m standalone

服务注册

在父工程中添加spring-cloud-alibaba的管理依赖

<!-- Nacos 管理依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.5.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

注释原有的eureka依赖,添加nacos的客户端依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

修改配置文件

spring:
  application:
    name: orderservice
  cloud:
    nacos:
      server-addr: localhost:8848 #nacos服务地址

Nacos服务分级存储模型

一个服务可以有多个实例,大型公司会将实例部署在不同的服务器内。一个服务器机房就称为一个集群。 服务调用尽可能调用本地集群的服务,跨集群调用延迟较高,只有当本地集群不可访问时,再去访问其他的集群。

配置服务集群属性,修改yml

spring:
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos 服务端地址
      discovery:
        cluster-name: HB # 配置集群名称,机房位置

如果想要设置优先访问同集群服务,则需要修改负载均衡的IRule

userservice: # 要做配置的微服务名称
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则

注意将user-service的权重都设置为1

小结NacosRule负载均衡策略:

  1. 优先选择同集群服务实例列表
  2. 本地集群找不到提供者,才会到其他集群找,并且会报警告
  3. 确定了可用实例列表后,再采用随机负载均衡挑选实例

根据权重负载均衡

实际部署中会出现这样的场景: 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求

Nacos提供了权重配置来控制访问频率,权重越大访问频率越高。 在Nacos控制台可以设置实例的权重值,点击编辑按钮

小结:实例的权重控制

  1. Nacos控制台可以设置实例的权重值,0~1之间
  2. 同集群内的多个实例,权重越高被访问的频率越高
  3. 权重设置为0则完全不会被访问

环境隔离 - namespace

Nacos 中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离 注意:服务只能访问当前命名空间,无法访问其他命名空间的服务

Namespace 下有 Group 下有 Service / Data

新建命名空间:在Nacos控制台中 - > 命名空间 -> 新建命名空间 在代码中修改服务到新的命名空间:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848 #nacos服务地址
      discovery:
        cluster-name: HB # 集群名称
        namespace: xxxx # 命名空间id

Nacos环境隔离

  1. namespace用来做环境隔离
  2. 每个namespace都有唯一id
  3. 不同namespace下的服务不可见

临时实例与非临时实例

服务注册到Nacos时,可以选择注册为临时或非临时实例,通过下面的配置来设置

spring:
  cloud:
    nacos:
      server-addr:
      discovery:
        namespace:
        ephemeral: false #是否为临时实例

临时实例宕机时,会从nacos的服务列表中剔除,而非临时实例则不会

Nacos与Eureka的对比

Nacos与eureka的共同点

  • 都支持服务注册和服务拉取
  • 都支持服务提供者心跳方式做健康检测

Nacos与Eureka的区别

  • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
  • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

AP 可用性保证 CP 一致性保证

统一配置管理

将一部分配置信息写到Nacos配置文件中,可以统一管理,并且这个文件支持热加载。

新建配置步骤:配置管理 -> 配置列表 -> 新建配置 Data ID(配置文件名id) 命名规范:服务名称-dev(profile运行环境).yaml Group(分组) :DEFAULT_GROUP

原先后端启动的步骤是:

  1. 项目启动
  2. 读取本地配置application.yml
  3. 创建spring容器
  4. 加载bean

现在要从Nacos中读取配置文件,而次操作必须要在读取本地配置文件之前。 那么获取Nacos地址的配置就要在application.yml之前进行 所以需要建立一个优先级比application.yml优先级更高的配置文件:bootstrap.yml并将Nacos地址的配置信息写进去。

操作 第一步:引入Nacos的配置管理客户端依赖

<!--Nacos 客户端依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--Nacos 配置管理依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

第二步:新建bootstrap.yml引导文件,将服务名,nacos地址,环境,等信息写到里面

spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev #环境
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos 地址
      config:
        file-extension: yaml # 文件后缀
      discovery:
        cluster-name: HB # 集群名称

第三步:后端想要获取该配置信息,可以通过@Value("${}")的方式获取

@Value("${pattern.dateformat}")
private String dateformat;
/**
 * 获取当前时间
 * @return
 */
@GetMapping("/now")
public String now(){
    return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}

配置热更新

Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要通过下面两种配置实现:

方式一:在@Value注入的变量所在类上添加注解@RefreshScope

@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
    @Value("${pattern.dateformat}")
    private String dateformat;
    /**
     * 获取当前时间
     * @return
     */
    @GetMapping("/now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
    }
}

方式二:新建一个配置类,使用@ConfigurationProperties注解

@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
	private String dateformat;
}

注意:

  • 不是所有的配置都适合放到配置中心,维护比较麻烦
  • 建议将一些关键参数,需要运行时调整的参数放到Nacos配置中心,一般都是自定义配置。

多环境配置共享

此处暂时省略

Nacos集群搭建

第一步:搭建数据库 推荐使用MySQL,建立一个nacos的数据库,并执行以下脚本

/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/******************************************/
/*   表名称 = config_info                  */
/******************************************/
CREATE TABLE `config_info` (
                               `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
                               `data_id` varchar(255) NOT NULL COMMENT 'data_id',
                               `group_id` varchar(128) DEFAULT NULL COMMENT 'group_id',
                               `content` longtext NOT NULL COMMENT 'content',
                               `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
                               `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
                               `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
                               `src_user` text COMMENT 'source user',
                               `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
                               `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
                               `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
                               `c_desc` varchar(256) DEFAULT NULL COMMENT 'configuration description',
                               `c_use` varchar(64) DEFAULT NULL COMMENT 'configuration usage',
                               `effect` varchar(64) DEFAULT NULL COMMENT '配置生效的描述',
                               `type` varchar(64) DEFAULT NULL COMMENT '配置的类型',
                               `c_schema` text COMMENT '配置的模式',
                               `encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥',
                               PRIMARY KEY (`id`),
                               UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';

/******************************************/
/*   表名称 = config_info  since 2.5.0                */
/******************************************/
CREATE TABLE `config_info_gray` (
                                    `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
                                    `data_id` varchar(255) NOT NULL COMMENT 'data_id',
                                    `group_id` varchar(128) NOT NULL COMMENT 'group_id',
                                    `content` longtext NOT NULL COMMENT 'content',
                                    `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
                                    `src_user` text COMMENT 'src_user',
                                    `src_ip` varchar(100) DEFAULT NULL COMMENT 'src_ip',
                                    `gmt_create` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_create',
                                    `gmt_modified` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_modified',
                                    `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
                                    `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
                                    `gray_name` varchar(128) NOT NULL COMMENT 'gray_name',
                                    `gray_rule` text NOT NULL COMMENT 'gray_rule',
                                    `encrypted_data_key` varchar(256) NOT NULL DEFAULT '' COMMENT 'encrypted_data_key',
                                    PRIMARY KEY (`id`),
                                    UNIQUE KEY `uk_configinfogray_datagrouptenantgray` (`data_id`,`group_id`,`tenant_id`,`gray_name`),
                                    KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`),
                                    KEY `idx_gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='config_info_gray';

/******************************************/
/*   表名称 = config_info_beta             */
/******************************************/
CREATE TABLE `config_info_beta` (
                                    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
                                    `data_id` varchar(255) NOT NULL COMMENT 'data_id',
                                    `group_id` varchar(128) NOT NULL COMMENT 'group_id',
                                    `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
                                    `content` longtext NOT NULL COMMENT 'content',
                                    `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
                                    `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
                                    `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
                                    `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
                                    `src_user` text COMMENT 'source user',
                                    `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
                                    `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
                                    `encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥',
                                    PRIMARY KEY (`id`),
                                    UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';

/******************************************/
/*   表名称 = config_info_tag              */
/******************************************/
CREATE TABLE `config_info_tag` (
                                   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
                                   `data_id` varchar(255) NOT NULL COMMENT 'data_id',
                                   `group_id` varchar(128) NOT NULL COMMENT 'group_id',
                                   `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
                                   `tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
                                   `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
                                   `content` longtext NOT NULL COMMENT 'content',
                                   `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
                                   `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
                                   `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
                                   `src_user` text COMMENT 'source user',
                                   `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
                                   PRIMARY KEY (`id`),
                                   UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';

/******************************************/
/*   表名称 = config_tags_relation         */
/******************************************/
CREATE TABLE `config_tags_relation` (
                                        `id` bigint(20) NOT NULL COMMENT 'id',
                                        `tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
                                        `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
                                        `data_id` varchar(255) NOT NULL COMMENT 'data_id',
                                        `group_id` varchar(128) NOT NULL COMMENT 'group_id',
                                        `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
                                        `nid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识',
                                        PRIMARY KEY (`nid`),
                                        UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
                                        KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';

/******************************************/
/*   表名称 = group_capacity               */
/******************************************/
CREATE TABLE `group_capacity` (
                                  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
                                  `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
                                  `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
                                  `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
                                  `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
                                  `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
                                  `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
                                  `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
                                  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
                                  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
                                  PRIMARY KEY (`id`),
                                  UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';

/******************************************/
/*   表名称 = his_config_info              */
/******************************************/
CREATE TABLE `his_config_info` (
                                   `id` bigint(20) unsigned NOT NULL COMMENT 'id',
                                   `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识',
                                   `data_id` varchar(255) NOT NULL COMMENT 'data_id',
                                   `group_id` varchar(128) NOT NULL COMMENT 'group_id',
                                   `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
                                   `content` longtext NOT NULL COMMENT 'content',
                                   `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
                                   `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
                                   `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
                                   `src_user` text COMMENT 'source user',
                                   `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
                                   `op_type` char(10) DEFAULT NULL COMMENT 'operation type',
                                   `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
                                   `encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥',
                                   `publish_type` varchar(50)  DEFAULT 'formal' COMMENT 'publish type gray or formal',
                                   `gray_name` varchar(50)  DEFAULT NULL COMMENT 'gray name',
                                   `ext_info`  longtext DEFAULT NULL COMMENT 'ext info',
                                   PRIMARY KEY (`nid`),
                                   KEY `idx_gmt_create` (`gmt_create`),
                                   KEY `idx_gmt_modified` (`gmt_modified`),
                                   KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';


/******************************************/
/*   表名称 = tenant_capacity              */
/******************************************/
CREATE TABLE `tenant_capacity` (
                                   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
                                   `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
                                   `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
                                   `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
                                   `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
                                   `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
                                   `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
                                   `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
                                   `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
                                   `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
                                   PRIMARY KEY (`id`),
                                   UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';


CREATE TABLE `tenant_info` (
                               `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
                               `kp` varchar(128) NOT NULL COMMENT 'kp',
                               `tenant_id` varchar(128) default '' COMMENT 'tenant_id',
                               `tenant_name` varchar(128) default '' COMMENT 'tenant_name',
                               `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
                               `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
                               `gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
                               `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
                               PRIMARY KEY (`id`),
                               UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
                               KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';

CREATE TABLE `users` (
                         `username` varchar(50) NOT NULL PRIMARY KEY COMMENT 'username',
                         `password` varchar(500) NOT NULL COMMENT 'password',
                         `enabled` boolean NOT NULL COMMENT 'enabled'
);

CREATE TABLE `roles` (
                         `username` varchar(50) NOT NULL COMMENT 'username',
                         `role` varchar(50) NOT NULL COMMENT 'role',
                         UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);

CREATE TABLE `permissions` (
                               `role` varchar(50) NOT NULL COMMENT 'role',
                               `resource` varchar(128) NOT NULL COMMENT 'resource',
                               `action` varchar(8) NOT NULL COMMENT 'action',
                               UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);

第二步:下载并且解压nacos 在conf目录下,有配置文件cluster.conf,每行配置成ip(三个或以上节点)

#it is ip  ip:port
#example
192.168.16.101:8847
192.168.16.102
192.168.16.103

第三步:修改配置文件nacos/conf/application.properties

增加支持MySQL数据源配置,添加MySQL数据源的url、用户名和密码。

spring.sql.init.platform=mysql

db.num=1
db.url.0=jdbc:mysql://${mysql_host}:${mysql_port}/${nacos_database}?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=${mysql_user}
db.password=${mysql_password}

开启默认鉴权插件(可选,推荐)

nacos.core.auth.enabled=true
nacos.core.auth.system.type=nacos
nacos.core.auth.plugin.nacos.token.secret.key=${自定义,保证所有节点一致}
nacos.core.auth.server.identity.key=${自定义,保证所有节点一致}
nacos.core.auth.server.identity.value=${自定义,保证所有节点一致}

第四步:启动Nacos集群 每个节点都执行以下命令

# Linux/Unix/Mac
sh startup.sh

# Ubuntu

bash startup.sh

# Windows
startup.cmd

第五步:使用Nginx反向代理,修改conf/nginx.conf

upstream nacos-cluster {
    server 127.0.0.1:8845;
    server 127.0.0.1:8846;
    server 127.0.0.1:8847;
}

server {
	listen       80;
	server_name		1ocalhost;

	location /nacos {
		proxy_pass http://nacos-cluster;
	}
}

最后一步:修改Java代码

spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev #环境
  cloud:
    nacos:
    #现在Nacos地址改为Nginx反向代理的80端口
      server-addr: localhost:80 # Nacos 地址
      config:
        file-extension: yaml # 文件后缀
      discovery:
        cluster-name: HB # 集群名称

Feign

RestTemplet存在的问题

String url = "http: //userservice/user/" + order.getUserId();
User user = restTemplate.getFor0bject(url, User.class);
  • 可读性差
  • 参数复杂
  • URL难以维护

Feign声明式的Http客户端,官方地址:https://github.com/OpenFeign/feign

使用案例

  1. 引入坐标
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 在需要发起请求的服务的启动类添加注解开启Feign的功能
@EnableFeignClients
  1. 编写Feign客户端
@FeignClient("userservice")	//指明服务名称
public interface UserClient {
    @GetMapping("/user/{id}")	//请求类型和请求路径
    User findById(@PathVariable("id") Long id);	//返回值与请求参数
}
  1. 使用声明出的方法代替原始的RestTemplate
@Autowired
private UserClient userClient;

public Order queryOrderById(Long orderId) {
    // 1.查询订单
    Order order = orderMapper.findById(orderId);

    /*
    String url = "http://userservice/user/" + order.getUserId();
    //使用RestTemplate发送请求
    User user = restTemplate.getForObject(url, User.class);
    */
    
    User user = userClient.getUser(order.getUserId());
    order.setUser(user);
    
    // 4.返回
    return order;
}

可以发现简化很多,并且代码十分美观

Feigen的自定义配置

配置项描述说明
feign.Logger.Level修改日志级别NONE、BASIC、HEADERS、FULL
feign.codec.Decoder响应结果的解析器默认解析 JSON 字符串为 Java 对象,可自定义解析器
feign.codec.Encoder请求参数编码默认将请求参数编码为 HTTP 请求格式,可自定义编码器
feign.Contract支持的注解格式默认支持 SpringMVC 注解,可自定义注解格式
feign.Retryer失败重试机制默认无重试机制(但会使用 Ribbon 的重试),可自定义重试策略

一般只需要配置日志级别

配置文件写法:

#全局生效:
feign:
	client:
		config:
			default: #这里配置全局生效
				loggerLevel: FULL
			# 或者
			userservice: #这里配置局部生效
				loggerLevel: full

通过配置类来配置Feign

//声明一个类,并且在其中声明一个Bean
public class FeignClientConfigurationP{
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC;
    }
}

/*
如果是全局配置,则吧它放到@EnableFeignClients这个注解中(启动类上)
@EnableFeignclients(defaultConfiguration = FeignclientConfiguration.class)

如果是局部配置,则把它放到@FeignClient这个配置类中(配置类上)
@FeignClient(value = "userservice", configuration = FeignClientConfiguration.class)
*/

性能优化

Feign底层客户端实现

  • URLConnection:默认实现,不支持连接池
  • Apache Httpclient :支持连接池
  • OKHttp:支持连接池

因此优化Feign的性能主要包括:

  1. 使用连接池代替默认的URLConnection
  2. 日志级别,使用basic或none

此处以添加HttpClient的支持举例

  1. 导坐标
<dependency>
    <groupid>io.github. openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
  1. 配置连接池
feign:
	client:
		config:
			default: #default全局的配置
				loggerLevel: BASIC #日志级别,BASIC就是基本的请求和响应信息
	httpclient:
		enabled: true #开启feign对HttpClient的支持
		max-connections: 200 #最大的连接数
		max-connections-per-route: 50 #每个路径的最大连接数

实际项目中的使用方式

将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块当中提供给消费者使用。

  1. 创建一个feign-api的module,引入feign的依赖
  2. 将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
  3. 在order-service中引入feign-api的依赖
  4. 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包

修改后存在的问题:自定义的FeignClient不再SpringBootApplication扫描包范围时,有两种解决方法: 1.指定FeignClient所在包

@EnableFeignClients(basePackages = 'cn.itcast.feign.clients')

2.指定FeignClient字节码 //更推荐

@EnableFeignClients(clients = {UserClient.class})