Java进阶之路(七)分布式中间件(集群、分布式、CAP、SpringCloud、Dubbo、Redis、消息队列MQ)

概念
集群

同一个业务,部署在多个服务器上

分布式

同一个业务,拆分为多个子业务,部署在不同的服务器上

CAP理论

C:数据一致性(consistency)

所有节点拥有数据的最新版本

A:可用性(availability)

数据具备高可用性

P:分区容错性(partition-tolerance)

容忍网络出现分区,分区之间网络不可达。

SpringCloud
基础功能

服务治理: Spring Cloud Eureka

客户端负载均衡: Spring Cloud Ribbon

服务容错保护: Spring Cloud Hystrix

声明式服务调用: Spring Cloud Feign

API网关服务:Spring Cloud Zuul

分布式配置中心: Spring Cloud Config

高级功能

消息总线: Spring Cloud Bus

消息驱动的微服务: Spring Cloud Stream

分布式服务跟踪: Spring Cloud Sleuth

Eureka

在服务很多的时候,对于多个节点的连接如果手动连接工作量太大,还容易出错,因此使用Eureka进行服务治理。

思路:

创建一个服务中心,维护已经注册的服务

img

在Eureka Server一般会这样配置:

register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
Eureka的治理机制:

服务提供者

服务注册:启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息

服务续约:在注册完服务之后,服务提供者会维护一个心跳用来持续告诉Eureka Server: "我还活着 ”

服务下线:当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server, 告诉服务注册中心:“我要下线了 ”

服务消费者

获取服务:当启动服务消费者的时候,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单

服务调用:服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。在进行服务调用的时候,优先访问同处一个Zone中的服务提供方。

Eureka Server(服务注册中心):

失效剔除:默认每隔一段时间(默认为60秒) 将当前清单中超时(默认为90秒)没有续约的服务剔除出去。

自我保护:。EurekaServer 在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%(通常由于网络不稳定导致)。 Eureka Server会将当前的实例注册信息保护起来, 让这些实例不会过期,尽可能保护这些注册信息。

img

使用Spring封装好的RestTemplate工具类

// 传统的方式,直接显示写死IP是不好的!
//private static final String REST_URL_PREFIX = “http://localhost:8001”;

// 服务实例名
private static final String REST_URL_PREFIX = “http://MICROSERVICECLOUD-DEPT”;
/**
* 使用 使用restTemplate访问restful接口非常的简单粗暴无脑。 (url, requestMap,
* ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
*/
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = “/consumer/dept/add”)
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX + “/dept/add”, dept, Boolean.class);
}
负载均衡

分类

客户端负载均衡(Ribbon)

服务实例的清单在客户端,客户端进行负载均衡算法分配。

(客户端可以从Eureka Server中得到一份服务清单,在发送请求时通过负载均衡算法,在多个服务器之间选择一个进行访问)

服务端负载均衡(Nginx)

服务实例的清单在服务端,服务器进行负载均衡算法分配

Ribbon

@Configuration
public class MySelfRule
{
@Bean
public IRule myRule()
{
//return new RandomRule();// Ribbon默认是轮询,我自定义为随机
//return new RoundRobinRule();// Ribbon默认是轮询,我自定义为随机

return new RandomRule_ZY();// 我自定义为每台机器5次

}
}
继承AbstractLoadBalancerRule类,重写public Server choose(ILoadBalancer lb, Object key)。

SpringCloud 在CAP理论是选择了AP的,在Ribbon中还可以配置重试机制的

雪崩及避免

雪崩定义

在高并发的情况下,由于单个服务的延迟,可能导致所有的请求都处于延迟状态,甚至在几秒钟就使服务处于负载饱和的状态,资源耗尽,直到不可用,最终导致这个分布式系统都不可用,这就是“雪崩”。

Hystrix

实现了断路器、线程隔离等一系列服务保护功能。

Fallback(失败快速返回):当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝), 向调用方返回一个错误响应, 而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

资源/依赖隔离(线程池隔离):它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响, 而不会拖慢其他的依赖服务。

Hystrix提供几个熔断关键参数:滑动窗口大小(20)、 熔断器开关间隔(5s)、错误率(50%)

每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。

直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。

img

整合Ribbon和Hystrix

Feign

基于 Netflix Feign 实现,整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix, 除了整合这两者的强大功能之外,它还提 供了声明式的服务调用(不再通过RestTemplate)

服务绑定:

// value —>指定调用哪个服务
// fallbackFactory—>熔断器的降级提示
@FeignClient(value = “MICROSERVICECLOUD-DEPT”, fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {


// 采用Feign可以使用SpringMVC的注解来对服务进行绑定!
@RequestMapping(value = “/dept/get/{id}”, method = RequestMethod.GET)
public Dept get(@PathVariable(“id”) long id);

@RequestMapping(value = “/dept/list”, method = RequestMethod.GET)
public List list();

@RequestMapping(value = “/dept/add”, method = RequestMethod.POST)
public boolean add(Dept dept);
}
Feign中使用熔断器:

/**

  • Feign中使用断路器
  • 这里主要是处理异常出错的情况(降级/熔断时服务不可用,fallback就会找到这里来)
    */
    @Component // 不要忘记添加,不要忘记添加
    public class DeptClientServiceFallbackFactory implements FallbackFactory {
    @Override
    public DeptClientService create(Throwable throwable) {
    return new DeptClientService() {
    @Override
    public Dept get(long id) {
    return new Dept().setDeptno(id).setDname(“该ID:” + id + “没有没有对应的信息,Consumer客户端提供的降级信息,此刻服务Provider已经关闭”)
    .setDb_source(“no this database in MySQL”);
    }

    @Override
    public List list() {
    return null;
    }

    @Override
    public boolean add(Dept dept) {
    return false;
    }
    };
    }
    }
    服务调用:

img

Zuul

在集合了Eureka、Ribbon、Hystrix之后的服务架构如图

img

这样的架构会有两个比较麻烦的问题:

路由规则与服务实例的维护间题:外层的负载均衡(nginx)需要维护所有的服务实例清单(图上的OpenService)

签名校验、 登录校验冗余问题:为了保证对外服务的安全性, 我们在服务端实现的微服务接口,往往都会有一定的权限校验机制,但我们的服务是独立的,我们不得不在这些应用中都实现这样一套校验逻辑,这就会造成校验逻辑的冗余。

img

每个服务都有自己的IP地址,Nginx想要正确请求转发到服务上,就必须维护着每个服务实例的地址!

购物车和订单模块都需要用户登录了才可以正常访问,基于现在的架构,只能在购物车和订单模块都编写校验逻辑,这无疑是冗余的代码。

为了解决上面这些常见的架构问题,API网关的概念应运而生。在SpringCloud中了提供了基于Netfl ix Zuul实现的API网关组件Spring Cloud Zuul。

Spring Cloud Zuul是这样解决上述两个问题的:

SpringCloud Zuul通过与SpringCloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。外层调用都必须通过API网关,使得将维护服务实例的工作交给了服务治理框架自动完成。

在API网关服务上进行统一调用来对微服务接口做前置过滤,以实现对微服务接口的拦截和校验。

Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。也就是说:Zuul也是支持Hystrix和Ribbon。

img

zuul是对外暴露的唯一接口相当于路由的是controller的请求,而Ribbonhe和Fegin路由了service的请求

zuul做最外层请求的负载均衡 ,而Ribbon和Fegin做的是系统内部各个微服务的service的调用的负载均衡

Config

Spring Cloud Config项目是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分,server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client通过接口获取数据、并依据此数据初始化自己的应用。

简单来说,使用Spring Cloud Config就是将配置文件放到统一的位置管理(比如GitHub),客户端通过接口去获取这些配置文件。

在GitHub上修改了某个配置文件,应用加载的就是修改后的配置文件。

img

总结

img

Hadoop(暂略)
分布式存储框架

Dubbo(暂略)
Redis
为什么用缓存

速度快,完全基于内存,使用C语言实现,网络层使用epoll解决高并发问题,单线程模型避免了不必要的上下文切换及竞争条件

redis数据类型

String(字符串)

List(列表)

Hash(字典)

Set(集合)

Sorted Set(有序集合)

redis单线程模型及为什么不需要多线程(6.0后又引入了多线程)

使用单线程模型的原因

通常瓶颈不在 CPU,而是在内存和网络IO;

多线程会带来线程不安全的情况;

多线程可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗;

单线程降低了Redis内部实现复杂度;

hash的惰性rehash,lpush等线程不安全的命令可以无锁执行;

引入多线程的原因

因为读写网络的 Read/Write 系统调用在 Redis 执行期间占用了大部分 CPU 时间,如果把网络读写做成多线程的方式对性能会有很大提升。

Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。

持久化策略

redis的持久化方式有俩种,持久化策略有4种:

RDB(数据快照模式),定期存储,保存的是数据本身,存储文件是紧凑的
AOF(追加模式),每次修改数据时,同步到硬盘(写操作日志),保存的是数据的变更记录
如果只希望数据保存在内存中的话,俩种策略都可以关闭
也可以同时开启俩种策略,当Redis重启时,AOF文件会用于重建原始数据

数据过期淘汰策略

设置过期时间

expire key time(以秒为单位)–这是最常用的方式

setex(String key, int seconds, String value)–字符串独有的方式

注意:

除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置时间

如果没有设置时间,那缓存就是永不过期

如果设置了过期时间,之后又想让缓存永不过期,使用persist key

策略一、被动删除(惰性删除)

当读写一个已过期的key就会触发此策略

策略二、主动删除

定期主动淘汰一批已过期的key

策略三、maxmemory

当前已用内存超过maxmemory限定时,触发主动清理策略

内存淘汰机制

没有配置时,默认为no-eviction

名称 描述
volatile-lru 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-lfu 从已设置过期时间的数据集中挑选最不经常使用的数据淘汰
volatile-ttl 从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random 从已设置过期时间的数据集中挑选任意数据淘汰
allkeys-lru 当内存不足写入新数据时淘汰最近最少使用的Key
allkeys-random 当内存不足写入新数据时随机选择key淘汰
allkeys-lfu 当内存不足写入新数据时移除最不经常使用的Key
no-eviction 当内存不足写入新数据时,写入操作会报错,同时不删除数据
volatile为前缀的策略都是从已过期的数据集中进行淘汰。
allkeys为前缀的策略都是面向所有key进行淘汰。
LRU(least recently used)最近最少用到的。
LFU(Least Frequently Used)最不常用的。
它们的触发条件都是Redis使用的内存达到阈值时。

缓存穿透 缓存雪崩 如何避免

缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免?
1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
如何避免?
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

如何保证redis-数据库数据一致性

更新的时候,先删除缓存,然后再更新数据库。

读的时候,先读缓存;如果没有的话,就读数据库,同时将数据放入缓存,并返回响应。

主从结构如何保证(主机-从机)数据一致性

从节点第一次进行连接时,主节点会生成 RDB 文件进行全量复制,同时将新写入的命令存储进缓冲区,发送给从节点,从而保证数据一致性;

为了减少数据同步给主节点带来的压力,可以通过从节点级联的方式进行同步。

网络断连重新连接后,主从节点通过分别维护的偏移量来同步写命令。

主机坏了如何选出新的主机

第一部分:sentinelRedisInstance *slave =sentinelSelectSlave(ri); 函数sentinelSelectSlave,这就真正执行从从库(slave)中选举新主库(master)的函数。 这个函数后面分析。
第二部分if (slave == NULL),也就是没有找到符合条件的从库,则日志中就会输出-failover-abort-no-good-slave 的信息,然后切换执行sentinelAbortFailover(ri);也就是切换中断。

消息队列
有哪些常见的消息队列?用过哪些?

RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq
用过kafka
队列模型和发布/订阅模型的区别?

点对点就不必多说了
发布订阅的特点是只有订阅了的才能收取
如何保证消息的有序性?

写 N 个内存 queue,然后对于 N 个线程,每个线程分别消费一个内存 queue 即可
如何保证消息不丢失?

消息持久化
ACK确认机制
如何不重复消费消息?也就是消息消费的幂等性

比较直观的方法就是利用hashset的幂等性,例如使用redis

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值