
1、spring里的IOC和AOP 2、java里的集中代理模式 3、如何实现一个延时队列 4、设计模式 5、beanFactory和FactoryBean的区别
分布式环境下如何保证一个数据在并发的情况下保证同一时间在一台机器只有一个线程在执行? 实现一个分布式锁需要解决的问题有以下几个主要点: 资源被唯一执行 良好的可重入机制:发生死锁情况下可被其他进程可重入 良好的性能问题:操作简单,额外的请求不要过多 容灾机制:个别的锁机器崩溃也能稳定运行 在 ZooKeeper 中,节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL ),具体在节点创建过程中,一般是组合使用,可以生成以下 4 种节点类型。 持久节点(PERSISTENT) 持久顺序节点(PERSISTENT_SEQUENTIAL) 临时节点(EPHEMERAL) 临时顺序节点(EPHEMERAL_SEQUENTIAL) 这里我们就基于临时有序节点去实现分布式锁:1、首先创建一个根节点用于存放分布式锁需要新建的临时节点2、在创建临时有序节点的时候,zk会根据client的创建请求会按顺序依次生成0000001、0000002、0000003这种类似序号的节点3、每个client判断当前自己创建的节点是否为序号最小的节点,是的话就认为拿到了锁,可以执行业务代码,否则就去监听最小节点的删除时间4、当处理完后需要删除节点,这样就能被其他线程监听到,开始竞争锁 当然,聪明的同学一看就能看出这个步骤有很多的问题,首先就是最小节点的删除时间会触发大量client的启动执行,需要发送大量的通知,而且如果在去创建监听的时候出现了最小节点已经被删除的情况,这时候永远都收不到最小节点的删除事件,而且如果按不同的资源创建了太多的目录的话,是否会对整个zk的性能产生影响,如果对不同资源采用同一个目录话,那么一个目录下的节点又过多,取子节点的性能又有问题... 所以该分布式锁的实现步骤可以优化一下1、首先不必所有client都去监听最小节点的删除事件,只需要监听比自己稍小点的节点的删除事件即可2、每此执行完处理后可以尝试性删除目录,避免随着时间的增长创建了过多的目录3、另外zookeeper提供的API中设置监听器的操作与读操作是原子执行的,也就是说在读子节点列表时同时设置监听器,保证不会丢失事件,所以可以在创建监听的时候一旦发现监听的节点为空就认为节点已删除,可以拿到锁 下面是代码:里面包含了测试main方法https://github.com/fengym201507411/lock package com.fym; import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.concurrent.*; /** * Created by fengyiming on 2018/12/24. * 基于zookeeper的代码 */ public class LockServiceImpl { private static volatile ZooKeeper zooKeeper; /** * zk连接超时时间/s */ private final static int SESSION_TIMEOUT = 10000; /** * 分布式锁创建key的根路径 */ private final static String PRE_ROOT_PATH = "/zkLockRoot"; /** * /字符 */ private final static String PATH = "/"; static { try { // 连接zookeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", SESSION_TIMEOUT, new Watcher() { @Override public void process(WatchedEvent event) { } }); Stat stat = zooKeeper.exists(PRE_ROOT_PATH, false); if (stat == null) { System.out.println("root path is null,create......"); zooKeeper.create(PRE_ROOT_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } System.out.println("root path ok"); } catch (Exception e) { System.out.println("加载zk信息异常" + e.getMessage()); } } public static void lock(String threadName, String key, long waitSeconds) { try { key = key + LocalDate.now().toString(); System.out.println(threadName + "begin lock"); String path = new StringBuffer(PRE_ROOT_PATH).append(PATH).append(key).toString(); Stat stat = zooKeeper.exists(path, false); if (stat == null) { System.out.println(threadName + "key path is null,create......"); try { zooKeeper.create(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } catch (KeeperException k) { System.out.println(threadName + "目录" + path + "已存在,无需重复创建"); } catch (Exception e) { System.out.println(threadName + "create key path error,error message:" + e.getMessage()); } } LocalDateTime begin = LocalDateTime.now(); String lockNodePre = new StringBuffer(PRE_ROOT_PATH).append(PATH).append(key).append(PATH).toString(); String lockNode = zooKeeper.create(lockNodePre, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode .EPHEMERAL_SEQUENTIAL); String lockNodeName = lockNode.substring(lockNodePre.length()); System.out.println(threadName + "创建有序临时节点:" + lockNode + ",节点名称:" + lockNodeName); // 取所有子节点 List<String> subNodes = zooKeeper.getChildren(path, false); System.out.println(threadName + "当前竞争资源下的节点数:" + subNodes.size()); //排序 subNodes.sort(String::compareTo); System.out.println(threadName + "first:" + subNodes.get(0) + ",last: " + subNodes.get(subNodes.size() - 1)); int index = subNodes.indexOf(lockNodeName); String minNodeName = subNodes.get(0); if (!lockNodeName.equals(minNodeName)) { String min1NodeName = subNodes.get(index - 1); String min1NodePath = new StringBuffer(PRE_ROOT_PATH).append(PATH).append(key).append(PATH) .append(min1NodeName).toString(); CountDownLatch countDownLatch = new CountDownLatch(1); System.out.println(threadName + "当前节点" + lockNodeName + "准备监听节点:" + min1NodeName); Stat min1Stat = zooKeeper.exists(min1NodePath, new Watcher() { @Override public void process(WatchedEvent event) { System.out.println(threadName + "节点" + event.getPath() + ",事件 : " + event.getType()); if (event.getType() == Event.EventType.NodeDeleted) { System.out.println(threadName + "节点删除"); countDownLatch.countDown(); } } }); if (min1Stat == null) { System.out.println(threadName + "节点不存在,无需等待,当前节点:" + lockNodeName + ",前一节点:" + min1NodeName); } else { System.out.println(threadName + "------wait-------"); countDownLatch.await(); } } //超时 if(LocalDateTime.now().compareTo(begin.plusSeconds(waitSeconds)) > 0){ throw new Exception("waite time out"); } System.out.println(threadName + "拿到了lock" + lockNodeName + ",do -----"); zooKeeper.delete(lockNode, -1); System.out.println(threadName + "执行完毕,解锁" + lockNodeName + "------"); String lastNodeName = subNodes.get(subNodes.size() - 1); if (lockNodeName.equals(lastNodeName)) { try { zooKeeper.delete(path, -1); System.out.println(threadName + "尝试删除该key目录成功,path" + path); } catch (KeeperException k) { System.out.println(threadName + "尝试删除该key目录,失败:" + k.getMessage()); } catch (Exception e) { System.out.println(threadName + "尝试删除该key目录,失败:" + e.getMessage()); } } } catch (Exception e) { System.out.println(threadName + "lock error" + e.getMessage()); } } public static void main(String[] args) throws InterruptedException { ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); for (int i = 0; i < 1000; i++) { String name = new StringBuffer("ThreadName[").append(i).append("]").toString(); executor.execute(() -> { LockServiceImpl.lock(name, "firstLock",10); }); } } }
一、时间复杂度的具体步骤是: ⑴ 找出算法中的基本语句; 算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。 ⑵ 计算基本语句的执行次数的数量级; 只需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。 ⑶ 用大Ο记号表示算法的时间性能。 二、算法的空间复杂度一般也以数量级的形式给出。如当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时,可表示为O(1);当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为0(log2n);当一个算法的空I司复杂度与n成线性比例关系时,可表示为0(n).若形参为数组,则只需要为它分配一个存储由实参传送来的一个地址指针的空间,即一个机器字长空间;若形参为引用方式,则也只需要为其分配存储一个地址的空间,用它来存储对应实参变量的地址,以便由系统自动引用实参变量。
大家在项目里面应该都用过MQ,MQ作为解耦神器,还是很有必要学习的。 RabbitMQ是基于AMQP(高级消息队列协议)。支持多种操作系统,支持多种语言。 1、Broker:负责接受客户端消息和路由等功能、2、Virtual host:虚拟主机的概念,类似权限控制组,一个Virtual host可以包括很多Exchange、Queue,不同mq的消费者生产者可以通过VHost隔离3、Exchange:交换机,可以理解为在收到生产者的消息后负责将将消息转发给对应的Queue4、Binding:Queue和Exchange之间绑定的关系,5、Queue:消息队列,存放所有消息,消费者从Queue消费消息6、Channel:通道,多路复用连接中的一条独立的双向数据流通道,不管是发出消息还是消费消息,都是通过信道完成,TCP连接7、Consumer:消费者 Exchange分发策略:主要有四种模式,分别是fanout、direct、topic、headers。但是由于headers匹配的是消息体内header信息,而不是routingKey信息,不但跟规则上和direct差不多,而且性能差,故不常用。下面主要介绍其他三种分发策略1、fanout:广播,所有发到exchange的消息都会被分发到每个绑定在该exchange的queue上去2、direct:单播,每个Queue在绑定exchange的时候可以设置routingKey信息,当exchange收到消息的时候,只会将消息转发给binding的routingKey与消息体内的routingKey一致的queue。3、topic:介于单播和广播之间,跟direct差不多,但是支持routingKey的通配符匹配,这个是日常用的最多的分发策略。*(星号)可以代替一个字。#(散列)可以替代零个或多个单词 RabbitMQ日常项目怎么用:不同事业部、大部门使用不同的MQ集群,同一大部门内不同组使用同一个MQ集群但通过VHost隔离,同一个组的不同AppId使用同一个VHost但是使用不同的Exchange(topic模式),每个AppId内不同功能模块使用不同的Queue(队列隔离可以很好的防止功能耦合,防止某一个功能的消息暴增导致其他功能受影响,最好功能之间queue隔离),每个Queue都按topic模式绑定在该appid对应的Exchange上。 命名方式:Vhost为组的名称,Exchange一般为appid的名称,queue为appid_功能名称_queue,routingKey为appid_功能名称_routingKey。这个一般是我的命名方式。 关于MQ能用来干嘛?1、RabbitMq是有ack机制的,所以用来做重试很好用,比如说一个操作里有两个步骤,第一个异常了第二个就不执行了,而往往都是分布式接口,在分布式系统环境下,数据就很难保持一致,这时候可以通过MQ的ack机制来保证最终数据一致性2、异步:对于一个操作带来的其他联动处理,往往不需要第一时间处理,可以在后台慢慢处理,比如写日志就可以异步消费。3、提高处理速度:正常情况处理一个数据可以同步调用,当你处理的数据达到10万+,百万+的时候就不能在用同步的方式去处理了,而也不能采用异步线程,异步线程太不保险,有无法预估的风险,这时候分发消息,再通过mq的ack机制保证每个消息都会被正确处理,不过再往上的批处理就不能用mq了,毕竟千万级的MQ堆积对整个集群也有不小的压力。4、多机房数据同布:大型分布式系统往往会有多个机房,通过DRC消息(Data Replication Center)底层保证最终数据一致。 善用MQ,会让你的项目更灵活.......
此篇文章只是了解一下redis部署架构方式,至于怎么搭建有兴趣自己研究 redis几个特点:1、redis支持5中数据类型,每种数据类型都有自己的数据结构2、redis提供两种数据持久化机制,RDB和AOF模式,RDB模式是定时用一个线程将数据保存成文件存储在硬盘中,优点是恢复数据块缺点是可能会丢失一整段时间内的数据。AOF机制是通过保存对数据的变更命令日志来实现数据持久化,可选择每次变更保存日志,也可以选择每秒内的日志保存。优点是数据完整性高,缺点是性能低。 redis cluster集群方案: 此种架构特点:1、若干节点组成一个集群,每个节点有主备多个实例,保证高可用性。整个集群将16384个插槽分别存储在不同的节点上,每个集群节点的数据均不一样,每个节点上的主备实例节点数据是一样的。2、Redis Cluster采用无中心架构,每个节点都保存数据和整个集群的节点状态,集群节点不会代理查询,Redis Cluster采用异步复制机制 ,master和slave在先写到master,然后master直接返回ok,然后再把变更异步传播更新到slave里去。3、应该是不支持不同slot的批量操作的,同一个slot的批量操作应该是没问题的。 实际部署架构: 代理层可做批量操作、支持更高的并发、高可用性。 redis集群架构参考redis集群知识