高并发40问

高并发设计40问


1: 明星有百万粉丝,发布消息,怎样让粉丝实时感知到消息的发布

2 双十一电商系统,商品的抢购怎样不会出现超卖的情况给

3 春运我们购买买票去12306,会出现页面打不开,怎样进行设计12306系统,要如何保证千万人访问的同时也能支持抢票功能

能力:你有木有高并发设计经验的能力,在系统到达百万级别出现瓶颈的时候,有什么优化的思路
其他知识
1: 消息队列是高并发系统常见的一种组件,明白其中的实质,不能光知道有什么特点,真正的应用到实际的生产环境中,首先解耦是最大的特点,在高并发中,消息中间件是最重要的法宝
2 缓存技术是空间换时间的思想
3 压缩是时间换空间的思想

在互联网技术这
  1. 池化技术——避免创建资源的性能损耗以及有效保护系统
  2. 异步化技术——比如:MQ,系统解藕,削峰填谷 缓存技术——提速大杀器
  3. 多线程技术——充分利用计算机多核的能力
  4. RPC技术——提高开发并行度,系统解藕 分布式技术——提高系统性能和存储容量`

异步和缓存【Scale-up与Scale-out(常用)】(高并发通用方法)

将类似追逐摩尔定律不断提升 CPU 性能的方案叫做 Scale-up(纵向扩展),把类似 CPU 多核心的方案叫做 Scale-out

是一种常见的高并发设计方法,我们在很多文章和演讲中都能听到这个名词,与之共同出现的还有它的反义词:同步。比如分布式服务框架 Dubbo 中有同步方法调用和异步方法调用,IO 模型中有同步 IO 和异步 IO。

思想重点

采用异步方式,后端处理会把请求丢到消息队列中,同时快速响应用户,告诉用户我们正在排队处理,然后释放资源来处理更多的请求,订票请求处理完之后,在通知用户订票成功或者失败

高并发40问_第1张图片

  1. 流量:也就是网站的访问量,用户在访问网站的过程中,产生的数据量的大小
  2. 宽带:我们可以形象地比喻为高速公路的车道,带宽越大,车道也就越宽,网络的带宽被作为衡量网络使用情况的重要指标
    总结:流量就是水流,一个水龙头,可以有无限的水流(流量),但是水龙头的限制了水龙头的大小,限制了流速,即使在流量再多,在相同时间里也不能流出多少水来。–》如果访问量增加,你的网站打开速度就会很慢,对于小型的可以,但是要求打开快的网站就有限制

高并发系统设计的三个原则:1:高可用 2: 高性能 3:可扩展

流量分为两种:

  1. 平时流量
  2. 峰值流量

优化原则:

  1. 性能优化一定不能盲目,一定是问题的向导【紧跟需要
  2. 性能优化遵循 ‘二八原则’,用20%的精力解决80%的性能问题,在优化的时候一定要抓住主要矛盾,优化项目中的瓶颈
  3. 性能优化需要数据支撑:在优化过程中,时刻了解你的优化让响应时间减少了多少,提升了多少吞吐量
  4. 最后,性能优化的过程是持续的,高并发系统一般业务逻辑非常复杂,系统中的性能问题可能会出现多方面因素影响,在做性能优化的过程中,要明确目标,支撑每秒1万次请求吞吐量下响应时间10秒,需要不断的测试,找原因,制定优化方案,直到最后达到目标为止

度量性能指标的响应时间 一般用吞吐量||响应时间来衡量并发和流量

注意:单次的响应时间是木有意义的,你需要知道一段时间是的性能情况是什么样的通常有一下集中进行衡量
概念:执行的任务的响应时间都在10ms,他的吞吐量是在每秒100次,那么我们怎样来优化性能从而提升系统的并发能力?
主要有两种思路: 1:提高系统的处理核心数, 2:另一种是减少单词任务的响应时间: 首先确定系统CPU密集型合适IO密集型的,
优化的方向:

  1. 数据库访问慢,那么就要看是否有锁表的情况,是不是有全表扫描,索引加的是否合适,是否有join操作,需不需要加缓存 ,
  2. 网络的参数是否需要优化,抓包来看是否有大量的超时重传,网卡是否有大量丢包等

作为开发目标更聚焦在降低响应时间这块:

1:采用非阻塞的RPC远程调用 2:将计算密集和io密集的逻辑分开,单独的线程池,调正线程比例压榨单机性能 3:做缓存,io消耗的缓存和计算耗时的缓存(多级缓存,数据压缩降低宽带【水龙头端口大小】)
4:采用享元模式,用好对象池和本地线程池空间,尽量减少对象创建和消耗,提高服用
5: 业务拆分,像状态变化后的外部通知,业务监控,通过es或者solr等副本同步数据,无需在主流程中做的事情都拆掉,走cannel监听表数据变化,推MQ保证最终一致的方式从业务完全解耦
6:fork_join 分而治之的处理大任务,并发编程,采用多线程并发的方式处理业务。
7:数据库配置优化,查询优化。

CPU密集型:常见线程数量一般就是CPU的数量,这样可以防止线程出现上下文的切换

问题:将问题丢该线程池,出现阻塞的问题,这个时候由可能会出现线程池中的核心数量和最大线程数过小,会出现这样的情况

在使用池化的时候【空间换时间的思想】,里面会内置缓存队列,一般不要使用无界队队列,虽然队列不会对任务不进行丢弃,但是队列中的任务是占用内存空间,一旦内存空间被占满,就会频繁的出现FULL GC 这样的情况,造成服不可用,专家有一个排查GC引起的宕机,就是因为系统中的线程池用了无界队列,这样频繁的GC使用内存占满,最终使系统宕机

池化的优点:1:池子的最大值和最小值设置非常重要.有的时候需要根据实际场景进行设置 2:池子中的对象先先进行初始化完成,这叫做池子的预热,比方说使用线程池时就需要预先初始化所有的核心线程。如果池子未经过预热,就有可能在系统在刚开始重启的时候请求会出现请求慢的情况 3:池化技术:核心是一种空间换时间的思想,我们需要时刻的关注空间内存的变化,避免出现内存占满的情况,导致JVM一直GC的出现

线程池中提供了预热线程的方法:ThreadpoolExecutor提供了prestartAllCoreThreads方法可以预先启动核心线程

请求量增加使用主从分离(数据库)【或者主从】

单机部署的Mysql部署在4核8G的服务器上,大概可以支撑500TPS和10000QPS
高并发40问_第2张图片

主从复制原理:
Mysql主从复制主要依赖于binlog,binlog中Mysql中所有变化并以二进制形式保存在磁盘上二进制日志文件。这个过程是异步的,主库上的操作不会等待binlog同步的完成。
复制流程
首先从库在连接到主节点时会创建一个 IO 线程,用以请求主库更新的 binlog,并且把接收到的 binlog 信息写入一个叫做 relay log 的日志文件中,而主库也会创建一个 log dump 线程来发送 binlog 给从库;同时,从库还会创建一个 SQL 线程读取 relay log 中的内容,并且在从库中做回放,最终实现主从的一致性。这是一种比较常见的主从复制方式。

分库分表两中:

垂直拆分(常规操作,将表作为操作对象,直接进行表最小单位操作):也就是将数据库的表拆分到多个不同的数据库中。垂直拆分的原则一般是按照业务类型来拆分,核心思想是专库专用,将业务耦合度比较高的表拆分到单独的库中。举个形象的例子,就是在整理衣服的时候,将羽绒服、毛衣、T 恤分别放在不同的格子里。这样可以解决我在开篇提到的第三个问题:把不同的业务的数据分拆到不同的数据库节点上,这样一旦数据库发生故障时只会影响到某一个模块的功能,不会影响到整体功能,从而实现了数据层面的故障隔离。缺点:因为数据库垂直拆分后依然不能解决某一个业务模块的数据大量膨胀的问题
水平拆分(以表中数据为最小单位,操作表中的数据):和垂直拆分的关注点不同,垂直拆分的关注点在于业务相关性,而水平拆分指的是将单一数据表按照某一种规则拆分到多个数据库和多个数据表中,关注点在数据的特点。拆分规则有下面两种:
1实体表中ID来进行拆分===按照某一个字段的哈希值做拆分,这种拆分规则比较适用于实体表,比如说用户表,内容表,我们一般按照这些实体表的 ID 字段来拆分。比如说我们想把用户表拆分成 16 个库,每个库是 64 张表,那么可以先对用户 ID 做哈希,哈希的目的是将 ID 尽量打散,然后再对 16 取余,这样就得到了分库后的索引值;对 64 取余,就得到了分表后的索引值。
2:按照时间进行分表,比如一个月:月表:. 另一种比较常用的是按照某一个字段的区间来拆分,比较常用的是时间字段。你知道在内容表里面有“创建时间”的字段,而我们也是按照时间来查看一个人发布的内容。我们可能会要看昨天的内容,也可能会看一个月前发布的内容,这时就可以按照创建时间的区间来分库分表,比如说可以把一个月的数据放入一张表中,这样在查询时就可以根据创建时间先定位数据存储在哪个表里面,再按照查询条件来查询。
性能出现问题:所以最合适的思路是你要建立一个昵称和 ID 的映射表,可以使用类似于HashMap的映射表进行关联字典查询

出现查询的时候比如统计count:比方说将计数的数据单独存储在一张表中或者记录在 Redis 里
高并发40问_第3张图片

发号器:如何保证分库分表中ID是唯一的:

1:使用业务作为主键:比如使用用户手机号或者email或者身份证作为主键
2:使用生成的唯一主键(重要)【雪花算法】
主键使用:1:主键有序会提升数据写入的性能,

NoSql:es || redis || hbase ||Mongo等

现在主流中间件与Mysql搭配使用:
目前的项目就是 MySql 配合 Elasticsearch 使用。Elasticsearch主要是做搜索用,写还是 MySql,同时发Kafka消息,消费端写ES。ES存在丢数据的问题,所以会定时全量/增量从 MySql 中捞数据,覆写 ES,保证数据的一直性。

缓存篇【强调命中率,不要什么都加入都缓存中

数据库成为瓶颈,动态数据查询要如何加速

DAU:日活跃用户数量
pv:页面访问量
vu:页面去重用户访问量

垂直电商系统在完成了对数据库的主从分离和分库分表之后,已经可以支撑十几万 DAU

常见缓存:静态缓存、分布式缓存和热点本地缓存

由于本地缓存是部署在应用服务器中,而我们应用服务器通常会部署多台,当数据更新时,我们不能确定哪台服务器本地中了缓存,更新或者删除所有服务器的缓存不是一个好的选择,所以我们通常会等待缓存过期。因此,这种缓存的有效期很短,通常为分钟或者秒级别,以避免返回前端脏数据。

缓存的意义:1: 缓存可以有多层,比如上面提到的静态缓存处在负载均衡层,分布式缓存处在应用层和数据库层之间,本地缓存处在应用层。我们需要将请求尽量挡在上层,因为越往下层,对于并发的承受能力越差;
2:缓存命中率是我们对于缓存最重要的一个监控项,越是热点的数据,缓存的命中率就越高。

在使用缓存的时候考虑这些问题:

实在不行就需要好好考虑一凡了,缓存穿透怎么解决?缓存击穿怎么解决?缓存雪崩怎么解决?数据不一致性问题怎么解决?数据结构众多怎么选择合适的数据结构?缓存的key:value怎么设计?缓存怎么加载?过期时间怎么设置?补偿机制怎么设计?缓存具体选择什么方案?需不需要多层缓存?多层缓存的复杂度怎么控制?

缓存是否使用:没有达到需要引用缓存需要的情况下,尽量不要过早使用缓存。
缓存的坑很多,并且维护成本极高。在处理缓存的适合需要多考虑很多问题。
曾经碰到这样的情况:
调用别人写的查询服务,但是查找到的数据却迟迟无法更新为最新数据。最后,重新写了直接查库的接口,才解决问题。
并且,缓存如果频繁更新,频繁失效 反而会带来性能的消耗。再带上杨晓峰老师的一句话:“过早的优化是万恶之源"

总结:缓存的分类

  • 静态缓存
  • 分布式缓存
  • 热点本地缓存

缓存的不足

  • 适用手读多写入的场景,并且数据最好有一定的热点属性
  • 缓存会使系统更复杂,并且带来数据不一致的风险
  • 缓存通常使用内存作为存储介质,但内存是有限的
  • 缓存会增加运维的成本`

###### memcache是分布式的,guava是本地的===》这个 类似于HashMap映射集合,下次使用的时候直接引入

怎样让缓存显示高可用

高并发40问_第4张图片

缓存命中率:=命中缓存请求数/总请求数 核心缓存维持在99%,哪怕1%有时候也会操守致命打击

在写策略中,能否先删除缓存,后更新数据库呢?答案是不行的

假设某个用户的年龄是 20,请求 A 要更新用户年龄为 21,所以它会删除缓存中的内容。这时,另一个请求 B 要读取这个用户的年龄,它查询缓存发现未命中后,会从数据库中读取到年龄为 20,并且写入到缓存中,然后请求 A 继续更改数据库,将用户的年龄更新为 21,这就造成了缓存和数据库的不一致

CDN :将静态资源分发到位于多个地理位置机房的服务器上,很好解决数据就近访问问题,加快静态资源访问速度

1:如何将用户的请求映射到 CDN 节点上;
2如何根据用户的地理位置信息选择到比较近的节点。

DNS:[域名系统]:存储域名和IP地址对应关系的分布式数据库

域名解析过程:

1:一开始,域名解析请求先会检查本机hosts文件,查看当前域名是否有对应ip

2:木有的话,就请求Local DNS 是否有域名解析结果的缓存

3:如果木有开始迭代DNS查询,先请求跟DNS,跟DNS返回顶级DNS的地址,在请求.com顶级DNS得到baidu.com域名服务器地址

特点总结
1.DNS 技术是 CDN 实现中使用的核心技术,可以将用户的请求映射到 CDN 节点上;
2.DNS 解析结果需要做本地缓存,降低 DNS 解析过程的响应时间;
3.GSLB 可以给用户返回一个离着他更近的节点,加快静态资源的访问速度。

缓存穿透:1:在使用过滤器的时候,要关注布隆过滤器对内存空间的消耗

消息队列:

你可能感兴趣的:(java,java)