超大规模流量热点key读问题解决思路
在程序中使用缓存技术,不仅能够提升系统整体的响应速度,还能在一定程度上有效降低关系数据库的负载压力。尽管对 Redis 进行 Cluster后可以理论上认为容量是可以无限延伸的,并且数据的读/写操作经过了水平化处理,不同的key均会落到不同的缓存节点上,以此避免所有的用户流量都集中落到同一个缓存节点上。但是对于限时抢购场景下的热卖商品来说,由于单价比平时更给力、更具吸引力,那么自然会比平时吸引更大的流量进来,这时同一个key必然会落到同一个缓存节点上,因此分布式缓存在这种情况下一定会出现单点瓶颈。
笔者举一个贴近生活的例子,某明星出轨的消息很容易引爆流量,有可能随着事件的发酵会在接下来的一周内稳坐各大新闻版面头条。单从技术细节上来分析,假设这条消息存储在分布式缓存中,但由于消息是缓存在固定的 Slot 上的,尽管缓存系统单点支撑 10w/s 的 QPS 没有任何压力,但是在短短几分钟内,光是用户留言就达到了上百万条,更别说是阅读这条消息时所产生的流量,因此分布式缓存的单点容量容易瞬间被撑爆,导致资源连接耗尽。为了解决分布式缓存存在的单点瓶颈,我们通常可以通过以下两种 ...
详解Spring循环依赖
一. 从类加载说起Java中的类加载器负载加载来自文件系统、网络或者其他来源的类文件。jvm的类加载器默认使用的是双亲委派模式。三种默认的类加载器Bootstrap ClassLoader、Extension ClassLoader和System ClassLoader(Application ClassLoader)每一个中类加载器都确定了从哪一些位置加载文件。于此同时我们也可以通过继承 java.lang.classloader 实现自己的类加载器。
Bootstrap ClassLoader:负责加载JDK自带的rt.jar包中的类文件,是所有类加载的父类
Extension ClassLoader:负责加载java的扩展类库从jre/lib/ect目录或者java.ext.dirs系统属性指定的目录下加载类,是System ClassLoader的父类加载器
System ClassLoader:负责从classpath环境变量中加载类文件
1.1 双亲委派当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层 ...
详解Spring SPI机制
一. 从类加载说起Java中的类加载器负载加载来自文件系统、网络或者其他来源的类文件。jvm的类加载器默认使用的是双亲委派模式。三种默认的类加载器Bootstrap ClassLoader、Extension ClassLoader和System ClassLoader(Application ClassLoader)每一个中类加载器都确定了从哪一些位置加载文件。于此同时我们也可以通过继承 java.lang.classloader 实现自己的类加载器。
Bootstrap ClassLoader:负责加载JDK自带的rt.jar包中的类文件,是所有类加载的父类
Extension ClassLoader:负责加载java的扩展类库从jre/lib/ect目录或者java.ext.dirs系统属性指定的目录下加载类,是System ClassLoader的父类加载器
System ClassLoader:负责从classpath环境变量中加载类文件
1.1 双亲委派当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层 ...
一文搞懂 Spring Bean 的生命周期
一. 前言在学习Spring框架的IOC、AOP两大功能之前,首先需要了解这两个技术的基础——Bean。在Spring框架中,Bean无处不在,IOC容器管理的对象就是各种各样的Bean。理解Bean的生命周期有助于我们更好的理解和使用Spring框架的IOC功能,也有助于我们理解框架如何初始化、使用和管理Bean。接下来我们通过代码实现观察 BeanFactory 与 ApplicationContext 中bean的生命周期。
二. BeanFactory中Bean的生命周期Bean 的生命周期概括起来就是 4 个阶段:
实例化(Instantiation)
属性赋值(Populate)
初始化(Initialization)
销毁(Destruction)
在四个阶段中,Spring框架会向外暴露多个扩展点,此时业务代码可以根据情况,从不同的扩展点切入影响Bean的默认创建行为。
其中橙色和绿色的是容器级别生命周期接口,也就是所有的Bean初始化时都会发生作用。主要包含两个接口InstantiationAwareBeanPostProcessor、BeanPostProc ...
布隆过滤器在缓存系统中的实践
一. 背景在业务开发中,如果遇到并发量很高的情况下,通常会使用缓存对系统查询性能进行优化,在缓存命中率很高的情况下,缓存的使用能够大幅提升系统查询性能。但是在缓存命中率非常低场景下,如果采用传统缓存读取模式,大部分的请求会穿透至数据库,造成数据库的巨大压力。
例如:最近上线一个“贵族”功能,由于贵族价格比较贵,拥有比较强的特权,该功能也主要面向平台头部大R用户,所以如果采用传统的缓存模式,查询一个用户的贵族信息就会大概率出现缓存无法命中去读库的情况。
有些同学可能会将“空结果”缓存至数据库,这样下次去查询该用户的结果时就会命中缓存。但是由于平台巨大的用户量,如果将所有用户的空结果进行缓存成本是非常高的。
此种场景就非常适合使用布隆过滤器去解决缓存命中率低的问题。
二. 什么是布隆过滤器本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。
相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的, ...
开发中你不得不知的一个Git小技巧
一. 背景在工作中大家应会碰到需要频繁在两个分支中切换工作的情况,我们通常做法是利用git stash命令暂存当前工作区中的变更,然后git checkout到目标分支中工作,工作完成后回到刚刚分支使用git stash pop命令还原历史工作区变动。
整体流程大致如下:
12345678910111213141516171819202122232425262728293031323334# 当前工作分支,存在变更$ worktree-test (dev1) git statusOn branch testChanges not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: bbb.txtno changes added to ...
tcc-transaction源码详解
本文主要介绍TCC的原理,以及从代码的角度上分析如何实现的;不涉及具体使用示例。本文通过分析tcc-transaction源码带大家了解TCC分布式事务的实现原理。
需要注意的是,本文所有代码都基于master-1.7.x分支,不同版本的源码会存在一定的差异。完整代码注释请参考:bigcoder84/tcc-transaction
一. 概述1.1 项目模块本文对 tcc-transaction 源码分析。主要涉及如下四个模块:
tcc-transaction-core:tcc-transaction 底层实现。
tcc-transaction-api:tcc-transaction 使用 API。
tcc-transaction-spring:tcc-transaction 对 Spring 的支持。
tcc-transaction-dubbo:tcc-transaction 对 Dubbo 的支持。
本文基于tcc-transaction 1.7.x版本源码进行分析。
1.2 tcc-transaction中的概念在详细分析框架源码之前,我们先熟悉 tcc-trans ...
详解进程Fork
一. fork函数详解一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
我们来看一个例子:
12345678910111213141516171819#include <unistd.h>#include <stdio.h>int main() { pid_t fpid; //fpid表示fork函数返回的值 int count = 0; fpid = fork(); if (fpid < 0) printf("error in fork!"); else if (fpid == 0) { printf("i am ...
深入理解Docker容器技术
本文参考转载至:《深入剖析Kubernetes - 张磊》
容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。对于 Docker 等大多数 Linux 容器来说,Cgroups 技术是用来制造约束的主要手段,而Namespace 技术则是用来修改进程视图的主要方法。你可能会觉得 Cgroups 和 Namespace 这两个概念很抽象,别担心,接下来我们一起动手实践一下,你就很容易理解这两项技术了。
一. Namespace假设你已经有了一个 Linux 操作系统上的 Docker 项目在运行,比如我的环境是 Ubuntu 16.04和 Docker CE 18.05。
接下来,让我们首先创建一个容器来试试。
12$ docker run -it busybox /bin/sh/ #
这个命令是 Docker 项目最重要的一个操作,即大名鼎鼎的 docker run。而 -it 参数告诉了 Docker 项目在启动容器后,需要给我们分配一个文本输入 / 输出环境,也就是 TTY,跟容器的标准输入相关联,这样我们就可以和这个 Docker ...
synchronized锁升级过程
JDK 1.6后锁的状态总共有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁,这四种锁状态分别代表什么,为什么会有锁升级?
其实在 JDK 1.6之前,synchronized 还是一个重量级锁,底层使用操作系统的 Mutex Lock(互斥锁)实现,而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么重量级锁效率低的原因。
但是在JDK 1.6后,JVM为了提高锁的获取与释放效率对(synchronized )进行了优化,引入了 偏向锁 和 轻量级锁 ,从此以后锁的状态就有了四种(无锁、偏向锁、轻量级锁、重量级锁),并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,也就是说只能进行锁升级(从低级别到高级别),不能锁降级(高级别到低级别),这种设计目的是为了提高获得锁和释放锁的效率。
一. 对象的内存布局要弄清楚加锁过程到底发生了什么需要看一下对象创建之后再内存中的布局是个什么样的?
一个对象在new出来之后在内存中主要分为4个部分:
markword:默认存储对象的HashCode,分代年龄和锁标 ...