程序员互联网科技

阿里Java架构师浅谈Java微服务架构设计

2018-08-30  本文已影响8人  Java高级架构狮

微服务架构模式(Microservice Architect Pattern)。近两年在服务的疯狂增长与云计算技术的进步,让微服务架构受到重点关注

微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。

微服务架构优势

首先简单介绍了微服务(Microservices)的内涵及优势,微服务架构的本质,是用一些功能比较明确、业务比较精练的服务去解决更大、更实际的问题。微服务架构将服务拆分,分别采用相对独立的服务对各方面进行管理,彼此之间使用统一的接口来进行交流,架构变得复杂,优势也很明显:

复杂度可控:在将应用分解的同时,规避了原本复杂度无止境的积累。每一个微服务专注于单一功能,并通过定义良好的接口清晰表述服务边界。由于体积小、复杂度低,每个微服务可由一个小规模开发团队完全掌控,易于保持高可维护性和开发效率。

大家可以点击加入群:【Java高级架构进阶群】:854180697 里面有Java高级大牛直播讲解知识点 走的就是高端路线,(如果你想跳槽换工作 但是技术又不够 或者工作上遇到了瓶颈,我这里有一个JAVA的免费直播课程,讲的是高端的知识点基础不好的误入哟,只要你有1-5年的开发经验可以加群找我要课堂链接,注意:是免费的 没有开发经验误入哦)

微服务架构

独立部署:由于微服务具备独立的运行进程,所以每个微服务也可以独立部署。当某个微服务发生变更时无需编译、部署整个应用。由微服务组成的应用相当于具备一系列可并行的发布流程,使得发布更加高效,同时降低对生产环境所造成的风险,最终缩短应用交付周期。

技术选型灵活:微服务架构下,技术选型是去中心化的。每个团队可以根据自身服务的需求和行业发展的现状,自由选择最适合的技术栈。由于每个微服务相对简单,当需要对技术栈进行升级时所面临的风险较低,甚至完全重构一个微服务也是可行的。

容错:当某一组建发生故障时,在单一进程的传统架构下,故障很有可能在进程内扩散,形成应用全局性的不可用。在微服务架构下,故障会被隔离在单个服务中。若设计良好,其他服务可通过重试、平稳退化等机制实现应用层面的容错。

扩展:单块架构应用也可以实现横向扩展,就是将整个应用完整的复制到不同的节点。当应用的不同组件在扩展需求上存在差异时,微服务架构便体现出其灵活性,因为每个服务可以根据实际需求独立进行扩展。

互联网高并发相关名词

页面浏览数(page views )

唯一身份浏览量(Unique PageViews)

独立访问者数量(unique visitors)

重复访问者数量(repeat visitors)

每个访问者的页面浏览数(Page Views per user)

高并发

之前我将高并发的解决方法误认为是线程或者是队列可以解决,因为高并发的时候是有很多用户在访问,导致出现系统数据不正确、丢失数据现象,所以想到 的是用队列解决,其实队列解决的方式也可以处理,比如我们在竞拍商品、转发评论微博或者是秒杀商品等,同一时间访问量特别大,队列在此起到特别的作用,将 所有请求放入队列,以毫秒计时单位,有序的进行,从而不会出现数据丢失系统数据不正确的情况。

经过查资料,高并发的解决方法有俩种,一种是使用缓存、另一种是使用生成静态页面;还有就是从最基础的地方优化我们写代码减少不必要的资源浪费:(

1.不要频繁的new对象,对于在整个应用中只需要存在一个实例的类使用单例模式.对于String的连接操作,使用StringBuffer或者StringBuilder.对于utility类型的类通过静态方法来访问。

避免使用错误的方式,如Exception可以控制方法推出,但是Exception要保留stacktrace消耗性能,除非必要不要使用 instanceof做条件判断,尽量使用比的条件判断方式.使用JAVA中效率高的类,比如ArrayList比Vector性能好。)

高并发 - 需要解决的问题

应用缓存

HTTP缓存

多级缓存

池化

异步并发

扩容

队列

高并发-应用缓存

堆缓存:使用Java堆内存来存储缓存对象。使用堆缓存的好处是没有序列化/反序列化,是最快的缓存。缺点也很明显,当缓存的数据量很大时,GC(垃圾回收)暂停时间会变长,存储容量受限于堆空间大小。一般通过软引用/弱引用来存储缓存对象,即当堆内存不足时,可以强制回收这部分内存释放堆内存空间。一般使用堆缓存存储较热的数据。

Guava Cache: 缓存和ConcurrentMap是非常相像的,但是它们也不完全一样。最根本的区别就是,ConcurrentMap会持有所有添加的对象,直到被显示的移除。而缓存为了限制其内存的使用,通常都会配置成可以自动的将对象移除。在某些情况下即使不自动移除对象也是非常有用的,如LoadingCache它会自动加载缓存对象。

Ehcache 3.x:是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。

MapDB: mapdb是一个内嵌的纯java的数据库,提供了并发的HashMap、TreeMap、Queue,可以基于堆外或者磁盘来存储数据

高并发-应用缓存

堆外缓存:即缓存数据存储在堆外内存,可以减少GC暂停时间(堆对象转移到堆外,GC扫描和移动的对象变少),但是,读取数据时需要序列化/反序列化,因此会比堆缓存要慢很多。有Ehcache 3.x、MapDB实现

磁盘缓存:即缓存数据存储在磁道上,在JVM重启时数据还存在的,而堆缓存/堆外缓存数据会丢失,需要重新加载。有Ehcache 3.x、MapDB实现

分布式缓存:进程内缓存和磁盘缓存,在多JVM实例的情况下,会存在三个问题:

1、单机容量问题;

2、数据一致性问题(多台JVM实例的缓存数据不一致怎么办?),这个问题不用纠结,既然数据允许缓存,则表示允许一定时间内的不一致,因此可以设置缓存数据的过期时间来定期更新数据;

3、缓存不命中时,需要回源到DB/服务请求多变问题:每个实例在缓存不命中的情况下都会回源到DB加载数据,因此多实例后DB整体的访问量变多了解决办法是可以使用如一致性哈希分片算法。因此,这些情况可以考虑使用分布式缓存来解决。

可以使用ehcache –clustered(配合 Terracotta server) 实现JAVA进程间分布式缓存。最好的办法是使用redis实现分布式缓存。

高并发- HTTP缓存

浏览器缓存是指当我们使用浏览器访问一些网站页面或者http服务时,根据服务端返回的缓存设置响应头将响应内容缓存到浏览器,下次可以直接使用缓存内容或者仅需要去服务端验证内容是否过期即可。这样的好处可以减少浏览器和服务端之间来回传输的数据量,节省带宽提升性能。

解决办法:内容不需要动态(计算、渲染等)速度更快,内容越接近于用户速度越快。像apache traffic server、squid、varnish、nginx等技术都可以来进行内容缓存。

还有CDN就是用来加速用户访问的:

即用户首先访问到全国各地的CDN节点(使用如ATS、Squid实现),如果CDN没命中,会回源到中央nginx集群,该集群如果没有命中缓存(该集群的缓存不是必须的,要根据实际命中情况等决定),最后回源到后端应用集群。

高并发- 多级缓存(分布式缓存)

高并发-池化

在应用系统开发过程中,我们经常会用到池化技术,如对象池、连接池、线程池等,通过池化来减少一些消耗,以提升性能。

对象池通过复用对象从而减少创建对象、垃圾回收 的开销。但是,池化不能太大,太大会影响GC时的扫描时间。

连接池如数据库连接池、Redis连接池、Http连接池,通过复用TCP连接减少创建和释放连接的时间来提升性能。

线程池也是类似的,通过复用线程提升性能。也就是说池化的目的就是通过复用技术提升性能。

高并发-扩容

1、读写分离:当数据库访问量还不是很大的时候,我们可以适当增加服务器,数据库主从复制的方式将读写分离

2、垂直分区:当写入操作一旦增加的时候,那么主从数据库将花更多的时间的放在数据同步上,这个时候服务器也是不堪重负的;那么就有了数据的垂直分区,数据的垂直分区思路是将写入操作比较频繁的数据表,如用户表_user,或者订单表_orders,那么我们就可以把这个两个表分离出来,放在不同的服务器,如果这两个表和其他表存在联表查询,那么就只能把原来的sql语句给拆分了,先查询一个表,在查询另一个,虽然说这个会消耗更过性能,但比起那种大量数据同步,负担还是减轻了不少;

3、水平分区:但是往往事情不尽人意,可能采取垂直分区能撑一段时间,由于网站太火了,访问量又每日100w,一下子蹦到了1000w,这个时候可以采取数据的进行分离,我们可以根据user的Id不同进行分配,如采取%2、 形式,当然这种形式对以后的扩展有了很大的限制,当我由10个分区增加到20个的时候,所有的数据都得重新分区,那么将是一个的很庞大的计算量;几种常见的算法: 哈希算法:就是采用user_id%的方式; 范围:可以根据user_id字符值范围分区,如1-1000为一区,1001-2000则是另一个区等; 映射关系:就是将user_id存在的所对应的分区放在数据库中保存,当用户操作时先去查询所在分区,再进行操作。

写在最后:欢迎留言讨论,加关注,持续更新!!!

上一篇下一篇

猜你喜欢

热点阅读