乐优商城练手项目相关技术总结

FastDFS(分布式文件系统)

为什么要使用FastDFS?

如果说文件上传的服务将资源都存放到本地的话,最终都会受限于磁盘大小。那么当然可以在本地配置多台文件服务器来存放文件,这个时候需要用Nginx做负载均衡,但是这可能会导致一个问题:可能我把文件上传到了服务器A,但是我在访问的时候可能正好分发到了服务器B,这个时候就会报404错误,可扩展能力很差。所以针对上述问题,需要引入分布式文件系统。

何谓分布式文件系统?

分布式文件系统就是说我的文件可以存储在多个文件服务器上,这些服务器通过网络连接,要被统一管理,无论是上传还是访问文件资源都要有管理中心来进行访问。功能丰富:文件存储、文件同步、文件的访问、存取负载均衡、在线扩容

FastDFS结构图

在这里插入图片描述

主要分为客户端,跟踪服务器(集群)和存储服务器(集群),文件的上传或访问都要经过跟踪服务器。首先存储服务器都会在跟踪服务器上注册自己的信息,当客户端发送请求的时候,跟踪服务器就会分配对应的存储服务器地址,那么客户端就进行文件的上传或者访问,存储服务器进行文件操作后也会向跟踪服务器上报(包括磁盘使用情况,文件同步情况及文件上传下载次数统计等信息)。这里存储服务器会分组,组与组之间的数据是不同,组内的数据是同步的。

为什么需要用Nginx访问?

FastDFS通过Tracker服务器,将文件放在Storage服务器存储,但是同组存储服务器之间需要进入文件复制,有同步延迟的问题。

假设Tracker服务器将文件上传到了192.168.4.125,上传成功后文件ID已经返回给客户端。此时FastDFS存储集群机制会将这个文件同步到同组存储192.168.4.126,在文件还没有复制完成的情况下,客户端如果用这个文件ID在192.168.4.126上取文件,就会出现文件无法访问的错误。

而fastdfs-nginx-module可以重定向文件连接到文件上传时的源服务器取文件,避免客户端由于复制延迟导致的文件无法访问错误

Nginx

web服务器分类

1、web应用服务器:如Tomcat,本质上相当于一个serverlet容器,可以解析静态资源

2、web服务器:web服务器不能解析jsp等页面,只能处理如js、css、html等静态资源,同时web服务器的并发能力远高于web应用服务器,如nginx默认连接数为50000,而Tomcat只有200

正向代理与反向代理

正向代理:正向代理代理的是用户,用户将请求发送到代理服务器并指定目标,然后代理服务器再将请求发送给真实的服务器,这个时候用户是透明的,真实的服务器并不知道用户是谁

反向代理:反向代理代理的是服务器,反向代理服务器替真实的服务器接收请求,客户端并不知道真实的服务器是谁,认为代理服务器就是真实服务器,隐藏了真实服务器。

项目中的使用nginx

项目中使用nginx主要是用来做反向代理的,根据不同的请求由nginx分发到对应的服务地址

实现了域名访问网站,中间的流程是怎样的呢?

在这里插入图片描述

  1. 浏览器准备发起请求,访问http://mamage.leyou.com,但需要进行域名解析

  2. 优先进行本地域名解析,因为我们修改了hosts,所以解析成功,得到地址:127.0.0.1

  3. 请求被发往解析得到的ip,并且默认使用80端口:http://127.0.0.1:80

    本机的nginx一直监听80端口,因此捕获这个请求

  4. nginx中配置了反向代理规则,将manage.leyou.com代理到127.0.0.1:9001,因此请求被转发

  5. 后台系统的webpack server监听的端口是9001,得到请求并处理,完成后将响应返回到nginx

  6. nginx将得到的结果返回到浏览器

Redis

Redis持久化

RDB

RDB:在指定的时间间隔内将内存中的数据集快照存盘,恢复的时候直接将文件读入内存。

备份生成的频率:每隔指定时间(如60秒),有指定数量的key(如100个)发生变化,则进行持久化操作。持久化时要先进性完整性检查,再存盘

持久化过程:redis主进程创建一个(fork)子进程来进行持久化,会先将数据写入到一个临时文件,持久化完成之后将这个临时文件替换rdb文件。整个过程中主进程不进行IO操作,确保了极高的性能。如果Redis要处理大量数据并且对数据的完整性要求不高时,RDB比AOF更加高效。

数据恢复过程:首先关闭Redis,然后将备份文件拷贝到工作目录,启动Redis备份数据就会直接加载

优点:适合大规模数据的恢复;占用磁盘空间少;恢复速度快

缺点:RDB最后一次持久化后的数据可能丢失。

RDB引申知识

写时复制技术:在并发访问的情况下,如果要修改数据并不是直接修改原数据,而是先拷贝一份副本,对副本做修改,修改完成之后,替换原数据。

写时复制带来的影响:

1、实现了读写分离,读操作是在原数据上,写操作是在副本中,因此可以对原数据进行并发的读。

2、数据一致性问题,读操作可能不会立即读取新修改的数据,因为改操作是在副本上,但是最终的修改完成后会更新到原数据上,因此这是最终一致性。

AOF(append only file)

AOF:以日志的形式记录Redis的写操作,就是说aof是Redis执行的所有写指令以及对应操作的数据保存到了日志文件,日志文件呢只能追加不能修改。然后恢复的时候,是将日志文件中的指令从头到尾执行一遍来恢复数据。

AOF同步频率:

appendfsync always:始终同步,每次Redis写入都会立刻记入日志;性能较差但是数据完整性好

appendfsync everysec:每秒同步,每秒记入日志一次,如果宕机,该秒数据可能丢失

appendfsync no:不主动同步,把同步时机交给操作系统

持久化过程:用户发起写操作请求时,会将相关指令和数据保存到AOF的缓存区;然后根据相关的持久化策略(always,everysec,no)追加到AOF文件中;追加的过程中,根据情况判断是否需要重写,如果AOF文件增加过快达到了阈值,进行重写,压缩文件容量;数据恢复时,Redis读取AOF文件,执行写操作记录完成数据的恢复。

重写压缩:当AOF文件增加过快,文件大小超过所设定的阈值时,主进程会fork一个子进程对文件进行重写压缩,只保留可以恢复数据的最小指令集。

优点:

1、备份机制更加稳健,数据丢失的概率更低

2、保存的日志可读,可以处理误操作

缺点:

1、需要占用更多的磁盘空间

2、备份速度更慢

3、每次读写同步,存在性能压力

RDB与AOF

官方推荐两个都启用,都启用的时候默认执行AOF策略;对数据不敏感的话,可以单独使用RDB

Redis高并发和快速原因

1.redis是基于内存的,内存的读写速度非常快;
2.redis是单线程的,省去了很多上下文切换线程的时间;
3.redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。

主从复制

主从特点

1、读写分离:写操作在主服务器上,读操作在从服务器上

2、容灾快速恢复:当某一台从服务器挂掉之后,能快速的切换到其他从服务器上

一主二仆特点

当从服务器挂掉重启后不能再作为主服务器的slave,需要再重新配置;配置之后,从服务器会同步主服务的所有数据

而当主服务器挂掉恢复之后,仍是主服务器,其从服务器依然是原有的

主从复制的原理

当从服务器连接上主服务器之后,会向主服务器发送同步数据的请求,主服务器接收到请求之后会先进行持久化(RDB),然后把rdb文件发送到从服务器,完成一次同步。(从服务器主动)

之后主服务器再进行写操作时,会主动向从服务器进行数据同步

哨兵模式

反客为主的自动版:能够监控主服务器是否有故障,如果故障了根据***投票数***自动将从库转 换为主库。

从服务器转为主服务器的选择条件:

1、选择优先级靠前的,slave-priority ,值越小优先级越高

2、选择偏移量最大的,偏移量是指获得原主机数据的完整度

3、选择runid最小的,runid是Redis实例启动后随机生成的一个40位的runid

新主机挑选出来后,原主机的从服务器都归属到新主机下,已下线的原主机恢复后也会成为从服务器。

Redis集群

主要用来解决扩容和并发写操作问题,集群实现了Redis的水平扩容,就集群启用N个Redis节点,将整个数据库分布存储在这N个节点上,每个节点存放1/N的数据。这也提高了Redis的可用性,即使其中一个节点失效,其他节点也可以正常操作。

redis-cli -c -p 端口号:切换到相应的写主机

cluster nodes:查看集群信息

什么 slots

一个redis集群包含16384个插槽(hash slot),每个节点处理相应数量的插槽,数据库中的每个键都属于这16384个插槽中的一个。每次在写入数据的时候,会先计算键key属于哪个槽,然后根据槽号存储到对应的节点上。(这个过程有点像hashmap新增值的时候,根据新增值的key计算hash值找存放位置类似)

故障恢复

如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?

如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉

如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。

redis.conf中的参数 cluster-require-full-coverage

缓存穿透

缓存穿透是指缓存和数据库都查不到数据,而用户还是不断发起请求。由于缓存是不被命中时被动写入的,如果存储层查不到就不会写入缓存,这将导致每次查这个不存在的数据是都会到存储层去查询,就失去了缓存的意义。

解决方案

1、对空值进行缓存。如果查询返回的数据为空,仍将这个空值缓存起来,但是设置一个较短的过期时间。

2、设置白名单。使用bitmaps定义一个可访问的名单,每次访问就和bitmaps中的id进行比较,如果id不在里面就进行拦截。

3、采用布隆过滤器:就是将所有可能存在的数据放到一个尽可能大的bitmaps中,一个一定不存在的数据会被拦截。

4、进行实时监控

缓存击穿

缓存击穿是指缓存中没有(一般是过期了)但是数据库中有要查询的数据,然后大量用户在缓存中查不到数据,都去访问数据库,引起数据库压力瞬间增大。

解决方案:

1、设置热点数据永不过期

2、接口限流与熔断降级。重要的接口要做好限流策略,

3、使用锁

缓存雪崩

缓存雪崩是指缓存中的数据大批量的到期,而查询量巨大,从而引起数据库压力过大甚至宕机。与缓存击穿不同的是,缓存击穿是指并发查同一条数据,缓存雪崩是不同的数据都过期了,都去查数据库去了。

解决方案

1、将缓存失效时间随机分散开来,让同一时间缓存过期的重复率降低。

2、设置过期标志更新缓存:纪录缓存数据是否过期,如果过期触发其他线程去后台更新缓存。

3、构建多级缓存架构:nginx缓存+redis缓存+其他缓存,当一层缓存过期后,继续去其它层读取缓存

分布式锁

由于业务的发展,原来单体单机部署的系统演化成分布式集群系统,而分布式系统的线程服务都是分布在不同的机器上,这使得原来单机部署的并发控制策略就失效了,单纯的javaAPI并不能提供分布式锁的能力。为了解决这种跨服务器之间的并发控制共享资源问题,引入了分布式锁。

主流的实现分布式锁的方案:

1、基于数据库实现分布式锁

2、基于缓存实现分布式锁(高性能)

3、基于zookeeper(高可靠)

Redis实现分布式锁

setnx key value:

nx是指当key不存在时才进行操作,相当于设置了一个锁;释放锁就直接delete掉就行

Redis分布式锁可能出现的问题

1、当一个线程持有锁长时间没有释放,其他线程会一直阻塞等待

解决方案:设置锁的过期时间(set key value ex 3000 nx)

2、在设置锁的过期时间后可能出现释放其他锁的问题

场景:如果业务逻辑的执行时间是7s。执行流程如下

\1. index1业务逻辑没执行完,3秒后锁被自动释放。

\2. index2获取到锁,执行业务逻辑,3秒后锁被自动释放。

\3. index3获取到锁,执行业务逻辑

\4. index1业务逻辑执行完成,开始调用del释放锁,这时释放的是index3的锁,导致index3的业务只执行1s就被别人释放。最终等于没锁的情况。

解决方案:获取锁的时候,设置一个唯一值UUID;释放前获取这个值,判断是否是自己的锁。

RabbitMQ

项目中为什么要用RabbitMQ?

因为我现在有三块数据需要做同步,分别是我的数据库,搜索服务的索引库以及商品详情静态页面的数据。当我数据库在做增删改查时,需要将数据同步更新到另外两个服务上。

当然,我可以定义一些接口供搜索服务和静态页面服务调用或者说改数据库的同时直接就对它们做修改。这两种方案都有问题:代码耦合度太高,我后台的服务还要嵌入搜索服务和商品页面服务,违背了微服务的独立原则

实现MQ的两种主要方式

JMS:java定义的一个统一的消息操作的接口,产品有RocketMQ

AMQP:高级消息队列协议,跨语言,统一了消息交互的格式,产品有RabbitMQ

RabbitMQ的五种消息模型

基本消费模型

最简单的一种消费模型,生产者生产一个消息提交到队列,消费者直接消费消息

工作模型

工作队列或者竞争消费者模式

就是说创建多个消费者来消费生产者产生的消息,避免消息堆积,但是一个消息职能被一个消费者获取。

工作模型中由于不同消费者的性能有好坏,但正常情况下RabbitMQ会把任务平均分配到各个消费者,这就会造成性能的浪费。此时可以设置prefetchCount = 1,就是让消费者在处理并确认完成一条消息之前,不会再接受到消息,实现能者多劳。

发布订阅

在之前的模型中,一条消息只能被一个消费者消费,但是现实场景中,一条消息可能是被多个消费者消费,所以就有了发布订阅模式。不同消费者都可订阅一个生产者,都处理生产者产生的消息。

发布订阅模式中,生产者将生产的消息发送到交换机,交换机再发送到与之绑定的队列中。交换机一方面可以接受生产者的消息,另一方面知道怎么处理接受的消息,是转发给队列还是丢弃,这取决于交换机的类型。

发布订阅之fanout

fanout也就是广播模式,交换机将消息发送到所有绑定的队列上。

发送流程:

1、可以有多个消费者,每个消费者有自己的队列

2、队列都绑定到交换机

3、生产者生产消息后发送到交换机,交换机来决定发送到哪些主线程,生产者决定不了

4、广播模式下交换机会将消息发送到所有的队列上

5、队列的消费者都能拿到消息,实现一条消息被多个消费者消息。

发布订阅之direct

广播模式下交换机是将消息发送到所有绑定的队列,但是现在我想把消息发到指定的队列进行处理,其他的消费者不能处理这些消息。direct就可以解决这种问题。

direct模式下,交换机发送消息是会携带一个routingkey,而队列在与交换机进行绑定时也会传一个routingkey,那么交换机在发送消息是就会发往routingkey相匹配的队列上。

发布订阅之topic

就是在direct模式的基础上,routingkey可以使用通配符

通配符规则:

#:匹配一个或多个词

*:匹配不多不少恰好1个词

举例:

audit.#:能够匹配audit.irs.corporate 或者 audit.irs

audit.*:只能匹配audit.irs

为什么消息中间件不采用http协议

1、因为http的请求报文头和响应报文头比较复杂,包含了cookie,数据的加解密,状态码以及响应码等附加的功能,而对于一个消息而言,我们并不需要这么复杂的附加信息,就只需要对其进行接收和转发就行,追求的是高性能,要尽量简洁

2、http大部分都是短连接,在实际的交互过程中,连接很有可能会中断,中断后就不能进行持久化,这样会造成请求的丢失,这并不利于消息中间件的业务场景,因为消息中间件是一个长期获取消息的过程,出现问题后要对数据进行持久化,为了保证消息的高可靠和稳健运行。

ttl与死信队列

ttl就是过期时间,就是说消息没有被消费的话不可能一直存放在消息队列,所以就给队列或者消息设置一个过期时间,过期后就像消息转移到死信队列中去。

死信队列呢就是接受那些被丢弃的消息,这些消息可能是过期了,被拒绝了或者是消息队列本身已经满了,存放不下的多余的消息。

RabbitMQ分布式事务案例

分布式服务之间如何保证数据的acid原则?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m4uPVBoH-1629721031991)(C:\Users\30509\AppData\Roaming\Typora\typora-user-images\image-20210729164316677.png)]

可靠生产

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qe1j3Pwd-1629721031993)(C:\Users\30509\AppData\Roaming\Typora\typora-user-images\image-20210729164630906.png)]

实际生产过程中,生产者发往MQ的消息不一定能被接收,MQ可能出现故障等原因无法正常接收消息。为了确保可靠生产,我们在业务端会有一个冗余表,这张表保存了发送的数据以及数据的状态,如果说MQ的交换机正常接收到数据后,就会回馈一个确认消息,这个时候我们更新冗余表这条数据的状态status=1;如果没有接收,name冗余表的状态值就不会发生变化,我们对通过定时器定时扫描冗余表中没有正常发送的数据,重复发送。当然,如果重复发送的次数过多,说明这个消息本身即有问题,需要人工干预。

可靠消费

当MQ中的消息没有被正常消费,会造成死循环,不断地发送请求,通常会采取以下方式:

1、控制重试次数

2、try、catch +手动ack确认机制

3、try、catch +手动ack确认机制+死信队列

ES(分布式的搜索和数据分析引擎)

为什么要用Elasticsearch?

因为一般情况下商品的数量非常多而且品类繁杂,面对这样复杂的搜索业务和数据量,传统的数据库搜索就显得力不从心,所以一般都会使用全文检索技术,那么我就是用的elasticsearch

ES是基于java开发,需要安装jdk1.8及以上

ES操作索引

基本概念

索引(indices)-----------------------------------------------------------------------------数据库(databases)

​ 类型(type)-----------------------------------------------------------------------------表(Table)

​ 文档(Document)------------------------------------------------------------------行(row)

​ 字段(Field)----------------------------------------------------------------------列(column)

​ 映射(mappings)-------------------------------------------------------------字段的数据类型、属性、是否是索引

索引的操作

添加索引:

PUT /heima
{
    "settings": {
        "number_of_shards": 1,//切片
        "number_of_replicas": 0//副本
      }
}

获取索引:GET /heima

删除索引:DELETE /heima

ES映射操作

创建映射字段

请求方式依然是PUT

PUT /索引库名/_mapping/类型名称
{
  "properties": {
    "字段名": {
      "type": "类型",
      "index": true,
      "store": true,
      "analyzer": "分词器"
    }
  }
}
//这里的操作相当于MySQL中在数据库中创建了一个表,然后声明了表中的各个字段的类型,是否建索引,是否保存,以及是否设置分词器
  • 类型名称:就是前面将的type的概念,类似于数据库中的不同表
    字段名:任意填写 ,可以指定许多属性,例如:
  • type:类型,可以是text、long、short、date、integer、object等
  • index:是否索引,默认为true
  • store:是否存储,默认为false,为false也会保存一份数据,为true会额外再保存一份
  • analyzer:分词器,这里的ik_max_word即使用ik分词器

实例

发起请求:

PUT heima/_mapping/goods
{
  "properties": {
    "title": {
      "type": "text",
      "analyzer": "ik_max_word"
    },
    "images": {
      "type": "keyword",
      "index": "false"
    },
    "price": {
      "type": "float"
    }
  }
}
//相当于在数据库heima中创建了goods表,表中有title,images,price三个字段,每个字段有各自的属性
查看映射

GET /索引库名/_mapping

字段属性详解

我们说几个关键的:

  • String类型,又分两种:

    • text:可分词,不可参与聚合
    • keyword:不可分词,数据会作为完整字段进行匹配,可以参与聚合
  • Numerical:数值类型,分两类

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

    elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。

类型中新增数据

新增数据

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

语法:

POST /索引库名/类型名
{
    "key":"value"
}

示例:

POST /heima/goods/
{
    "title":"小米手机",
    "images":"http://image.leyou.com/12479122.jpg",
    "price":2699.00
}
//这种情况是随机生成id,也可以自定义id,就在类型后边加自己id就行

新增数据的时候要是新增了字段,ES可以自动将字段添加到类型的映射关系中去。

修改数据

新增的请求方式改为PUT,就是修改了。不过修改必须指定id,

  • id对应文档存在,则修改
  • id对应文档不存在,则新增
删除数据

删除使用DELETE请求,同样,需要根据id进行删除:

语法

DELETE /索引库名/类型名/id值

查询

基本语法
GET /索引库/_searching
{
	"query":{
		"查询类型":{
			"查询条件":"查询条件值"
		}
	}
}
//query表示一个查询对象
匹配查询match

match类型查询,会把查询条件进行分词,然后进行查询,多个词条之间是or的关系

GET /heima/_search
{
    "query":{
        "match":{
            "title":"小米电视"
        }
    }
}
//在上面的案例中,不仅会查询到电视,而且与小米相关的都会查询到,多个词之间是`or`的关系。
  • and关系

某些情况下,我们需要更精确查找,我们希望这个关系变成and,可以这样做:

GET /heima/_search
{
    "query":{
        "match": {
          "title": {
            "query": "小米电视",
            "operator": "and"
          }
        }
    }
}
//本例中,只有同时包含`小米`和`电视`的词条才会被搜索到。
  • or和and之间?

orand 间二选一有点过于非黑即白。 如果用户给定的条件分词后有 5 个查询词项,想查找只包含其中 4 个词的文档,该如何处理?将 operator 操作符参数设置成 and 只会将此文档排除。

有时候这正是我们期望的,但在全文搜索的大多数应用场景下,我们既想包含那些可能相关的文档,同时又排除那些不太相关的。换句话说,我们想要处于中间某种结果。

match 查询支持 minimum_should_match 最小匹配参数, 这让我们可以指定必须匹配的词项数用来表示一个文档是否相关。我们可以将其设置为某个具体数字,更常用的做法是将其设置为一个百分数,因为我们无法控制用户搜索时输入的单词数量:

GET /heima/_search
{
    "query":{
        "match":{
            "title":{
            	"query":"小米曲面电视",
            	"minimum_should_match": "75%"
            }
        }
    }
}

本例中,搜索语句可以分为3个词,如果使用and关系,需要同时满足3个词才会被搜索到。这里我们采用最小品牌数:75%,那么也就是说只要匹配到总词条数量的75%即可,这里3*75% 约等于2。所以只要包含2个词条就算满足条件了。

多字段匹配(multi_match)

multi_matchmatch类似,不同的是它可以在多个字段中查询

GET /heima/_search
{
    "query":{
        "multi_match": {
            "query":    "小米",
            "fields":   [ "title", "subTitle" ]
        }
	}
}

本例中,我们会在title字段和subtitle字段中查询小米这个词

词条匹配

term 查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串

GET /heima/_search
{
    "query":{
        "term":{
            "price":2699.00
        }
    }
}
多词条匹配

terms 查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:

GET /heima/_search
{
    "query":{
        "terms":{
            "price":[2699.00,2899.00,3899.00]
        }
    }
}

结果过滤

默认情况下,elasticsearch在搜索的结果中,会把文档中保存在_source的所有字段都返回。

如果我们只想获取其中的部分字段,我们可以添加_source的过滤

直接指定字段

示例:

GET /heima/_search
{
  "_source": ["title","price"],
  "query": {
    "term": {
      "price": 2699
    }
  }
}
指定includes和excludes

我们也可以通过:

  • includes:来指定想要显示的字段
  • excludes:来指定不想要显示的字段

二者都是可选的。

高级查询

布尔组合

bool把各种其它查询通过must(与)、must_not(非)、should(或)的方式进行组合

GET /heima/_search
{
    "query":{
        "bool":{
        	"must":     { "match": { "title": "大米" }},
        	"must_not": { "match": { "title":  "电视" }},
        	"should":   { "match": { "title": "手机" }}
        }
    }
}
//上述查询条件是结果中必须有大米,不能有电视,可有手机
范围查询

range 查询找出那些落在指定区间内的数字或者时间

GET /heima/_searching
{
	"query":{
		"range":{
			"price":{
				"gte":1000.0,
				"lt":5000.0
			}
		}
	}
}

range查询允许以下字符:

操作符说明
gt大于
gte大于等于
lt小于
lte小于等于

模糊查询fuzzy

fuzzy 查询是 term 查询的模糊等价。它允许用户搜索词条与实际词条的拼写出现偏差,但是偏差的编辑距离不得超过2:

GET /heima/_search
{
  "query": {
    "fuzzy": {
      "title": "appla"
    }
  }
}

上面的查询,也能查询到apple手机

es调优

设计阶段

1、根据分词的字段,合理设置分词器(设置最小匹配参数)

2、采用冷热分离机制,热数据(比如最近3天或者一周的数据)存储到ssd(高I/O能力),提高检索效率;冷数据定期进行shrink操作,以缩小存储。

3、创建映射的时候,根据字段的属性,是否需要检索,以及是否需要存储

4、根据业务增量需求,采取基于日期模板创建索引,通过roll over API滚动索引;举例:设计阶段定义:blog索引的模板格式为:blog_index_时间戳的形式,每天递增数据

写入阶段

1、写入前设置副本为0

2、尽量使用自增id

3、写入过程中采用批量bulk批量写入

查询阶段

1、禁用批量terms

2、充分利用倒排索引机制,能用keyword的就用keyword

ES索引文档的过程(理解为文档写入ES)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9IKPj9CK-1629721031995)(C:\Users\30509\Desktop\面试文档\练手项目相关技术总结.assets\image-20210802094357168.png)]

第一步:客户向集群某节点写入数据,发送请求

第二步:假设是节点1接受到了请求,会根据文档id确定属于哪个分片,假设为分片0,然后将请求转发到分片0的主分片所在节点,假设为节点3

第三步:节点3在主分片上执行写操作,如果成功,则将请求并行转发到其他节点的副本分片上,等待结果返回。所有的副本分片都报告成功,节点3最后向协调节点1发送报告成功,节点1向客户端报告写入成功

第二步中的文档获取分片的过程? 回答:借助路由算法获取,路由算法就是根据路由和文档id计算目标的分片id的过程

ES搜索过程

搜索过程分为两个阶段,称之为query then fetch

1、在初始查询阶段,查询会广播到索引中的每一个分片(包括主分片和副本分片),然后每个分片在本地进行搜索所有并构建一个匹配文档的优先队列。

2、各个分片将优先队列中的文档id和排序值返回给协调节点,它将这些值合并到自己的有限队列形成了一个全局排序的结果列表。

3、接下来是取回阶段,协调节点选出要被取回的文档并向相关的分片发出get请求,每个分片加载相应的文档(需要的话还可以将文档返回给协调节点),一旦所有的文档被取回了,协调节点就将结果返回给客户端。

ES更新、删除过程过程

删除和更新操作都是写操作,es中的文档是不可变的,所以并不是真正的删除和更新。

当文档被删除时,只是在.del文件中将文档标记为删除,文档还能被匹配到,只是在结果中被过滤了。

当新的文档别写入是,es会为其指定一个版本号,那么当文档进行更新时,会将旧版本的文档在.del文件中标记为删除,新版本的文档会被索引到一个新段。

项目难点之单点登录

我们项目中我觉得算难点的算是单点登录吧,单点登录的是个学校的项目,学校找了个第三方公司给了我们一套单点登录的接口,让我们把业务系统集成到他们的系统,能通过单点登录。首先是,他们提供的单点登录的代码框架版本和我们的不一致,直接放到我们的系统中不能用,一直融合失败,然后就将他们提供的demo给单独部署了一套,接着又出现了端口跨域问题,我们的业务系统并不能直接访问demo,一开始准备用nginx做反向代理,但是由于时间仓促,最后我们是在demo中将获取的信息加密封装成一个接口,然后我们的业务系统调这个接口最终拿到了登录信息。

单点登录

单点登录就是在多个应用系统中,只需要登录一次,就可以访问其他互相信任的应用系统

技术实现

同域单点登录

一般企业都只有一个域名,然后通过二级域名来区分不同的应用系统。比如我们有个域名叫做:a.com,同时有两个业务系统分别为:app1.a.com和app2.a.com。我们要做单点登录(SSO),需要一个登录系统,叫做:sso.a.com。

现在我想实现在sso系统登录后,也能登录app1和app2。sso登录之后,会向服务器写入session记录了当前用户已登录,并且会在浏览器中写入cookie。由于cookie不能跨域,所以只有sso系统可以使用,无法共享。

针对这个问题,我们可以将cookie的domain设置到顶域,这样其他二级域名的系统也可以访问到顶域中的cookie。

cookie做了共享之后,session也需要共享,比如:spring-session

至此,同域下的单点登录就实现了,但这并不是真正的单点登录。

跨域单点登录(CAS)

票据:

1.TGT

TGT是CAS为用户签发的登录票据,TGT可以证明用户登录过

2.TGC

CAS 生成TGT放入自己的session中,而TGC就是这个session的唯一标识(SessionId),以cookie形式放到浏览器端。

3.ST

ST是CAS为用户签发的访问某一应用服务的票据。用户访问服务是,如果发现用户没有ST,就会要求用户去CAS获取ST

CAS实现流程
  1. 用户访问产品 a,域名是 http://www.a.cn
  2. 由于用户没有携带在 a 服务器上登录的 a cookie,所以 a 服务器重定向到SSO 服务器的地址。
  3. 由于用户没有携带在 SSO 服务器上登录的 TGC,所以 SSO 服务器判断用户未登录,给用户显示统一登录界面。
  4. 登录成功后,SSO 服务器构建用户在 SSO 登录的 TGT,同时返回一个 http 重定向(包含 sso 服务器派发的 ST )。
  5. 重定向的 http response 中包含写 cookie。这个 cookie 代表用户在 SSO 中的登录状态,它的值是 TGC。
  6. 浏览器重定向到产品 a。此时重定向的 url 中携带着 SSO 服务器生成的 ST。根据 ST,a 服务器向 SSO 服务器发送请求,SSO 服务器验证票据的有效性。验证成功后,a 服务器知道用户已经在 sso 登录了,于是 a 服务器构建用户登录 session。
  7. 用户访问产品 b,域名是 http://www.b.cn
  8. 由于用户没有携带在 b 服务器上登录的 b cookie,所以 b 服务器重定向到SSO 服务器,去询问用户在 SSO 中的登录状态。
  9. 浏览器重定向到 SSO服务器。由于已经向浏览器写入了携带 TGC 的cookie,所以此时 SSO 服务器可以拿到,根据 TGC 去查找 TGT,如果找到,就判断用户已经在 sso 登录过了。
  10. SSO 服务器返回一个重定向,重定向携带 ST。
  11. 浏览器带 ST 重定向到 b 服务器。
  12. b 服务器根据票据向 SSO 服务器发送请求,票据验证通过后,b 服务器知道用户已经在 sso 登录了,于是生成 b session,向浏览器写入 b cookie。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-up8u2DQI-1629721031996)(C:\Users\30509\Desktop\面试文档\练手项目相关技术总结.assets\image-20210804105956564.png)]
.cn)。
2. 由于用户没有携带在 a 服务器上登录的 a cookie,所以 a 服务器重定向到SSO 服务器的地址。
3. 由于用户没有携带在 SSO 服务器上登录的 TGC,所以 SSO 服务器判断用户未登录,给用户显示统一登录界面。
4. 登录成功后,SSO 服务器构建用户在 SSO 登录的 TGT,同时返回一个 http 重定向(包含 sso 服务器派发的 ST )。
5. 重定向的 http response 中包含写 cookie。这个 cookie 代表用户在 SSO 中的登录状态,它的值是 TGC。
6. 浏览器重定向到产品 a。此时重定向的 url 中携带着 SSO 服务器生成的 ST。根据 ST,a 服务器向 SSO 服务器发送请求,SSO 服务器验证票据的有效性。验证成功后,a 服务器知道用户已经在 sso 登录了,于是 a 服务器构建用户登录 session。
7. 用户访问产品 b,域名是 http://www.b.cn
8. 由于用户没有携带在 b 服务器上登录的 b cookie,所以 b 服务器重定向到SSO 服务器,去询问用户在 SSO 中的登录状态。
9. 浏览器重定向到 SSO服务器。由于已经向浏览器写入了携带 TGC 的cookie,所以此时 SSO 服务器可以拿到,根据 TGC 去查找 TGT,如果找到,就判断用户已经在 sso 登录过了。
10. SSO 服务器返回一个重定向,重定向携带 ST。
11. 浏览器带 ST 重定向到 b 服务器。
12. b 服务器根据票据向 SSO 服务器发送请求,票据验证通过后,b 服务器知道用户已经在 sso 登录了,于是生成 b session,向浏览器写入 b cookie。

[外链图片转存中...(img-up8u2DQI-1629721031996)]

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值