admin管理员组

文章数量:1598858

文章目录

  • 黑豹程序员技术路线图
  • 一、分布式架构
    • 1、容器化技术解决方案:docker
      • 1.1、安装一个操作系统要多久?
      • 1.2、安装一个软件要多久?
      • 1.3、启动软件要多久?
      • 1.4、卸载软件要多久?
      • 1.5、docker实现毫秒级操作
      • 1.6、docker怎么实现这一切呢?
      • 1.7、docker的好处
      • 1.8、docker和vm区别
      • 1.9、docker底层核心
      • 1.10、docker-compose 服务编排
      • 1.11、k8s
    • 2、单点登录解决方案:SSO+token
      • 2.1 什么是SSO
      • 2.2 SSO和早期CAS和OAuth2有什么不同
      • 2.3 SSO的流程
      • 2.4 CAS的流程
      • 2.5 JWT解决session共享问题
        • 什么是jwt
        • 基于session的用户认证流程
        • 基于session认证所显露的问题
        • JWT认证的原理
        • JWT的数据结构
          • Header
        • JWT 的几个特点
        • JWT过期问题解决方案,下面 4 种方案:
        • JWT 的续签问题
      • 2.5 SSO基于jwt实现
      • 2.6 誉天在线项目实现SSO
    • 3、负载均衡解决方案:nginx、ribbon、loadbanlance、gateway
      • 3.1 什么是nginx?
      • 3.2 正向代理和反向代理
      • 3.3 nginx负载均衡5种算法
      • 3.4 Ribbon本地负载均衡和Nginx服务器端负载均衡的区别
      • 3.5 Nginx和Gateway网关的不同
    • 3、分布式缓存解决方案:redis
      • 3.1 redis和memcache比较
      • 3.2 redis简介
      • 3.3 redis配置文件配置项
      • 3.4 redis的数据类型
      • 3.5 redis的内存淘汰策略
        • 为什么需要内存淘汰
        • Redis的key过期策略
          • 定期删除
          • 惰性删除
          • Redis目前共提供了8种内存淘汰策略
          • redis独特的LRU算法实现
      • 3.6 redis持久化两种方式比较:aof、rdb
        • Redis 提供了两种持久化的方式:
        • RDB(Redis DataBase)
        • AOF(Append Only File)
        • AOF 三种策略
        • AOF 重写实现原理
        • AOF 优缺点
        • RDB 和 AOF 对比
        • RDB 和 AOF 的选择
      • 3.7 redis集群:分片sharding,哨兵 sentinel(高可用),集群cluster(主从,高可用)
        • redis的sentinel哨兵
        • redis的cluster集群
      • 3.8 redis缓存雪崩、穿透、击穿
      • 3. hash算法、hash一致性算法(redis分片)、hash槽算法比较(redis cluster集群)
        • 哈希槽分区
        • 为什么Redis的槽数是16384?
      • 3.10 redis分布式锁
        • 1、什么是分布式锁
        • 3.11 redis管道
    • 4、分布式数据库解决方案:shardingsphere
    • 5、分布式文件存储解决方案:minio
  • 二、微服务架构
    • SpringCloud Netflix
    • SpringCloud Alibaba
      • 详解Nacos和Eureka的区别
      • 网关就应该这样设计
  • 三、项目背景
    • 1、永和门店系统
    • 2、书城系统
    • 3、长安分局费用车辆管理系统
    • 4、招商银税互动平台
    • 5、誉天在线平台
  • 四、简历
    • 你在做项目过程中,遇到什么样的难题,你是如何解决的?
      • 树型联动动态编辑表格
      • 我们项目引入新的自增id算法:雪花算法结果出了问题

黑豹程序员技术路线图

每个点去搜5篇文章。通过读别人文章,丰富我们知识,查漏补缺。

一、分布式架构

1、容器化技术解决方案:docker

当遇到大促活动(双11,618,过节12306订票),我们需要迅速的扩充机器,安装软件,配置环境,初始化数据。
在依赖硬件的时代,我们配置最好的服务器,小型机,配置最好网络,配置最好的软件。
数据库:oracle、db2,web中间件:Weblogic、WebSpare,开发语言:b/s asp微软(c#)、java,j2ee:ejb(远程过程调用RPC),Spring挽救java。

可以使用开源软件一样能达到甚至超过这些收费的产品。可以蚂蚁撼大树。
开发运维一体化DevOpts,它解决开发和运维之间环境不一致情况,以前都是vm虚拟机。性能低,对硬件资源浪费。
docker就来解决开发运维一体化环境统一文档。

1.1、安装一个操作系统要多久?

1个小时不要,30分钟不要,1分钟搞定。

1.2、安装一个软件要多久?

10分钟不要,5分钟不要,1分钟不要,几十秒搞定。

1.3、启动软件要多久?

1分钟不要,30秒不要,5秒不要,1秒搞定。

1.4、卸载软件要多久?

1分钟不要,卸载不干净,一堆垃圾,不要,借助杀毒软件,让它直接看你所以文件的机会不要,1秒搞定。就好像它没来过。

这就是你需要docker的理由。

1.5、docker实现毫秒级操作

毫秒级启动服务,毫秒级停止服务,毫秒级删除服务。让你操作所有软件就和操作记事簿那样的速度,只有惬意两字。

1.6、docker怎么实现这一切呢?

它有几项重大发明:image镜像,container容器,volumes存储卷,dockerfile神器。

  • image镜像,静态的封装了运行环境,有中央仓库直接提供官方做好的镜像,开箱即用。要centos随便拿,要nginx随便拿,要redis随便拿。担心有木马,一则中央仓库提供,二则image为只读。只能扩展不能篡改,安全性有保障,放心大胆使用。
  • container容器,要启动程序,image镜像死的,container活的,内容都放在内存中,速度飞快。容器底层lxc技术实现操作系统级别隔离,容器间资源隔离,不会窜台。启停程序太爽了,毫秒级搞定。不需要随时删除,里面程序过程内容统统干掉,毫无痕迹。
  • volumes存储卷,container容器内容都在内存中,程序删除,数据也随之丢失。volumes实现数据持久化。妈妈不担心我丢数据了。
  • dockerfile更是神器,你想要自己的花园吗?你想要自己的房子吗?你想要自己的生活吗?dockerfile让你随学所欲去定制自己的环境。想和运维人员、测试人员同步环境(环境一致)可以轻松制定dockerfile,然后发送给其他人。dockerfile只有几十k,和其他人同步环境就特别轻松。vm需要几g,几十g。
  • 还有一个牛的地方漏说了,就是它的速度,那叫快,天下武功唯快不破,就说的是它。因为它采用了谷歌狗,go语言。

1.7、docker的好处

有了docker我们可以统一环境,而不需要传递笨重的vmware虚拟机程序。只需复制个几十k的dockerfile。一键运行安装配置。开发运维测试不在吵架了,因为他们环境一样。
安装软件在轻松不过,一行命令,想要啥要啥,centos有,mysql有,tomcat有,nginx有,redis有,shardingspere、rabbitmq有、es有你想要什么吧?
卸载软件在轻松不过了,一下多选,全部统统干掉,后悔了?一个docker compose瞬间服务全有了。
还有potainer可视化管理平台,晦涩的命令瞬间都掉,直接鼠标点点都搞定。

1.8、docker和vm区别

二者的不同:

VM(VMware)在宿主机器、宿主机器操作系统的基础上创建虚拟层、虚拟化的操作系统、虚拟化的仓库,然后再安装应用;
Container(Docker容器),在宿主机器、宿主机器操作系统上创建Docker引擎,在引擎的基础上再安装应用。
所以说,新建一个容器的时候,docker不需要像虚拟机一样重新加载一个操作系统,避免引导。docker是利用宿主机的操作系统,省略了这个复杂的过程,秒级!

虚拟机是加载Guest OS ,这是分钟级别的

  • 与传统VM特性对比:
    作为一种轻量级的虚拟化方式,Docker在运行应用上跟传统的虚拟机方式相比具有显著优势:

  • Docker 容器很快,启动和停止可以在秒级实现,这相比传统的虚拟机方式要快得多。

  • Docker 容器对系统资源需求很少,一台主机上可以同时运行数千个Docker容器。

  • Docker 通过类似Git的操作来方便用户获取、分发和更新应用镜像,指令简明,学习成本较低。

  • 通过Dockerfile配置文件来支持灵活的自动化创建和部署机制,提高工作效率。

  • Docker容器除了运行其中的应用之外,基本不消耗额外的系统资源,保证应用性能的同时,尽量减小系统开销。

  • Docker利用Linux系统上的多种防护机制实现了严格可靠的隔离。从1.3版本开始,Docker引入了安全选项和镜像签名机制,极大地提高了使用Docker的安全性。

  • 特性 容器 虚拟机
    启动速度 秒级 分钟级
    硬盘使用 一般为MB 一般为GB
    性能 接近原生 弱于原生
    系统支持量 单机支持上千个容器 一般几十个

1.9、docker底层核心

Linux 命名空间、控制组和 UnionFS 三大技术支撑了目前 Docker 的实现,也是 Docker 能够出现的最重要原因。

namespace,命名空间
命名空间,容器隔离的基础,保证A容器看不到B容器.
6个命名空间:User,Mnt,Network,UTS,IPC,Pid

cgroups,
Cgroups 是 Control Group 的缩写,控制组
cgroups 容器资源统计和隔离

主要用到的cgroups子系统:cpu,blkio,device,freezer,memory

实际上 Docker 是使用了很多 Linux 的隔离功能,让容器看起来像一个轻量级虚拟机在独立运行,容器的本质是被限制了的 Namespaces,cgroup,具有逻辑上独立文件系统,网络的一个进程。

unionfs 联合文件系统
典型:aufs/overlayfs,分层镜像实现的基础

UnionFS 其实是一种为 Linux 操作系统设计的用于把多个文件系统『联合』到同一个挂载点的文件系统服务。

AUFS 即 Advanced UnionFS 其实就是 UnionFS 的升级版,它能够提供更优秀的性能和效率。

AUFS 作为先进联合文件系统,它能够将不同文件夹中的层联合(Union)到了同一个文件夹中,这些文件夹在 AUFS 中称作分支,整个『联合』的过程被称为联合挂载(Union Mount)。

总之,docker就是由LXC和AUFS组成

1.10、docker-compose 服务编排

Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。

Compose 定位是:定义和运行多个 Docker 容器的应用。

使用一个Dockerfile 模板文件,可以让用户很方便的定 义一个单独的应用容器。

在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。

如:要实现一个Web项目,除了Web服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose正好满足了这样的需求.它允许用户通过一个单独的docker-compose.yml 模板文件 (YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

有了Compose,我们就不需要再一个个组件去写一份Dockerfile,只需要将整体环境同一起来,写在一份docker-compose.yml文件即可

  • Compose 中有两个重要的概念:

服务 ( service ):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
项目 ( project ):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

1.11、k8s

Kubernetes是Google开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。在生产环境中部署一个应用程序时,通常要部署该应用的多个实例以便对应用请求进行负载均衡。
在Kubernetes中,我们可以创建多个容器,每个容器里面运行一个应用实例,然后通过内置的负载均衡策略,实现对这一组应用实例的管理、发现、访问,而这些细节都不需要运维人员去进行复杂的手工配置和处理。

产品特点编辑 播报

  • 可移植: 支持公有云,私有云,混合云,多重云(multi-cloud)
  • 可扩展: 模块化,插件化,可挂载,可组合
  • 自动化: 自动部署,自动重启,自动复制,自动伸缩/扩展

2、单点登录解决方案:SSO+token

2.1 什么是SSO

SSO单点登录(Single sign-on)

所谓单点登录就是在多个应用系统中,用户只需登录一次就可以访问所有相互信任的系统。

2.2 SSO和早期CAS和OAuth2有什么不同

  • CAS 中央认证服务(Central Authentication Service)

CAS是由美国耶鲁大学发起的一个企业级开源项目,旨在为WEB应用系统提供一种可靠的单点登录解决方案(WEB SSO)。

  • OAuth2.0 开放授权(Open Authorization)

OAuth2.0是一个为用户资源授权定义的安全标准,主要用于第三方应用授权登录,由于OAuth1.0标准存在安全漏洞现在已升级到2.0版本。

  • SSO单点登录与CAS 和OAuth之间有什么区别

SSO仅仅是一种设计架构,而CAS和OAuth是SSO的一种实现方式,他们之间是抽象与具象的关系。

2.3 SSO的流程


打个比方,SSO 和我们去迪士尼玩时购买的通票很像,我们只要买一次通票,就可以玩所有游乐场内的设施,而不需要在过山车或者摩天轮那里重新买一次票。在这里,买票就相当于登录认证,游乐场就相当于使用一套 SSO 的公司,各种游乐设施就相当于公司的各个产品。

2.4 CAS的流程

CAS系统 (cas.qiandu)

门户系统 (www.qiandu)

邮箱系统(mail.qiandu)

  1. 用户访问门户系统,门户系统是需要登录的,但用户现在没有登录。
  2. 跳转到CAS认证服务,即CAS的登录系统。 CAS系统也没有登录,弹出用户登录页。
  3. 用户填写用户名、密码,CAS系统进行认证后,将登录状态写入CAS的session,浏览器中写入cas.qiandu域下的Cookie。
  4. CAS系统登录完成后会生成一个ST(Service Ticket),然后跳转到门户系统,同时将ST作为参数传递给门户系统。
  5. 门户系统拿到ST后,从后台向CAS系统发送请求,验证ST是否有效。
  6. 验证通过后,门户系统将登录状态写入session并设置www.qiandu域下的Cookie。

至此,跨域单点登录就完成了。以后我们再访问门户系统时,门户就是登录的。接下来,我们再看看访问邮箱系统时的流程。

  1. 用户访问邮箱系统,邮箱系统没有登录,跳转到CAS系统。
  2. 由于CAS系统已经登录了,不需要重新登录认证。
  3. CAS系统生成ST,浏览器跳转到邮箱系统,并将ST作为参数传递给邮箱系统。
  4. 邮箱系统拿到ST,后台访问CAS系统,验证ST是否有效。
  5. 验证成功后,邮箱系统将登录状态写入session,并在mail.qiandu域下写入Cookie。

这样,app2系统不需要走登录流程,就已经是登录了。SSO,app和app2在不同的域,它们之间的session不共享也是没问题的。

2.5 JWT解决session共享问题

什么是jwt

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).

该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。

JWT一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

基于session的用户认证流程

互联网服务离不开用户认证。一般流程是下面这样。

1、用户向服务器发送用户名和密码。

2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

3、服务器向用户返回一个 session_id,写入用户的 Cookie。

4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。(自动实现)

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

基于session认证所显露的问题
  • Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
    扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在当前服务器内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

  • CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
    该问题可概括为,扩展性不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求session 数据共享,每台服务器都能够读取 session。81,82,83,用户在81上登录,它session记录,82,83上没有。怎么解决?session同步技术,把81上的session信息复制到82和83上。用户请求,此时后台系统正在session同步(session复制)这时可能造成时间差。还是有隐患。

所以session共享这种方式就被淘汰,目前主流的方式就是jwt方式。

JWT认证的原理

JWT 的原理是,服务器认证以后,生成一个 JWT-JSON 对象,发回给用户,就像下面这样。

{
“姓名”: “张三”,
“角色”: “管理员”,
“到期时间”: “2022年9月11日0点0分”
}
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

JWT的数据结构

JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:

Header(头部)
Payload(负载)
Signature(签名)
因此,一个典型的JWT看起来是这个样子的:

Header.Payload.Signature:xxxxx.yyyyy.zzzzz

在实际中的形式如下所示:

eyJhbGci0iJIUzI1NiIsInR5cCI6IkpXVcJ9.
eyJzdWIi0iIxMjMNTY3ODkwIiwibmFtZSI61kpvaG4gRG91IiwiaXNTb2NpYWwi0nRydwv9.
4pcPyMD09o1PSyXnrXCjTwXyr4BsezdI1AVTmud2fU4

Header

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

{
“alg”: “HS256”,
“typ”: “JWT”
}
上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);

typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

JWT 的几个特点

(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。

(2)JWT 不加密的情况下,不建议将隐私数据写入 JWT。

(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

额外的逻辑:将jwt放在redis之中,只要逻辑上去找到uid对应的jwt,删了就可以了,或者设置其失效时间

(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。例如:京东,在购物时已经登录过了,但是在支付时,手机短信验证,确保安全。

(6)为了减少盗用,JWT 不应该使用 HTTP 协议明文传输,要使用 HTTPS 协议传输。

JWT过期问题解决方案,下面 4 种方案:

1、将 JWT 存入内存数据库
将 JWT 存入 DB 中,Redis 内存数据库在这里是不错的选择。如果需要让某个 JWT 失效就直接从 Redis 中删除这个 JWT 即可。但是,这样会导致每次使用 JWT 发送请求都要先从 DB 中查询 JWT 是否存在的步骤,而且违背了 JWT 的无状态原则。
2、黑名单机制
和上面的方式类似,使用内存数据库比如 Redis 维护一个黑名单,如果想让某个 JWT 失效的话就直接将这个 JWT 加入到 黑名单 即可。然后,每次使用 JWT 进行请求的话都会先判断这个 JWT 是否存在于黑名单中。
前两种方案的核心在于将有效的 JWT 存储起来或者将指定的 JWT 拉入黑名单。
虽然这两种方案都违背了 JWT 的无状态原则,但是一般实际项目中我们通常还是会使用这两种方案。
3、修改密钥 (Secret) :
我们为每个用户都创建一个专属密钥,如果我们想让某个 JWT 失效,我们直接修改对应用户的密钥即可。但是,这样相比于前两种引入内存数据库带来了危害更大:

如果服务是分布式的,则每次发出新的 JWT 时都必须在多台机器同步密钥。为此,你需要将密钥存储在数据库或其他外部服务中,这样和 Session 认证就没太大区别了。
如果用户同时在两个浏览器打开系统,或者在手机端也打开了系统,如果它从一个地方将账号退出,那么其他地方都要重新进行登录,这是不可取的。
4、保持令牌的有效期限短并经常轮换
很简单的一种方式。但是,会导致用户登录状态不会被持久记录,而且需要用户经常登录。
另外,对于修改密码后 JWT 还有效问题的解决还是比较容易的。说一种我觉得比较好的方式:使用用户的密码的哈希值对 JWT 进行签名。因此,如果密码更改,则任何先前的令牌将自动无法验证。

JWT 的续签问题

JWT 有效期一般都建议设置的不太长,那么 JWT 过期后如何认证,如何实现动态刷新 JWT,避免用户经常需要重新登录?
我们先来看看在 Session 认证中一般的做法:假如 Session 的有效期 30 分钟,如果 30 分钟内用户有访问,就把 Session 有效期延长 30 分钟。
JWT 认证的话,我们应该如何解决续签问题呢?查阅了很多资料,我简单总结了下面 4 种方案:

1、类似于 Session 认证中的做法
这种方案满足于大部分场景。假设服务端给的 JWT 有效期设置为 30 分钟,服务端每次进行校验时,如果发现 JWT 的有效期马上快过期了,服务端就重新生成 JWT 给客户端。客户端每次请求都检查新旧 JWT,如果不一致,则更新本地的 JWT。这种做法的问题是仅仅在快过期的时候请求才会更新 JWT ,对客户端不是很友好。
2、每次请求都返回新 JWT
这种方案的的思路很简单,但是,开销会比较大,尤其是在服务端要存储维护 JWT 的情况下。
3、JWT 有效期设置到半夜
这种方案是一种折衷的方案,保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。
4、用户登录返回两个 JWT
第一个是 accessJWT ,它的过期时间 JWT 本身的过期时间比如半个小时,另外一个是 refreshJWT 它的过期时间更长一点比如为 1 天。客户端登录后,将 accessJWT 和 refreshJWT 保存在本地,每次访问将 accessJWT 传给服务端。服务端校验 accessJWT 的有效性,如果过期的话,就将 refreshJWT 传给服务端。如果有效,服务端就生成新的 accessJWT 给客户端。否则,客户端就重新登录即可。
这种方案的不足是:

需要客户端来配合;
用户注销的时候需要同时保证两个 JWT 都无效;
重新请求获取 JWT 的过程中会有短暂 JWT 不可用的情况(可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT)。

2.5 SSO基于jwt实现

2.6 誉天在线项目实现SSO


过程:
1、前台系统展现登录页面Login.vue,用户填写用户名和密码,发起axios跨域(前端:1573,访问后端:6060)请求。
2、后台的admin系统中 LoginController接收参数,调用LoginService进行验证,先通过用户名去数据库查询,查询到用户信息,在和密码进行比较,比较成功,继续判断,如果比较失败,返回前台提示用户名或密码不正确。
3、如果用户名和密码通过,代表用户是系统的真用户。通过jwt创建token,它含有3部分值,进行隐藏信息(用户编号、用户名、公司编号、公司名称),加上签名(盐),然后进行加密256。形成一个难以破解的16进制字符串。这个字符串就是我们的令牌token。
4、产生token后,返回前台,前台axios得到返回值,进行解析,获取到token的值。早期我们将token是放在cookie中的。后期升级又把它放在header中。使用了vue中全局守卫,拦截器。
5、这样访问其他资源时,全局守卫进行拦截,判断是否有token,如果有token直接放行(继续访问),如果没有token,就直接返回登录页面。
从而实现了整个的SSO单点登录。

3、负载均衡解决方案:nginx、ribbon、loadbanlance、gateway

3.1 什么是nginx?

Nginx (engine x) 是一款轻量级的Web 服务器 、反向代理服务器及电子邮件(IMAP/POP3)代理服务器。

3.2 正向代理和反向代理

什么是反向代理?
反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

用户发起对nginx的请求,nginx它负责转发到内网中的某个服务器。转发。
请求的路由不同,按配置的ip地址和端口,转发到指定的服务器。

3.3 nginx负载均衡5种算法

配置方式
NGINX配置负载均衡主要是在nginx.conf文件中里upstream模块

1、upstream模块应放于nginx.conf配置的http{}标签内
2、upstream模块默认算法是wrr (权重轮询 weighted round-robin)

分配算法
Nginx的upstream支持5种分配方式,下面将会详细介绍,其中前三种为Nginx原生支持的分配方式,后两种为第三方支持的分配方式。

1、轮询
轮询是upstream的默认分配方式,即每个请求按照时间顺序轮流分配到不同的后端服务器,如果某个后端服务器down掉后,能自动剔除。

upstream backend {
    server 192.168.1.101:8888;
    server 192.168.1.102:8888;
    server 192.168.1.103:8888;
}

2、加权轮询(权重)
轮询的加强版,即可以指定轮询比率,weight和访问几率成正比,主要应用于后端服务器异质的场景下。

upstream backend {
    server 192.168.1.101 weight=1;
    server 192.168.1.102 weight=2;
    server 192.168.1.103 weight=3;
}

3、IP_HASH
每个请求按照访问ip(即Nginx的前置服务器或者客户端IP)的hash结果分配,这样每个访客会固定访问一个后端服务器,可以解决session一致问题。

upstream backend {
    ip_hash;
    server 192.168.1.101:7777;
    server 192.168.1.102:8888;
    server 192.168.1.103:9999;
}

注意:
1、当负载调度算法为ip_hash时,后端服务器在负载均衡调度中的状态不能是weight和backup。
2、导致负载不均衡。

4、fair 最少时间算法
fair顾名思义,公平地按照后端服务器的响应时间(rt)来分配请求,响应时间短即rt小的后端服务器优先分配请求。如果需要使用这种调度算法,必须下载Nginx的upstr_fair模块。

upstream backend {
    server 192.168.1.101;
    server 192.168.1.102;
    server 192.168.1.103;
    fair;
}

5、URL_HASH
与ip_hash类似,但是按照访问url的hash结果来分配请求,使得每个url定向到同一个后端服务器,主要应用于后端服务器为缓存时的场景下。

upstream backend {
    server 192.168.1.101;
    server 192.168.1.102;
    server 192.168.1.103;
    hash $request_uri;
    hash_method crc32;
}

总结:
1、轮询是默认的方式,最简单易用。
2、权重是如果机器性能不同,使用此配置。
3、IP_HASH和URL_HASH能解决session共享,但IP_HASH是nginx直接支持,URL_HASH需要第三方支持。
4、fair也需要第三方支持。

3.4 Ribbon本地负载均衡和Nginx服务器端负载均衡的区别

Ribbon本地负载均衡

原理:在调用接口的时候、会在eureka注册中心上获取注册信息服务列表,获取到之后,缓存在jvm本地,使用本地实现rpc远程技术进行调用,即是客户端实现负载均衡

Nginx服务器负载均衡

客户端所有请求都会交给nginx,然后由nginx实现转发请求,即负载均衡是由服务端实现

应用场景:
ribbon本地负载均衡适合微服务rpc远程调用,比如:dubbo,springcloud

nginx服务负载均衡适合于针对服务器端,比如:tomcat、jetty

3.5 Nginx和Gateway网关的不同

- Nginx是流量网关。
用于反向代理、负载均衡,处理高并发能力十分强大,据测试最高能支持5w个并发连接数。前面章节已经讲述了nginx的各项功能,这里不再重复。

- Gateway是业务网关。
微服务网关,拥有统一路由、统一鉴权、跨域、限流、服务发现等功能。

通常API网关指的是业务网关。 有时候我们也会模糊流量网关和业务网关,让一个网关承担所有的工作,所以这两者之间并没有严格的界线。

下图是一个工程中Nginx先将客户端的请求负载均衡到SpringGateway,然后SpringGateway再通过服务发现,将请求负载均衡到各个业务微服务上。


Gateway:统一鉴权
当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
以后每次请求,客户端都携带认证的token
服务端对token进行解密,判断是否有效。

3、分布式缓存解决方案:redis

3.1 redis和memcache比较

在出现分布式缓存产品之前,我们的项目缓存都放在本地。例如:mybatis一级二级缓存使用本地缓存,spring三级缓存,它也是使用本地缓存。
在memcache它开了分布式内存的先和。但是首先它所在时代,计算机分布式才开始普及,所以它的步伐有点小。它只支持字符串kv方式,而且它的value值大小非常小:1m。

redis诞生就改善了mencache。


市场主流采用Redis?
1)支持持久化,这样好处是在高并发的情况下,如果缓存宕机重启时,memcache因为没有数据,用户大量请求都会涌向数据库,数据库无法承受这样的压力,直接宕机。memcache这种高并发情况下存在巨大风险。
2)memcache它的字符串value大小限制1m以下,这点在现在分布式信息量爆炸时代,这个值太小了。而redis大胆,字符串value支持512m。而且其他的类型,直接value支持到2^32。
3)redisd数据类型5种,表现力比memcache强很多。

3.2 redis简介

Redis简介:

Redis是一款开源的、高性能的键-值存储(key-value store)。它常被称作是一款数据结构服务器(data structure server)。

Redis的键值可以包括字符串(strings)类型,同时它还包括哈希(hashes)、列表(lists)、集合(sets)和 有序集合(sorted sets)等数据类型。 对于这些数据类型,你可以执行原子操作。例如:对字符串进行附加操作(append);递增哈希中的值;向列表中增加元素;计算集合的交集、并集与差集等。

为了获得优异的性能,Redis采用了内存中(in-memory)数据集(dataset)的方式。同时,Redis支持数据的持久化,你可以每隔一段时间将数据集转存到磁盘上(snapshot),或者在日志尾部追加每一条操作命令(append only file,aof)。

Redis同样支持主从复制(master-slave replication),并且具有非常快速的非阻塞首次同步( non-blocking first synchronization)、网络断开自动重连等功能。同时Redis还具有其它一些特性,其中包括简单的事物支持、发布订阅 ( pub/sub)、管道(pipeline)和虚拟内存(vm)等 。

3.3 redis配置文件配置项

  1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
    daemonize no

  2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
    pidfile /var/run/redis.pid

  3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
    port 6379

  4. 绑定的主机地址
    bind 127.0.0.1
    5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
    timeout 300

  5. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
    loglevel verbose

  6. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
    logfile stdout

  7. 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
    databases 16

  8. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
    save
    Redis默认配置文件中提供了三个条件:
    save 900 1
    save 300 10
    save 60 10000
    分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

  9. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
    rdbcompression yes

  10. 指定本地数据库文件名,默认值为dump.rdb
    dbfilename dump.rdb

  11. 指定本地数据库存放目录
    dir ./

  12. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
    slaveof

  13. 当master服务设置了密码保护时,slav服务连接master的密码
    masterauth

  14. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭
    requirepass foobared

  15. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
    maxclients 128

  16. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
    maxmemory

  17. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
    appendonly no

  18. 指定更新日志文件名,默认为appendonly.aof
    appendfilename appendonly.aof

  19. 指定更新日志条件,共有3个可选值: no:表示等操作系统进行数据缓存同步到磁盘(快) always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) everysec:表示每秒同步一次(折衷,默认值)
    appendfsync everysec

  20. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
    vm-enabled no

  21. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
    vm-swap-file /tmp/redis.swap

  22. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
    vm-max-memory 0

  23. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
    vm-page-size 32

  24. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
    vm-pages 134217728

  25. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
    vm-max-threads 4

  26. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
    glueoutputbuf yes

  27. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
    hash-max-zipmap-entries 64
    hash-max-zipmap-value 512

  28. 指定是否激活重置哈希,默认为开启
    activerehashing yes

  29. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
    include /path/to/local.conf

3.4 redis的数据类型

Redis的数据类型:

Keys
非二进制安全的字符类型( not binary-safe strings )

Values
1)字符串,使用最多,Strings。大小:512m。
2)Lists ,可以实现双向队列。大小:2^32。
3)Sets 乱序集合。大小:2^32。
4)排序集合 Sorted sets 。大小:2^32。
5)存储对象使用 Hash方式。大小:2^32。

3.5 redis的内存淘汰策略

为什么需要内存淘汰

我们都知道redis的性能很高,最主要的原因之一就是redis的数据都在内存中放着,我们在从redis中获取数据或者更新redis中的数据时,都是操作的内存中的数据。而当内存被占满了之后怎么办呢?这时就有必要将一些数据清理掉,以便新的数据能够放到redis中。而清理掉哪些数据?保留哪些数据?什么时候清理?如何配置这些策略?这些就是接下来要研究的内容。

Redis的key过期策略

Redis中可以为key设置过期时间,当到达过期时间后,就需要将这个key删除掉。Redis中提供了两种过期删除策略:惰性删除和定期删除。

定期删除

Redis会将每个设置了过期时间的key放入到一个独立的字典中,以后会定期遍历这个字典来删除到期的key。Redis默认每秒进行10次过期扫描(100ms一次,可以通过修改配置文件redis.conf 的 hz 选项来调整这个次数),但这个扫描并不会扫描过期字典中所有的key,而是通过一种贪心策略来随机筛选删除key,步骤如下:

从过期字典中随机选出20个key;

删除这20个key中已经过期的key;

如果过期的key的比例超过了1/4,那就重复从步骤1开始执行。

之所以采用这种扫描策略,还是为了性能考虑。假如过期字典中有数百万个key,每隔100ms就扫描一次这数百万个key,会给CPU造成很大的负荷,所以,就选择了这种随机筛选部分key,然后按照过期比例来判断是否需要重复执行筛选过期的动作。

正是由于使用了这种扫描策略,定期删除可能会造成很多已经过期的key无法及时删除,所以就有了接下来的惰性删除策略。

惰性删除

所谓惰性删除就是在客户端访问这个key的时候,Redis对key的过期时间进行检查,如果过期了就立即删除,然后返回null。

不管是定期删除还是惰性删除,都是一种不完全精确的删除策略,始终还是会存在已经过期的key无法被删除的场景。而且这两种过期策略都是只针对设置了过期时间的key,不适用于没有设置过期时间的key的淘汰,所以,Redis还提供了内存淘汰策略,用来筛选淘汰指定的key。

Redis目前共提供了8种内存淘汰策略

1、noeviction:只返回错误,不会删除任何key。该策略是Redis的默认淘汰策略,一般不会选用。
2、volatile-ttl:将设置了过期时间的key中即将过期(剩余存活时间最短)的key删除掉。
3、volatile-random:在设置了过期时间的key中,随机删除某个key。
4、allkeys-random:从所有key中随机删除某个key。
5、volatile-lru:基于LRU算法,从设置了过期时间的key中,删除掉最近最少使用的key。
6、allkeys-lru:基于LRU算法,从所有key中,删除掉最近最少使用的key。该策略是最常使用的策略。
7、volatile-lfu:基于LFU算法,从设置了过期时间的key中,删除掉最不经常使用(使用次数最少)的key。
8、allkeys-lfu:基于LFU算法,从所有key中,删除掉最不经常使用(使用次数最少)的key。

redis独特的LRU算法实现

需要注意的是,Redis中的LRU算法并没有严格按照常规的LRU算法的方式实现,而是基于LRU算法的思想做了自己的优化。我们知道,实现LRU算法时,需要将所有的数据按照访问时间距离当前时间的长短排序放到一个双向链表中,基于这个链表实现数据的淘汰。但Redis中存储的数据量是非常庞大的,如果要基于常规的LRU算法,就需要把所有的key全部放到这个双向链表中,这样就会导致这个链表非常非常大,不止需要提供更多的内存来存放这个链表结构,而且操作这么庞大的链表的性能也是比较差的。

所以,Redis中的LRU算法是这样实现的:首先定义一个淘汰池,这个淘汰池是一个数组(大小为16),然后触发淘汰时会根据配置的淘汰策略,先从符合条件的key中随机采样选出5(可在配置文件中配置)个key,然后将这5个key按照空闲时间排序后放到淘汰池中,每次采样之后更新这个淘汰池,让这个淘汰池里保留的总是那些随机采样出的key中空闲时间最长的那部分key。需要删除key时,只需将淘汰池中空闲时间最长的key删掉即可。

3.6 redis持久化两种方式比较:aof、rdb

没有redis之前,我们一般是如何备份数据?
1、文件备份,复制文件即可;
2、数据库备份:dump(冷备份)
a. 全量备份,把数据库全部的内容进行备份。先停止数据库访问,然后利于备份命令把数据库进行全部备份,oracle备份处理是二进制文件(BLOG二进制大对象,CLOG字符大对象,4g,日常varchar2:4000字符)dmp。mysql备份sql文件(sql本身是纯文本)。特点:如果数据量很大,备份过程慢,备份时间长,备份时造成数据库不能访问,影响用户操作。一般都是深夜备份,并且通知用户系统升级。

MySQL备份过程:
a. 把某个库中的所有表设置为只读,不允许写操作,备份过程中,就不担心用户的数据被修改;
FLUSH TABLES WITH READ LOCK;
b. 备份完成,执行下面的命令解锁,数据库正常运行;
UNLOCK TABLES;

b. 增量备份(热备份),不停机情况下进行备份,只备份新改变的内容。类似主从复制,主的信息发生变化,把修改类的SQL记录下来,然后在新的机器上执行。是在上一次备份的基础上,发现变化的新内容进行备份。特点:备份的内容就非常少,速度快,处理也快(那到新库中恢复)。

redis的持久化就类似上面的行为,它把内存中的数据备份到磁盘,形成磁盘文件,完成数据的持久化。
redis 所有的数据都是保存在内存中,当 redis 进程挂了或者机器出现宕机等异常情况,如果不将数据保存在硬盘中,那么数据将会丢失。redis 就提供了持久化的功能,就是可以将所有的数据修改也会异步更新在磁盘上。

Redis 提供了两种持久化的方式:

RDB:这是一种快照的方式,它将 Redis 某时间点的数据都进行快照存储。比如 Mysql Dump 也是这种方式。
AOF:写日志的方式,记录每次对服务器写的操作, 当服务器重启的时候会重新执行这些命令来恢复原始的数据。例如 Mysql binlog,Hbase HLog。

RDB(Redis DataBase)

在 Redis 运行时, RDB 将当前内存中的数据库生成一个 Snapshot 快照保存到磁盘文件中, 在 Redis 重启动时, RDB 可以通过载入 RDB 文件来还原数据库的状态。

RDB 的触发方式:.
RDB 有三种触发方式,其实就是生成 RDB 文件的方式。

save 命令:这个是同步方式,会阻塞当前其他的命令执行,直到save命令执行完毕。

bgsave 命令:这个是一个异步命令,会单独在后台去执行,不会阻塞其他命令。它其实是新创建(fork)了一个子进程,这个进程就是负责生成RDB文件的工作。

自动方式:就是在某些条件下会自动去生成,这个是在Redis配置文件中去设置。

这里说一下上面配置时间策略具体的意思。

900 秒内有一个 key 变化
300 秒内 10 个 key 变化
60 秒内有 10000 个 key 发生变化。

RDB的文件生成策略
如果存在老的 RDB 文件,那么会生成一个临时文件,然后新生成的文件就会替换老的 RDB 文件。

save和gbsave的区别:

AOF(Append Only File)

Redis 的另一种持久化方式就是 AOF(Append Only File),与 RDB 持久化通过保存数据库中的键值对来记录数据库状态不同,AOF 是通过保存Redis所执行的写命令来记录数据库状态的。

RDB所存在的问题
耗时,耗性能,每次保存 RDB 的时候Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时 fork() 可能会非常耗时,比如写的数据量很大,内存页设置的比较大,会产生很大的内存消耗,造成服务器在某某毫秒内停止处理客户端, 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至会更长。同时 RDB 也是一个 IO 的过程,RDB 文件很大拷贝速度就会很慢。

AOF 三种策略

Redis 提供了三种决定 AOF 写入的频率。

Redis 在执行写入 AOF 日志文件的时候不是直接去写入文件中,而是先记录在硬盘的缓冲区,缓冲区会根据一些刷新策略来决定什么时候刷新到磁盘中,这样会提高写入的效率。

  1. always: 每次写入一条数据就立即将这个数据对应的写日志 fsync 到磁盘上去,虽然可以确保 Redis 里的数据一条都不丢,但是性能非常差,吞吐量很低。
  2. everysec:每秒将缓冲中的数据 fsync 到磁盘,这个比较最常用的,生产环境一般都这么配置,而且性能很高。但是缺点就是不像 always 那样保证每个命令都会记录,Redis 服务器出现故障有可能会丢失一秒钟的数据。
    1. no: 仅仅 redis 负责将数据写入缓冲区,什么时候刷新到磁盘中是根据操作系统自己决定。这种一般不会使用。
AOF 重写实现原理

AOF 策略保证 Redis 命令写入到 AOF 文件中,不过随着命令逐步的写入,时间的推移,并发量写入量逐步的变大,AOF 文件的体积也会逐渐的变大。这时候使用 AOF 进行恢复数据会变的很慢。当 AOF 文件无限制的变大,无论是对于文件的管理,写入命令的速度都会有一定的影响。所以 Redis 提供了一个 AOF 重写的机制来解决这些问题。


根据上图分析原生 AOF 那一列最上面写入三条命令,都是对同一个 key 进行操作的,最后一次将前面两次写入的值更新了,实际上只有最后一次修改对我们是有用的。中间的 incr 自增命令也是一样的。下面三次 rpush 也是可以优化的,可以统一成一个命令。同时对于一些过期的数据,当时写入到 AOF 文件中,但是某个时间点已经过期了,这个 key 内部会执行一个删除命令操作并同步到 AOF 文件中,这个在 AOF 重写中是没有用的。

所以可以得出 AOF重写的作用:

将过期的,没有用的,重复的命令,以及一些可以优化的命令都进行一个精简,来缩小 AOF 文件的体积,减少对磁盘的占用量。同时文件体积缩小也可以加速Redis恢复的速度。

AOF 优缺点
  1. 优点
    AOF 机制可以带来更高的数据安全性。
    由于该机制对日志文件的写入操作采用的是 append 模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容
    AOF 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。
  2. 缺点
    对于相同数量的数据集而言,AOF 文件通常要大于 RDB 文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快
    AOF 对系统开销有一定的影响。AOF 常用的持久化策略是 everysec,在这种策略下,fsync 同步文件操作由专门线程每秒调用一次。当系统磁盘较忙时,会造成 Redis 主线程阻塞,所以在 Redis 的负载较高情况下,RDB 比 AOF 具好更好的性能保证。
RDB 和 AOF 对比

RDB 和 AOF 的选择

如果可以忍受一小段时间内数据的丢失,毫无疑问使用 RDB 是最好的,定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,而且使用 RDB 还可以避免 AOF 一些隐藏的 bug;否则就使用 AOF 重写。但是一般情况下建议不要单独使用某一种持久化机制,而是应该两种一起用,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。Redis后期官方可能都有将两种持久化方式整合为一种持久化模型。

3.7 redis集群:分片sharding,哨兵 sentinel(高可用),集群cluster(主从,高可用)

redis追求快,缓存。特性:使用它就是要利用它的快,缓存意味着数据即使丢失,还有数据库保底。
高可用,就是集群,如果集群中有机器宕机,其他节点顶上来,对用户而言好像没影响。特别:主从,日常用户连接主节点,配置从节点,从节点会实时备份主节点的数据。当主节点宕机,从节点自动顶上,因为它实时备份主的数据,所以顶上来后,用户没有感觉。如果再配置到docker上,秒级就可以重启我们的主节点,它就变成当前主的从节点。如果一个主节点挂接了多个从节点,集群会自动选举新的主节点。(大数据就需要选举)

哨兵sentinel解决方案是一个过渡解决方案,最终redis出品了cluster redis集群。它就替代了sentinel。

redis的sentinel哨兵

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复,实现系统的高可用。

1、哨兵原理

2、哨兵的作用如下:

  • 监控:Sentinel 会不断检查您的master和slave是否按预期工作
  • 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
  • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端

3、集群监控原理
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:

主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

4、集群故障恢复原理
一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:

首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点

然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举

如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高

最后是判断slave节点的运行id大小,越小优先级越高。

当选出一个新的master后,该如何实现切换呢?

流程如下:

sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master

sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。

最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点

redis的cluster集群

1、什么是集群
由于数据量过大,单个Master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展。每个数据集只负责存储整个数据集的一部分,这就是Redis的集群,其作用是提供在多个Redis节点间共享数据的程序集。

2、Redis集群支持多个Master,每个Master又可以挂载多个Slave
读写分离
支持数据的高可用
支持海量数据的读写存储操作
由于Cluster自带Sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能
客户端与Redis的节点连接,不再需要连接集群中所有的节点,只需要任意连接集群中的一个可用节点即可
槽位slot负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系

3、Redis集群没有使用一致性hash,而是引入了哈希槽的概念。

集群的有16384(2^14)个槽,有效地设置了16384个主节点的集群大小上限(但是官网建议的最大节点大小约为1000个节点),每个key通过CRC16校验后对16384取模(HASH_SLOT = CRC16(key) mod 16384)来决定放置在哪个槽,集群的每个节点负责一部分hash槽(数据分片)。

当没有集群重新配置正在进行时(即哈希槽从一个节点移动到另一个节点),集群是稳定的。

当集群稳定时,单个哈希槽将由单个节点提供服务(但是,服务节点可以有一个或多个副本,在网络分裂或故障的情况下替换它,并目可以用于扩展读取陈旧数据是可接受的操作)

分片
使用Redis集群时,我们会将存储的数据分散到多台Redis机器上,这就是分片。换句话说,就是集群中的每个Redis实例都被认为是整个数据的一个分片。

那么如何找到给定key的分片呢?

即对key进行CRC16(key)算法处理并通过总分片数量取模,然后,使用确定性哈希函数,这意味着给定的key将始终映射到同一个分片,因此可以推断将来读取特定key的位置

使用槽位+分片的优势
最大优势:方便扩缩容和数据分派查找

这种结构很容易添加或者删除节点。

举几个例子:

如果我想新添加个节点D,就需要从节点A、B、C中得部分槽到D上

如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可

由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态

3.8 redis缓存雪崩、穿透、击穿

3. hash算法、hash一致性算法(redis分片)、hash槽算法比较(redis cluster集群)

redis分片采用一致性hasn算法,redis cluster集群采用hash slot hash槽算法。

分区映射方法有三种:哈希取余分区、一致性哈希算法分区和哈希槽分区
1、哈希取余分区
hash(key)%n
n 为机器台数

缺点

原来规划好的节点,进行扩容或者缩容就比较麻烦。不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化。

举个例子,2亿条记录就是2亿个k,v,单机不行就要考虑分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式hash(key)%N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。

如果原来有3台,增加了1台,就会从Hash(key)%3变成Hash(key)%4。

此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。即某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。

2、一致性哈希
一致性Hash算法背景

一致性哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决分布式缓存数据变动和映射问题(当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系),某个机器宕机了,分母数量改变了,自然取余数不行了。
注意:一致性hash算法能极大缓解n变化情况,但是不能根除。

算法构建一致性哈希环

将节点全部放到一个虚拟的圆环上,圆环上0在232处重合,其节点范围在0-232-1

一致性哈希算法必然有个哈希函数并按照算法产生哈希值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,232-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0=232),这样让它逻辑上形成了一个环形空间。它也是按照使用取模的方法,不过前面介绍的哈希取余分区取模法是对节点(服务器)的数量进行取模,而一致性哈希算法是对232取模(因此解决了取模数会变化的问题),简单来说,一致性哈希算法就是将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数的值空间为0~232-1(即哈希值是一个32位无符号整形),整个哈希环如下图:

整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4……直到232-1,也就是说0点左侧的第一个点代表232-1,0和232在零点中方向重合,我们把这个由232个点组成的圆环称为Hash环。
服务器IP节点映射

IP或者主机名

节点映射

将集群中各个IP节点映射到环上的某一个位置。

将各个服务器使用Hash()进行次哈希,具体可以选择服务器的IP或主机名作为关键字,这样每台机器就能确定其在哈希环上的位置。举个例子,如下图4个节点NodeA、B、C、D,经过IP地址的哈希函数计算Hash(IP)

key落到服务器的落键规则

当我们需要存储一个kv键值对时,首先计算key的hash值(hash(key))将这个key使用相同的哈希函数计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。

其实,搞个环也就是为了在节点数目发生改变时,尽量少的迁移数据,将所有的节点排列在首尾相接的Hash环上,每个key在计算后会顺时针找到邻近的储存节点存放,而当有节点加入或者退出时仅影响该节点在Hash环上顺时针相邻的后续节点。

优点

加入和删除只影响hash环中顺时针方向相邻的节点,对其他节点无影响

缺点

数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。

这就是一致性哈希算法在节点较少时会有数据倾斜的问题,因此我们有了 —— 哈希槽分区

哈希槽分区

为了解决一致性哈希算法数据倾斜的问题,产生了哈希槽分区

其实质就是一个数组,数组[0, 16383]形成哈希槽空间(共16384个槽位)

16384就是2^14-1

没有什么是加一层解决不了的,如果有,那就再加一层

解决均匀分配的问题,在数据和节点之间又加了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,就相当于节点上放的是槽,槽里放的是数据。

槽解决粒度的问题,相当于粒度调大了,便于数据的移动。哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。

一个集群只能有16384个槽,编号0-16383(0~2^14-1),这些槽会分配给集群中的所有主节点,分配策略没有要求。

集群会记录节点和槽的对应关系,解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取模,余数是几key就落入对应的槽里(HASH_SLOT=CRC16(key)mod 16384)。

以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。

为什么Redis的槽数是16384?

Redis集群并没有使用一致性hash而是引入了哈希槽的概念。

Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置那个槽,集群的每个节点负责一部分哈希槽,但为什么哈希槽的数量是16384(2^14)个呢?

CRC16算法产生的哈希值有16bit,该算法可以产生2^16=65536个值,换句话说值是分布在0~65535之间,有更大的65536不用为什么只用16384呢?

简单翻译下:

正常的心跳数据包带有节点的完整配置,可以用幂等方式用旧的节点替换旧节点,以便更新旧的配置。这意味着它们包含原始节点的插槽配置,该节点使用2k的空间和16k的插槽,但是会使用8k的空间(使用65k的插槽)。
同时,由于其他设计折衷,Redis集群不太可能扩展到1000个以上的主节点。
因此16k处于正确的范围内,以确保每个主机具有足够的插槽,最多可容纳1000个矩阵,但数量足够少,可以轻松地将插槽配置作为原始位图传播。请注意,在小型群集中,位图将难以压缩,因为当N较小时,位图将设置的slot/N占设置位的很大百分比。

(1)如果槽位为65536,发送心跳信总的消息头达8k,发送的心跳包过于庞大。

在消息头中最占空间的是myslots[CLUSTER_SL0TS/8],当槽位为65536时,这块的大小是:65536÷8÷1024=8kb;当槽位为16384时,这块的大小是:6384÷8÷1024=2kb

因为每秒钟,Redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,会浪费带宽。

(2)Redis的集群主节点数量基本不可能超过1000个。

集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。

因此Redis作者不建议redis cluster节点数量超过1000个,而对于节点数在1000以内的redis cluster集群,16384个槽位够用了,没有必要拓展到65536个。

(3)槽位越小,节点少的情况下,压缩比高,容易传输

Redis主节点的配置信息中它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率slots/N很高的话(N表示节点数),bitmap的压缩率就很低。如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率也会很低。

3.10 redis分布式锁

1、什么是分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式,通过互斥来保持一致性。

了解分布式锁之前先了解下线程锁和进程锁:

线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如Synchronized、Lock等

进程锁:控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁

比如Golang语言中的sync包就提供了基本的同步基元,如互斥锁

但是以上两种适合在单体架构应用,但是分布式系统中多个服务节点,多个进程分散部署在不同节点机器中,此时对于资源的竞争,上诉两种对节点本地资源的锁就无效了。

这个时候就需要分布式锁来对分布式系统多进程访问资源进行控制,因此分布式锁是为了解决分布式互斥问题!

分布式锁的特性
互斥
互斥性很好理解,这也是最基本功能,就是在任意时刻,只能有一个客户端才能获取锁,不能同时有两个客户端获取到锁。

避免死锁
为什么会出现死锁,因为获取锁的客户端因为某些原因(如down机等)而未能释放锁,其它客户端再也无法获取到该锁,从而导致整个流程无法继续进行。
面对这种情况,当然有解决办法啦!

引入过期时间:通常情况下我们会设置一个 TTL(Time To Live,存活时间) 来避免死锁,但是这并不能完全避免。

比如TTL为5秒,进程A获得锁
问题是5秒内进程A并未释放锁,被系统自动释放,进程B获得锁
刚好第6秒时进程A执行完,又会释放锁,也就是进程A释放了进程B的锁
仅仅加个过期时间会设计到两个问题:锁过期和释放别人的锁问题

锁附加唯一性:针对释放别人锁这种问题,我们可以给每个客户端进程设置【唯一ID】,这样我们就可以在应用层就进行检查唯一ID。

自动续期:锁过期问题的出现,是我们对持有锁的时间不好进行预估,设置较短的话会有【提前过期】风险,但是过期时间设置过长,可能锁长时间得不到释放。

这种情况同样有处理方式,可以开启一个守护进程(watch dog),检测失效时间进行续租,比如Java技术栈可以用Redisson来处理。

可重入:

一个线程获取了锁,但是在执行时,又再次尝试获取锁会发生什么情况?
是的,导致了重复获取锁,占用了锁资源,造成了死锁问题。

我们了解下什么是【可重入】:指的是同一个线程在持有锁的情况下,可以多次获取该锁而不会造成死锁,也就是一个线程可以在获取锁之后再次获取同一个锁,而不需要等待锁释放。

解决方式:比如实现Redis分布式锁的可重入,在实现时,需要借助Redis的Lua脚本语言,并使用引用计数器技术,保证同一线程可重入锁的正确性。

容错
容错性是为了当部分节点(redis节点等)宕机时,客户端仍然能够获取锁和释放锁,一般来说会有以下两种处理方式:

一种像etcd/zookeeper这种作为锁服务能够自动进行故障切换,因为它本身就是个集群,另一种可以提供多个独立的锁服务,客户端向多个独立锁服务进行请求,某个锁服务故障时,也可以从其他服务获取到锁信息,但是这种缺点很明显,客户端需要去请求多个锁服务。

分类
自旋方式
基于数据库和基于Etcd的实现就是需要在客户端未获得锁时,进入一个循环,不断的尝试请求是否能获得锁,直到成功或者超时过期为止。

监听方式
这种方式只需要客户端Watch监听某个key就可以了,锁可用的时候会通知客户端,客户端不需要反复请求,基于zooKeeper和基于Etcd实现分布式锁就是用这种方式。

实现方式
分布式锁的实现方式有数据库、基于Redis缓存、ZooKeeper、Etcd等,文章主要从这几种实现方式并结合问题的方式展开叙述!

3.11 redis管道

4、分布式数据库解决方案:shardingsphere

5、分布式文件存储解决方案:minio

二、微服务架构

SpringCloud Netflix

SpringCloud Alibaba

详解Nacos和Eureka的区别

网关就应该这样设计

三、项目背景

1、永和门店系统

2、书城系统

3、长安分局费用车辆管理系统

4、招商银税互动平台

5、誉天在线平台

四、简历

你在做项目过程中,遇到什么样的难题,你是如何解决的?

树型联动动态编辑表格

需求:我们项目中需要一个对数据分类表的CURD实现。这个功能较复杂,要求左边是一颗树,右边是一个动态表格。
点击树的节点,右侧跟着变化。

我们项目使用ElementPlus,我发现它只有树和动态表格,但没有把两个在一起联动的实现。
我自己实现了这个增强功能。

第一、先页面上加载了el-tree组件,从后台查询出数据分类的数据,按树节点的要求构建其结构:节点的编号,节点的展示名称,子节点集合。

第二、又在页面上加载了el-table组件,并改造成可编辑的表格,其实实现很好理解,它把每个单元格内容用Input框包裹,这样就可以编辑。又利于把input边框设置为0,让input框和背景融为一体,都是白色,从而让用户感觉其在编辑单元格,其实编辑的是input框。

第三、就是让树和表格进行联动
首先给树增加nodeclick节点点击事件,点击是对表格的数据数组进行过滤,过滤条件为记录的parantid=node.id。
这样其他数据就被过滤掉,从而只显示本节点下的数据。

而且我把这个整理成文章和项目的成员共享,大家可以少走弯路。

我们项目引入新的自增id算法:雪花算法结果出了问题

现象:id产生没有问题,数据库也都正常新增。但在显示时出了问题,我在删除时却删除不了记录。
追查原因发现一个奇怪现象,删除记录时失败。这个特别容易实现的代码,却报错。分析后发现,提交的id竟然和数据库中的id不同。

我刷新几次,把id记录下来,发现果然id不同。

最终百度发现,竟然java和javascript处理long型的方式不同。
java Long的取值范围是2^64(19位)
javascript Long的取值范围是2^54(16位)
这样当后台返回前台时,转化精度后就出现上面一幕,精度丢失。

那如何解决呢?解决的办法好几个。有数据库表字段不使用Long型,有后台返回前台时不使用Long型。
那我选择了一个最简单的方式:实体上增加转化注解,把Long按String字符串转化,就没有精度丢失问题了。

我还总结了一个CSDN帖子。
https://blog.csdn/nutony/article/details/132863461

面试官:你还写CSDN帖子?
是啊,我平时就喜欢看各类技术的帖子,搜索很多技术难题chatGPT啊、百度啊。然后测试可用后,就习惯在CSDN上总结和大家分享,让和我一样的初学者少走弯路。毕竟送人玫瑰手留余香。

本文标签: 黑豹程序员月薪秘籍大题