专栏首页程序猿 Damon 带你进阶全栈今天被问微服务,这几点,让面试官刮目相看

今天被问微服务,这几点,让面试官刮目相看

油菜花开了,春天来了,金三银四,换新季!

在金三银四的季节,不少人在换工作,也许在面试时,觉得技术交流的都不错,在跟HR交流时,薪资谈得也很得意,但是在离开之后,却一直没有HR联电的下文,似乎已经忘记,甚至当从来没发生过了。这就是面试时,可能认为回答的不错,但是实际已经给对方留下了反面的影响。其实也许是你的技术可以的,但是跟HR谈的薪资可能给不了,也许你的技术还是少了火候,没有与期望薪资对等,尤其在一个二线城市,对薪资那更是。。。你懂的!

今天分享的是微服务的高并发情形,在前面讲过了微服务的理念以及模块的角度划分等。

在微服务高并发下有几点需要考虑:微服务的划分、高并发、数据DB、中间件或缓存问题、IO性能瓶颈问题、监控问题、自动化部署问题等。

一、微服务的划分

微服务的划分:前面说过了,服务的划分,可以从水平的功能划分,也可从垂直的业务划分,粒度的大小,可以根据当前的产品需求来定位,最关键的是要做到:高内聚、低耦合。

听到高内聚、低耦合这六个字,面试官也许会觉得这小伙子不错,有一定的技术设计的基础。那么接下来,问题来了。什么是高内聚,什么是低耦合呢?所谓高内聚:就是说每个服务处于同一个网络或网域下,而且相对于外部,整个的是一个封闭的、安全的盒子,宛如一朵玫瑰花。

盒子对外的接口是不变的,盒子内部各模块之间的接口也是不变的,但是各模块内部的内容可以更改。模块只对外暴露最小限度的接口,避免强依赖关系。增删一个模块,应该只会影响有依赖关系的相关模块,无关的不应该受影响。

所谓低耦合:从小的角度来看,就是要每个Java类之间的耦合性降低,多用接口,利用Java面向对象编程思想的封装、继承、多态,隐藏实现细节。从模块之间来讲,就是要每个模块之间的关系降低,减少冗余、重复、交叉的复杂度,模块功能划分尽可能单一。

二、高并发

一个公司一旦做大了,就需要考虑兼容、扩展、压力等问题。高并发是一个常见的词语。然后如何才能保证高并发,这是一个问题。

高并发从以下几个方面来讲:

1. 幂等性

2. 接口代码的规范性

3. 操作 DB 的性能

4. 读写分离操作

5. 服务的横向扩展

6. 服务的健壮性(缓存、限流、熔灾)

幂等性:所谓幂等性,就是说一次和多次请求某一个资源时对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意次执行所产生的效果和返回的结果都是一样的。这种场景是一个很有效的实现高并发的情景,设想,用户充值某个会员,在并发情况下,用户由于误操作,或者由于网络、时间等问题导致重试机制的发生时,可能会触发触发多次交易的扣费,这样给用户一个很不好的体验。此时,就需要接口幂等性来解决这类问题。

幂等性解决方案有以下几种:

(1) token机制

(2) 接口逻辑实现幂等性

(3) 数据库层处理实现幂等性

token机制:数据提交时携带token,token放到redis,token有效时间,提交后台后校验token,同时删除token,生成新的token并返回。

接口的幂等性:常见的接口幂等性,是定义接口时,加上参数序列号、来源等,序列号与请求来源联合唯一索引,这样可以有效判断本次请求方与请求的序列号,防止重复的请求。

数据库处理:DB层处理有多种方式,1. 悲观锁,2. 乐观锁,3. 唯一索引、组合唯一索引,4. 分布式锁

悲观锁:所谓悲观锁,是指存在危机意识,事先(查询时)加锁处理,防止事情发生。如:

select * from xxx where id= 1 for update

乐观锁:是指存在乐观心理,只在更新时加锁,乐观锁通常用 version 版本号来控制如:

update xxx set name=#name#,version=version+1 where version=#version#

也可以通过条件限制,这里就使用了组合唯一索引来处理,如:

update xxx set name=#name#,version=version+1 where id=#id# and version=#version#

分布式锁:通过 redis、zookeeper 来设置分布式锁,当插入或更新数据时,获取分布式锁,然后做操作,之后释放锁。

接口的规范性:接口的性能如何,最终还是跟接口的实现逻辑有关,比如代码规范,逻辑实现等,尤其是业务逻辑复杂的情况下,这点需要注意的。

操作DB:对于业务的持久层,用的比较多的就是mybatis、hibernate,还有可能是JPA,无论是哪个,最终都是通过工厂类注入 bean,最后执行 SQL 来操作 DB。所以这里尤为重要的是 SQL 的写法,SQL的优化决定着操作DB的时间以及效果,如果写得不好的话,则会导致死循环,或死锁,或内存溢出。另外测试时,使用真实、规范的数据进行测试,并在测试时不要局限于相同的数据,最后就是并发压测了。

读写分离:当服务足够多,数据足够多时,有可能读与写的占比为:10:1,此时读写应该分离,这样可以有效减少因为读的频繁操作导致的写的性能下降。常见的读写分离的方法有:采用mycat中间件方式、amoeba直接实现读写分离、手动修改mysql操作类直接实现读写分离和随机实现的负载均衡,权限独立分配、mysql-proxy(还是测试版本,时间消耗有点高)。

服务的横向扩展:对于服务的请求越来越多时,此时需要对服务进行多节点部署,这样减少单机带来的服务负载压力。

服务的健壮性:服务的健壮性包括缓存、限流、熔灾。

对于缓存,大家都知道,有常见的许多中间件如:redis、kafka、RabbitMq、zookeeper。对于一些session等常用redis来缓存、共享。对于一些大一点的数据如果嫌弃加载慢,也可以采用缓存机制来解决。

什么叫限流呢?很好理解,就是限制节点的流量,限制服务的请求数。那么如何做到限流呢?常用的限流算法比如有计数器算法、令牌桶算法、漏桶算法。有几种方式:利用 springcloud 组件 zuul 来对请求进行限流,主要是通过谷歌提供的 RateLimiter 结合一些限流算法来限流比较常用。利用 redis 同样可以做限流算法的,甚至可以利用 nginx 直接作计数限流,可以对请求速率进行限制、对每个 ip 连接数量进行限制、对每个服务的连接数量进行限制。如:

#对请求速率进行限制
limit_req_zone $binary_remote_addr zone=req_one:20m rate=12r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
#对每个ip连接数量进行限制
limit_conn_zone $server_name zone=perserver:20m;
#对每个服务的连接数量进行限制
server{
  listen 80;
  location / {
    proxy_pass http://ip:port;
    limit_req zone=req_one burst= 80 nodelay;
    limit_conn addr 20;
  }
}

其实在Springboot2.x中,推出自己的Spring-Cloud-Gateway来作网关,同时Spring-Cloud-Gateway中提供了基于Redis的实现来达到限流的目的。

对于熔灾,或者说熔断,这个在实际的业务当中是很有必要的。比如:用户在某一商城秒杀某一件物品,或在某米商城上抢购某一部手机,在准点抢购时,发现人很多,请求很多,这时,主要是需要有限流机制,同时也需要有熔灾(熔断),给用户留下一个很好的体验的感觉。当用户在点击抢购按钮后,如果当前的请求数很多,需要用户等待,这是需要给一个友好的界面让用户去等待,而不是直接给用户提示请求失败,或者报异常,这样的红色抛出是一个非常不好的事情,用户可能会骂街的,下次也不会逛了。

Spring-Cloud-Gateway作网关时,过滤器时使用 HystrixGatewayFilterFactory 来创建一个 Filter 实现基于 Route 级别的熔断功能。

三、中间件或缓存问题

随着用户的越来愈多,所有的服务压力也会指数型递增,这时候缓存是一个很好的减轻服务压力的方式。这样可以有效缓冲请求对服务的负载压力。常见的缓存可能是Redis、MQ(RabbitMQ、RocketMQ)、Kafka、ZooKeeper等。

Redis 一般主要做 session 或用户信息的缓存,实现多机中 session 的共享。也会用来作分布式锁,在分布式高并发下实现锁的功能,例如实现秒杀、抢单等功能。还会被用作一些订单信息的缓存,防止大量的订单信息被积压而导致服务器的负载很高。总之,Redis 常被用来作为一种缓冲剂使用。

MQ 常见的有 RabbitMQ、RocketMQ、ActiveMQ、Kafka等,以下是各种之间的对比:

特性

ActiveMQ

RabbitMQ

RocketMQ

Kafka

单机吞吐量

万级,吞吐量比RocketMQ和Kafka要低了一个数量级

万级,吞吐量比RocketMQ和Kafka要低了一个数量级

10万级,RocketMQ也是可以支撑高吞吐的一种MQ

10万级别,这是kafka最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景

时效性

ms级

微秒级

ms级

延迟在ms级以内

可用性

高,基于主从架构实现高可用性

高,基于主从架构实现高可用性

非常高,分布式架构

非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用

消息可靠性

有较低的概率丢失数据

经过参数优化配置,可以做到0丢失

经过参数优化配置,消息可以做到0丢失

功能支持

MQ领域的功能极其完备

基于erlang开发,所以并发能力很强,性能极其好,延时很低

MQ功能较为完善,还是分布式的,扩展性好

功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准

ZooKeeper 也是经常会存储海量数据,例如 Hadoop 中,在使用 YARN 作资源调度时,采用 ZooKeeper 来存储海量的状态机状态以及任务的信息(包括历史信息)。

四、IO性能瓶颈问题

每个行业的业务也许不同,但是大部分行业是存在存储的,说到最直接的数据库,其他的包括电商、物流、AI算法等。电商的存储在于页面的数据与后端存储的交互;物流的存储在于物流信息、物品信息的存储;AI算法在于数据集、模型、镜像文件、训练代码等的存储。整的来说,不管是什么存储,只要跟磁盘、硬盘有关系,就会涉及到IO的问题。

对于IO,在阻塞模式下,经常有线程不够用,就算使用线程池复用线程也无济于事;阻塞I/O模式下,会有大量的线程被阻塞,一直在等待数据,这个时候的线程被挂起,只能干等,CPU利用率很低,即导致系统的吞吐量差,内存占用很高,甚至导致内存溢出。如果网络I/O堵塞或者有网络抖动或者网络故障等,线程的阻塞时间可能很长。整个系统也变的不可靠。

什么是NIO?java.nio 是指JDK 1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。NIO核心API:Channel、Buffer、Selector。

Channel:NIO的通道类似于流,但有些区别:1. 通道可以同时进行读写,而流只能读或者只能写,2. 通道可以实现异步读写数据,3. 通道可以从缓冲读数据,也可以写数据到缓冲。

缓存Buffer:缓冲区本质上是一个可以写入数据的内存块,然后可以再次读取,该对象提供了一组方法,可以更轻松地使用内存块,使用缓冲区读取和写入数据通常有这四个步骤:

1. 写数据到缓冲区;

2. 调用buffer.flip()方法;

3. 从缓冲区中读取数据;

4. 调用buffer.clear()或buffer.compat()方法

当向Buffer写入数据时,Buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从 write 模式切到 read 模式,在 read 模式下可以读取之前写入到Buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可再被写入。

Selector:一个组件,可以检测多个NIO channel,看看读或者写事件是否就绪。多个Channel以事件的方式可以注册到同一个Selector,从而可以用一个线程处理多个请求。

当你调用Selector的select()或者 selectNow() 方法时它只会返回有数据读取的SelectableChannel的实例。

另外,就是利用NIO实现大文件的分片处理。

五、监控问题

随着业务规模的不断扩大,面临着服务数量不断增加、线上环境日益复杂、服务依赖错综复杂等运维痛点,服务依赖自动梳理、调用实时追踪、异常明细分析、调用链路追踪、实时容量规划、问题根因分析等基本的运维诉求及解决方案就尤其重要。

监控的目的主要包括性能监控、服务的健壮性、运维管理、问题自动分析、动态扩容等,监控的方式也有很多,比如Springcloud自己提供的Dashboard,实现服务的链路跟踪。

K8S的Dashboard,可以追踪看到每个服务pod的状态以及各项服务的系统指标。除了k8s的基本监控外(pod运行状况、占用内存、cpu)。为了对微服务项目中的各种参数线程池、TPS、QPS、RT、系统负载、thread、mem、class、tomcat、gc、等jvm指标进行监控。可以采用基于 K8S 的 promethus job 对业务的metrics指标采取收集。同时由于 promethus 支持 grafana前端UI界面。

六、自动化部署问题

随着服务的越来越多,服务的运维管理也是一个麻烦事,如果有一套自动化部署的机制,则可以一键触发自动部署所有微服务,那绝壁是很好的一件事。这块可以参考前面发表的文章:微服务自动化部署CI/CD 一文,很详细的介绍了自动化部署的实战。

好了,以上所说的,也许对于面试的你会有一定作用的!

本文分享自微信公众号 - 程序猿Damon(Damon4X),作者:Damon

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Kubernetes 经典命令

    如果想玩玩单机版、集群版 k8s,可参见:基础设施服务k8s快速部署之HA篇,快速助力部署 k8s,还没毕业的都可以部署哟!

    程序猿Damon
  • Spring cloud 之多种方式限流(实战)

    在频繁的网络请求时,服务有时候也会受到很大的压力,尤其是那种网络攻击,非法的。这样的情形有时候需要作一些限制。例如:限制对方的请求,这种限制可以有几个依据:请求...

    程序猿Damon
  • k8s master机器文件系统故障的一次恢复过程

    研发反馈他们那边一套集群有台master文件系统损坏无法开机,他们是三台openstack上的虚机,是虚拟化宿主机故障导致的虚机文件系统损坏。三台机器是mast...

    程序猿Damon
  • 分布式限流

    https://github.com/crossoverJie/distributed-redis-tool

    纯洁的微笑
  • Gof设计模式之原型模式(三)

    模式定义: 复制现有对象实例来创建一个新的实例 模式用途: 例如做发送邮件服务,发送给所有人的短信内容都是基本相同,只有收件人,收件地址不同,...

    用户1257393
  • 洛谷 2458 花费最少覆盖所有点 经典树形动归

    在一棵点有点权的树上,选择一些点,这些点能将所有与它们相连的点覆盖,最终将整棵树上的点全部覆盖,试求最小代价

    用户2965768
  • Python爬虫(十四)_BeautifulSoup4 解析器

    CSS选择器:BeautifulSoup4 和lxml一样,Beautiful Soup也是一个HTML/XML的解析器,主要的功能也是如何解析和提取HTML/...

    用户1174963
  • nginx跨域问题纪录

    之前在公司内部的一后台界面,所有的视频都无法播放,浏览器抓包,从报错信息看是跨域问题导致,这里就简单纪录下

    dogfei
  • MySQL SQL剖析(SQL profile)

        分析SQL执行带来的开销是优化SQL的重要手段。在MySQL数据库中,可以通过配置profiling参数来启用SQL剖析。该参数可以在全局和sessio...

    Leshami
  • JDK1.9-等待唤醒机制

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    cwl_java

扫码关注云+社区

领取腾讯云代金券