09_项目二Ⅰ

0项目二Ⅰ

Day01RabbitMq

1.1介绍

  • 消息队列 简称MQ (Message Queue ) , 基于生产消费者模型 ,用于实现应用与应用间的消息传递
  • 实际应用场景
    • 异步处理
    • 应用解耦
    • 流量削峰
    • 消息通知
  • 常见消息协议
    • JMS java消息服务接口
    • AMQP 高级消息队列传输协议
    • MQTT 物联网消息传输协议
  • 中间件的相关产品
    • activeMQ 基于java实现 JMS
    • rabbitmq 基于erlang实现 AMQP
    • rocketMQ 基于java实现
    • kafka 基于scale 实现 处理大数据
  • 默认端口:
    • 后台管理端web服务器 15672
    • MQ服务器的端口 5672
  • 协议模型及相关概念
    • Message : 要传递的消息
    • Producer : 生产消息的客户端
    • Consumer : 消费消息的客户端
    • Exchange : 交换机,消息要先发送到交换机,根据规则发送到队列
    • Queue : 消息的目的地,具体存储消息的地方
    • route_key : 路由字符串,定义消息的传递规则
    • Connection : 连接对象
    • Channel : 信道
    • ViraulHost : 虚拟主机

1.2Spring整合RabbitMQ

  • 引用依赖

    • 引用spring父工程
  • 发送者

    • AmqpTemplate 是spring封装的基于amqp消息协议的接口
    • RabbitTemplate 是amqpTemplate的具体实现
  • 消息的转换器作用

    • 配置jackson序列化jackson-databind

    • 在配置类中配置对应的对象即可覆盖默认的MessageConverter

      	//发送者用的是什么序列化机制
      	// 消费者也需要使用对应的序列化机制
      	@Bean
          MessageConverter messageConverter(){
              return new Jackson2JsonMessageConverter();
          }
      
  • 声明交换器、队列及绑定关系

    • 通过在@RabbitListener注解上 声明队列 交换机 绑定关系
    • 通过在@Configuration配置类中通过@Bean 声明队列 交换机 绑定关系

1.3MQ面试题

  • 如何保证消息的可靠性

    • rabbitmq提供了生产确认机制 来保证消息发送到mq中,并有对应的交换机处理
    • 消息队列的持久化
    • 消费者确认机制
  • Spring整合MQ的自动补偿机制

    • 底层使用Aop拦截,如果程序(消费者)没有抛出异常,自动提交事务

    • 如果Aop使用异常通知拦截获取到异常后,自动实现补偿机制

      spring:
        rabbitmq:
          listener:
            simple:
              acknowledge-mode: manual #手动确认
              # acknowledge-mode: auto #自动确认    manual #手动确认
              # 重试策略相关配置
              retry:
                enabled: true # 是否开启重试功能
                max-attempts: 5 # 最大重试次数
                # 时间策略乘数因子   0  1  2  4  8
                # 时间策略乘数因子
                multiplier: 2.0
                initial-interval: 1000ms # 第一次调用后的等待时间
                max-interval: 20000ms # 最大等待的时间值
      
  • 如何避免消息重复消费

    • 使用全局MessageID判断消费方是否消费
    • 使用业务ID+逻辑保证唯一
  • 如何避免消息堆积

    • 通过同一个队列多消费者监听,实现消息的争抢,加快消息消费速度。

Day02ElasticSearch

2.1搜索技术

  • 关系型数据库搜索的问题
    1. 性能瓶颈:不容易解决大量数据
    2. 复杂业务:不容易解决复杂的业务需求
    3. 并发能力:难以应对高并发场景
  • 倒排索引
    • 文档(Document):用来检索的海量数据,其中的每一条数据就是一个文档。
    • 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。
  • 倒排索引流程
    • 倒排索引需要把文档数据逐个编号(从0递增),存储到文档表中。并且给每一个编号创建索引,这样根据编号检索文档的速度会非常快。
    • 对文档中的数据按照算法做分词,得到一个个的词条,记录词条和词条出现的文档的编号、位置、频率信息
    • 对用户输入内容分词,到词条列表中查找
    • 得到包含词条的文档编号,到文档列表中查找具体文档。
  • Lucene
    • 目前主流的java搜索框架都是依赖Lucene来实现的。
    • 由其制作搜索引擎产品,比较知名的搜索产品有:Solr、ElasticSearch
    • 从性能来看Elasticsearch略胜一筹
  • Elasticsearch
    • 基于Lucene的搜索web服务,对外提供了一系列的Rest风格的API接口
    • 任何语言的客户端都可以通过发送Http请求来实现ElasticSearch的操作。
    • 速度快、扩展性高、强大的查询和分析、操作简单
  • Kibana
    • 基于Node.js的Elasticsearch索引库数据统计工具。

2.2elasticsearch入门

与MySQL类比

索引库(indices) 类型(type) 映射(mappings) 文档(document) 字段(field)
数据库(Database) 表(table) 表的字段约束 行(Row) 列(Column)

分词器

  • elasticsearch中的默认分词器:standard
  • IK分词器:ik_max_wordik_smart

索引库操作

  • 创建索引库: PUT /库名称

    # 定义一个索引库,并且设置动态模板
    PUT heima3 
    {
      "mappings": {
          "properties": {
            "title": {
              "type": "text",
              "analyzer": "ik_max_word"
            }
          },
          "dynamic_templates": [	# 模板名称,随便起
            {
              "strings": { 			#匹配条件
                "match_mapping_type": "string",	# 凡是string类型的
                "mapping": {		#映射规则			||
                  "type": "keyword"				#统一按照 keyword来处理
                }
              }
            }
          ]
        }
    }
    
  • 查询索引库: GET /索引库名称

  • 删除索引库: DELETE /索引库名称

比较常见的数据类型:

  • string类型,又分两种:

    • text:可分词,存储到elasticsearch时会根据分词器分成多个词条
    • keyword:不可分词,数据会完整的作为一个词条
  • Numerical:数值类型,分两类

    • 基本数据类型:long、interger、short、byte、double、float、half_float
    • 浮点数的高精度类型:scaled_float
      • 需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
  • Date:日期类型

  • Object:对象,对象不便于搜索。因此ES会把对象数据扁平化处理再存储。

2.3创建类型映射

  • 可以给一个已经存在的索引库添加映射关系,也可以创建索引库的同时直接指定映射关系。

  • 索引库已经存在

    PUT /索引库名/_mapping
    {
      "properties": {
        "字段名1": {
          "type": "类型",
          "index": true"analyzer": "分词器"
        },
        "字段名2": {
          "type": "类型",
          "index": true"analyzer": "分词器"
        },
        ...
      }
    }
    
  • 索引库不存在

    PUT /索引库名
    {
    	"mappings":{
          "properties": {
            "字段名1": {
              "type": "类型",
              "index": true"analyzer": "分词器"
            },
            "字段名2": {
              "type": "类型",
              "index": true"analyzer": "分词器"
            },
            ...
          }
        }
    }
    
  • 查看映射关系

    GET /索引库名/_mapping
    

2.4文档的操作

新增文档

  • 通过POST请求,可以向一个已经存在的索引库中添加文档数据

  • ES中,文档都是以JSON格式提交的。

    POST /{索引库名}/_doc			随机生成id
    POST /{索引库名}/_doc/{id}		指定id
    {
        "key":"value"
    }
    

查看文档

  • get加上条件进行查询

    GET /{索引库名称}/_doc/{id}
    

修改文档

  • id对应文档存在,则修改

  • id对应文档不存在,则新增

    PUT /{索引库名称}/_doc/{id}
    

删除文档

  • DELETE加上条件进行查询

    DELETE /{索引库名称}/类型名/id值
    

2.5查询

批量导入数据

  • POST _bulk {1}{2}{1}{2}…

基本语法

GET /索引库名/_search
{
    "query":{	# query代表一个查询对象,里面可以有不同的查询属性
        "查询类型":{
            "查询条件":"查询条件值"
        }
    }
}
  • 查询类型

    • match_all:查询所有

    • match:分词查询

      GET /heima/_search
      {
          "query":{
              "match":{
                  "title":{"query":"三星手机","operator":"and"}#
                  "title":"三星手机"# 默认是 or
              }
          }
      }
      
    • term:词条查询

      • 不会对条件分词,直接作为关键词去指定字段上查找
    • fuzzy:模糊查询

      GET /heima/_search
      {
          "query":{
              "fuzzy": {
                "title":{
                  "value": "飞毛腿",
                  "fuzziness": 2	#取值的范围 0 1 2
                }						#0 不允许出现偏差
              }						#1 允许错一次词
          }							#2 最大是2
      }
      
    • range:范围查询

      GET /heima/_search
      {
          "query":{
              "range":{
                  "price":{
                      "gte":1000,
                      "lte":2000
                  }
              }
          }
      }
      
    • bool:布尔查询

      GET /heima/_search
      {
          "query":{
              "bool":{
                  "should":[    # 或的条件
                      {第一个条件},
                      {第二个条件},
                      {第三个条件},
                  ],
                  "must":[  # 必须满足的条件,会对文档进行得分的计算 
      
                  ],
                  "must_not":[ # 必须不满足的条件
      
                  ]
              }
          }
      }
      
  • 查询条件

    • 只有index为true的字段才可以作为查询条件
  • 查询结果

    {
      "took" : 3,				# 查询花费时间,单位是毫秒
      "timed_out" : false,		# 是否超时
      "_shards" : {				# 分片信息
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {				# 搜索结果总览对象
        "total" : {				# 搜索到的总条数
          "value" : 10,
          "relation" : "eq"		# eq 代表结果是准确    gt 
        },
        "max_score" : 1.0,		# 所有结果中文档得分的最高分
        "hits" : [				# 搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
          {
            "_index" : "heima",	# 索引库
            "_type" : "_doc",	# 文档类型
            "_id" : "2",		# 文档id
            "_score" : 1.0,		# 文档得分,按照文档与查询的相关度倒序返回结果
            "_source" : {		# 文档的源数据
              "title" : "三星 Note II (N7100) 云石白 联通3G手机",
              "images" : "http://image.leyou.com/12479122.jpg",
              "price" : 2599
            }
          },
          。。。。。。
          }
        ]
      }
    }
    
    

使用filter查询

GET /heima/_search
{
    "query":{
        "bool":{   
                
			.....
                
			"filter":{	#对满足条件的结果进行一个过滤, 不会对文档进行得分的计算
                    "range":{
                        "price":{
                            "gte":1000,
                            "lte":5000
                        }
                    }
                } 
        }
    }
}

目标 sort_排序设置

GET /heima/_search
{
    "query":{},
    "sort":[
        {
            "排序字段":{
                "order":"asc 和 desc"
            }
        }
    ]
}

分词字段不能参与排序

目标 from+size_分页设置

GET /heima/_search
{
    "query":{},
    "sort":[
        {
            "排序字段":{
                "order":"asc 和 desc"
            }
        }
    ],
    "from":       # 从第几条记录开始
    "size":      # 查询的条数
}

目标 highlight_高亮设置

GET /heima/_search
{
  "query": {
    "match": {
      "title": "联通手机"
    }
  },
  "highlight": { #处理的结果放到了highlight字段中 , source字段的内容不受影响
    "pre_tags": "",
    "post_tags": "", 
    "fields": {
      "title": {}
    }
  }
}

目标 aggs_聚合设置

GET /car/_search
{	
    "size":0,
    "aggregations":{  			# 聚合设置
        "group_by_color":{ 		# 自定义名称
            "terms":{  			# 分桶类型 按照词条分桶
                "field":"color" # 根据color字段分桶
            },
            "aggs":{
                "avg_price":{
                    "avg":{
                        "field":"price"
                    }
                },
                "group_by_make":{
                    "terms":{
                        "field":"make"
                    }
                }
            }
        }
    }
}
  • 分桶类型
    • 桶聚合
      • 按照词条分桶 terms
    • 度量聚合
      • 求最大值 max
      • 最小值 min
      • 平均值 avg
      • 求和 sum
      • 求数量 count
      • 求上面5种 stats

Day03java客户端操作es

3.1环境搭建

  • 引入es客户端依赖

    • elasticsearch-rest-high-level-client
  • 准备实体类

  • 创建es的java客户端对象

    public class EsDemo01 {
        RestHighLevelClient restHighLevelClient;
        @Before
        public void init(){
            restHighLevelClient = new RestHighLevelClient(
                    RestClient.builder(
                            new HttpHost("localhost",9200)
                    )
            );
        }
        @After
        public void close() throws IOException {
            restHighLevelClient.close();
        }
    }
    

3.2创建索引和映射

  • 创建请求对象 :new CreateIndexRequest对象

  • 调用CreateIndexRequest的settings方法,设置settings

  • 调用CreateIndexRequest的mapping方法,设置mappings

  • 通过client执行请求

    CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
    //这里的request是CreateIndexRequest对象。
    
  • 解析返回结果

    createIndexResponse.isAcknowledged()
    

3.2文档的CRUD

  • 创建文档请求

    IndexRequest request = new XxxxRequest("user",...);//Get,Update,Delete
    
  • 设置参数

    request.xxx(...)
    
  • 客户端执行请求

    Xxxx xxxx = restHighLevelClient.Xxxx(request, RequestOptions.DEFAULT);
    
  • 解析返回结果

    xxxx.xxxx();
    

3.3批量添加文档

  • 创建一个 批处理的请求 BulkRequest
  • 创建添加文档的请求 IndexRequest
  • 将要执行的添加请求 加入到批处理中
  • 客户端 执行 bulk方法

3.4索引库查询

搜索功能API说明

1.  new 一个SearchRequest			创建搜索的请求(request)
  new 一个SearchSourceBuilder	对象来构建所有的查询参数JSON(builder)
  	builder.query(查询条件) : 设置查询条件
  		QueryBuilders : 构建查询条件的工具类
  	builder.sort: 排序设置
  	builder.from: 分页设置
  	builder.size:
  	builder.highlighter 高亮设置
  	builder.aggregations 聚合设置
  request.source(builder)		设置查询参数
2.	客户端执行 . search方法,得到SearchResponse对象
3.	解析响应结果
 SearchResponse.getHits   查询总结果
 		getTotalHits.value : 总记录数
 		getHits :    文档列表
 			遍历文档列表
 				getSource ==> user对象
 				getSourceAsString ==> 字符串格式
 				getHighlight结果
 	
 getAggregations : 获取聚合结果 

部分查询代码

//查询全部
MatchAllQueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
//分词查询
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("note","唱歌javaee");
//词条查询
TermQueryBuilder gender = QueryBuilders.termQuery("gender", "0");
//范围查询
builder.query(QueryBuilders.rangeQuery("age").gte(25).lte(30));
//布尔查询
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("note","同学唱歌"));
boolQueryBuilder.must(QueryBuilders.rangeQuery("age").gt(20));

索引库查询-高亮处理

// 高亮条件的构建
HighlightBuilder highlightBuilder = new HighlightBuilder();
// 哪个字段高亮?
highlightBuilder.field("note");
// 前置标签
highlightBuilder.preTags("");
// 后置标签
highlightBuilder.postTags("");
builder.highlighter(highlightBuilder);

索引库查询-聚合处理

// es 提供了 AggregationBuilders  可以快捷的创建聚合条件
// 参数: 自定义的聚合处理名称       参数: 分桶的字段
TermsAggregationBuilder termsBuilder = AggregationBuilders.terms("group_by_gender").field("gender");
MaxAggregationBuilder maxBuilder = AggregationBuilders.max("age_max").field("age");
// 设置分桶后 求最大值
termsBuilder.subAggregation(maxBuilder);
builder.aggregation(termsBuilder);
searchRequest.source(builder);
...
// 整个的聚合处理结果
Aggregations aggregations = searchResponse.getAggregations();
// 根据自定义的聚合处理名称 得到处理结果
Terms termsResult = aggregations.get("group_by_gender");
// 得到该分桶下的 分桶处理结果
List<? extends Terms.Bucket> buckets = termsResult.getBuckets();
for (Terms.Bucket bucket : buckets) {
    // 获取该分桶的 key
    String keyAsString = bucket.getKeyAsString();
    // 获取该分桶文档的数量
    long docCount = bucket.getDocCount();
    // 获取子聚合 整个的处理结果
    Aggregations subAggregations = bucket.getAggregations();
    Max maxResult = subAggregations.get("age_max");
    double value = maxResult.getValue();
}

3.5ES集群

  • ES单机部署的缺陷

    • 单台机器存储容量有限
    • 单服务器容易出现单点故障,无法实现高可用
    • 单服务的并发处理能力有限
  • ES集群的概念

    • 多台Cluster-name相同的es实例,可以组成es集群(Cluster)
    • 集群中的每一台 es实例 称为 集群节点(Node)
    • 我们在创建索引库时需要设置索引库的分片与分片副本
  • 搭建Windows下集群

    • 进入到ES的安装目录的bin目录下,打开3个CMD命令窗口

    • 分别运行下面指令:

      # node0指令:
      elasticsearch.bat -E node.name=node0 -E cluster.name=elastic -E path.data=node0_data
      # node1指令:
      elasticsearch.bat -E node.name=node1 -E cluster.name=elastic -E path.data=node1_data
      # node2指令:
      elasticsearch.bat -E node.name=node2 -E cluster.name=elastic -E path.data=node2_data
      
    • -E代表环境参数

    • node.name代表节点的名称

    • cluster.name代表集群的名称

    • path.data代表索引库数据的存储目录

    • 没有设置端口信息 es默认选用 9200 9300端口

    • 如果端口被占用es会默认重试端口+1 直到端口未占用

  • Kibana连接集群

    • 修改kibana安装目录下config目录中的kibana.yml

      # elasticsearch.hosts: ["http://localhost:9200"]
      改为
      elasticsearch.hosts: ["http://localhost:9200","http://localhost:9201","http://localhost:9202"]
      
    • 配置分片和副本信息

    • 集群动态伸缩

Day04SpringCloud

4.1系统架构演变

  1. 集中式架构
    • 所有功能都部署在一起,以减少部署节点和成本
    • 用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键
    • 代码耦合,开发维护困难,无法针对不同模块进行针对性优化
    • 无法水平扩展,单点容错率低,并发能力差
  2. 垂直拆分
    • 根据业务功能对系统进行拆分
    • 系统拆分实现了流量分担,解决了并发问题
    • 可以针对不同模块进行优化
    • 方便水平扩展,负载均衡,容错率提高
    • 系统间相互独立,会有很多重复开发工作,影响开发效率
  3. 分布式服务
    • 将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心
    • 用于提高业务复用及整合的分布式调用是关键。
    • 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率
    • 系统间耦合度变高,调用关系错综复杂,难以维护
  4. 服务治理架构(SOA)
    • 增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率
    • 用于提高机器利用率的资源调度和治理中心(SOA)是关键
    • 服务间会有依赖关系,一旦某个环节出错会影响较大
    • 服务关系复杂,运维、测试部署困难,不符合DevOps思想
  5. 微服务
    • 一种架构风格,一个或多个微服务组成,微服务之间是松耦合
    • 微服务中每一个服务都对应唯一的业务能力,做到单一职责
    • 微服务的服务拆分粒度很小,但“五脏俱全”。
    • 每个服务都要对外暴露Rest风格服务接口API。
    • 服务间互相独立,互不干扰

4.2服务调用方式

  1. RPC和HTTP
    • RPC:Remote Produce Call远程过程调用,类似的还有RMI(remote method invoke)。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表.
    • Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。
  2. Http客户端工具
    • 实现对请求和响应的处理
      • HttpClient
      • OKHttp
      • JDK原生的URLConnection
  3. Spring的RestTemplate
    • 对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化
    • 目前常用的3种都有支持,默认的是URLConnection

4.3SpringCloud

实现微服务的一种技术架构,主要涉及的组件包括

  1. Eureka:注册中心
    • 服务的自动注册、发现、状态监控。
  2. gateway:服务网关
  3. Ribbon:负载均衡
  4. Feign:服务调用
  5. Hystix:熔断器
  6. config: 配置中心
  7. bus: 消息总线

4.4入门案例

创建父工程

  1. 依赖
    1. parent
    2. properties
    3. dependencies

服务提供者

  1. 依赖

    1. spring-boot-starter-web
    2. spring-cloud-starter-netflix-eureka-client //注册
  2. application

    server:
      port: 9001
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/javaee109?useSSL=false
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver
    #注册
      application:
        name: user-service
    eureka:
      client:
        service-url: # EurekaServer地址
          defaultZone: http://127.0.0.1:10086/eureka/
      instance:
        ip-address: 127.0.0.1  # 指定一个ip地址
        prefer-ip-address: true  # 优先使用IP地址
        lease-renewal-interval-in-seconds: 30  # 服务注册成功 每隔30s向eureka发送一次心跳
        lease-expiration-duration-in-seconds: 90 # 服务没有发送心跳,将在90s后失效
    
  3. 启动类

    @MapperScan("com.itcast.user.mapper")
    
  4. 创建实体类

  5. 创建映射类

    public interface UserMapper {
        @Select("select * from tb_user where id = #{id}")
        User findById(@Param("id") Long id);
    }
    
  6. 新建一个Service

  7. 新建一个Controller(对外查询的接口)

服务调用者

  1. 依赖

    1. spring-boot-starter-web
    2. spring-cloud-starter-netflix-eureka-client //注册
  2. 新建一个启动类

    // 注册RestTemplate的对象到Spring的容器中
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
  3. application

    server:
      port: 8080
      
    #注册
    spring:
      application:
        name: consumer # 应用名称
    eureka:
      client:
        service-url: # EurekaServer地址
          defaultZone: http://127.0.0.1:10086/eureka
        registry-fetch-interval-seconds: 30 # 每隔多长时间拉取一次服务
    
  4. 复制一个实体类

  5. controller中服务拉取,再调用RestTemplate,远程访问user-service的服务接口

    		// 根据服务id(spring.application.name),获取服务实例列表
            List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
            // 取出一个服务实例
            ServiceInstance instance = instances.get(0);
            // 从实例中获取host和port,组成url
            String url = String.format("http://%s:%s/user/%s", instance.getHost(), instance.getPort(), id);
            // 查询
            User user = restTemplate.getForObject(url, User.class);
            return user;
    

编写EurekaServer

  1. 依赖

    1. spring-cloud-starter-netflix-eureka-server
  2. 启动类

    @EnableEurekaServer // 声明这个应用是一个EurekaServer
    
  3. 编写配置

    server:
      port: 10086
    spring:
      application:
        name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
    eureka:
      client:
        service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。
          defaultZone: http://127.0.0.1:10086/eureka
        register-with-eureka: false # 不注册自己,集群时删掉
        fetch-registry: false #不拉取服务,集群时删掉
    

4.5EurekaServer集群

  • eureka分为 服务端 与 客户端
    • 服务需要单独启动,用于服务的注册与发现
    • 微服务中需要引入eureka的客户端
  • 服务的注册和发现都是可控制的,可以关闭也可以开启。默认都是开启
  • 注册后需要心跳,心跳周期默认30秒一次,超过90秒没法认为宕机
  • 服务拉取默认30秒拉取一次
  • Eureka每个60秒会剔除标记为宕机的服务
  • Eureka会有自我保护,当心跳失败比例超过阈值,那么开启自我保护,不再剔除服务。
  • Eureka高可用就是多台Eureka互相注册在对方上

步骤

  1. 修改EurekaServer配置,添加多个地址列表,以“,”隔开
  2. 再启动一台eureka服务-Dserver.port=10087
  3. 注册和拉取,改回默认

4.6负载均衡Ribbon

  • 基于负载均衡的算法,从服务列表中选择指定的服务进行调用

  • 默认: 轮询的算法

    在consumer-service的启动类中,RestTemplate的配置方法上添加@LoadBalanced注解:
    
    restTemplate.getForObject("http://user-service/user/1",User.class);
    
    拦截器会获取  user-service 
    
    从服务列表中 根据负载均衡算法,选择对应的服务去执行
    

Day05SpringCloud

5.1熔断器组件Hystrix

概念

  • Netflix开源的一个延迟和容错库

作用

  • 用于隔离访问远程服务、第三方库,防止出现级联失败,来为我们的微服务提供防护。

手段

  • 线程隔离
  • 服务熔断
  • 服务降级

步骤

  1. 引入依赖

    • spring-cloud-starter-netflix-hystrix
  2. 开启熔断器注解

    • 三合一@SpringCloudApplication
  3. controller方法开启熔断保护

    • @HystrixCommand(fallbackMethod = "xxxFallback")
    • public String xxxFallback(xxx){return xxx;}
  4. 熔断配置

    hystrix:
      command:
        default:
          execution.isolation.thread.timeoutInMilliseconds: 2000 # 超时时间
          circuitBreaker:
            errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
            sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
            requestVolumeThreshold: 10 # 触发熔断的最小请求次数,默认20
    
  5. 熔断触发的工作原理

    1. 默认的熔断状态为closed关闭状态
    2. 如果服务在最近的20次请求中有百分之50以上的请求失败
    3. 熔断状态改为open状态
    4. 在open状态下所有的请求全部自动服务降级处理
    5. 此过程会持续5秒(默认),5秒后状态会变更为HalfOpen
    6. HalfOpen半开状态会运行部分请求通过,
      • 如果这些请求执行成功熔断状态变为closed状态
      • 如果这些请求执行失败熔断状态再次变为Open重新计时

5.2服务调用组件Feign

概念

  • Feign是springcloud中的声明式服务调用组件,只需要定义接口和配置注解.
  • 在调用接口方法时,Feign会根据配置帮我们调用对应的目标服务和方法

步骤

  • 引入feign依赖spring-cloud-starter-openfeign

  • 开启feign的注解@EnableFeignClients,在启动类上

  • 定义feign的接口

    @FeignClient(value = "user-service",// 要调用的服务名称
            fallback = UserClientFallBack.class,//熔断
            configuration = FeignConfig.class) //日志
    public interface UserClient {
        // fei会根据我们的配置  生成要调用的路径
        // GET http://user-service/user/{id}
        //  根据拼接的地址进行调用
        // feign 内部整合了Ribbon
        // GET http://127.0.0.1:9001/user/{id}
        @GetMapping("/user/{id}")
        String findById(@PathVariable Long id);
    }
    
    
  • 具体调用

    userClient.findById(id)
    
  • 相关配置

    #feign内置了ribbon, 直接提供了负载均衡的效果
    
    ribbon:
      ConnectTimeout: 1000 # 连接超时时长
      ReadTimeout: 2000 # 数据通信超时时长
      MaxAutoRetries: 0 # 当前服务器的重试次数
      MaxAutoRetriesNextServer: 0 # 重试多少次服务
      OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
    #feign的内部,内置了Hystrix的功能,需要手动开启
    feign:
      hystrix:
        enabled: true  # 开启了熔断功能
    #更改日志的打印级别
    logging:
      level:
        com.itcast.consume: debug
    
  • 服务熔断

    • 创建实现类,实现上面的feign的接口,重写方法
  • 日志控制

    • 创建配置类,定义日志级别
    Configuration
    public class FeignConfig {
        @Bean
        Logger.Level level(){
    //        - NONE:不记录任何日志信息,这是默认值。
    //        - BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
    //        - HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
    //        - FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
            return Logger.Level.FULL;
        }
    }
    
    • 在feign的接口中,指定配置类

5.3网关组件Gateway

概念

  • 微服务的网关入口,可以统一处理来自客户端的请求,根据路由规则将请求交给指定服务处理
  • 作为统一的入口 API网关还可以做鉴权,过滤,统计等处理

核心概念

  • 路由 (route)
    • 路由ID
    • 目的地URL
    • 一组断言工厂
    • 一组过滤器
  • 断言 (predicate)
    • 断言函数,也就是一些返回boolean类型的方法,用于匹配请求
  • 过滤器 (filter)
    • GatewayFilter: 局部过滤器,对指定路由生效
      • 需要配置在route下,作用于当前路由,也可以配置成默认过滤器
      • 作用于全部route,SpringCloud中有内置过滤器,也可以自定义
      • 内置过滤器的源码 继承了AbstractGatewayFilterFactory
    • GlobalFilter: 全局过滤器,对所有请求生效
      • 全局过滤器需要实现GlobalFilter接口,创建后不需要配置即可生效

步骤

  • 引入网关依赖spring-cloud-starter-gateway

  • 引入熔断器依赖spring-cloud-starter-netflix-hystrix

  • 网关的配置

    server:
      port: 10010
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:10086/eureka
    spring:
      application:
        name: gateway-service
      cloud:
        gateway:
          routes:
          - id: user-service # 当前路由的唯一标识
            uri: lb://user-service # 路由的目标微服务,lb:代表负载均衡,user-service:代表服务id
            predicates: # 断言
            - Path=/user/** # 按照路径匹配的规则
            filters:
            - AddRequestHeader=info, java is best!
    
          default-filters: # 默认过滤项
          - StripPrefix=1 # 配置后 忽略第一个路径  /user-service/user/1   ==>  /user/1
          - name: Hystrix # 指定过滤工厂名称(可以是任意过滤工厂类型)
            args: # 指定过滤的参数
              name: fallbackcmd  # hystrix的指令名
              fallbackUri: forward:/fallbackTest # 失败后的跳转路径
    hystrix:
      command:
        default:
          execution.isolation.thread.timeoutInMilliseconds: 1000 # 失败超时时长
    
  • 自定义全局过滤器

    • 全局过滤器Global Filter 与局部的GatewayFilter会在运行时合并到一个过滤器链中

    • 根据org.springframework.core.Ordered来排序后执行,顺序可以通过getOrder()方法或者@Order注解来指定。

    • 一个过滤器的执行包括"pre""post"两个过程,

      • 在GlobalFilter.filter()方法中编写的逻辑属于pre阶段
      • 在使用GatewayFilterChain.filter().then()的阶段,属于Post阶段。
      /**
       * 全局过滤器
       * @作者 itcast
       * @创建日期 2020/9/17 14:18
       **/
      @Component // 加上组件注解,不加不生效
      public class LoginFilter implements GlobalFilter {
          @Override
          public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
              // exchange: 封装了 请求对象  和  响应对象的信息
              ServerHttpRequest request = exchange.getRequest(); // 获取请求对象
              // 凭证信息
              String token = request.getQueryParams().toSingleValueMap().get("access-token");//
              // chain :  过滤器链
              if(StringUtils.equals(token,"admin")){
                  // 放行
                  return chain.filter(exchange);
              }else {
                  // 终止请求
                  ServerHttpResponse response = exchange.getResponse();
                  // 设置没有权限的状态
                  response.setStatusCode(HttpStatus.UNAUTHORIZED);
                  // 设置请求终止
                  return response.setComplete();
              }
          }
      }
      
      @Configuration
      @Slf4j
      public class GlobalConfig {
      
          @Bean
          @Order(-1)
          GlobalFilter filter1(){
              return (exchange,chain)->{
                  // 写前置的处理代码
                  log.info("filter1 的pre方法");
                  return chain.filter(exchange).then(Mono.fromRunnable(()->{
                      log.info("filter1 的post方法");
                  }));
              };
          }
      }
      

5.4配置中心组件Config

概念

  • 可以将所有配置文件统一放到Git服务器上,所有其他微服务通过配置中心获取到git上的配置
  • 实现配置文件的统一管理

创建配置中心步骤

  • 在gitee中创建仓库

  • 创建config-server 工程 并引入依赖

    • spring-cloud-starter-netflix-eureka-client
    • spring-cloud-config-server
  • 配置中心启动类

    • @EnableConfigServer
  • 配置文件内容

    server:
      port: 12580
    spring:
      application:
        name: config-server
      cloud:
        config:
          server:
            git:
              uri: https://gitee.com/sh_itheima/cloud_config.git
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:10086/eureka
    

微服务获取配置步骤

  • 引config依赖spring-cloud-starter-config

  • 创建一个boostrap.yml配置文件,将在服务启动时被加载

    在这个配置文件中需要配置 eureka的地址  和 config配置中心的配置
    server:
      port: 9101
    spring:
      application:
        name: consume-service
      cloud:
        config:
          discovery:
            service-id: config-server # 配置中心的名称
            enabled: true   # 开启从注册中心 获取 配置中心的地址
          label: master    # 分支的名称
          name: consume      # application: 应用名称
          profile: dev  # 环境名称
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:10086/eureka
    #开启 暴露 refresh的方法
    management:
      endpoints:
        web:
          exposure:
            include: "*" # 暴露refresh接口
    
  • 提供配置内容读取类,用于读取配置

  • 提供方法,访问配置

微服务动态刷新最新配置

  • 开启消费者刷新配置功能spring-boot-starter-actuato
  • 开启 暴露 refresh的方法

5.5消息总线组件bus

概念

  • 消息总结bus组件,基于MQ可以实现服务各组件间的消息通知。

config配置

  • 引入bus依赖spring-cloud-starter-bus-amqp

  • 端点spring-boot-starter-actuator

  • config配置中添加 Rabbitmq依赖 及 暴露 刷新接口

    server:
      port: 12580
    spring:
      application:
        name: config-server
      cloud:
        config:
          server:
            git:
              uri: https://gitee.com/taft31/config-test.git # 需要拉取配置的Git仓库地址
      rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:10086/eureka/ # eureka地址
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    

消费者服务

  • 引入bus依赖spring-cloud-starter-bus-amqp

  • 消费者配置添加 mq信息,在spring下

    rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest
    
  • 配置webhooks实现配置更新动态自动刷新

    • 添加依赖spring-cloud-config-monitor
    • 通过内网穿透工具 将外网地址 映射到本地12580端口中
    • 在git的管理中配置 webhooks地址

5.6微服务结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-klz9lOcn-1605669045472)(assets/微服务相关.png)]

Day06docker

6.1相关概念

容器和虚拟机

  • 共同点:
    • 都能够虚拟化出软件独立运行环境
  • 虚拟机:
    • 先虚拟化出硬件,在硬件上安装操作系统,在运行各种不同的软件,像一台真实的计算机一样
    • 体积小、启动快、性能接近原生、单机可部署的数量多
  • 容器:
    • 不虚拟化硬件, 共享宿主机的操作系统内核. 在容器上也可以运行部同的软件
    • 隔离性更好

docker

  • 概念
    • 一个开源的 C/S架构的软件,通过这款软件我们可以管理容器
  • 组件
    • 镜像(image)
      • 镜像的本质是一些特殊的文件,提供了容器运行时所必须的资源
    • 容器(container)
      • 容器是根据镜像创建出来的可运行实例
    • 注册中心(registry)
      • 存储镜像的服务器,分为公有(DockerHub)和私有

6.2docker的安装和启动

安装

# 查看linux内核版本
uname -r
# 查看是否已经安装过
yum list installed | grep docker
# 删除之前安装的docker
yum remove -y docker-ce docker-ce-cli containerd.io
# 删除之前生成的docker数据
rm -rf /var/lib/docker
# 安装依赖工具
yum install -y yum-utils device-mapper-persistent-data lvm2
# 设置阿里云镜像
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 必须先更新yum源,否则找不到docker-ce,只能找到docker
yum makecache fast
# 查看仓库的可安装版本
yum list docker-ce --showduplicates|sort -r  
# 安装64位的版本
yum install -y docker-ce.x86_64
# 检查是否安装成功
yum list installed | grep docker



# 或  查看版本信息
docker -v 
docker version 
# 查询docker 详细信息
docker info
# 查看命令
docker --help
# 查看指定命令相关选项信息
docker 命令 --help

启动

# 启动docker服务
systemctl start docker 

# 检查docker服务状态
systemctl status docker

# 重启
systemctl restart docker

# 停止
systemctl stop docker

# 开启自动启动
systemctl enable docker

配置docker的镜像加速

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker


sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://efiewyyy.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

6.3Docker的基本操作

#查看镜像
docker images
docker images -q

#搜索镜像
#在官网网站上查找镜像: hub.docker.com
docker search 仓库名称

#拉取镜像
docker pull [注册中心的地址/]仓库名称[:版本号]

#[强制]删除镜像
docker rmi [-f] 镜像仓库名称or镜像ID[:版本号]
docker rmi -f `docker images -q`

6.4container 容器操作

# 查看正在运行的容器
docker ps 
docker ps -a 

#创建并运行交互式容器,会直接进入到容器,退出后容器会停止
docker run  --name=mycentos  -it  centos:latest [/bin/]bash

#创建并运行守护式的容器,退出容器后容器依旧在后台运行【常用】
docker run --name=mycentos  -id centos
docker exec -it mycentos [/bin/]bash

# 停止正在运行容器
docker stop 容器名称(容器ID)
# 启动
docker start 容器名称(容器ID)
docker restart 容器名称(容器ID)
# 暂停
docker pause 容器名称(容器ID)
# 恢复
docker unpause 容器名称(容器ID)
# 查看信息
docker logs -f 容器名称

#文件拷贝
docker cp ./test.txt  mycentos:/
docker cp mycentos:/test.txt  ./

#目录挂载,宿主机目录:容器目录,宿主机目录必须是以/或~开头,设置容器只读
docker run -id --name=mycentos -v /root/myvolumn:/myvolumn[:ro] centos

#删除容器,需要先停止运行,或强制删除
docker rm 容器的名称or容器的id

#docker伴随容器的启动自动启动
docker run -id --name=mycentos centos --restart always
docker update ly-mysql --restart=always

6.5应用部署

  • mysql的部署

    docker run -d --name=mysql -p 3306:3006 -e MYSQL_ROOT_PASSWORD=root mysql:5.7
    
  • redis部署

    docker run -d --name=myredis -p 6379:6379 redis
    
  • tomcat的部署

    docker run -d --name=mytomcat -p 9001:8080 -v /root/mytomcat:/usr/local/tomcat/webapps tomcat
    
  • nginx部署

    • 静态资源拷贝到 /usr/share/nginx/html
    docker run -d --name=mynginx -p 9002:80 nginx
    
  • elasticsearch部署

    # -e中的内容的设置是单节点启动
    docker run -d --name=myes -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.4.2
    
    # 安装IK分词器:
    docker exec -it myes /bin/bash
    
    ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
    
    # 下载完毕后重启
    
  • kibana部署

    # Kibana和ES安装到同一个docker服务下
    # --link后面的myes 是es容器的名称
    docker run -d --name=my_kibana -p 5601:5601 --link myes:elasticsearch kibana:7.4.2
    
  • rabbitmq部署

    docker pull rabbitmq:3.7.7-management
    
    docker run -d --name myrabbit -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin rabbitmq:3.7.7-management
    
    RABBITMQ_DEFAULT_USER: 设置默认的用户名
    RABBITMQ_DEFAULT_PASS: 设置默认的密码
    
    管理后台: http://ip:15672    通过 admin admin登录
    

6.6备份与迁移

// 在容器上做更改, 可以将容器提交成新的镜像
docker commit  容器的名称  镜像的名称:版本

// 将镜像打包成压缩包
docker save -o 压缩包的名称.tar 镜像的名称:版本

// 将压缩包加载到docker中
docker load -i 压缩包的名称.tar 

6.7Dockerfile构建镜像

  • Dockerfile是一个脚本文件,在里面定义构建镜像的命令和参数通过这个文件可以快速构建镜像

  • 使用Dockerfile 在centos基础镜像上安装JDK在打包成新镜像

    #1. mkdir /root/centosjdk8
    	cd /root/centosjdk8
    #2.  将jdk1.8上传到docker服务器上的/root/centosjdk8文件夹下
    
    #3. 创建Dockerfile脚本文件 用于构建镜像
    	vi Dockerfile
    #4. 编写脚本文件内容
    
    	# 基于centos镜像
        FROM centos
        # 声明作者
        MAINTAINER mrchen
        # 创建一个文件夹
        RUN mkdir /usr/local/java
        # 拷贝压缩包
        ADD ./jdk-8u161-linux-x64.tar.gz /usr/local/java/
        # 设置工作目录
        WORKDIR /usr
        # 配置环境变量
        ENV JAVA_HOME /usr/local/java/jdk1.8.0_161
        ENV JRE_HOME $JAVA_HOME/jre
        ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
        ENV PATH $JAVA_HOME/bin:$PATH
    # 5. 根据脚本构建镜像
     	    构建     新镜像名称     dockerfile脚本所在目录(就是那个点)
     	docker build -t centosjdk8 .
    

6.8搭建私有Registry注册中心

  • 搭建私人注册中心

    #拉取 注册中心 镜像
    docker pull registry
    #启动私人的注册中心
    docker run -d --name=myregistry -p 5000:5000 registry
    #访问注册中心
    http://192.168.12.134:5000/v2/_catalog
    
  • 上传镜像到私有注册中心

    #设置信任注册中心的地址
    #修改daemon.json文件添加信任列表
    vim /etc/docker/daemon.json
    {
    "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
    ,"insecure-registries":["192.168.12.134:5000"]
    }
    #重启docker
    systemctl daemon-reload 
    systemctl restart docker
    
    #注册中心地址/仓库名称:版本
    docker tag centosjdk8 192.168.12.134:5000/centosjdk8
    
    docker push 192.168.12.134:5000/centosjdk8
    
  • 基于docker部署微服务项目

    • 开启docker远程访问
    # 编辑该文件
    vi /lib/systemd/system/docker.service
    
    # 将原来的 ExecStart注释掉 加#号注释
    ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock -H tcp://0.0.0.0:7654
    
    # 保存后重启docker 
    systemctl daemon-reload
    systemctl restart docker
    
    # 检查能否远程访问 如果访问不通注意防火墙
    http://自己的IP:2375/version 
    
    • 父工程中引入docker-maven-plugin插件

    • 在要部署的工程上打开终端窗口

      mvn clean package -DskipTests docker:build -DpushImage
      命令依次作用:
         清除target 打包   忽略测试    docker构建镜像  推送到注册中心
      

6.9DevOps持续自动化集成

概念

  • 随着软件开发复杂度的不断提高,确保团队开发成员间的更好的协同工作
  • 倡导团队开发成员必须经常集成他们的工作
  • 自动化的周期性的集成测试过程无需人工干预
  • 需要有专门的集成服务器来执行集成构建

所涉及相关工具

  • 远程代码库
    • Gitee
    • Github
    • GitLab
  • 自动化构建工具
    • jenkins
      • 一个开源的、可扩展的持续集成、交付、部署(软件/代码的编译、打包、部署)
  • 容器引擎
    • docker
  • 可视化容器管理
    • Rancher
      • 提供了在生产环境中使用的管理Docker和Kubernetes的全栈化容器部署与管理平台。

Day07项目搭建

7.1电商行业模式

分类

  • B2C:企业通过网络销售产品或服务给个人消费者
  • B2B:企业与企业之间的电子商务(Business to Business)
  • C2C平台:消费者与消费者之间的互动交易行为
  • O2O:线上与线下相结合的电子商务(Online to Offline)
  • P2P:个人对个人的金融借贷

高并发对技术的要求

  • 技术范围广
  • 技术新
  • 要求双高:
    • 高并发(分布式、静态化技术、CDN服务、缓存技术、异步并发、池化、队列)
    • 高可用(集群、负载均衡、限流、降级、熔断)
  • 数据量大
  • 业务复杂

7.2乐优商城

架构方式

  • 前端采用Vue技术栈
    • 前台门户系统,H5
    • 后台管理系统,基于Vue实现的单页应用(SPA)
  • 服务端采用SpringCloud技术栈形成微服务集群
    • 商品微服务
    • 搜索微服务
    • 交易微服务
    • 用户服务
    • 短信服务
    • 支付服务
    • 授权服务

业务架构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a3u6GOD5-1605669045474)(assets/image-20200107161255077.png)]

技术架构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eDECzlAr-1605669045475)(assets/image-20200414113210392.png)]

主要技术

  • 前端技术包括:

    • 基础的HTML、CSS、JavaScript(基于ES6标准)
    • JQuery
    • Vue.js 2.0
    • 基于Vue的UI框架:Vuetify、类似于BootStrap、element-ui
    • 前端构建工具:WebPack,项目编译、打包工具
    • 前端安装包工具:NPM
    • Vue脚手架:Vue-cli
    • Vue路由:vue-router
    • ajax框架:axios
    • 基于Vue的富文本框架:quill-editor
  • 服务端技术包括:

    • 基础的SpringMVC、Spring和MyBatis(MybatisPlus)
    • Spring Boot 2
    • Spring Cloud 技术栈
    • OpenResty(Nginx + Lua)
    • Redis、Jedis、Redission、Lua脚本
    • RabbitMQ
    • Elasticsearch
    • nginx
    • MongoDB
    • Canal
    • 数据库PXC集群、Mysql读写分离集群
    • MyCat中间件
    • JWT
    • GrayLog日志系统
    • Skywalking链路追踪
    • Seata分布式事务
    • 阿里OSS、SMS等服务
    • 微信支付

7.3后台管理页面SPA

概念

  • Single Page Application,即单页应用
  • 整个系统只会有一个HTML页面,和一个根Vue实例
  • 页面由许多定义好的组件组合而成
  • UI交互式通过一个名为Vue-router完成

资源导入

  • 解压、导入、运行
    • 运行package.json文件
    • npm run serve

单文件组件

  • .vue文件是vue组件的特殊形式

  • 把html、css、js代码做了分离

    
    
    
    

页面菜单

  • menu.js

组件路由

  • 配置了一些路由的path路径和组件的映射关系

  • 组件默认的路径前缀是src/views

    route("/item/category",'/item/Category',"Category")
    
    
    //代表的意思是:
    
    - path:/item/category
    - 组件:./src/views下的/item/Category文件
    
    访问path:/item/category,会被路由到:./src/views/item/Category组件
    

7.4搭建微服务集群

统一环境

  • IDE、JDK、maven版本
  • 配置hosts文件,采用域名访问

项目结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0d6RCzl9-1605669045477)(assets/image-20200108103538061.png)]

父工程

  • MySQL驱动和自己定义的组件不需要启动器形式,其他的全部需要

注册中心

  • 注册中心需要开关。@EnableEurekaServer

网关

  • gateway不需要开关@EnableEurekaServer
  • 网关限流
    • 计数器算法
    • 漏桶算法
    • 令牌桶算法
  • 令牌桶算法
    • 令牌相关信息记录在redis,需要响应式的Redis依赖
    • Gateway会在Redis中记录令牌相关信息,我们可以自己定义令牌桶的规则
    • 方法是编写SpringCloudGateway给桶信息存入Redis时的key

7.5统一异常处理

ResponseEntity是一个Spring提供的,用于封装响应结果的实体类。可以自定义响应状态码、响应头、响应头。

  • ResponseEntity.status(HttpStatus.CREATED):用于指定这次响应的状态码,HttpStatus枚举中定义了常见的返回状态码。另外,ResponseEntity提供了几个便捷方法,代表常用状态码:

    • ResponseEntity.ok():代表200
    • ResponseEntity.noContent():代表204
  • .body(result):用于指定这次响应的返回值结果,就是响应体

步骤

  1. 定义异常处理器

  2. 创建一个类@ControllerAdvice

    • 默认情况下,会拦截所有加了@Controller的类
  3. 创建一个方法@ExceptionHandler(RuntimeException.class)

    @ControllerAdvice
    public class ControllerExceptionAdvice {
    	@ExceptionHandler(RuntimeException.class)//如果有,写自定义的异常类
        public ResponseEntity<String> handleLyException(RuntimeException e) {
            return ResponseEntity.status(e.getStatus()).body(e.getMessage());
        }
    }
    
  4. 需要使用异常处理器的模块引入依赖

  5. 在启动类的注解上添加扫描包信息,当前的和新加的都要扫描

    @SpringBootApplication(scanBasePackages = {"com.leyou.item", "com.leyou.common.advice"})
    

7.6统一日志记录

@Slf4j
@Aspect		//把当前类标识为一个切面供容器读取
@Component
public class CommonLogAdvice {

    @Around("within(@org.springframework.stereotype.Service *)")//可以用||
    public Object handleExceptionLog(ProceedingJoinPoint jp) throws Throwable {
        try {
            // 记录方法进入日志
            log.debug("{}方法准备调用,参数: {}", jp.getSignature(), Arrays.toString(jp.getArgs()));
            long a = System.currentTimeMillis();
            // 调用切点方法
            Object result = jp.proceed();
            // 记录方法结束日志
            log.debug("{}方法调用成功,执行耗时{}", jp.getSignature(), System.currentTimeMillis() - a);
            return result;
        } catch (Throwable throwable) {
            log.error("{}方法执行失败,原因:{}", jp.getSignature(), throwable.getMessage(), throwable);
            // 判断异常是否是LyException
            if(throwable instanceof LyException){
                // 如果是,不处理,直接抛
                throw throwable;
            }else{
                // 如果不是,转为LyException
                throw new LyException(500, throwable);
            }
        }
    }
}

Day08MybatisPlus

8.1域名访问

  • 保证所有环境的一致,我们会在各种环境下都使用域名来访问。
    • 主域名是:www.leyou.com,
    • 管理系统域名:manage.leyou.com
    • 网关域名:api.leyou.com
    • 图片的域名:image.leyou.com

8.2Nginx

  • CMD命令

    #启动:
    start nginx.exe
    #停止:
    nginx.exe -s stop
    #重新加载:
    nginx.exe -s reload
    
  • 配置nginx反向代理

    • 打开nginx.conf文件,然后在文件的最后一行的}上面引入配置:

      include vhost/*.conf;
      
    • vhost/leyou.conf

      # 负载均衡配置,默认是轮询
      upstream leyou-manage{
      	server	127.0.0.1:9001; # 节点信息
          #server	127.0.0.1:9002; # 节点信息
          #server	127.0.0.1:9003; # 节点信息
          #server	127.0.0.1:9004; # 节点信息
      }
      # 加权轮询
      upstream leyou-manage{
      	server	127.0.0.1:9001 weight=1; # 节点信息
          server	127.0.0.1:9002 weight=2; # 节点信息
      }
      #IP哈希
      upstream  leyou-manage {
          ip_hash; 
      	server	127.0.0.1:9001; # 节点信息
          server	127.0.0.1:9002; # 节点信息
      }
      #最少连接
      upstream leyou-manage{
          least_conn;
      	server	127.0.0.1:9001; # 节点信息
          server	127.0.0.1:9002; # 节点信息
      }
      
      server {	#定义一个监听服务配置
      	listen       80;	#监听的端口
      	server_name  manage.leyou.com;	#监听的域名,端口一致,域名不同也不会处理
      	
      	location / {	#匹配当前域名下的哪个路径。/ 代表的是一切路径
      	    proxy_pass   http://leyou-manage;	#监听并匹配成功后,反向代理的目的地,可以指向某个ip和port
      		proxy_connect_timeout 600;	#反向代理后的连接超时时间
      		proxy_read_timeout 5000;	#反向代理后的读取超时时间
      	}
      }
      	...	
      

8.3MybatisPlus

介绍

  • MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变
  • 无侵入、损耗小、强大的 CRUD 操作、
  • 支持 Lambda 形式调用、支持主键自动生成、支持主键自动生成、支持 ActiveRecord 模式、支持自定义全局通用操作
  • 内置代码生成器、内置分页插件、分页插件支持多种数据库、内置性能分析插件、内置全局拦截插件

注解

官方文档:https://mp.baomidou.com/guide/annotation.html

  • @TableName声明当前类关联的表名称
  • @TableId主键注解
  • @TableField字段注解(非主键)

8.4BaseMapper的CRUD

  • 新增

    // 插入一条记录
    int insert(T entity);
    
  • 删除

    // 根据 entity 条件,删除记录
    int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
    // 删除(根据ID 批量删除)
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    // 根据 ID 删除
    int deleteById(Serializable id);
    // 根据 columnMap 条件,删除记录
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
    
  • 修改

    // 根据 whereEntity 条件,更新记录
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
    // 根据 ID 修改
    int updateById(@Param(Constants.ENTITY) T entity);
    
  • 查询单个

    // 根据 ID 查询
    T selectById(Serializable id);
    // 根据 entity 条件,查询一条记录
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    
  • 查询集合

    // 查询(根据ID 批量查询)
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    // 根据 entity 条件,查询全部记录
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 查询(根据 columnMap 条件)
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
    // 根据 Wrapper 条件,查询全部记录
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
  • 分页查询

    • 引入分页插件
    @Configuration
    public class MybatisConfig {
    
        /**
         * 注册mybatis plus的分页插件
         * @return
         */
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
            // 开启 count 的 join 优化,只针对部分 left join
            paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
            return paginationInterceptor;
        }
    
    public void testPageQuery(){
        // 分页条件
        Page<User> page = new Page<>();
        // 当前页
        page.setCurrent(1);
        // 每页大小
        page.setSize(3);
        // 分页查询,结果会放到Page中,因此无需返回
        userMapper.selectPage(page, null);
    }
    

8.5IService的CRUD

  • 新增

    // 插入一条记录(选择字段,策略插入)
    boolean save(T entity);
    // 插入(批量)
    boolean saveBatch(Collection<T> entityList);
    // 插入(批量)
    boolean saveBatch(Collection<T> entityList, int batchSize);
    
  • 保存更新

    // TableId 注解存在更新记录,否插入一条记录
    boolean saveOrUpdate(T entity);
    // 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
    boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
    // 批量修改插入
    boolean saveOrUpdateBatch(Collection<T> entityList);
    // 批量修改插入
    boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
    
  • 删除

    // 根据 entity 条件,删除记录
    boolean remove(Wrapper<T> queryWrapper);
    // 根据 ID 删除
    boolean removeById(Serializable id);
    // 根据 columnMap 条件,删除记录
    boolean removeByMap(Map<String, Object> columnMap);
    // 删除(根据ID 批量删除)
    boolean removeByIds(Collection<? extends Serializable> idList);
    
  • 修改

    // 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
    boolean update(Wrapper<T> updateWrapper);
    // 根据 whereEntity 条件,更新记录
    boolean update(T entity, Wrapper<T> updateWrapper);
    // 根据 ID 选择修改
    boolean updateById(T entity);
    // 根据ID 批量更新
    boolean updateBatchById(Collection<T> entityList);
    // 根据ID 批量更新
    boolean updateBatchById(Collection<T> entityList, int batchSize);
    
  • 查询单个

    // 根据 ID 查询
    T getById(Serializable id);
    // 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
    T getOne(Wrapper<T> queryWrapper);
    // 根据 Wrapper,查询一条记录
    T getOne(Wrapper<T> queryWrapper, boolean throwEx);
    // 根据 Wrapper,查询一条记录
    Map<String, Object> getMap(Wrapper<T> queryWrapper);
    // 根据 Wrapper,查询一条记录
    <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
    
  • 查询多个

    // 查询所有
    List<T> list();
    // 查询列表
    List<T> list(Wrapper<T> queryWrapper);
    // 查询(根据ID 批量查询)
    Collection<T> listByIds(Collection<? extends Serializable> idList);
    // 查询(根据 columnMap 条件)
    Collection<T> listByMap(Map<String, Object> columnMap);
    // 查询所有列表
    List<Map<String, Object>> listMaps();
    // 查询列表
    List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
    // 查询全部记录
    List<Object> listObjs();
    // 查询全部记录
    <V> List<V> listObjs(Function<? super Object, V> mapper);
    // 根据 Wrapper 条件,查询全部记录
    List<Object> listObjs(Wrapper<T> queryWrapper);
    // 根据 Wrapper 条件,查询全部记录
    <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
    
  • 分页查询

    // 无条件翻页查询
    IPage<T> page(IPage<T> page);
    // 翻页查询
    IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
    // 无条件翻页查询
    IPage<Map<String, Object>> pageMaps(IPage<T> page);
    // 翻页查询
    IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
    
  • 查询数量

    // 查询总记录数
    int count();
    // 根据 Wrapper 条件,查询总记录数
    int count(Wrapper<T> queryWrapper);
    
  • 链式查询

    // 链式查询 普通
    QueryChainWrapper<T> query();
    // 链式查询 lambda 式。注意:不支持 Kotlin
    LambdaQueryChainWrapper<T> lambdaQuery(); 
    
    // 示例:
    query().eq("column", value).one();
    lambdaQuery().eq(Entity::getId, value).list();
    

8.6MP查询步骤

环境搭建

  1. 引入依赖ly-item-service

    1. mybatis-plus-boot-starter
  2. 全局配置ly-item-service

    mybatis-plus:
      type-aliases-package: com.leyou.item.entity # 别名扫描包
      mapper-locations: classpath*:/mappers/*.xml # mapper的xml文件地址
      global-config:
        db-config:
          id-type: auto # 全局主键策略,默认为自增长
          update-strategy: not_null # 更新时,只更新非null字段
          insert-strategy: not_null # 新增时,只新增非null字段
    
  3. 启动类

    1. 添加@MapperScan注解
    2. 修改@SpringBootApplication注解
  4. 开启分页插件ly-item-service/config

    @Configuration
    public class MybatisConfig {
    
        /**
         * 注册mybatis plus的分页插件
         * @return
         */
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
            // 开启 count 的 join 优化,只针对部分 left join
            paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
            // 设置最大单页限制数量,默认 500 条,-1 不受限制
            paginationInterceptor.setLimit(500);
            return paginationInterceptor;
        }
    
  5. 修改日志advice的切入点语法,注意引入依赖

     @Around("within(@org.springframework.stereotype.Service *) || within(com.baomidou.mybatisplus.extension.service.IService+)")
    

基本代码

  • 实体类
    • 通用实体类BaseEntity, common中
    • 商品分类对应的实体类Category,继承通用实体类, service中
    • 中间表实体类, service中
    • DTO类, pojo中
  • mapper
    • ly-item-service中添加CategoryMapper接口
    • 继承BaseMapper<实体类>
  • service
    • ly-item-service中添加CategoryService接口
    • 继承IService<实体类>
  • service实现类
    • 创建对应实体类,加@service注解
    • 继承ServiceImpl
  • controller
    • ly-item-service中添加CategoryController
    • 注入CategoryService

8.7DTO对象

在开发中,根据对象作用的领域不同,会把实体类划分成这样:

  • PO:Persistent Object 持久层对象,与数据库的表一一对应,完全一致,出现在DAO层和Service层
  • BO:Business Object 业务层对象,用来组装PO,在业务层传递
  • DTO:Data Transfer Object 数据转移对象,用于服务端和客户端间的数据传输,比如服务端返回数据到浏览器
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class CategoryDTO extends BaseDTO {
	private Long id;
	private String name;
	private Long parentId;
	private Boolean isParent;
	private Integer sort;

	public CategoryDTO(BaseEntity entity) {
		super(entity);
	}

	/**
	 * 为了方便实现DTO与PO转换,在DTO中定义了工具方法
	 * 		BaseDTO中定义了通用的单个对象的转换方法
	 * 		CategoryDTO中定义了从PO集合转为DTO集合的功能 		
	 */
	public static <T extends BaseEntity> List<CategoryDTO> convertEntityList(Collection<T> list){
		if(list == null){
			return Collections.emptyList();
		}
		return list.stream().map(CategoryDTO::new).collect(Collectors.toList());
	}
}

8.8分类管理

  • 根据id查询分类
  • 根据id集合查询分类
  • 根据品牌id查询分类

8.9CORS跨域

  • 跨域是指跨域名及端口的访问,如果域名和端口都相同,但是请求路径不同,不属于跨域
  • 跨域问题是浏览器对于ajax请求的一种安全限制
    • 一个页面发起的ajax请求,只能是于当前页同域名的路径,能有效的阻止跨站攻击
  • CORS是一个W3C标准,全称是"跨域资源共享",需要浏览器和服务器同时支持
    • 浏览器会在发出ajax时询问服务端是否允许当前网站的跨域请求,并根据服务端响应做出拦截或放行的处理。
    • 服务端接收到以后,需要对这些信息做出判断,如果允许则需要在返回的头信息中携带一些许可的声明。
  • 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

SpringCloudGateway的CORS

spring:
  cloud:
    gateway:
      # 。。。
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 是否将当前cors配置加入到SimpleUrlHandlerMapping中,解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://manage.leyou.com"
              - "http://www.leyou.com"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

Day09阿里云OSS

9.1品牌管理

搭建工程

  • 设计实体类
    • 根据数据库设计的PO
    • 根据前端交互需求的DTO
    • 中间表加@TableId(type = IdType.INPUT)代表主键采用自己填写而不是自增长。
  • 实体类对应的Mapper
  • 实体类对应的Service
  • 实体类对应的Serviceimpl
  • Controller

根据品牌id查询品牌

根据品牌id集合查询品牌集合

  • xxxService.listByIds(idList);

分页查询品牌

  • 分页的DTO类,其中包含一个list列表

    @Override
        public PageDTO<BrandDTO> queryBrandOfPages(Integer page, Integer rows, String key) {
            Page<Brand> brandPage = new Page<>(page, rows);
            //select * from tb_brand [where name lick %key% or letter = key] limit 123,456
            this.page(brandPage, new QueryWrapper<Brand>()
                    .like(null!=key,"name",key)
                    .or()
                    .eq(null!=key,"letter",key)
            );
            return new PageDTO(brandPage.getTotal(),brandPage.getPages(),BrandDTO.convertEntityList(brandPage.getRecords()));
        }
    

根据分类id查询品牌

  1. 利用中间表查询

    List<Long> brandIds =categoryBrandService.query()
        			.eq("category_id", cid)
        			.list()
        			.stream()
        			.map(CategoryBrand::getBrandId)
        			.collect(Collectors.toList());
    
  2. 通过注解关联查询

    @Select("SELECT b.id, b.name, b.letter, b.image FROM tb_category_brand cb INNER JOIN tb_brand b ON b.id = cb.brand_id WHERE cb.category_id = #{cid}")
    List<Brand> queryByCategoryId(@Param("cid") Long cid);
    
    ======================================================
    
        @Override
    public List<BrandDTO> queryBrandByCategory(Long id) {
        List<Brand> list = getBaseMapper().queryByCategoryId(id);
        return BrandDTO.convertEntityList(list);
    }
    

新增品牌

  • 业务方法上@Transactional开启事务
  • 封装中间表对象的集合,批量写入中间表数据

更新品牌

  • 中间表先删除,再新增

    @RequestMapping(method = {RequestMethod.POST,RequestMethod.PUT})
    

根据ID删除品牌

9.2阿里云OSS

基本概念

  • 存储类型(Storage Class)
    • OSS提供标准、低频访问、归档三种存储类型
  • 存储空间(Bucket)
    • 存储空间是您用于存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。
  • 对象/文件(Object)
    • 对象是 OSS 存储数据的基本单元,也被称为OSS的文件。
  • 地域(Region)
    • 地域表示 OSS 的数据中心所在物理位置。
  • 访问域名(Endpoint)
    • OSS以HTTP RESTful API的形式对外提供服务,当访问不同地域的时候,需要不同的域名。通过内网和外网访问同一个地域所需要的域名也是不同的。
  • 访问密钥(AccessKey)
    • AccessKey,简称 AK,指的是访问身份验证中用到的AccessKeyId 和AccessKeySecret。

9.3OSS的使用

  1. 创建一个子用户
  2. 创建一个bucket
  3. 下载AccessKey
  4. 为用户添加权限
  5. 上传文件
    1. web前端签名后直传
    2. 服务端签名后直传流程
  6. 解决跨域问题

9.4搭建授权签名微服务

  • 导入依赖

    • aliyun-sdk-oss
  • 配置

    • 网关配置

      	  routes:
            - id: auth-service # 授权服务
              uri: lb://auth-service
              predicates:
              - Path=/auth/**
      
    • 微服务配置

      ly:
        oss:
          accessKeyId: LTAI4FhtSrGpB2mq4N36XbGb
          accessKeySecret: OEavFEiAyGm7OsGYff5TClHx88KJ28
          host: http://ly-images.oss-cn-shanghai.aliyuncs.com # 访问oss的bucket的域名
          endpoint: oss-cn-shanghai.aliyuncs.com # 你选择的oss服务器的地址
          dir: "heima01" # 保存到bucket的某个子目录
          expireTime: 1200000 # 过期时间,单位是ms
          maxFileSize: 5242880 #文件大小限制,这里是5M
      
  • 实体类

    • 用来加载配置信息的实体类
      • 添加注解@ConfigurationProperties("ly.oss")
      • 添加注解@Component
    • 返回文件的DTO
  • 配置类

    • 将OSS注入到Spring容器
  • controller

  • service

  • serviceImpl

    • 官网下载的SDK文件

Day10商品设计

10.1商品规格参数设计

  • 规格参数模板与分类有关,我们可以设计规格参数表,然后与商品分类关联。一个分类,对应多个规格参数,形成一对多关系,让商品规格与商品分类关联即可。
    • 规格组
    • 数值类型
    • 搜索字段
    • 通用属性
    • 待选项

10.2商品设计

SPU和SKU

  • SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集
  • SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品

Day11Es进阶和WebFlux

11.1Elasticsearch进阶

特殊数据类型

  • Object类型
    • Lucene是不支持对象数据的,因此ES会将数据扁平化处理
  • nested类型
    • Nested类型其实是Object类型的一种特殊版本,它允许包含一组属性相似Object的数组中的每个对象,可以被独立的搜索,互不影响。

自动补全和提示

  • ES的推荐功能(Suggester)包含三种不同方式

    • 用的最多的,还是Completion模式,实现自动补全和基于上下文的提示功能。
  • 为了提高suggester的速度,数据类型是completion类型。

  • 查询时要指定要在哪个completion类型的字段上进行查询

    POST articles/_search
    {
      "suggest": {	#代表接下来的查询是一个suggest类型的查询
        "article-suggester": {	#这次查询的名称,自定义
          "prefix": "el ",	#用来补全的词语前缀
          "completion": {	#代表是completion类型的suggest,其它类型还有:Term、Phrase
            "field": "suggestion",	#要查询的字段
            "size": 10
          }
        }
      }
    }
    

拼音搜索

  • analyzer(分析器)
    • Tokenizer:分词器,对文本内容分词,得到词条Term
    • filter:过滤器,对分好的词条做进一步处理,例如拼音转换、同义词转换等
  • 组合分词器
    • 可以把各种下载的分词插件组合,作为tokenizer或者filter,来完成自定义分词效果。

11.2RestAPI

  1. 引入依赖

    1. elasticsearch-rest-high-level-client
  2. log4j2.xml配置

    
    <Configuration status="WARN">
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            Console>
        Appenders>
        <Loggers>
            <Root level="error">
                <AppenderRef ref="Console"/>
            Root>
        Loggers>
    Configuration>
    
  3. 准备实体类

  4. 创建库和映射

  5. 创建索引库

    1. 创建ES的客户端

      client = new RestHighLevelClient(
              RestClient.builder(
                      new HttpHost("192.168.206.99", 9200, "http")
              )
      );
      
    2. 删除以前的Goods索引库

    3. 创建CreateIndexRequest对象,并指定索引库名称

    4. 指定settings配置和mapping配置

    5. 发起请求,得到响应

  6. 导入文档数据

  7. 基本查询

  8. Suggest查询

11.3异步API

  • ES也提供异步调用的API,利用回调函数来处理执行结果。

  • 其底层是异步的Http请求,并且将执行结果用Future来封装。

    public void testAsyncAddDocument() throws InterruptedException {
        // 准备文档
        Goods goods = new Goods(5L, "松下电吹风", "松下电吹风 网红电吹风", 1599L);
    
        // 创建请求(创建请求)
        IndexRequest request = new IndexRequest("goods")
            .id(goods.getId().toString())
            .source(JSON.toJSONString(goods), XContentType.JSON);
        
        // 创建请求(删除请求)
        DeleteRequest request = new DeleteRequest("goods", "5");
        
        // 创建请求(查询请求)
        GetRequest request = new GetRequest("goods", "1");
    
        // 执行请求,第三个参数是回调处理
        client.xxxAsync(request, RequestOptions.DEFAULT, new ActionListener<IndexResponse>() {
            /**
                 * 执行成功时的回调,参数是IndexResponse结果
                 * @param indexResponse 执行结果
                 */
            @Override
            public void onResponse(IndexResponse indexResponse) {
                System.out.println("我是成功的回调!" + indexResponse);
            }
            /**
                 * 执行失败时的回调,参数是异常信息
                 * @param e 异常信息
                 */
            @Override
            public void onFailure(Exception e) {
                System.out.println("我是失败的回调!");
                e.printStackTrace();
            }
        });
    
        System.out.println("我的异步方法调用完成~~");
        // 因为我们的程序结束会立即停止,接收不到回调结果,这里我们休眠一下,等待下回调结果
        Thread.sleep(2000L);
    }
    

11.4WebFlux概念

WebFlux

  • Spring框架中包含的原始Web框架Spring Web MVC是专门为Servlet API和Servlet容器而构建的,是一套同步阻塞的Web应用方案。
  • 响应式Web框架Spring WebFlux在更高版本5.0中添加,它是完全非阻塞的,支持 Reactive Streams、背压,并在Netty,Undertow和Servlet 3.1+容器等服务器上运行。

响应式

  • 传统的编程范式中,我们一般通过迭代器(Iterator)模式来遍历一个序列。这种遍历方式是由调用者来控制节奏的,采用的是的方式。每次由调用者通过 next()方法来获取序列中的下一个值。
  • 响应式流采用的则是的方式,即常见的发布者-订阅者模式。当发布者有新的数据产生时,这些数据会被主动推送到订阅者来进行处理。在响应式流上可以添加各种不同的操作来对数据进行处理,形成数据处理链。

Reactor Project

  • 完全基于反应式流规范设计和实现的库
  • 在使用上比 JVM 反应式编程的先驱RxJava 更加的直观易懂
  • 是 Spring 5 中反应式编程的基础

Flux 和 Mono

  • Flux 表示的是包含 0 到 N 个元素的异步序列,是一个数据的发布者(publisher)
  • Mono 表示的是包含 0 或者 1 个元素的异步序列

11.5WebFlux入门

使用spring的initialize

选择依赖

  • Web -> Spring Reactive Web

Mono

  • 构建Mono,参数是一个Supplier(无参有返回值的函数式接口)
  • 返回值是Mono

Flux

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> getUserStream() {
    log.info("stream 开始执行");
    Flux<User> flux = getUserWithFlux();
    log.info("stream 执行完毕");
    return flux;
}

11.6Flux的API

静态方法

  • just():可以指定序列中包含的全部元素。创建出来的 Flux 序列在发布这些元素之后会自动结束。
  • fromArray(),fromIterable()和 fromStream():可以从一个数组、Iterable 对象或 Stream 对象中创建 Flux 对象。
  • empty():创建一个不包含任何元素,只发布结束消息的序列。
  • error(Throwable error):创建一个只包含错误消息的序列。
  • never():创建一个不包含任何消息通知的序列。
  • range(int start, int count):创建包含从 start 起始的 count 个数量的 Integer 对象的序列。
  • interval(Duration period)和 interval(Duration delay, Duration period):创建一个包含了从 0 开始递增的 Long 对象的序列。其中包含的元素按照指定的间隔来生成

generate方法

  • generate()方法通过同步和逐一的方式来产生 Flux 序列。
  • generate(Callable stateSupplier, BiFunction generator)

create方法

  • 使用的是 FluxSink 对象。FluxSink 支持同步和异步的消息产生,并且可以在一次调用中产生多个元素

11.7Mono的API

与Flux类似的API

  • just():指定Mono中的固定元素
  • empty():创建一个不包含任何元素,但会发送消息的Mono。
  • error(Throwable error):创建一个只包含错误消息的Mono。
  • never():创建一个不包含任何元素的Mono。
  • create():通过MonoSink来构建包含一个元素的Mono,可以使用MonoSink的success()、error()等方法。

独特的方法

  • fromCallable()、fromCompletionStage()、fromFuture()、fromRunnable()和 fromSupplier():分别从 Callable、CompletionStage、CompletableFuture、Runnable 和 Supplier 中创建 Mono。
  • delay(Duration duration)和 delayMillis(long duration):创建一个 Mono 序列,在指定的延迟时间之后,产生数字 0 作为唯一值。
  • ignoreElements(Publisher source):创建一个 Mono 序列,忽略作为源的 Publisher 中的所有元素,只产生结束消息。
  • justOrEmpty(Optional data)和 justOrEmpty(T data):从一个 Optional 对象或可能为 null 的对象中创建 Mono。只有 Optional 对象中包含值或对象不为 null 时,Mono 序列才产生对应的元素。

Day12自定义启动器

SpringBoot的starter结构一般如下:

  • spring-boot-xxx-autoconfigure:实现自动配置的代码、工具类、配置类等
  • spring-boot-starter-xxx:管理spring-boot-xxx-autoconfigure及其它所需依赖的版本

12.1ElasticSearch的starter

  • 搭建工程
  • 定义工具类
    • 分页DTO
  • 定义工具接口
  • 定义实现类
    • 通过构造函数注入属性
  • 编写实现代码
    • 获取泛型中的类型信息
      • 固定代码
    • 获取索引库名称和ID属性
      • 通过自定义注解
  • 与spring整合
    • 动态代理
      • 用户使用的时候会定义接口并继承我们的接口
      • 我们要动态代理来生成MyRepository的实现类。
    • 定义FactoryBean
    • 注入Bean到容器
      • 扫描包,找到项目中的接口
      • 过滤出所有的继承了Repository接口的接口,例如MyRepository
      • 给扫描到的接口生成 BeanDefinition
      • BeanDefinition注册到Spring容器
    • 编写starter
      • 读取elasticsearch地址
      • 配置HighLevelRestClient
      • 注册RepositoryScanner
      • 配置spring.factories

12.2es的starter的使用

  • 安装到本地仓库

    • IDEA通过maven窗口中的install命令

    • 命令行导入

      mvn source:jar install -Dmaven.test.skip=true
      
  • 引入依赖

  • 配置地址

  • 编写实体类

    • @Index("goods"):声明实体类相关的索引库名称,如果没指定,会以类名首字母小写后做索引库名称
    • @Id:实体类中的id字段的类型,名称可以不叫id,只要加注解就可以
  • 准备客户端Repository

    • 创建接口,继承工具包中的接口:Repository,将来该接口就会被动态代理
  • 文档的CRUD

    //新增数据
    boolean save(T t);
    boolean save(Iterable<T> iterable);
    
    //文档删除
    boolean deleteById(ID id);
    
    //根据Id查询
    Mono<T> queryById(ID id);
    
    //根据条件查询
    @Test
    public void testQuery() throws InterruptedException {
        // 搜索条件的构建器
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 1.查询条件
        sourceBuilder.query(QueryBuilders.matchQuery("title", "红米手机"));
        // 2.分页条件
        sourceBuilder.from(0);
        sourceBuilder.size(20);
        // 3.高亮条件
        sourceBuilder.highlighter(new HighlightBuilder().field("title"));
    
        System.out.println("开始查询。。。");
        Mono<PageInfo<Goods>> mono = repository.queryBySourceBuilderForPageHighlight(sourceBuilder);
    
    
        mono.subscribe(info -> {
            long total = info.getTotal();
            System.out.println("total = " + total);
            List<Goods> list = info.getContent();
            list.forEach(System.out::println);
        });
        System.out.println("结束查询。。。");
        Thread.sleep(2000);
    }
    
    //自动补全
    @Test
    public void testSuggest() throws InterruptedException {
        log.info("开始查询");
        Mono<List<String>> mono = repository.suggestBySingleField("name", "s");
        mono.subscribe(list -> list.forEach(System.out::println));
        log.info("结束查询");
        Thread.sleep(2000);
    }
    

12.3调用其他微服务

  • 搭建搜索微服务
    • 依赖
    • 配置
    • 路由配置
  • 创建存入Elasticsearch的最终实体类
  • 接入Feign客户端
    • 业务微服务中创建API模块
    • 创建一个接口,并加上注解@FeignClient("item-service")
    • 当调用接口中的方法时,Feign会通过方法上的请求方式,根据服务名称去请求
  • 引入Feign客户端
    • 引入API模块的依赖
    • 启动类中启用FeignClient功能
      • @EnableFeignClients(basePackages = "...")
      • @SpringBootApplication(scanBasePackages = {...})
  • 开始调用
    • 引入上面创建的API接口,调用业务微服务中的方法

12.4建立本地服务器

  • 依赖支持
    • node -v
    • npm -v
  • 安装live-server
    • npm install live-server -g
    • -g表示全局安装
  • 使用
    • live-server --port=9002
    • 不写端口,默认8080

Day13搜索过滤及数据同步

13.1搜索框补全

  • 页面发请求到服务端,携带用户输入的关键字
  • 服务端根据关键字查询elasticsearch,得到自动补全的提示信息
  • 服务端返回信息到页面,在页面完成渲染

13.2基本搜索

  • 用户点击提示的内容或自己输入内容点击搜索
  • 页面跳转到搜索列表页,并携带搜索关键字
  • 搜索列表页发起请求到服务端,携带搜索参数
  • 服务端利用搜索关键字,向ElasticSearch发起请求,查询数据
  • 返回数据到页面,完成渲染

13.3数据同步

流程分析

  • 商品微服务在完成上架、下架后,发送消息到MQ,通知是哪个商品更新了
  • 搜索微服务在接收到消息后,更新索引库数据

商品微服务发送消息

  • 引入依赖

  • spring-boot-starter-amqp

  • 配置文件

    spring:
      rabbitmq:
        host: ly-mq
        username: leyou
        password: 123321
        virtual-host: /leyou
        template:	#有关AmqpTemplate的配置
          retry:	#失败重试
            enabled: true	#开启失败重试
            initial-interval: 10000ms	#第一次重试的间隔时长
            max-interval: 80000ms		#最长重试间隔,超过这个间隔将不再重试
            multiplier: 2				#下次重试间隔的倍数
        publisher-confirms: true	#缺省的交换机名称,此处配置后,发送消息如果不指定交换机就会使用这个
    

搜索服务接收消息

  • 添加配置
  • 消息转换器
  • 编写监听器
  • 编写创建和删除索引方法

Day14OpenResty渲染静态页

14.1商品详情页实现

传统模式

  • 高并发情况下,数据库难以支撑
  • 在服务之前加入缓存,减小数据库压力,就受限于Tomcat了

静态化页面

  • 优点:

    • 用户请求渲染好的Html,响应速度快
    • 通过MQ异步更新,保证数据同步
  • 缺陷:

    • 小部分数据如价格变更,整个静态页都要重新生成
    • 随着商品数量增加,页面会越来越多
    • 页面模板变更,所有商品的静态页都要重新生成,非常困难

动态模板,静态化数据

  • 将页面模板动态化,需要的数据静态化
  • 把页面分成几部分,对应的数据也分成几部分,数据可能来自不同的微服务
  • 将模板渲染、数据放到nginx中做,利用nginx的高并发能力提高系统吞吐量
  • 需要的数据可以缓存在Nginx的本地共享词典中,未命中则查询Redis集群,如果Redis集群依然未命中,再去查询后台微服务然后写入缓存中.
  • 为了保证Redis数据与数据库数据一致,我们还要用到Canal技术,监听数据库变化,及时更新Redis数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XjrhF3qv-1605669045478)(assets/image-20200310181707858.png)]

需要的服务

  • 静态页数据服务:一个收集商品相关数据,并更新Redis缓存的数据服务
  • Nginx服务:接收用户请求,查询模板数据,利用模板渲染商品页面
  • Canal服务:监听数据库变化,同步通知静态页数据服务,更新Redis数据

14.2Lua语言

语法入门Lua菜鸟教程

  1. 变量声明

    • 声明一个局部变量,用local关键字即可
    -- 定义数字
    local a = 123
    -- 定义字符串
    local b = "hello world"
    

– 定义数组
local c = {“hello”, “world”, “lua”}
– 定义table
local d = {
name = “jack”,
age = 21
}


2. 打印结果

```lua
print('hello world')
  1. 条件控制

    if( 布尔表达式 1)
    then
       --[ 在布尔表达式 1 为 true 时执行该语句块 --]
    
    elseif( 布尔表达式 2)
    then
       --[ 在布尔表达式 2 为 true 时执行该语句块 --]
    else 
       --[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
    end
    
  2. 循环语句

    • 遍历数字
    for i=0, 10, 1 do
        print(i)
    end
    
    • 遍历数组:
    --打印数组a的所有值  
    local a = {"one", "two", "three"}
    for i, v in ipairs(a) do
        print(i, v)
    end 
    -- 遍历时,i是角标,v是元素。Lua中数组角标从1开始
    
    • 遍历table:
    -- 定义table
    local b = {
        name = "jack",
        age = 21
    }
    for k, v in pairs(b) do
        print(k, v)
    end 
    -- 遍历时,k是key,v是值。
    

14.3OpenResty

介绍

  • 一个基于 Nginx 与 Lua 的高性能 Web 平台
  • 目标是让Web服务直接跑在 Nginx 服务内部

安装OpenResty

  • 安装开发库
  • 安装OpenResty仓库
  • 安装OpenResty
  • 安装opm工具
  • 配置nginx的环境变量

基本配置

  • 为了不影响OpenResty安装目录的结构,我们在新的目录中来启动和配置

    nginx -p `pwd` -c conf/nginx.conf			#使用当前目录下的配置启动
    nginx -p `pwd` -c conf/nginx.conf -s reload	#重新加载配置
    nginx -p `pwd` -c conf/nginx.conf -s stop	#停止
    
  • 配置文件:nginx.conf

    worker_processes  1;
    error_log logs/error.log;
    events {
        worker_connections 1024;
    }  
    http {
        lua_package_path "/usr/local/openresty/lualib/?.lua;;";  #lua 模块  
        lua_package_cpath "/usr/local/openresty/lualib/?.so;;";  #c模块 
    	lua_shared_dict item_local_cache 50m; #共享全局变量,在所有worker间共享
    	
        default_type  text/html; # 默认响应类型是html
        include lua.conf;    # 引入一个lua.conf文件
    }
    
  • 配置文件:lua.conf

    server {
        listen 80;
        location / {
            # 响应数据由 lua/test.lua这个文件来指定
            content_by_lua_file lua/test.lua;
        }
        
        # 采用正则表达式映射路径,有两个(\d+),分别是第1组、第2组正则
    	location ~ /lua_request/(\d+)/(\d+) {  
        	# $1代表获取第1组正则捕获的内容,set $a $1代表把$1的值赋值给$a这个变量
        	set $a $1;
        	# $2代表获取第2组正则捕获的内容,set $b $2代表把$2的值赋值给$b这个变量
        	set $b $2; 
        	#nginx内容处理  
       	 	content_by_lua_file lua/test_request.lua;  
    }
    }
    
  • lua脚本:test.lua

    ngx.say("

    hello, world

    ")
  • lua脚本:test_request.lua

    -- 定义一个函数,打印table数据,,-- 获取路径占位符中通过正则得到的参数,,-- 获取请求url参数
    function sayTables(val)
    	for k,v in pairs(val) do
    		if type(v) == "table" then
    			ngx.say(k, " : ", table.concat(v, ", "), "
    ") else ngx.say(k, " : ", v, "
    ") end end end ngx.say('
    ') ngx.say('') ngx.say('
    ') ngx.say("

    -----请求路径占位符参数-------

    "); ngx.say("

    ") ngx.say("ngx.var.a : ", ngx.var.a, "
    ") ngx.say("ngx.var.b : ", ngx.var.b, "
    ") ngx.say("ngx.var[1] : ", ngx.var[1], "
    ") ngx.say("ngx.var[2] : ", ngx.var[2], "
    ") ngx.say("

    ") ngx.say("

    -----请求url参数-------

    "); ngx.say("

    ") local params = ngx.req.get_uri_args() sayTables(params) ngx.say("

    ") return ngx.exit(200)

14.4OpenResty模板渲染模块

  • 安装模板渲染模块
  • 定义模板位置
  • 创建上面定义的目录
  • lua.conf中定义一个location映射
  • 新建lua文件,编写脚本
    • template模块
    • Redis模块
    • 其他模块
  • 新建模板文件:xxx.html

14.5OpenResty内部请求代理capture

  • location

    #用来处理内部请求,然后反向代理到百度
    location ~ /baidu/(.*) {
        # 重写路径,去掉路径前面的 /baidu
        rewrite /baidu(/.*) $1 break;
        # 禁止响应体压缩
        proxy_set_header Accept-Encoding '';
        # 反向代理到百度
        proxy_pass https://www.baidu.com;
    }
    #测试接口
    location ~ /lua_http/(.*) {
        # 关闭lua代码缓存
        lua_code_cache off;
        # 指定请求交给lua/test_http.lua脚本来处理
        content_by_lua_file lua/test_http.lua;
    }
    
  • test_http.lua

    -- 向 /baidu 这个location发请求,并且携带请求参数
    local resp = ngx.location.capture("/baidu/s?wd="..ngx.var[1], {  
    	method = ngx.HTTP_GET
    })
    
    -- 查询失败的处理
    if not resp then
    	ngx.say("request error"); 
    end 
    -- 查询成功的处理,这里是打印响应体
    ngx.say(resp.body)
    

14.6商品详情页渲染

流程

  • 监听用户请求,进入定义好的lua脚本
  • lua脚本中尝试读取redis数据
  • 读取数据失败,尝试从ly-page微服务读取数据
    • 获取数据失败:返回404
    • 获取数据成功:开始渲染
  • 把数据和模板交给template模块渲染,然后返回

步骤

  • 配置内部请求代理
  • 定义通用工具模块:
    • 访问Redis的工具
    • 访问ly-page的http工具
  • 定义商品详情页面模板,并上传
  • 编写商品页面请求的路径映射
  • 编写处理请求,查询数据,处理数据,渲染模板的lua脚本

优化

  • Redis可能难以支持海量商品信息,此时可以用SSDB来代替

14.7缓存数据同步

介绍

  • canal是阿里巴巴旗下的一款开源项目,基于Java开发。
  • 基于数据库增量日志解析,提供增量数据订阅&消费。
    • 数据库镜像
    • 数据库实时备份
    • 索引构建和实时维护(拆分异构索引、倒排索引等)
    • 业务 cache 刷新
    • 带业务逻辑的增量数据处理
  • 基本原理
    • 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave,向 MySQL master 发送dump 协议
    • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
    • canal 解析 binary log 对象(原始为 byte 流)

设置主从同步

  • 修改mysql配置文件,设置binary log

  • 设置账号权限

    • 添加一个仅用于数据同步的账户,仅提供对heima这个库的操作权限
  • 安装canal

    • docker inspect 容器id 查看容器地址
    docker run -p 11111:11111 --name canal \
    -e canal.destinations=test \
    -e canal.instance.master.address=172.17.0.2:3306  \	#数据库地址和端口
    -e canal.instance.dbUsername=canal  \
    -e canal.instance.dbPassword=canal  \
    -e canal.instance.connectionCharset=UTF-8 \
    -e canal.instance.tsdb.enable=true \
    -e canal.instance.gtidon=false  \
    -e canal.instance.filter.regex=heima.tb_spu,heima.tb_sku,heima.tb_spu_detail,heima.tb_category,heima.tb_brand,heima.tb_spec_param \
    --network host \
    -d canal/canal-server
    

编写canal客户端

  • 引入依赖

    • canal-spring-boot-starter
  • 配置文件

    canal:
      destination: test
      server: ly-canal:11111 # canal地址
    
  • 添加Redis操作方法

  • 编写监听器

    • EntryHandler>
    • implements EntryHandler>

数据,-- 获取路径占位符中通过正则得到的参数,-- 获取请求url参数
function sayTables(val)
for k,v in pairs(val) do
if type(v) == “table” then
ngx.say(k, " : ", table.concat(v, ", "), “
”)
else
ngx.say(k, " : ", v, “
”)
end
end
end
ngx.say(’

’)
ngx.say(’’)
ngx.say(’
’)

ngx.say("

-----请求路径占位符参数-------

“);
ngx.say(”
")
ngx.say("ngx.var.a : ", ngx.var.a, “
”)
ngx.say("ngx.var.b : ", ngx.var.b, “
”)
ngx.say("ngx.var[1] : ", ngx.var[1], “
”)
ngx.say(“ngx.var[2] : “, ngx.var[2], “
”)
ngx.say(”
”)

ngx.say("

-----请求url参数-------

“);
ngx.say(”
")
local params = ngx.req.get_uri_args()
sayTables(params)
ngx.say("
")

return ngx.exit(200)


## 14.4OpenResty模板渲染模块

- 安装模板渲染模块
- 定义模板位置
- 创建上面定义的目录
- 在`lua.conf`中定义一个`location`映射
- 新建`lua`文件,编写脚本
- template模块
- Redis模块
- 其他模块
- 新建模板文件:`xxx.html`

## 14.5OpenResty内部请求代理capture

- location

```nginx
#用来处理内部请求,然后反向代理到百度
location ~ /baidu/(.*) {
    # 重写路径,去掉路径前面的 /baidu
    rewrite /baidu(/.*) $1 break;
    # 禁止响应体压缩
    proxy_set_header Accept-Encoding '';
    # 反向代理到百度
    proxy_pass https://www.baidu.com;
}
#测试接口
location ~ /lua_http/(.*) {
    # 关闭lua代码缓存
    lua_code_cache off;
    # 指定请求交给lua/test_http.lua脚本来处理
    content_by_lua_file lua/test_http.lua;
}
  • test_http.lua

    -- 向 /baidu 这个location发请求,并且携带请求参数
    local resp = ngx.location.capture("/baidu/s?wd="..ngx.var[1], {  
    	method = ngx.HTTP_GET
    })
    
    -- 查询失败的处理
    if not resp then
    	ngx.say("request error"); 
    end 
    -- 查询成功的处理,这里是打印响应体
    ngx.say(resp.body)
    

14.6商品详情页渲染

流程

  • 监听用户请求,进入定义好的lua脚本
  • lua脚本中尝试读取redis数据
  • 读取数据失败,尝试从ly-page微服务读取数据
    • 获取数据失败:返回404
    • 获取数据成功:开始渲染
  • 把数据和模板交给template模块渲染,然后返回

步骤

  • 配置内部请求代理
  • 定义通用工具模块:
    • 访问Redis的工具
    • 访问ly-page的http工具
  • 定义商品详情页面模板,并上传
  • 编写商品页面请求的路径映射
  • 编写处理请求,查询数据,处理数据,渲染模板的lua脚本

优化

  • Redis可能难以支持海量商品信息,此时可以用SSDB来代替

14.7缓存数据同步

介绍

  • canal是阿里巴巴旗下的一款开源项目,基于Java开发。
  • 基于数据库增量日志解析,提供增量数据订阅&消费。
    • 数据库镜像
    • 数据库实时备份
    • 索引构建和实时维护(拆分异构索引、倒排索引等)
    • 业务 cache 刷新
    • 带业务逻辑的增量数据处理
  • 基本原理
    • 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave,向 MySQL master 发送dump 协议
    • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
    • canal 解析 binary log 对象(原始为 byte 流)

设置主从同步

  • 修改mysql配置文件,设置binary log

  • 设置账号权限

    • 添加一个仅用于数据同步的账户,仅提供对heima这个库的操作权限
  • 安装canal

    • docker inspect 容器id 查看容器地址
    docker run -p 11111:11111 --name canal \
    -e canal.destinations=test \
    -e canal.instance.master.address=172.17.0.2:3306  \	#数据库地址和端口
    -e canal.instance.dbUsername=canal  \
    -e canal.instance.dbPassword=canal  \
    -e canal.instance.connectionCharset=UTF-8 \
    -e canal.instance.tsdb.enable=true \
    -e canal.instance.gtidon=false  \
    -e canal.instance.filter.regex=heima.tb_spu,heima.tb_sku,heima.tb_spu_detail,heima.tb_category,heima.tb_brand,heima.tb_spec_param \
    --network host \
    -d canal/canal-server
    

编写canal客户端

  • 引入依赖

    • canal-spring-boot-starter
  • 配置文件

    canal:
      destination: test
      server: ly-canal:11111 # canal地址
    
  • 添加Redis操作方法

  • 编写监听器

    • EntryHandler>
    • implements EntryHandler>

你可能感兴趣的:(spring,cloud)