zoukankan      html  css  js  c++  java
  • SpringCloud Alibaba学习笔记(转发)

    SpringCloud Alibaba学习笔记

     

    目录#

    目录

    导学#

    为什么学#

    • 组件性能更强
    • 良好的可视化界面
    • 搭建简单,学习曲线低
    • 文档丰富并且是中文

    学习目标#

    • Spring Cloud Alibaba核心组件的用法及实现原理
    • Spring Cloud Alibaba结合微信小程序从"0"学习真正开发中的使用
    • 实际工作中如何避免踩坑,正确的思考问题方式
    • Spring Cloud Alibaba的进阶:代码的优化和改善,微服务监控

    进阶目标#

    • 如何提升团队的代码质量
      • 编码技巧
      • 心得总结
    • 如何改善代码结构设计
      • 借助监控工具
      • 定位问题
      • 解决问题

    思路#

    分析并拆解微服务->编写代码->分析现有架构问题->引入微服务组件->优化重构->总结完善

    Spring Cloud Alibaba的重要组件#

    • 服务发现Nacos
      • 服务发现原理剖析
      • Nacos Server/Client
      • 高可用Nacos搭建
    • 实现负载均衡Ribbon
      • 负载均衡的常见模式
      • RestTemplate整合Ribbon
      • Ribbon配置自定义
      • 如何扩展Ribbon
    • 声明式HTTP客户端-Feign
      • 如何使用Feign
      • Feign配置自定义
      • 如何扩展Feign
    • 服务容错Sentinel
      • 服务容错原理
      • Sentinel
      • Sentinel DashBoard
      • Sentinel核心原理分析
    • 消息驱动RocketMq
      • Spring Cloud Stream
      • 实现异步消息推送与消费
    • API网关Gateway
      • 整合Gateway
      • 三大核心
      • 聚合微服务请求
    • 用户认证与授权
      • 认证授权的常见方案
      • 改造Gateway
      • 扩展Feign
    • 配置管理Nacos
      • 配置如何管理
      • 配置动态刷新
      • 配置管理的最佳实践
    • 调用链监控Sleuth
      • 调用链监控原理剖析
      • Sleuth使用
      • Zipkin使用

    环境搭建#

    • JDK8
    • MySQL
    • Maven的安装与配置
    • IDEA

    Spring Boot必知必会#

    Spring Boot特性#

    • 无需部署WAR文件
    • 提供stater简化配置
    • 尽可能自动配置Spring以及第三方库
    • 提供"生产就绪"功能,例如指标、健康检查、外部配置等
    • 无代码生成&无XML

    编写第一个Spring Boot应用#

    Spring Boot应用组成分析#

    • 依赖:pom.xml
    • 启动类:注解
    • 配置:application.properties
    • static目录:静态文件
    • templates目录:模板文件

    Spring Boot开发三板斧#

    • 加依赖
    • 写注解
    • 写配置

    Spring Boot Actuator#

    监控工具

    /actuator#

    入口

    /health#

    健康检查

    显示详情配置

    management.endpoint.health.show-details=always
    # 显示所有监控端点
    management.endpoints.web.exposure.include=*
    
    # 描述信息(自定义键值对)
    info.app-name=spring-boot-demo
    info.author=kim
    info.email=xxx@163.com
    

    Spring Boot配置管理#

    支持的配置格式

    management:
      endpoint:
        health:
          show-details: always
      endpoints:
        web:
          exposure:
            include: '*'
    

    # 描述信息
    info:
    app-name: spring-boot-demo
    author: kim
    email: xxx@163.com

    注意:值是*,yml写法需要加引号

    • yml是使用趋势
    • yml在有的配置中可以表达顺序,properties不行

    17种配置方式

    实际项目种经常用到的配置管理方式:

    • 配置文件
    • 环境变量
    • 外部配置文件
    • 命令行参数

    环境变量方式配置管理#

    application.yml

    management:
      endpoint:
        health:
          show-details: ${SOME_ENV}
      endpoints:
        web:
          exposure:
            include: '*'
    

    # 描述信息
    info:
    app-name: spring-boot-demo
    author: kim
    email: xxx@163.com

    设置环境变量SOME_ENV

    环境变量方式配置管理(java -jar方式)#

    mvn clean install -DskipTests

    java -jar spring-boot-demo-0.0.1-SNAPSHOT.jar --SOME_ENV=always

    外部配置文件方式配置管理#

    将打的jar包和配置文件放在同一目录,会优先读取该配置文件内配置

    命令行参数方式配置管理#

    java -jar spring-boot-demo-0.0.1-SNAPSHOT.jar --server.port=8081

    最佳实践#

    KISS,规避掉优先级,没人会记住17中配置姿势的优先级。

    Profile#

    # 所有环境下公用的配置属性
    management:
      endpoint:
        health:
          show-details: ${SOME_ENV}
      endpoints:
        web:
          exposure:
            include: '*'
    

    # 描述信息
    info:
    app-name: spring-boot-demo
    author: kim
    email: xxx@163.com

    # 连字符
    ---
    # profile=x的专用属性,也就是说某个环境下的专用属性
    # 开发环境
    spring:
    profiles: dev

    ---
    # profile=y的专用属性,也就是说某个环境下的专用属性
    # 生产环境
    spring:
    profiles: prod
    server:
    tomcat:
    max-threads: 300
    max-connections: 1000

    IDEA启动配置

    访问http://localhost:8080/actuator/configprops通过actuator端口查看

    默认使用default,可以通过添加配置设置默认profile

    spring:
      profiles:
        active: dev
    

    最佳实践#

    KISS,不要使用优先级,规划好公用和专用配置

    微服务拆分与编写#

    • 单体架构vs微服务架构
      • 单体架构是什么
      • 微服务是什么
      • 微服务特性
      • 微服务全景架构图
      • 微服务优缺点
      • 微服务适用场景
    • 业务分析与建模
      • 项目功能演示与分析
      • 微服务拆分
      • 项目架构图
      • 数据库设计
      • API文档
    • 编写微服务
      • 创建小程序
      • 创建项目
      • 编写用户微服务
      • 编写内容微服务

    单体架构#

    优点:

    • 架构简单
    • 开发、测试、部署方便

    缺点:

    • 复杂性高
    • 部署慢,频率低
    • 扩展能力受限(比如用户模块是CPU密集的,只能通过买更好的CPU的机器,比如内容模块是IO密集的,只能通过购买更多内存)
    • 阻碍技术创新(SpringMVC->Spring Web Flux,改动大)

    不适合庞大复杂的系统

    微服务#

    拆分后的小型服务

    微服务的特性#

    • 每个微服务可独立运行在自己的进程里;(每个服务一个Tomcat)
    • 一系列独立运行的微服务共同构建起整个系统
    • 每个服务为独立的业务开发,一个微服务只关注某个特定的功能,例如订单管理、用户管理
    • 可以使用不同的语言与数据存储技术(契合项目情况和团队实力)
    • 微服务之间通过轻量的通信机制进行通信,例如通过Rest API进行调用;(通信协议轻量、跨平台)
    • 全自动的部署机制

    微服务全景架构图#

    优点#

    • 单个服务更易于开发、维护
    • 单个微服务启动较快
    • 局部修改容易部署
    • 技术栈不受限

    缺点#

    • 运维要求高
    • 分布式固有的复杂性
    • 重复劳动(不同语言调用相同功能时)

    适用场景#

    • 大型、复杂的项目
    • 有快速迭代的需求
    • 访问压力大(微服务去中心化,把业务和数据都拆分了,可以应对访问压力)

    不适用微服务的场景#

    • 业务稳定
    • 迭代周期长

    项目演示#

    微服务拆分#

    • 业界流行的拆分方法论
    • 个人心得
    • 合理粒度
    • 小程序的拆分

    方法论#

    • 领域驱动设计(Domain Driven Design)(概念太多,学习曲线高)

    • 面向对象(by name./by verb)(通过名词(状态),动词(行为)拆分)

    个人心得#

    职责划分

    规划好微服务的边界。比如订单微服务只负责订单功能。

    通用性划分

    把一些通用功能做成微服务。比如消息中心和用户中心。

    合理的粒度#

    • 良好的满足业务(这是前提)
    • 幸福感(你的团队没有人认为微服务太大,难以维护,同时部署也非常高效,不会每次发布都发布N多微服务)
    • 增量迭代
    • 持续进化

    小程序的拆分#

    以面向对象方式拆分

    用户中心按照通用性划分,内容中心按照职责划分。

    项目初期不建议拆分太细,后期如果发现某个微服务过分庞大再细分。

    项目架构图#

    数据库设计#

    数据建模
    建表

    user-center-create-table.sql

    USE `user_center`;
    

    -- -----------------------------------------------------
    -- Table user
    -- -----------------------------------------------------
    CREATE TABLE IF NOT EXISTS user (
    id INT NOT NULL AUTO_INCREMENT COMMENT 'Id',
    wx_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '微信id',
    wx_nickname VARCHAR(64) NOT NULL DEFAULT '' COMMENT '微信昵称',
    roles VARCHAR(100) NOT NULL DEFAULT '' COMMENT '角色',
    avatar_url VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像地址',
    create_time DATETIME NOT NULL COMMENT '创建时间',
    update_time DATETIME NOT NULL COMMENT '修改时间',
    bonus INT NOT NULL DEFAULT 300 COMMENT '积分',
    PRIMARY KEY (id))
    COMMENT = '分享';

    -- -----------------------------------------------------
    -- Table bonus_event_log
    -- -----------------------------------------------------
    CREATE TABLE IF NOT EXISTS bonus_event_log (
    id INT NOT NULL AUTO_INCREMENT COMMENT 'Id',
    user_id INT NULL COMMENT 'user.id',
    value INT NULL COMMENT '积分操作值',
    event VARCHAR(20) NULL COMMENT '发生的事件',
    create_time DATETIME NULL COMMENT '创建时间',
    description VARCHAR(100) NULL COMMENT '描述',
    PRIMARY KEY (id),
    INDEX fk_bonus_event_log_user1_idx (user_id ASC) )
    ENGINE = InnoDB
    COMMENT = '积分变更记录表';

    content-center-create-table.sql

    USE `content_center`;
    

    -- -----------------------------------------------------
    -- Table share
    -- -----------------------------------------------------
    CREATE TABLE IF NOT EXISTS share (
    id INT NOT NULL AUTO_INCREMENT COMMENT 'id',
    user_id INT NOT NULL DEFAULT 0 COMMENT '发布人id',
    title VARCHAR(80) NOT NULL DEFAULT '' COMMENT '标题',
    create_time DATETIME NOT NULL COMMENT '创建时间',
    update_time DATETIME NOT NULL COMMENT '修改时间',
    is_original TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否原创 0:否 1:是',
    author VARCHAR(45) NOT NULL DEFAULT '' COMMENT '作者',
    cover VARCHAR(256) NOT NULL DEFAULT '' COMMENT '封面',
    summary VARCHAR(256) NOT NULL DEFAULT '' COMMENT '概要信息',
    price INT NOT NULL DEFAULT 0 COMMENT '价格(需要的积分)',
    download_url VARCHAR(256) NOT NULL DEFAULT '' COMMENT '下载地址',
    buy_count INT NOT NULL DEFAULT 0 COMMENT '下载数 ',
    show_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否显示 0:否 1:是',
    audit_status VARCHAR(10) NOT NULL DEFAULT 0 COMMENT '审核状态 NOT_YET: 待审核 PASSED:审核通过 REJECTED:审核不通过',
    reason VARCHAR(200) NOT NULL DEFAULT '' COMMENT '审核不通过原因',
    PRIMARY KEY (id))
    ENGINE = InnoDB
    COMMENT = '分享表';

    -- -----------------------------------------------------
    -- Table mid_user_share
    -- -----------------------------------------------------
    CREATE TABLE IF NOT EXISTS mid_user_share (
    id INT NOT NULL AUTO_INCREMENT,
    share_id INT NOT NULL COMMENT 'share.id',
    user_id INT NOT NULL COMMENT 'user.id',
    PRIMARY KEY (id),
    INDEX fk_mid_user_share_share1_idx (share_id ASC) ,
    INDEX fk_mid_user_share_user1_idx (user_id ASC) )
    ENGINE = InnoDB
    COMMENT = '用户-分享中间表【描述用户购买的分享】';

    -- -----------------------------------------------------
    -- Table notice
    -- -----------------------------------------------------
    CREATE TABLE IF NOT EXISTS notice (
    id INT NOT NULL AUTO_INCREMENT COMMENT 'id',
    content VARCHAR(255) NOT NULL DEFAULT '' COMMENT '内容',
    show_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否显示 0:否 1:是',
    create_time DATETIME NOT NULL COMMENT '创建时间',
    PRIMARY KEY (id));

    API 文档#

    课程文档主要分四类:

    1. API文档:https://t.itmuch.com/doc.html
    2. 课程配套代码:https://git.imooc.com/coding-358/
    3. 课程相关资源(例如检表语句、数据模型、课上用到的软件等):https://git.imooc.com/coding-358/resource
    4. 课上用到的一些课外读物(慕课网手记):http://www.imooc.com/t/1863086

    如何创建小程序#

    注册账号:https://mp.weixin.qq.com

    按照提示填写信息

    前端代码如何使用#

    创建项目#

    技术选型
    • Spring Boot
    • Spring MVC
    • Mybatis+通用Mapper
    • Spring Cloud Alibaba(分布式)
    工程结构规划

    创建项目,整合框架

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.1.13.RELEASE</version>
    		<relativePath/> <!-- lookup parent from repository -->
    	</parent>
    	<groupId>com.itmuch</groupId>
    	<artifactId>user-center</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>user-center</name>
    	<description>Demo project for Spring Boot</description>
    
    <span class="hljs-tag">&lt;<span class="hljs-name">properties&gt;
    	<span class="hljs-tag">&lt;<span class="hljs-name">java.version&gt;1.8<span class="hljs-tag">&lt;/<span class="hljs-name">java.version&gt;
    <span class="hljs-tag">&lt;/<span class="hljs-name">properties&gt;
    
    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies&gt;
    	<span class="hljs-tag">&lt;<span class="hljs-name">dependency&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">groupId&gt;org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">artifactId&gt;spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId&gt;
    	<span class="hljs-tag">&lt;/<span class="hljs-name">dependency&gt;
    

    <!-- 引入通用mapper-->
    <dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.1.5</version>
    </dependency>

    	<span class="hljs-tag">&lt;<span class="hljs-name">dependency&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">groupId&gt;mysql<span class="hljs-tag">&lt;/<span class="hljs-name">groupId&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">artifactId&gt;mysql-connector-java<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">scope&gt;runtime<span class="hljs-tag">&lt;/<span class="hljs-name">scope&gt;
    	<span class="hljs-tag">&lt;/<span class="hljs-name">dependency&gt;
    	<span class="hljs-tag">&lt;<span class="hljs-name">dependency&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">groupId&gt;org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">artifactId&gt;spring-boot-starter-test<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">scope&gt;test<span class="hljs-tag">&lt;/<span class="hljs-name">scope&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">exclusions&gt;
    			<span class="hljs-tag">&lt;<span class="hljs-name">exclusion&gt;
    				<span class="hljs-tag">&lt;<span class="hljs-name">groupId&gt;org.junit.vintage<span class="hljs-tag">&lt;/<span class="hljs-name">groupId&gt;
    				<span class="hljs-tag">&lt;<span class="hljs-name">artifactId&gt;junit-vintage-engine<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId&gt;
    			<span class="hljs-tag">&lt;/<span class="hljs-name">exclusion&gt;
    		<span class="hljs-tag">&lt;/<span class="hljs-name">exclusions&gt;
    	<span class="hljs-tag">&lt;/<span class="hljs-name">dependency&gt;
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies&gt;
    
    <span class="hljs-tag">&lt;<span class="hljs-name">build&gt;
    	<span class="hljs-tag">&lt;<span class="hljs-name">plugins&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">plugin&gt;
    			<span class="hljs-tag">&lt;<span class="hljs-name">groupId&gt;org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId&gt;
    			<span class="hljs-tag">&lt;<span class="hljs-name">artifactId&gt;spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId&gt;
    		<span class="hljs-tag">&lt;/<span class="hljs-name">plugin&gt;
    		<span class="hljs-tag">&lt;<span class="hljs-name">plugin&gt;
    			<span class="hljs-tag">&lt;<span class="hljs-name">groupId&gt;org.mybatis.generator<span class="hljs-tag">&lt;/<span class="hljs-name">groupId&gt;
    			<span class="hljs-tag">&lt;<span class="hljs-name">artifactId&gt;mybatis-generator-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId&gt;
    			<span class="hljs-tag">&lt;<span class="hljs-name">version&gt;1.3.6<span class="hljs-tag">&lt;/<span class="hljs-name">version&gt;
    			<span class="hljs-tag">&lt;<span class="hljs-name">configuration&gt;
    				<span class="hljs-tag">&lt;<span class="hljs-name">configurationFile&gt;
    					${basedir}/src/main/resources/generator/generatorConfig.xml
    				<span class="hljs-tag">&lt;/<span class="hljs-name">configurationFile&gt;
    				<span class="hljs-tag">&lt;<span class="hljs-name">overwrite&gt;true<span class="hljs-tag">&lt;/<span class="hljs-name">overwrite&gt;
    				<span class="hljs-tag">&lt;<span class="hljs-name">verbose&gt;true<span class="hljs-tag">&lt;/<span class="hljs-name">verbose&gt;
    			<span class="hljs-tag">&lt;/<span class="hljs-name">configuration&gt;
    			<span class="hljs-tag">&lt;<span class="hljs-name">dependencies&gt;
    				<span class="hljs-tag">&lt;<span class="hljs-name">dependency&gt;
    					<span class="hljs-tag">&lt;<span class="hljs-name">groupId&gt;mysql<span class="hljs-tag">&lt;/<span class="hljs-name">groupId&gt;
    					<span class="hljs-tag">&lt;<span class="hljs-name">artifactId&gt;mysql-connector-java<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId&gt;
    					<span class="hljs-tag">&lt;<span class="hljs-name">version&gt;8.0.19<span class="hljs-tag">&lt;/<span class="hljs-name">version&gt;
    				<span class="hljs-tag">&lt;/<span class="hljs-name">dependency&gt;
    				<span class="hljs-tag">&lt;<span class="hljs-name">dependency&gt;
    					<span class="hljs-tag">&lt;<span class="hljs-name">groupId&gt;tk.mybatis<span class="hljs-tag">&lt;/<span class="hljs-name">groupId&gt;
    					<span class="hljs-tag">&lt;<span class="hljs-name">artifactId&gt;mapper<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId&gt;
    					<span class="hljs-tag">&lt;<span class="hljs-name">version&gt;4.1.5<span class="hljs-tag">&lt;/<span class="hljs-name">version&gt;
    				<span class="hljs-tag">&lt;/<span class="hljs-name">dependency&gt;
    			<span class="hljs-tag">&lt;/<span class="hljs-name">dependencies&gt;
    		<span class="hljs-tag">&lt;/<span class="hljs-name">plugin&gt;
    	<span class="hljs-tag">&lt;/<span class="hljs-name">plugins&gt;
    <span class="hljs-tag">&lt;/<span class="hljs-name">build&gt;
    

    </project>

    通用Mapper包扫描配置

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import tk.mybatis.spring.annotation.MapperScan;//注意是tk的MapperScan注解
    

    @SpringBootApplication
    @MapperScan("com.itmuch")
    public class UserCenterApplication {

    <span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-keyword">static <span class="hljs-keyword">void <span class="hljs-title">main<span class="hljs-params">(String[] args) {
    	SpringApplication.run(UserCenterApplication.class, args);
    }
    

    }

    在resources目录下新建generator目录,添加mybatis.generator配置

    generator/generatorConfig.xml

    <!DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    

    <generatorConfiguration>
    <properties resource="generator/config.properties"/>

    <span class="hljs-tag">&lt;<span class="hljs-name">context <span class="hljs-attr">id=<span class="hljs-string">"Mysql" <span class="hljs-attr">targetRuntime=<span class="hljs-string">"MyBatis3Simple" <span class="hljs-attr">defaultModelType=<span class="hljs-string">"flat"&gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">property <span class="hljs-attr">name=<span class="hljs-string">"beginningDelimiter" <span class="hljs-attr">value=<span class="hljs-string">"`"/&gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">property <span class="hljs-attr">name=<span class="hljs-string">"endingDelimiter" <span class="hljs-attr">value=<span class="hljs-string">"`"/&gt;
    
        <span class="hljs-tag">&lt;<span class="hljs-name">plugin <span class="hljs-attr">type=<span class="hljs-string">"tk.mybatis.mapper.generator.MapperPlugin"&gt;
            <span class="hljs-tag">&lt;<span class="hljs-name">property <span class="hljs-attr">name=<span class="hljs-string">"mappers" <span class="hljs-attr">value=<span class="hljs-string">"tk.mybatis.mapper.common.Mapper"/&gt;
            <span class="hljs-tag">&lt;<span class="hljs-name">property <span class="hljs-attr">name=<span class="hljs-string">"caseSensitive" <span class="hljs-attr">value=<span class="hljs-string">"true"/&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugin&gt;
    
        <span class="hljs-tag">&lt;<span class="hljs-name">jdbcConnection <span class="hljs-attr">driverClass=<span class="hljs-string">"${jdbc.driverClass}"
                        <span class="hljs-attr">connectionURL=<span class="hljs-string">"${jdbc.url}"
                        <span class="hljs-attr">userId=<span class="hljs-string">"${jdbc.user}"
                        <span class="hljs-attr">password=<span class="hljs-string">"${jdbc.password}"&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">jdbcConnection&gt;
    
        <span class="hljs-tag">&lt;<span class="hljs-name">javaModelGenerator <span class="hljs-attr">targetPackage=<span class="hljs-string">"com.itmuch.usercenter.domain.entity.${moduleName}"
                            <span class="hljs-attr">targetProject=<span class="hljs-string">"src/main/java"/&gt;
    
        <span class="hljs-tag">&lt;<span class="hljs-name">sqlMapGenerator <span class="hljs-attr">targetPackage=<span class="hljs-string">"com.itmuch.usercenter.dao.${moduleName}"
                         <span class="hljs-attr">targetProject=<span class="hljs-string">"src/main/resources"/&gt;
    
        <span class="hljs-tag">&lt;<span class="hljs-name">javaClientGenerator <span class="hljs-attr">targetPackage=<span class="hljs-string">"com.itmuch.usercenter.dao.${moduleName}"
                             <span class="hljs-attr">targetProject=<span class="hljs-string">"src/main/java"
                             <span class="hljs-attr">type=<span class="hljs-string">"XMLMAPPER"/&gt;
    
        <span class="hljs-tag">&lt;<span class="hljs-name">table <span class="hljs-attr">tableName=<span class="hljs-string">"${tableName}"&gt;
            <span class="hljs-tag">&lt;<span class="hljs-name">generatedKey <span class="hljs-attr">column=<span class="hljs-string">"id" <span class="hljs-attr">sqlStatement=<span class="hljs-string">"JDBC"/&gt;
        <span class="hljs-tag">&lt;/<span class="hljs-name">table&gt;
    <span class="hljs-tag">&lt;/<span class="hljs-name">context&gt;
    

    </generatorConfiguration>

    generator/config.properties

    jdbc.driverClass=com.mysql.cj.jdbc.Driver
    # nullCatalogMeansCurrent=true 如果不加这个配置,出现表名user在其他库,比如系统库的,会生产系统库的user
    jdbc.url=jdbc:mysql://localhost:3306/user_center?nullCatalogMeansCurrent=true
    jdbc.user=root
    jdbc.password=kim@2020
    
    # 包名
    moduleName=user
    # 表名
    tableName=user
    

    application.yml

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/user_center
        hikari:
          username: root
          password: kim@2020
          # >=6.x com.mysql.cj.jdbc.Driver
          # <=5.x com.mysql.jdbc.Driver
          driver-class-name: com.mysql.cj.jdbc.Driver
    

    执行逆向生产代码

    整合Lombok简化代码#

    <dependency>
    			<groupId>org.projectlombok</groupId>
    			<artifactId>lombok</artifactId>
    			<version>1.18.10</version>
    			<scope>provided</scope>
    		</dependency>
    

    常用注解

    @Data
    @NoArgsConstructor//生成无参构造
    @AllArgsConstructor//为所有参数生成构造
    @RequiredArgsConstructor//为final属性生成构造方法
    @Builder //建造者模式
    @Slf4j
    

    更多查询官网

    通用mapper wikilombok,看有没有生成支持lombok的配置

    mybatis.generator添加lombok支持
    <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
                <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
                <property name="caseSensitive" value="true"/>
                <property name="lombok" value="Getter,Setter,ToString"/><!-- 添加的行 -->
            </plugin>
    

    文档也说了,目前只支持@Getter@Setter@ToString@Accessors(chain = true)4种注解,一般我们自己的domain上还是习惯加如下注解:

    @Data
    @NoArgsConstructor//生成无参构造
    @AllArgsConstructor//为所有参数生成构造
    @Builder //建造者模式
    

    可以手动加,更简单。

    解决IDEA的红色警告#

    出现警告的原因:

    IDEA是非常智能的,它可以理解Spring的上下文。然而 UserMapper 这个接口是Mybatis的,IDEA理解不了。

    而 @Autowired 注解,默认情况下要求依赖对象(也就是 userMapper )必须存在。而IDEA认为这个对象的实例/代理是个null,所以就友好地给个提示。

    解决方法:参见这篇手记

    作业1: 课后研究一下@Resource和@Autowired注解
    作业2: 研究@Repository、@Component、@Service、@Controller之间的区别和联系

    编写用户微服务和内容微服务#

    注意:核心业务,一定要设计好业务流程,分析的过程中,使用业务流程图、活动图、用例图、序列图。重视业务和建模,没有建模的微服务是没有灵魂的。

    实际开发流程

    Schema First

    1、分析业务(流程图、用例图...架构图等) 建模业务,确定架构

    2、敲定业务流程(评审)

    3、设计API/数据模型(表结构设计|类图|ER图)

    4、编写API文档

    5、编写代码

    API First

    1、分析业务(流程图、用例图...架构图等) 建模业务,确定架构

    2、敲定业务流程(评审)

    3、设计API/数据模型(表结构设计|类图|ER图)

    4、编写代码

    5、编写API文档

    但是实际也不是完全按照这样等流程走。

    编码。。。

    RestTemplate的使用

    现有架构存在的问题#

    • 硬编码IP,IP变化怎么办
    • 如何实现负载均衡?
    • 用户中心挂了怎么办?

    Spring Cloud介绍#

    什么是Spring Cloud Alibaba#

    • Spring Cloud的子项目
    • 致力于提供微服务开发的一站式解决方案
      • 包含微服务开发的必备组件
      • 基于Spring Cloud,符合Spring Cloud标准
      • 阿里的微服务解决方案

    版本与兼容性#

    • Spring Cloud 版本命名
    • Spring Cloud 生命周期
    • Spring Boot 、Spring Cloud、Spring Cloud Alibaba的兼容性关系
    • 生产环境怎么选择版本?

    Spring Cloud 版本命名#

    语义化

    2.1.13.RELEASE

    2:主版本,第几代

    1:次版本,一些功能的增加,但是架构没有太大变化,是兼容的

    13:增量版本,bug修复

    RELEASE:里程碑。SNAPSHOT:开发版 ,M:里程碑 ,RELEASE:正式版

    Greenwich SR1 :Greenwich版本的第一个bug修复版

    SR:Service Release bug修复

    Release Train. 发布列车

    伦敦地铁站站名。避免混淆,噱头。

    Greenwich RELEASE: Greenwich版本的第一个正式版

    Spring Cloud 生命周期#

    版本兼容性#

    https://spring.io/projects/spring-cloud-alibaba#overview

    https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

    生产环境怎么选择版本?#

    • 坚决不用非稳定版本/end-of-life版本
    • 尽量用最新一代
      • xxx.RELEASE版本缓一缓
      • SR2之后一般可大规模使用

    整合Spring Cloud Alibaba#

    整合好后,引入组件不需要指定版本

    服务发现#

    服务提供者与服务消费者#

    名次定义
    服务提供者 服务的被调用方(即:为其他微服务提供接口的微服务)
    服务消费者 服务的调用方(即:调用其他微服务接口的微服务)

    如何让服务消费者感知到服务提供者#

    服务消费者内部使用定时任务去服务发现组件获取提供者信息,并缓存到本地,服务消费者每次调用服务提供者从本地缓存那提供者信息。

    添加心跳机制,通过心跳机制改变服务状态

    什么是Nacos#

    官网什么是Nacos

    搭建Nacos Server#

    选择Nacos Server版本

    查看引入到spring-cloud-alibaba-dependencie依赖

    启动服务器

    startup.sh -m standalone

    访问控制台

    http://localhost:8848/nacos

    默认用户名密码都是nacos

    将应用注册到Nacos#

    • 用户中心注册到Nacos
    • 内容中心注册到Nacos
    • 测试:内容中心总能找到用户中心

    引入依赖

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

    配置

    spring:
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
      application:
        # 服务名称尽量用-,不要用_,不要用特殊字符
        name: content-center
    

    引入服务发现#

    @Slf4j
    @Service
    @RequiredArgsConstructor(onConstructor = @__(@Autowired))
    public class ShareService {
        private final ShareMapper shareMapper;
    
    <span class="hljs-keyword">private <span class="hljs-keyword">final RestTemplate restTemplate;
    
    <span class="hljs-keyword">private <span class="hljs-keyword">final DiscoveryClient discoveryClient;
    
    <span class="hljs-function"><span class="hljs-keyword">public ShareDto <span class="hljs-title">findById<span class="hljs-params">(Integer id){
        <span class="hljs-comment">//获取分享详情
        Share share = <span class="hljs-keyword">this.shareMapper.selectByPrimaryKey(id);
        <span class="hljs-comment">//发布人id
        Integer userId = share.getUserId();
        <span class="hljs-comment">//用户中心所有实例的信息
        List&lt;ServiceInstance&gt; instances = discoveryClient.getInstances(<span class="hljs-string">"user-center");
        String targetURL = instances.stream()
                .map(instance -&gt; instance.getUri().toString() + <span class="hljs-string">"/users/{id}")
                .findFirst()
                .orElseThrow(() -&gt; <span class="hljs-keyword">new IllegalArgumentException(<span class="hljs-string">"当前没有实例!"));
    
        log.info(<span class="hljs-string">"请求的目标地址:{}", targetURL);
        UserDto userDto = restTemplate.getForObject(
                targetURL,
                UserDto.class,
                userId);
        ShareDto shareDto = <span class="hljs-keyword">new ShareDto();
    
        <span class="hljs-comment">//消息的装配
        BeanUtils.copyProperties(share, shareDto);
        shareDto.setWxNickName(userDto.getWxNickname());
        <span class="hljs-keyword">return shareDto;
    }
    

    }

    Nacos服务发现的领域模型#

    Namespace:只要用来实现环境隔离,默认public

    Group:默认DEFAULT_GROUP,管理服务分组

    Service:微服务

    Cluster:微服务集群,对指定微服务的虚拟划分

    Instance:微服务实例

    如何使用#

    Namespace,在控制台页面创建。配置的时候使用生成的uuid。

    spring:
      cloud:
        nacos:
          discovery:
            # 指定nacos server的地址
            server-addr: localhost:8848
            cluster-name: BJ
            namespace: 56116141-d837-4d15-8842-94e153bb6cfb
    

    Nacos元数据#

    元数据的作用:

    • 提供描述信息
    • 让微服务调用更灵活
      • 例如:微服务版本控制

    如何为微服务设置元数据

    • 控制台界面
    • 配置文件指定
    spring:
      cloud:
        nacos:
          discovery:
            # 指定nacos server的地址
            server-addr: localhost:8848
            cluster-name: BJ
            namespace: 56116141-d837-4d15-8842-94e153bb6cfb
            metadata:
              instance: c
              haha: hehe
              version: 1
    

    实现负载均衡-Ribbon#

    负载均衡的两种方式#

    • 服务端负载均衡
    • 客户端负载均衡(客户端调用的时候使用选择负载均衡算法)

    手写一个客户端负载均衡器#

    改写一下ShareServicefindById方法。从Nacos获取到URL列表,然后随机从列表中取一个作为本次请求的服务提供者实例。

    List<String> targetURLs = instances.stream()
                    .map(instance -> instance.getUri().toString() + "/users/{id}").collect(Collectors.toList());
    
        <span class="hljs-keyword">int i = ThreadLocalRandom.current().nextInt(targetURLs.size());
        String targetURL= targetURLs.get(i);
    

    随后启动content-center

    启动多个user-center#

    配置允许并行运行

    修改端口,运行启动类

    server:
      port: 8082
    

    使用Ribbon实现负载均衡#

    • Ribbon是什么
    • 引入Ribbon后到架构演进
    • 整合Ribbon实现负载均衡

    Ribbon是什么#

    负载均衡器

    架构演进#

    整合Ribbon实现负载均衡#

    引入Nacos

    我们引入spring-cloud-starter-alibaba-nacos-discovery时,已经引入了Ribbon

    直接使用就行了。

    写注解

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
      return new RestTemplate();
    }
    

    配置RestTemplate的地方添加@LoadBalanced注解即可。

    使用#

    UserDto userDto = restTemplate.getForObject(
                    "http://user-center/users/{userId}",
                    UserDto.class,
                    userId);
    

    Ribbon组成#

    先有个印象。二次开发再回头看

    Ribbon内置的负载均衡规则#

    默认是ZoneAvoidanceRule。

    每一个负载均衡算法源码都值得看一下。

    细粒度配置自定义#

    • Java代码配置
    • 用配置属性配置
    • 最佳实践总结

    场景:当内容中心调用用户中心微服务的时候使用随机负载,当内容中心调用其他微服务的时候使用默认负载均衡策略。

    Java代码配置#

    新建配置类,注册一个RandomRule。

    package ribbonconfiguration;
    

    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RandomRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**

    • 配置类所在的包必须是和启动类不一样的包
      */
      @Configuration
      public class RibbonConfiguration {

      @Bean
      public IRule ribbonRule(){
      return new RandomRule();
      }
      }

    新建一个user-centerribbon负载配置类,配置规则使用上面的随机规则。

    package com.itmuch.contentcenter.configuration;
    

    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    import org.springframework.context.annotation.Configuration;
    import ribbonconfiguration.RibbonConfiguration;

    @Configuration
    @RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
    public class UserCenterRibbonConfiguration {
    }

    @RibbonClient注解配置Ribbon自定义配置

    name="user-center"表示为user-center配置的。

    configuration = RibbonConfiguration.class用来指定负载均衡算法,或者负载均衡规则

    父子上下文

    这里的上下文是指Spring Context

    启动类拥有一个上下文,是父上下文,Ribbon会启动一个子上下文,父子上下文不能重叠。

    启动类的上下文,会扫描启动类所在包及子包下的Bean。

    Ribbon的配置类不能被启动类的上下文扫描到。因为Spring context是一个树状上下文。父子上下文扫描到包如果重叠会有各种问题。比如,导致事务不生效

    如果上面配置的RibbonConfiguration在启动类扫描范围内,会导致自定义配置失效,RibbonConfiguration配置的随机负载均衡全局生效。

    配置属性方式#

    user-center:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    

    这种方式没有上下文重叠的坑

    两种配置方式的对比#

    image-20200411175449054

    细粒度配置最佳实践#

    • 尽量使用属性配置,属性方式实现不了的情况下再考虑用代码配置
    • 在同一个微服务内尽量保持单一性,比如统一使用属性配置,不要两种方式混用,增加定位代码的复杂性

    全局配置#

    • 方式一:让ComponentScan上下文重叠(强烈不建议使用)
    • 方式二:唯一正确的途径:@RibbonClients(defaultConfiguration = xxx.class)
    package com.itmuch.contentcenter.configuration;
    

    import org.springframework.cloud.netflix.ribbon.RibbonClients;
    import org.springframework.context.annotation.Configuration;
    import ribbonconfiguration.RibbonConfiguration;

    @Configuration
    @RibbonClients(defaultConfiguration = RibbonConfiguration.class)
    public class UserCenterRibbonConfiguration {
    }

    支持的配置项#

    Java Config方式:见Ribbon组成一节的接口

    配置文件方式:

    image-20200411180522524

    饥饿加载#

    默认是懒加载,在调用restTemplate时才会创建一个叫user-centerRibbon Client

    user-center是要调用的客户端名字

    懒加载的问题:在第一次调用user-center的接口时,访问会慢。

    可以使用饥饿加载避免这个问题。

    ribbon:
      eager-load:
        enabled: true
        clients: user-center
    

    扩展Ribbon#

    支持Nacos权重#

    首先了解一下,Nacos的权重在0-1之间,1最大

    Ribbon内置的负载均衡规则都不支持Nacos的权重,需要自己定义一个负载均衡规则。

    @Slf4j
    public class NacosWeightedRule extends AbstractLoadBalancerRule {
    
    <span class="hljs-meta">@Autowired
    <span class="hljs-keyword">private NacosDiscoveryProperties nacosDiscoveryProperties;
    
    <span class="hljs-meta">@Override
    <span class="hljs-function"><span class="hljs-keyword">public <span class="hljs-keyword">void <span class="hljs-title">initWithNiwsConfig<span class="hljs-params">(IClientConfig iClientConfig) {
        <span class="hljs-comment">//读取配置文件,并初始化当前配置NacosWeightedRule,一般不需要实现
    }
    
    <span class="hljs-meta">@Override
    <span class="hljs-function"><span class="hljs-keyword">public Server <span class="hljs-title">choose<span class="hljs-params">(Object key) {
        BaseLoadBalancer loadBalancer = (BaseLoadBalancer) <span class="hljs-keyword">this.getLoadBalancer();
        log.info(<span class="hljs-string">"loadBalancer = {}", loadBalancer);
    
        <span class="hljs-comment">//想要请求的微服务的名称
        String name = loadBalancer.getName();
    
        <span class="hljs-comment">//实现负载均衡算法
        <span class="hljs-comment">//这里不自己实现,直接使用nacos提供的
        <span class="hljs-comment">//拿到服务发现的相关API
        NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
        <span class="hljs-keyword">try {
            Instance instance = namingService.selectOneHealthyInstance(name);
            log.info(<span class="hljs-string">"选择的实例是:port = {}, instance = {}", instance.getPort(), instance);
            <span class="hljs-keyword">return <span class="hljs-keyword">new NacosServer(instance);
        } <span class="hljs-keyword">catch (NacosException e) {
            <span class="hljs-keyword">return <span class="hljs-keyword">null;
        }
    }
    

    }

    配置为全局规则

    /**
     * 配置类所在的包必须是和启动类不一样的包
     */
    @Configuration
    public class RibbonConfiguration {
    
    <span class="hljs-meta">@Bean
    <span class="hljs-function"><span class="hljs-keyword">public IRule <span class="hljs-title">ribbonRule<span class="hljs-params">(){
        <span class="hljs-keyword">return <span class="hljs-keyword">new NacosWeightedRule();
    }
    

    }

    更多扩展方式可以扩展Ribbon支持Nacos权重的三种方式

    同一集群优先调用#

    为了实现容灾,把内容中心和用户中心部署在北京机房和南京机房里,希望调用的时候同机房优先。

    使用Nacos服务发现领域模型里的Cluster

    编写同集群优先调用规则

    @Slf4j
    public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
        @Autowired
        private NacosDiscoveryProperties nacosDiscoveryProperties;
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {
    
    }
    
    <span class="hljs-meta">@Override
    <span class="hljs-function"><span class="hljs-keyword">public Server <span class="hljs-title">choose<span class="hljs-params">(Object key) {
        <span class="hljs-comment">//拿到配置文件中的集群名称
        String clusterName = nacosDiscoveryProperties.getClusterName();
        BaseLoadBalancer loadBalancer = (BaseLoadBalancer) <span class="hljs-keyword">this.getLoadBalancer();
        log.info(<span class="hljs-string">"loadBalancer = {}", loadBalancer);
    
        <span class="hljs-comment">//想要请求的微服务的名称
        String name = loadBalancer.getName();
    
        <span class="hljs-comment">//拿到服务发现的相关API
        NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
        <span class="hljs-keyword">try {
            <span class="hljs-comment">//1 找到指定服务的所有实例
            List&lt;Instance&gt; instances = namingService.selectInstances(name, <span class="hljs-keyword">true);
            <span class="hljs-comment">//2 过滤出相同集群下的所有实例
            Stream&lt;Instance&gt; instanceStream = instances.stream()
                    .filter(instance -&gt; Objects.equals(instance.getClusterName(), clusterName));
            List&lt;Instance&gt; sameClusterInstances = instanceStream.collect(Collectors.toList());
    
            List&lt;Instance&gt; instancesToBeChosen;
            <span class="hljs-keyword">if(CollectionUtils.isEmpty(sameClusterInstances)){
                instancesToBeChosen = instances;
                log.warn(<span class="hljs-string">"发生跨集群调用,name = {}, clusterName = {}, instances = {}",
                        name,
                        clusterName,
                        instances);
            }<span class="hljs-keyword">else {
                instancesToBeChosen = sameClusterInstances;
            }
            <span class="hljs-comment">//3 基于权重的负载均衡算法,返回1个实例
            Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
            log.info(<span class="hljs-string">"选择的实例是 port = {}, instances = {} ",instance.getPort(), instance);
    
            <span class="hljs-keyword">return <span class="hljs-keyword">new NacosServer(instance);
        } <span class="hljs-keyword">catch (NacosException e) {
            log.error(<span class="hljs-string">"发生异常",e);
        }
        <span class="hljs-keyword">return <span class="hljs-keyword">null;
    }
    

    }

    class ExtendBalancer extends Balancer{
    //Nacos没有暴露从实例列表中选一个,只有selectOneHealthyInstance
    public static Instance getHostByRandomWeight2(List<Instance> hosts) {
    return getHostByRandomWeight(hosts);
    }
    }

    配置全局NacosSameClusterWeightedRule

    @Configuration
    public class RibbonConfiguration {
    
    <span class="hljs-meta">@Bean
    <span class="hljs-function"><span class="hljs-keyword">public IRule <span class="hljs-title">ribbonRule<span class="hljs-params">(){
        <span class="hljs-keyword">return <span class="hljs-keyword">new NacosSameClusterWeightedRule();
    }
    

    }

    配置所在集群

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/content_center
        hikari:
          username: root
          password: kim@2020
          # >=6.x com.mysql.cj.jdbc.Driver
          # <=5.x com.mysql.jdbc.Driver
          driver-class-name: com.mysql.cj.jdbc.Driver
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
            cluster-name: BJ
      application:
        # 服务名称尽量用-,不要用_,不要用特殊字符
        name: content-center
    

    logging:
    level:
    com.itmuch.usercenter.dao.content: debug

    server:
    servlet:
    context-path:
    port: 8010

    #user-center:
    # ribbon:
    # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    ribbon:
    eager-load:
    enabled: true
    clients: user-center

    启动内容中心服务

    接下来,配置两个用户中心服务,分别配置不同的集群和端口

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/user_center
        hikari:
          username: root
          password: kim@2020
          # >=6.x com.mysql.cj.jdbc.Driver
          # <=5.x com.mysql.jdbc.Driver
          driver-class-name: com.mysql.cj.jdbc.Driver
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
            # 多集群配置
            cluster-name: BJ
      application:
        # 服务名称尽量用-,不要用_,不要用特殊字符
        name: user-center
    

    logging:
    level:
    com.itmuch.usercenter.dao.user: debug
    server:
    # 本地启动多个实例,启动前记得改端口
    port: 8081

    查看Nacos控制台

    image-20200412103656785

    观察到user-center的集群数目是2。点击详情

    image-20200412103753967

    页面访问请求http://localhost:8010/shares/1

    image-20200412103847501

    可以看到总是请求到相同机房的实例(8081也属于BJ集群)。

    模拟BJ集群下线。选择Nacos控制台里BJ集群的8081实例,将其下线。

    再次浏览器访问http://localhost:8010/shares/1

    可以观察到已经请求到了异地机房的NJ机房的实例8082

    image-20200412104229107

    番外:为开源项目贡献代码#

    目前同集群优先调用规则已经在新版本中被采纳了,可以直接配置。我用的是2.1.0.RELEASE版本

    image-20200412104443239

    同集群优先调用规则的类是com.alibaba.cloud.nacos.ribbon.NacosRule,直接配置这个类使用,不需要再扩展了。

    基于元数据的版本控制#

    配置元数据,只要在spring.cloud.nacos.discovery.metadata下配置key-value对就可以

    spring:
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
            cluster-name: BJ
            metadata:
              version: v1.0
    

    核心逻辑是,服务提供者和服务消费者配置相同的或不同的version元数据,在服务消费者请求服务提供者的时候,从待选实例中过滤一下,找到相同版本号的实例列表,再用一种负载算法从从版本号列表中选一个实例。

    String version = nacosDiscoveryProperties.getMetadata().get("version");
    NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
            try {
                //1 找到指定服务的所有实例
                List<Instance> instances = namingService.selectInstances(name, true);
                //过滤出同集群的实例列表
                //过滤出版本号相同的实例列表
                List<Instance> sameVersionInstances = instancesToBeChosen.stream()
                        .filter(instance -> Objects.equals(instance.getMetadata().get("version"), version))
                        .collect(Collectors.toList());
                //从列表中选出一个实例
              Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
    

    具体实现参见手记

    深入理解Namespace#

    配置namespace

    image-20200412110517289

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/content_center
        hikari:
          username: root
          password: kim@2020
          # >=6.x com.mysql.cj.jdbc.Driver
          # <=5.x com.mysql.jdbc.Driver
          driver-class-name: com.mysql.cj.jdbc.Driver
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848
            cluster-name: BJ
            metadata:
              version: v1.0
            # 指定namespace
            namespace: bc4f4e1a-bf4e-4bcc-86f1-7f6252f81e45
      application:
        # 服务名称尽量用-,不要用_,不要用特殊字符
        name: content-center
    

    跨namespace不能调用

    在用户中心和内容中心分别配上同样的命名空间ID。才可以正常访问。

    现有架构存在的问题#

    1. 代码不可读
    2. 复杂的url难以维护
    3. 难以响应需求变化,变化没有幸福感
    4. 编程体验不统一

    声明式HTTP客户端Feign#

    使用Feign实现远程HTTP调用#

    引入依赖#

    <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    

    写注解#

    启动类上加上@EnableFeignClients注解

    写配置#

    暂时没有

    实现一个Feign接口

    @FeignClient(name = "user-center")
    public interface UserCenterFeignClient {
    
    <span class="hljs-comment">/**
     * http://user-center/users/{id}
     * <span class="hljs-doctag">@param id
     * <span class="hljs-doctag">@return
     */
    <span class="hljs-meta">@GetMapping("/users/{id}")
    <span class="hljs-function">UserDto <span class="hljs-title">findById<span class="hljs-params">(<span class="hljs-meta">@PathVariable Integer id);
    

    }

    @Slf4j
    @Service
    @RequiredArgsConstructor(onConstructor = @__(@Autowired))
    public class ShareService {
        private final ShareMapper shareMapper;
    
        private final UserCenterFeignClient userCenterFeignClient;
    
        public ShareDto findById(Integer id){
            //获取分享详情
            Share share = this.shareMapper.selectByPrimaryKey(id);
            //发布人id
            Integer userId = share.getUserId();
    
            UserDto userDto = this.userCenterFeignClient.findById(userId);
            ShareDto shareDto = new ShareDto();
    
            //消息的装配
            BeanUtils.copyProperties(share, shareDto);
            shareDto.setWxNickName(userDto.getWxNickname());
            return shareDto;
        }
    }
    

    所谓的声明式HTTP客户端,就是只需要声明一个Feign Client接口,Feign就会根据声明的接口,自动帮我们构造请求的目标地址,并帮助你请求。

    Feign的组成#

    image-20200412165849453

    细粒度配置自定义#

    默认Feign不打印任何日志,可以自定义Feign日志级别,让其打印日志

    Feign日志级别

    image-20200412170429751

    Java配置方式#

    UserCenterFeignConfiguration

    /**
     * feign的配置类,最佳实践不要加@Configuration注解,否则必须挪到@ComponentScan能扫描的包以外。
     * 是因为重复扫描,父子上下文的问题
     */
    public class UserCenterFeignConfiguration {
    
    <span class="hljs-meta">@Bean
    <span class="hljs-keyword">public Logger.<span class="hljs-function">Level <span class="hljs-title">level<span class="hljs-params">(){
        <span class="hljs-comment">//打印所有请求的细节
        <span class="hljs-keyword">return Logger.Level.FULL;
    }
    

    }

    UserCenterFeignClient

    @FeignClient(name = "user-center", configuration = UserCenterFeignConfiguration.class)
    public interface UserCenterFeignClient {
    
    <span class="hljs-comment">/**
     * http://user-center/users/{id}
     * <span class="hljs-doctag">@param id
     * <span class="hljs-doctag">@return
     */
    <span class="hljs-meta">@GetMapping("/users/{id}")
    <span class="hljs-function">UserDto <span class="hljs-title">findById<span class="hljs-params">(<span class="hljs-meta">@PathVariable Integer id);
    

    }

    logging:
      level:
        com.itmuch.usercenter.dao.content: debug
        com.itmuch.contentcenter.feignclient.UserCenterFeignClient: debug
    

    属性方式配置#

    feign:
      client:
        config:
          # 想要调用的微服务的名称
          user-center:
            loggerLevel: full
    

    全局配置#

    代码方式#

    将细粒度的配置方式都注释掉

    在启动类配置上全局配置

    @EnableFeignClients(defaultConfiguration = UserCenterFeignConfiguration.class)
    

    配置属性方式#

    feign:
      client:
        config:
          default:
            loggerLevel: full
    

    支持的配置项#

    代码方式

    image-20200412173444979

    属性配置方式

    image-20200412173508756

    配置最佳实践#

    Ribbon配置 vs Feign配置#

    Ribbon是一个负载均衡器,帮我们选择一个实例

    Feign是一个声明式HTTP客户端,帮助我们更优雅的请求

    image-20200412175511967

    Feign代码方式vs属性方式#

    image-20200412175730431

    优先级:全局代码<全局属性<细粒度代码<细粒度属性

    最佳实践#

    • 尽量使用属性配置,属性方式实现不了的情况再考虑用代码配置
    • 在同一个微服务内尽量保持单一性,比如统一使用属性配置,不要两种方式混用,增加定位代码的复杂性

    Feign的继承#

    这个特性带来了紧耦合,因为在微服务间共享接口,官方不建议使用。

    现状:很多公司用,代码复用。

    新项目如何选择:权衡利弊,会得到什么好处,失去什么,是不是划算,划算就上。

    多参数请求构造#

    如何使用Feign构造多参数的请求

    Get请求参数使用@SpringQueryMap注解

    @FeignClient(name = "user-center")
    public interface TestUserCenterFeignClient {
    
    <span class="hljs-meta">@GetMapping("/q")
    <span class="hljs-function">UserDto <span class="hljs-title">query<span class="hljs-params">(<span class="hljs-meta">@SpringQueryMap UserDto userDto);
    

    }

    Post请求多参数,也可以使用@RequestBody。

    Feign脱离Ribbon使用#

    @FeignClient(name = "baidu", url="http://www.baidu.com")
    public interface TestBaiduFeignClient {
    
    <span class="hljs-meta">@GetMapping("")
    <span class="hljs-function">String <span class="hljs-title">index<span class="hljs-params">();
    

    }

    @GetMapping("baidu")
        public String baiduIndex(){
            return this.testBaiduFeignClient.index();
        }
    

    RestTemplate vs Feign#

    image-20200412205018818

    如何选择?

    • 原则:尽量用Feign,杜绝使用RestTemplate

    尽量减少开发人员的选择,共存会带来风格的不统一,额外的学习成本和额外的代码理解成本

    • 事无绝对,合理选择

    Feign解决不了,才用RestTemplate

    Feign的性能优化#

    • 连接池【提升15%左右】,默认使用URLConnection,可以修改

    可以选用httpclient或者okhttp

    添加依赖

    <dependency>
                <groupId>io.github.openfeign</groupId>
                <artifactId>feign-httpclient</artifactId>
            </dependency>
            <dependency>
                <groupId>io.github.openfeign</groupId>
                <artifactId>feign-okhttp</artifactId>
            </dependency>
    
    feign:
      client:
        config:
          default:
            loggerLevel: full
      httpclient:
        # 让feign使用apache httpclient做请求,而不是默认的urlclient
        enabled: true
        # feign的最大连接数
        max-connections: 200
        # feign单个路径的最大连接数
        max-connections-per-route: 50
      okhttp:
        enabled: true
        # feign的最大连接数
        max-connections: 200
        # feign单个路径的最大连接数
        max-connections-per-route: 50
    
    • 日志级别

    生产环境建议设置为basic

    Feign常见问题总结#

    常见问题总结

    现有架构总结#

    image-20200412210834103

    服务容错-Sentinel#

    雪崩效应:基础服务故障,导致导致上层服务故障,并且故障不断放大。又称为cascading failure,级联失效,级联故障。

    image-20200412211328332

    雪崩效应是因为服务没有做好容错。

    常见的容错方案(容错思想)#

    • 超时
    • 限流
    • 仓壁模式(线程池隔离)
    • 断路器模式

    5秒内错误率、错误次数达到就跳闸。

    断路器三态:

    image-20200418230211926

    使用Sentinel实现容错#

    是什么:轻量级的流量控制、熔断降级Java库。

    整合Sentinel#

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

    可以使用/actuator/sentinel断点查看sentinel相关信息。

    整合Actuator#

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    

    需要加入配置才能暴露sentinel端点。

    management:
      endpoints:
        web:
          exposure:
            include: '*'
    

    Sentinel控制台#

    搭建Sentinel控制台#

    https://github.com/alibaba/Sentinel/releases

    生产环境,控制台版本最好和整体版本一致。

    启动sentinel控制台

    java -jar /Users/kim/Downloads/sentinel-dashboard-1.7.2.jar

    默认在localhost:8080端口,用户名密码都是sentinel。

    为内容中心整合sentinel控制台

    # 指定sentinel 控制台地址
    spring.cloud.sentinel.transport.dashboard: localhost:8080
    

    确保nacos、sentinel控制台、内容中心和用户中心都启动了。然后访问http://localhost:8010/shares/1多次,就可以在实时监控里看到效果。

    image-20200418233034998

    流控规则#

    点击簇点链路,点击/shares/1的流控按钮,就可以为这个访问路径设置流控规则。

    image-20200419110648290

    image-20200419110733958

    资源名#

    默认是请求路径。

    针对来源#

    针对调用者限流。针对来源是调用者微服务名称。

    阈值类型#

    QPS、线程数。比如选择QPS,表示:当调用当前资源的QPS达到阈值时,就去限流。

    是否集群#

    流控模式#

    直接
    关联

    <1>当关联的资源达到阈值,就限流自己

    比如我们设置关联资源为/actuator/sentinel,当关联资源的qps达到1时,就限流/shares/1

    image-20200419120020951

    写一个测试类,调用/actuator/sentinel

    public class SentinelTest {
        public static void main(String[] args) throws InterruptedException {
            RestTemplate restTemplate = new RestTemplate();
            for (int i = 0; i < 10000; i++) {
                String forObject = restTemplate.getForObject("http://localhost:8010/actuator/sentinel", String.class);
                Thread.sleep(500);
            }
        }
    }
    

    运行这个测试类,再去调用/shares/1,发信啊已经被限流了。

    image-20200419121201930

    实际应用,如果希望修改优先,可以配置关联API为修改的API,资源名设置为查询的API。当修改的测试过多,就限流查询,保证性能。

    链路

    只记录指定链路上的流量

    流控效果#

    原文 :https://www.cnblogs.com/xinrong2019/p/13834448.html
    QQ群 247823727 博客文件如果不能下载请进群下载
    如果公司项目有技术瓶颈问题,如有需要,请联系我,提供技术服务 QQ: 903464207
  • 相关阅读:
    idea找不到或无法加载主类
    Scala核心编程_第09章 面向对象编程(高级特性)
    spring源码:学习线索
    Redis
    spring源码:Aware接口
    spring源码:核心组件(li)
    java socket编程
    spring源码:ApplicationContext的增强功能(li)
    spring源码:web容器启动
    spring源码:BeanPostProcessor(li)
  • 原文地址:https://www.cnblogs.com/zx-admin/p/14552381.html
Copyright © 2011-2022 走看看