利用Redisson快速实现分布式锁--Redisson的介绍与使用

什么是Redisson Redisson 是一个使用 Java编写的Redis 客户端。 其以Redis为基础,构建了一种内存数据网格( in-memory data grid),并提供了redis支持的分布式Java对象和服务。包括超过50中Java对象和若干服务(Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Publish / Subscribe, Bloom filter, Spring Cache, Tomcat, Scheduler, JCache API, Hibernate, MyBatis, RPC, local cache…)。 简单来讲,Redisson可以帮助我们集中精力处理业务,而不必将过多的精力投入到redis的用法上。在项目中我们常常用它来实现redis分布式锁,替代原生redis+lua脚本的方式。 Maven依赖 项目地址https://github.com/redisson/redisson <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.15.5</version> </dependency> 配置 Redis配置 在使用之前,首先得确保redis的安装与使用。在这里不做赘述。 Redisson支持如下的redis配置方式: 单机模式 主从模式 哨兵模式 集群模式 复制节点(Amazon Web Services (AWS) ElastiCache Cluster and Azure Redis Cache) Java代码 Config config = new Config(); config.useSingleServer() .setAddress("redis://127.0.0.1:6379"); RedissonClient client = Redisson.create(config); 本文示例采用redis单机模式的配置,首先利用SingleServerConfig构建出单机模式下redis的配置对象,并使用setAddress 方法配置IP和端口信息,除此之外,还可以通过对应的setter方法设置retryAttempts、connectionTimeout 和 clientName等参数。然后,通过SingleServerConfig构建出Config对象,并将其传递给Redisson类的create静态方法,获得RedissonClient对象,后续我们便可以使用其进行各种业务操作。 ...

September 27, 2024

当100个任务同时请求提交任务,线程池时如何工作的

问题 如果100个任务同时往线程池(最小线程5,最大线程10,队列时LinkedBlockingDeque)提交,线程池会发生什么?线程池如何处理这些任务? 线程池 我们首先来看看线程池的工作流程。 当线程池被创建成功后,其中是没有任何一个线程的。任务队列是作为线程池的一个构造参数传入的,如果初始队列中有任务,线程池也并不会马上执行其中的任务。 如图所示,当应用通过execute方法往线程池提交任务时,线程池会做出如下的动作: 1、如果正在运行的线程数小于核心线程数时(corePoolSize),通过addWorker方法创建新线程,并将此任务作为当前线程的初始任务执行 2、如果当前运行的线程数量大于或者等于核心线程数时,addWorker会return false,这时,将任务放入队列 如果队列满了,通过addWorker创建非核心线程,并立即将任务执行 如果队列满了,通过addWorker创建非核心线程失败,则通过reject方法执行失败策略 如果一个工作线程长时间无事可做,超过keepAliveTime参数设置的超时时间,如果当前运行的线程数大于核心线程数,这个线程便会执行停止策略。 那么其是如何实现的呢? while (task != null || (task = getTask()) != null) { ... } 在getTask()方法中 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); 其实利用的是阻塞队列poll方法的超时时间,当达到超时时间,仍然没有获取到任务,便会返回null,进而Worker中的while循环跳出,结束线程。 问题解答 回到问题本身,当100个请求同时执行execute方法往线程池提交任务时, 由于一开始工作线程数是小于核心线程的,这100个任务会通过CAS争抢的方式创建新核心线程(worker),立即执行任务 如果核心线程数达到5,将任务放入队列中 如果放入成功,调用addWorker创建工作线程,从队列中取任务执行(工作线程数大于等于10时,会创建失败,导致任务堆积在队列中,等待其他工作线程执行) 如果放入失败,addWorker创建工作线程,立即执行该任务,如果创建失败,抛出异常 扩展 线程池参数的设置策略 线程数 一般根据业务时IO密集型还是计算密集型 如果时计算密集型,那么一般根据设置成CPU核心数,防止过多线程,导致上下文切换,降低运行效率 如果时IO密集型,考虑到充分压榨CPU的计算能力,可以设置为2倍CPU核心数,具体根据业务情况而定 超时时间 根据每一个任务执行所需的时间而定,避免频繁创建和销毁线程,提高线程利用率。

September 27, 2024

无法将交换文件 /vmfs/volumes/.../XXXXX.vswp 从 0 KB 扩展到 6291456 KB : No space left on device

在编辑虚拟机,增加内存时,出现如下的错误 解决方案: 在编辑虚拟机,增加内存时,将预留内存也一并分配

September 27, 2024

策略模式

策略模式 策略模式定义了一组算法,将它们逐个封装起来,并使它们可以相互替换。策略可以让算法独立于使用它们的客户而变化。 应用 在业务场景中,我们经常需要根据不同的条件而执行不同方法,这个时候便可以为每一种条件定制一类特定的方法。 类图 如图所示 Strategy: 策略接口或者策略抽象类,并且策略执行的接口 ConcreateStrategyA、ConcreateStrategyB、ConcreateStrategyC:实现策略接口的具体策略类 Context:上下文类,持有具体策略类的实例,并负责调用相关的算法 示例 普通实现 定义接口 public interface PrintStrategy { boolean support(String code); void print(Message message); } 实现类 public class OnePrintStrategy implements PrintStrategy { @Override public boolean support(String code) { return "1".equals(code); } @Override public void print(Message message) { System.out.println(message.toString()); } } public class TwoPrintStrategy implements PrintStrategy{ @Override public boolean support(String code) { return "2".equals(code); } @Override public void print(Message message) { System.out.println(message.toString()); } } 策略实例持有类 public class StrategyContext{ private final List<PrintStrategy> strategies; public StrategyContext(){ strategies = new ArrayList<>(); strategies.add(new OnePrintStrategy()); strategies.add(new TwoPrintStrategy()); } public PrintStrategy getStrategy(String code){ for(PrintStrategy printStrategy:strategies){ if(printStrategy.support(code)){ return printStrategy; } } throw new RuntimeException("unsupported code"); } } 测试 @Data @AllArgsConstructor public class Message { private String code; private String body; } public class Demo { public static void main(String[] args) { StrategyContext strategyContext = new StrategyContext(); Message message = new Message("2","打印策略2"); strategyContext.getStrategy(message.getCode()).print(message); } } Message(code=2, body=打印策略2) Process finished with exit code 0 枚举实现 枚举 public enum PrintStrategyEnum { ONE("1"){ @Override protected void print(Message message) { System.out.println("枚举类策略1 "+message.toString()); } }, TWO("2") { @Override protected void print(Message message) { System.out.println("枚举类策略2 "+message.toString()); } }; private final String code; PrintStrategyEnum(String code) { this.code = code; } protected abstract void print(Message message); public String getCode() { return code; } } 测试 public class Demo { public static void main(String[] args) { StrategyContext strategyContext = new StrategyContext(); Message message = new Message("2","打印策略2"); strategyContext.getStrategy(message.getCode()).print(message); for(PrintStrategyEnum printStrategyEnum: PrintStrategyEnum.values()){ if(printStrategyEnum.getCode().equals(message.getCode())){ printStrategyEnum.print(message); } } } }

September 27, 2024

设计模式之观察者模式-谈谈你对观察者模式的理解

定义 先来段wiki上面的定义:观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。简单来讲,就是定义了一组一对多的关系。当被观察者的状态改变,会通知观察者做出相应的动作。 在开发过程中,常常会遇到如下的场景: 游戏开发中,当人物走到某个格子,触发加血、战斗等动作 微信公众号,当用户关注了某公众号,一旦公众号更新了文章,便可以给用户进行推送 在这种情况下,我们该如何实现符合开闭原则的代码呢,观察者模式便是一个很好的选择。 模式结构及实现 下图便是观察者模式的模式结构。 从图中可以看出,观察者模式主要结构主要包括发布者和订阅者两个行为对象,发布者持有所有订阅者的集合,当需要发布事件或者消息时,通知订阅者做出相应的动作。 代码 定义事件接口,作为观察者与被观察者之间交互的媒介 public interface Event { } 定义Subscriber接口,作为观察者的接口 public interface Subscriber { void update(Event event); } Subscriber的两个简单实现类 public class ReadSubscriber implements Subscriber{ @Override public void update(Event e) { System.out.println("ReadSubscriber"); } } public class WriteSubscriber implements Subscriber{ @Override public void update(Event e) { System.out.println("WriteSubscriber"); } } 定义Publisher接口,作为被观察者的接口 public interface Publisher { void subscribe(Subscriber subscriber); void unsubscribe(Subscriber subscriber); void notifySubscribers(Event event); } Publisher接口实现类 public class DefaultPublisher implements Publisher{ private final List<Subscriber> subscribers = new ArrayList<>(); @Override public void subscribe(Subscriber subscriber) { subscribers.add(subscriber); } @Override public void unsubscribe(Subscriber subscriber) { subscribers.remove(subscriber); } @Override public void notifySubscribers(Event event) { for(Subscriber subscriber:subscribers){ subscriber.update(event); } } } Demo测试 ...

September 27, 2024

限流的方式-几种限流算法的介绍与对比

简述 java限流大体可以分为3个方面。 合法性 验证码 黑名单 容器限流 tomcat nginx 算法限流 固定时间窗口算法 滑动时间窗口算法 漏桶算法 令牌桶算法 算法限流 固定时间窗口 这是限流算法中最简单也是最暴力的一种朴素算法。既然我们的目标是让APP在一分钟内只能被调用N次。那么我们便可以统计这一分钟内接口被调用的次数,一旦调用的次数超过我们设置的阈值,则拒绝提供服务。这种算法的优势是逻辑容易理解,实现也很容易。劣势则是由于时间窗口是固定的,可能在某些时间段服务器会承受超过设定阈值的请求。 如图所示,比如我们设置一分钟只能提供100次访问服务,如果某个应用在某一分钟后半部访问了100次,在下一分钟前半步访问了100次,看似符合窗口限制条件,实际上,服务器在0 :30~1:30这个时间区间上提供了200次服务(已经超出了服务器的设置的阈值)。并且,这个用户在瞬间耗光了两个时间窗口区间的服务定额,导致其他用户均不能访问了,这在业务上也是不能接受的。 滑动时间窗口算法 滑动时间窗口算法可以认为是对固定时间窗口的改进。也可被称为精度不够,时间来凑。 如图所示,将一分钟分为10个更加小的时间区间,每个小时间区间都有其自己的计数器,对其自身小时间区间进行控制。除此以外,若干个小时间区间组成一个时间窗口,并且随着时间的推移,时间窗口也随之右移。每个一段时间删除最左端的小区间,在最右端添加小区间,对整个时间窗口的总流量也进行控制。如果恶意用户分时间窗口进行大量请求,也能被检测出来。最后,我们也可以得出结论,时间区块分配的越精细,流量控制也会随之更加精细。 dubbo有使用 漏桶算法 漏桶算法(Leaky Bucket)是网络世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。这是漏桶算法的基本定义。 形象一点的解释的话: 漏桶可以看作是一个队列,将客户端的请求依次放入队列中,服务端以一定的速率从队列中获取请求进行处理,如果某一时刻收到了客户端发起的大量请求,队列放不下了,则丢弃数据包。可以用来平滑网络上的突发流量。有点类似于消息队列的作用。 如图所示,请求好比水流,出水速度好比服务端处理请求的速率,一旦大量水流冲击过来,当桶装不下时水变回溢出,体现为直接拒绝客户端的请求。 令牌桶算法 令牌桶是一种用于分组交换计算机网络和电信网络的算法。它可用于检查数据包形式的数据传输是否符合定义的带宽和突发性限制(流量不均匀或变化的度量)。 其原理就是,以一定的速率将令牌放入池子中,当请求到来时,就从池子中获取令牌,持有令牌才能进行后续的访问,获取不到令牌则直接拒绝访问。 当请求的速率等于令牌生成速率时,每一个请求能无延迟的通过令牌池 当请求的速率小于令牌生成速率时,每一个请求能无延迟的通过令牌池,并且多余的令牌会在池子中积累起来,供后续的突发流量使用 当请求的速率大于令牌生成速率时,当前面请求消耗完池子中的令牌后,后续请求获取不到令牌,不能获得服务 特点总结 漏桶能强制以一定的速率消费请求 令牌桶既能限制请求的平均速率,也能应对突发流量情况 固定时间窗口不能实现高精度的控制

September 27, 2024

CAP定理

概述 1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标。 Consistency Availability Partition tolerance 这三个指标不可能全部做到。 Partition tolerance 分区容错性 大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。 (数据同步失败) Consistency 一致性 不管客户端向哪个节点发送请求,得到的结果应该是一致的。 Availability 可用性 用户只要向集群中任一节点发送了请求,就必须收到响应,否则就不满足可用性。 为什么三者不能同时满足 一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)。 如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性。 如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。 综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。 参考 https://www.zhihu.com/question/54105974 http://www.ruanyifeng.com/blog/2018/07/cap.html

September 27, 2024

Dubbo笔记-概述

dubbo 学习 Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。 背景 常见的架构 随着互联网的发展,网站应用的规模不断扩大,应用的架构也在不断的迭代发展。 单一应用架构 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。 垂直应用架构 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。 分布式服务架构 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。 流动计算架构 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。 需求 随着服务的不断增多,暴露出诸多的问题。如服务URL配置管理变得非常困难、服务间依赖关系错踪复杂、服务的容量无法快速准确估计等。 dubbo服务的诞生便是应对如上的几个需求。 dubbo架构 服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。 服务消费者(Consumer): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 调用关系: 服务容器负责启动,加载,运行服务提供者。 服务提供者在启动时,向注册中心注册自己提供的服务。 服务消费者在启动时,向注册中心订阅自己所需的服务。 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 使用

September 27, 2024

Java注解-元注解

元注解 @Retention public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); } 此注解表明了注解的生命周期 public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */ RUNTIME } 有三种,分别是源代码、字节码、和运行时 ...

September 27, 2024

Mysql查询优化

查询执行流程 如图所示,mysql的查询一般分为如下步骤: 客服端发送一条查询给服务器 服务器先检查查询缓存,如果命中缓存,则立刻返回存储在缓存中的结果。否则进入下一个阶段。 服务器端进行SQL解析、预处理,在由优化器生成对应的执行计划。 MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询 将结果返回给客户端 注意点 字段 尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED VARCHAR的长度只分配真正需要的空间 使用枚举或整数代替字符串类型 尽量使用TIMESTAMP而非DATETIME, 单表不要有太多字段,建议在20以内 避免使用NULL字段,很难查询优化且占用额外索引空间 用整型来存IP 索引 索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描 应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描 值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段 字符字段最好不要做主键 不用外键,由程序保证约束 尽量不用UNIQUE,由程序保证约束 使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引 SQL 可通过开启慢查询日志来找出较慢的SQL 不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边 sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库 不用SELECT * OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内 不用函数和触发器,在应用程序实现 避免%xxx式查询 少用JOIN 使用同类型进行比较,比如用'123’和'123’比,123和123比 尽量避免在WHERE子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描 对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5 列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大

September 27, 2024
苏ICP备2021016520号-1