分布式系统

本文介绍了云原生的概念及其技术体系,探讨了Java本地缓存的实现与优化,包括缓存策略如FIFO、LFU和LRU。同时,文章详细阐述了JVM的工作原理,以及从非高可用架构到高可用架构的演进,涉及负载均衡、应用层缓存、同步转异步处理等技术。最后,讨论了数据库架构设计的优化方法。
摘要由CSDN通过智能技术生成

云原生:

云原生是基于分布部署和统一运管的浪潮分布式云 ,以容器、微服务、DevOps等技术为基础建立的一套云技术产品体系。
在这里插入图片描述

Java本地缓存

在java应用中,对于访问频率比较高,又不怎么变化的数据,常用的解决方案是把这些数据加入缓存。相比DB,缓存的读取效率快好不少。java应用缓存一般分两种,一是进程内缓存,就是使用java应用虚拟机内存的缓存;另一个是进程外缓存,现在我们常用的各种分布式缓存。相比较而言,进程内缓存比进程外缓存快很多,而且编码也简单;但是,进程内缓存的存储量有限,使用的是java应用虚拟机的内存,而且每个应用都要存储一份,有一定的资源浪费。进程外缓存相比进程内缓存,会慢些,但是,存储空间可以横向扩展,不受限制。
这里是几中场景的访问时间:

| 从数据库中读取一条数据(有索引) | 十几毫秒 |
| 从远程分布式缓存读取一条数据 | 0.5毫秒 |
| 从内存中读取1MB数据 | 十几微妙 |
对于数据量不大的,我们可以采用进程内缓存
本地缓存的实现:
实现本地缓存,存储容器肯定是key/vale形式的数据结构,在Java中,也就是我们常用的Map集合。Map中有HashMap、Hashtable、ConcurrentHashMap几种供我们选择,考虑高并发情况下数据安全问题,我们优先选择ConcurrentHashMap,因为ConcurrentHashMap的性能比Hashtable要好。
本地缓存直接存储在内存中,如果不处理过期缓存,内存将被大量无效缓存占用,过期缓存处理可以参考Redis的策略来实现,Redis采用的是定期删除+懒惰淘汰策略。
缓存淘汰策略:
先进先出策略
最先进入缓存的数据在缓存空间不够的情况下会被优先被清除掉,以腾出新的空间接受新的数据。该策略主要比较缓存元素的创建时间。在一些对数据实效性要求比较高的场景下,可考虑选择该类策略,优先保障最新数据可用。
最少使用策略
无论是否过期,根据元素的被使用次数判断,清除使用次数较少的元素释放空间。该策略主要比较元素的hitCount(命中次数),在保证高频数据有效性场景下,可选择这类策略。
最近最少使用策略
无论是否过期,根据元素最后一次被使用的时间戳,清除最远使用时间戳的元素释放空间。该策略主要比较缓存最近一次被get使用时间。在热点数据场景下较适用,优先保证热点数据的有效性。
缓存实现代码
缓存对象类

public class Cache implements Comparable<Cache>{
  // 键
  private Object key;
  // 缓存值
  private Object value;
  // 最后一次访问时间
  private long accessTime;
  // 创建时间
  private long writeTime;
  // 存活时间
  private long expireTime;
  // 命中次数
  private Integer hitCount;
  ...getter/setter()...

添加缓存

/**
 * 添加缓存
 *
 * @param key
 * @param value
 */
public void put(K key, V value,long expire) {
  checkNotNull(key);
  checkNotNull(value);
  // 当缓存存在时,更新缓存
  if (concurrentHashMap.containsKey(key)){
    Cache cache = concurrentHashMap.get(key);
    cache.setHitCount(cache.getHitCount()+1);
    cache.setWriteTime(System.currentTimeMillis());
    cache.setAccessTime(System.currentTimeMillis());
    cache.setExpireTime(expire);
    cache.setValue(value);
    return;
  }
  // 已经达到最大缓存
  if (isFull()) {
    Object kickedKey = getKickedKey();
    if (kickedKey !=null){
      // 移除最少使用的缓存
      concurrentHashMap.remove(kickedKey);
    }else {
      return;
    }
  }
  Cache cache = new Cache();
  cache.setKey(key);
  cache.setValue(value);
  cache.setWriteTime(System.currentTimeMillis());
  cache.setAccessTime(System.currentTimeMillis());
  cache.setHitCount(1);
  cache.setExpireTime(expire);
  concurrentHashMap.put(key, cache);
}

获取缓存

/**
 * 获取缓存
 *
 * @param key
 * @return
 */
public Object get(K key) {
  checkNotNull(key);
  if (concurrentHashMap.isEmpty()) return null;
  if (!concurrentHashMap.containsKey(key)) return null;
  Cache cache = concurrentHashMap.get(key);
  if (cache == null) return null;
  cache.setHitCount(cache.getHitCount()+1);
  cache.setAccessTime(System.currentTimeMillis());
  return cache.getValue();
}

获取最少使用的缓存

  /**
   * 获取最少使用的缓存
   * @return
   */
  private Object getKickedKey() {
    Cache min = Collections.min(concurrentHashMap.values());
    return min.getKey();
  }

过期缓存检测方法

/**
 * 处理过期缓存
 */
class TimeoutTimerThread implements Runnable {
  public void run() {
    while (true) {
      try {
        TimeUnit.SECONDS.sleep(60);
        expireCache();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * 创建多久后,缓存失效
   *
   * @throws Exception
   */
  private void expireCache() throws Exception {
    System.out.println("检测缓存是否过期缓存");
    for (Object key : concurrentHashMap.keySet()) {
      Cache cache = concurrentHashMap.get(key);
      long timoutTime = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime()
          - cache.getWriteTime());
      if (cache.getExpireTime() > timoutTime) {
        continue;
      }
      System.out.println(" 清除过期缓存 : " + key);
      //清除过期缓存
      concurrentHashMap.remove(key);
    }
  }
}

JVM

JVM是Java虚拟机(Java Virtual Machine 简称JVM)的缩写,它是一个虚构出来的计算机,是运行所有Java程序的抽象计算机,是Java语言的运行环境。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。
在这里插入图片描述类装载器(ClassLoader)(用来装载.class文件)
执行引擎(执行字节码,或者执行本地方法)
运行时数据区(方法区、堆、java栈、PC寄存器、本地方法栈)
Java代码编译和执行
开发人员编写Java代码(.java文件),然后将之编译成字节码(.class)文件,再然后字节码被装入JVM内存,它就会被解释器解释执行,解释执行即为目标代码生成并执行。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。
(1)Java代码编译是由Java源码编译器来完成,也就是Java代码到JVM字节码(.class文件)的过程。
(2)Java字节码的执行是由JVM执行引擎来完成
编译:
创建完.java源文件后,程序先要被JVM中的java编译器进行编译为.class文件,java编译一个类时,若这个类所依赖的类还没有被编译,编译器会自动的先编译这个所依赖的类,然后引用;若java编译器在指定的目录下找不到该类所依赖的类的 .class文件或者 .java源文件,就会报"Can’t found sysbol"的异常错误。
编译后的字节码文件格式主要分为两部分:常量池和方法字节码。
  常量池记录的是代码出现过的字面量(文本字符串、八种基本类型的值、被声明为final的常量等)以及符号引用(类和方法的全限定名、字段的名称和描述符、方法的名称和描述符);
  方法字节码中放的是各个方法的字节码(依赖操作数栈和局部变量表,由JVM解释执行)

执行:
类的加载:
JVM的类加载是通过ClassLoader及其子类来完成的
JVM主要在程序第一次运行时主动使用类的时候,才会立即去加载,加载完毕就会生成一个java.lang.Class对象,并且存放在方法区。换言之,JVM并不是在运行时就会把所有使用到的类都加载到内存中,而是用到,不得不加载的时候,才加载进来,而且只加载一次
类的执行:
JVM是基于堆栈的虚拟机。JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
JVM执行class字节码,线程创建后,都会产生程序计数器(PC)和栈(Stack),程序计数器存放下一条要执行的指令在方法内的偏移量,栈中存放一个个栈帧,每个栈帧对应着每个方法的每次调用,而栈帧又是有局部变量区和操作数栈两部分组成,局部变量区用于存放方法中的局部变量和参数,操作数栈中用于存放方法执行过程中产生的中间结果。

JRE/JDK/JVM是什么关系?
JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。
JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是 安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。
JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。

JVM调优
在这里插入图片描述参考:https://blog.csdn.net/qq_41701956/article/details/81664921

分布式系统

分布式系统时由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现时为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据
当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。
分布式系统分为分布式计算与分布式存储。计算与存储是相辅相成的,计算需要数据,要么来自实时数据(流数据),要么来自存储的数据;而计算的结果也是需要存储的。
在这里插入图片描述

https://www.cnblogs.com/xybaby/p/7787034.html

从非高可用架构到高可用架构

非高可用(一台ECS或数据库,单点故障,宕机对应用产生影响)
在这里插入图片描述这样部署还有个风险,应用服务器放在同一个机房,如果机房断电或者网络挖断光缆之类的,那么整个应用访问也会出现问题。
服务器可以部署在多个可用区
同城灾备:一个城市的两个区域,有2个机房
当可用区A出现问题,LSB会无缝切换到可用区B,通常部署成双可用区(应用服务器、RDS,RDS自带容灾数据库)
在这里插入图片描述提高应用并发度
1、应用层横向扩展
2、使用应用层缓存
3、同步转异步
4、数据库读写分离及提高IO能力

应用层横向扩展-负载均衡

web服务器和应用服务器之间搭建一个负载均衡服务器,负责均衡服务器负责分发请求到应用服务器。负载均衡服务器能提供4层(TCP/IP协议)和7层(HTTP协议)的负载均衡进行流量分发
在这里插入图片描述

大促、某一段时间高峰,能够自动调整扩容机器资源
弹性伸缩的原理是:把部署好的资源做成镜象,负载均衡感知到新的流量,然后把镜象资源配置到新的机器上
在这里插入图片描述

应用层缓存提升性能

应用的瓶颈通常会发生在应用层和数据库层之间,使用应用缓存来提升应用并发度

在这里插入图片描述

比如用户的基本信息就是一个热点数据,当用户登录网站时,就会第一次从数据库取出放入缓存,不经常变化,后续用户下单、加购等操作用到用户数据就可以直接从缓存中取出,这样就可以大大加快应用访问速度。
缓存应用场景:
1、为数据库减负并增加并发度
我们会把常用的信息从数据库中取出,放入在缓存服务器中,应用在下一次访问时,就不需要从数据库再取出,这样就大大缓解性能瓶颈问题
2、临时数据存储
我们通常会把应用会话信息,http session放在应用服务器中,会有一个问题:当应用服务器出现故障时,会话信息会丢失,导致用户需要重新登录,所以在现在很多应用设计中,会话信息类似session会存放到集中的缓存服务器,任何一台应用服务器出现问题,都可以随时移除掉。
Redis数据存储在磁盘上。

同步转异步处理

可以大大加快应用访问速度和提高并发度
在这里插入图片描述解决方案是使用同步转异步方案
在这里插入图片描述

创建订单后,如果使用同步,那么支付、扣减库存、创建物流订单这些一连串做完动作返回给用户创建订单成功,可能需要几百毫秒
比如支付环节很重要,一定要告诉用户是支付成功还是支付失败,那么这个环节可以做成同步,其它的比如扣减库存、生成物流可以做成异步方式,这样每个请求过来处理速度会很快,可能几十毫秒,这样就提高了应用的响应速度和并发度。
创建订单后,将订单信息放入消息队列(MQ)中,支付模块、物流模块可以从消息队列中获取订单信息,做后续的操作。
在这里插入图片描述

数据库架构设计优化

最难进行横向扩展的是数据库,因为要保证ACID,提高数据库响应能力,主要是用读写分离
数据库操作分类:
读:select
写:insert delete update
写操作一般并发度不高,为了数据隔离性、一致性,要对数据进行加锁,避免产生脏数据,一旦锁级别比较高,会影响到读操作;只有写操作完成后才能进行读操作。由于MySQL认为写请求一般比读请求要重要,所以如果有读写请求同时进行的话,MYSQL将会优先执行写操作。这样MyISql表在进行大量的更新操作时(特别是更新的字段中存在索引的情况下),会造成查询操作很难获得读锁,从而导致查询阻塞。比如库存由100扣减为95,扣减时,其它应用读时需要等库存扣减完成才能进行,如果读写操作在一个数据库上执行,那么写操作会影响到读操作效率,当一个主库有大量写操作时,读操作效率就无法提升上去。
将主库数据复制到只读库,写操作转发到主库上进行操作。复制技术要保证延迟低,主要通过数据库的同步日志binlog实现,大量查询转发到只读库上,这样降低主库压力,整个应用处理速度会加快。通过只读库可以提升应用查询效率。
在这里插入图片描述

zk
dubbo
mq
分布式链路追踪skywalking
分布式配置中心applo

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值