服务系统设计原则

 

1. 服务系统设计原则

1.1 高并发原则

1. 无状态:设计的应用是无状态的,那么应用容易水平扩展,生产中常见的是应用无状态,配置文件有状态,例如不同机房读取不同的数据源,这就需要配置文件或配置中心指定。

2. 拆分:垂直拆分、水平拆分、读写服务拆分、功能维度拆分。

3. 服务化:基础服务模块化,引进服务框架,服务注册与发现。服务的分组与隔离,服务限流、黑白名单等等。

4. 消息队列:解耦不同应用、异步处理、流量削峰等。要注意处理消息失败和重复接收的问题。对不能忍受失败的场景,要做好数据持久化工作,同时增加日志,报警等。

5. 缓存使用:浏览器缓存、客户端缓存、CDN、Nginx缓存、应用曾缓存、分布式缓存等。

1.2 高可用原则

1. 降级:开关集中化配置、可降级的多级读服务、开关前置化、业务降级。

2. 限流:恶意请求只到cache、恶意IP进行屏蔽。

3. 切流量:DNS、LVS/Nginx层切换。

2. 高可用

2.1 负载均衡与反向代理

1. 外网DNS用来实现全局负载均衡的流量调度。为提高Nginx性能,一般在DNS和Nginx之间接入LVS/F5做4层负载均衡。首先DNS解析请求到LVS/F5,然后LVS/F5转发给Nginx,再由Nginx转发给后端应用服务器。

2. 负载均衡算法:

1. round-robin(轮询):默认负载均衡算法,即以轮询方式将请求发给应用服务器。

2. ip_hash:根据客户IP进行负载均衡,即同一个IP的请求发给相同的服务器。

3. hash_key:对某一个key使用hash或一致性hash算法来进行负载均衡。

注:使用一致性Hash来解决增减节点造成的key重新映射的命中率低的问题。

3. 失败重试:在一定时间内重试多次都失败,则认为服务器不可用/不存活,然后可以通知服务中心将上有服务器摘掉,过一段时间后,再继续将服务器加入到存活服务器列表进行重试。

4. 健康检查:两种方式:TCP心跳检查、HTTP心跳检查。检查间隔时间不能太短、否则可能因为心跳检查太多造成压力过大。

public void main(String[] args){

  SpringApplication.run(Bootstrap.Class,args);

  //服务注册

  agentClient.register();

  //JVM停止时摘除服务

  RunTime.getRunTime().addShutDownHook(new Thread(){

@Override

Public void run(){

  agentClient.deregister(serviceId);

}

})

 

}

3. 隔离

3.1 线程隔离

主要是指线程池隔离,实际中通常是将请求分类,交给不同线程池处理。

3.2 应用隔离

不同应用进行拆分单独部署,这样某个服务出现问题不会影响到其他系统。

3.3 读写隔离

DB和redis集群的读写分离。读服务只从redistribution集群获取数据,当主redis集群出现问题时,从redis集群还可以使用。

3.4 热点隔离

当出现秒杀、抢购等热点活动时,为避免给其他正常服务产生冲击,将这些服务做成独立系统或对服务进行隔离,保证这些活动不会影响到主交易流程。

3.5 servlet隔离

tomcat收到http请求后的处理流程:

1)容器负责接收并解析请求为HttpServletRequest;

2)交给Servlet进行业务处理;

3)最后通过HttpServletResponse写回响应。

在servlet2.x中,上述过程是在同一个线程中处理的,即同步处理方式;在servlet3.x中,变化有一下几点:基于NIO的异步处理能处理更高的并发连接数,请求解析和业务处理线程池分开;根据业务重要性对业务分级,并分级线程池;对线程池进行监控、运维、降级等处理。

请求解析使用单线程,解析完成后放入业务队列时,由业务线程池处理。

4. 限流

限流主要是对访问/请求进行限速,使其在一个时间窗口内的请求数目不会过大,从而来保护系统,一旦达到限制速率则可拒绝服务、排队或等待、降级等。

常见的限流策略有:限制总并发数(如数据库连接池、线程池等)、限制瞬时并发数(如Nginx的limit_conn参数)、限制时间窗口内的平均速率(如Nginx的limit_req参数)、以及限制远程接口的调用速率等等。

4.1 限流算法

常见有令牌桶、漏桶等算法。

1)令牌桶算法:一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。桶中令牌达到最大值则丢弃或暂停添加令牌。每次请求过来则从桶中获取一个令牌,无令牌则排队等候。

2)漏桶算法:允许任意请求进来排队,应用程序按固定速率取请求并处理。当排队请求过多时则拒绝接收请求。

4.2 应用级限流

tomcat连接数配置:acceptCount、maxConnections、maxThreads等参数。

限流稀缺资源(如数据库连接池、线程池)数量。

限制某接口的总并发数、总请求量。可以使用AtomicLong或Semaphore来进行限流。

限流某接口的时间窗请求数:限制某接口每秒/每分钟/每小时的请求量,可以使用Guava的Cache来存储计数,过期时间设置为1分钟、1小时等等。

5. 降级

服务降级通常是通过开关来实现的,开关可以存放在配置文件、数据库、Redis/Zookeeper中,应用可以定期同步开关数据,然后来判断是否需要降级。

读服务降级:读服务降价的策略有:暂时切换读(降级到缓存、页面静态化)、暂时屏蔽读。

写服务降级:通常将同步操作改成异步操作,或限制写操作的请求量/请求比例。

配置中心:通常需要通过配置方式来开启/关闭降级开关,考虑通过配置文件或配置中心来进行配置,实现时要做到不需要重新启动就能动态配置开关。

6. 超时重试

实际中,如果不设置超时,可能导致请求响应过慢,慢请求累积至系统拥塞,甚至造成雪崩。

6.1 代理层超时

Nginx超时:主要有客户端超时、DNS解析超时、代理超时等。客户端超时主要设置有读取请求头超时时间、读取请求体超时时间、发送响应超时时间、长连接超时时间。

6.2 web容器超时

主要是tomcat超时,包括connectTimeout、socket.soTimeout、asyncTimeout等。

6.3 中间件客户端超时与重试

中间件超时设置包括有各个环节的调用超时,如消息中间件的注册中心、borker、生产者、消费者。服务框架的注册中心、服务提供者、服务消费者等。

重试机制可能会产生重复消息,需要消费者进行去重处理。

7. 应用级缓存

7.1 缓存回收:

基于空间:超过设置的存储上限,开始清除数据;

基于容量:缓存条数大于上限值时,开始清除缓存数目;

基于时间:即设置缓存存活期,超过存活期的缓存数据清除;

基于Java对象引用:

软引用:当JVM堆内存不足时,垃圾回收器将回收软引用对象,因此,软引用对象可用来做缓存。

弱引用:当垃圾回收期回收对象时,若发现弱引用,那么会立即将其回收(每次GC都会回收弱引用对象)。

7.2 缓存类型

堆缓存:使用Java堆来存储缓存对象,不需要进行序列化和反序列化,是最快的缓存,缺点是缓存数据大时,GC暂停时间会变得很长,容量受堆大小限制。一般通过软引用/弱引用来存储缓存对象,内存不足时能回收掉这些对象,释放内存空间。可以使用Guava.Cache来实现。

堆外缓存:即缓存数据存放在堆外内存,可以减小GC暂停的时间。读取需要序列化/反序列化。

7.3. 分布式缓存与应用本地热点

分布式缓存一般采用分片实现,即将数据分散到多个实例或多台机器上,算法一般采用取模和一致性hash。

缓存崩溃与修复:一是主从备份,做好冗余机制;二是如果整个缓存机制失效,考虑应用降级,慢慢减小请求量,同时慢慢将缓存重建。让一部分用户先用起来,同时更新缓存数据。另外后台worker需要对缓存数据进行预热。

8. 连接池

你可能感兴趣的:(服务系统设计原则)