前言
Java架构面试
一、微服务架构
 将项目里面的一个模块作为一个微服务,可以更方便的水平扩展
二、缓存和异步消息
缓存:Redis 常用数据结构  redis线程模型,redis为什么性能好?
- 单线程,NIO,异步事件处理
redis内部只有一个线程,给所有请求排队,socket来访问,排队,通过文件事件分派器分给不同的事件处理器 client socket 和 server socket 建立连接,连接事件、readable事件,分配给事件处理器,处理完之后异步的给客户端返回ok。 redis里面命令单线程执行,socket连接中的事件异步执行 场景:分布式锁,分布式Session redis架构:哨兵,集群

消息中间件(activemq (1w并发) ,rabbitmq(1w并发) ,rocketmq(几十万消息) ,kafka(大数据,性能高,几十万、百万消息) ) 区别。
优点: 解耦,异步,削峰
问题:
kafka解决问题:  如果主节点挂了,选一个从节点当做主节点; 重复消费:消费者消费partition后会提交,kafka会指定从哪个消费起然后提交(自动或手动)commit,记录消费到哪里的偏移量存到zookeeper,下次消费取出,继续消费。正常消费到5提交,结果消费到3,消费者挂了,会产生重复消费。 解决:设置一个主键冲突,把要写数据的表设置一个唯一键,如果插入重复订单(订单重发)会报逐渐冲突;设置一个redis分布式锁,谁拿到谁可以执行,设置判断条件,消息是否存在(redis原子性操作)。
 发送者丢消息:消费者把消息发到kafka集群,做持久化防止丢消息,发到(broker1),同步到(broker2,broker3)的时候节点挂了,kafka选择broker2作为主节点,消费者没来得及消费,消息也没同步过去,消费者下次发送消息会发送到broker2节点,也是连接到消费者,第一条消息就丢掉了。主从切换,同步不及时导致丢消息。 解决方式:kafka有一个参数设置,发送者有一个acks参数设置为all ,就是说,发送者发送一个消息给消息中间件,如果是集群架构,他必须所有节点同步成功,才会返回发送者发送成功。 一旦做了这种设置,性能会变差,每秒接收消息量会减少。发送失败还可以设置重试参数。
消费者丢消息:为了提高性能,消费者接收一条消息后不做任何业务处理,直接commit返回消息中间件,告诉接收成功,你可以删掉消息或者位移消息,再去做业务,业务失败会丢消息。 解决方式:收到消息,先放数据库存起来,返回给中间件接收成功,然后后台有一个线程从本地表里面读消息,消息上有状态码,业务处理完,改成已经处理,设置一个定时任务,如果任务处理失败,再去补偿处理。
消息积压:业务场景碰到秒杀,很多人提交订单,消费者只有两个,瞬间积累几百万消息,消费者只能处理几十或者几百条,或者消费者挂了,如果中间件阻塞消息会影响其他业务。 解决方式:其他中间件可以加消费者,kafka不能开多个消费者,因为一个分区只能被一个消费者消费。 kafka需要先多建立几个分区,原来的消费者业务改成转发,转发给其他分区,将消息迁移到多个分区,再多加几个消费者,用脚本自动化部署,快速消费消息。
顺序消费:kafka课程
三、分布式锁
- 基于redis
- 基于zookeeper
 多线程互斥操作 一段业务操作,下单减少库存,查库存是10,减一,更新库存操作。 三步代码要求三个代码保持顺序,原子性。不同JVM下不能用synchronized控制住,
Redis分布式锁:SETNX,INCR。 INCR KEY会返回1,不存在覆盖,哪个线程返回是1,会认为哪个线程拿到锁了 SETNX,在redis里面设置一个值,哪个设置成功会返回一个1,setnx lock netgrow,哪个设置成功会拿到锁,后面的重复设置不会做任何操作。 setnx == 1执行业务,最后把锁删掉:如果执行业务过程中,业务抛异常了,删除锁的命令就无法执行了,新命令来了不能加锁,就会导致死锁。 可以设置一个超时: String lockkey = “lockkey”; Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(“lockkey”,“netgrow”); 业务代码 最后stringRedisTemplate.delete(lockkey);
抛异常用try catch finally解决,但是可能程序挂了,运维可能kill进程或者kill-9,直接kill所有进程,温柔点的用stop,导致整个jvm进程挂了。 可以设置一个超时时间解决,stringRedisTemplate.expire(lockkey,10,TimeUnit.SECONDS); 有可能在上一条代码挂掉,Jedis有set(key,value,nx,expire,…);可以用一条命令让redis来保证代码执行成功。 **超时时间设置多少秒?**设置10s正常够了,但是如果有的不小心超时了,来新的线程可能会被第一个释放掉,第二个会释放第三个,导致后面锁全部失效了。自己的锁被别的线程释放掉 解决:加一个clientId,为UUID,把它放到value里面去,删除锁之前先get一下,判断一下lockkey的值是否为clientId,只有自己加的锁才能删掉,但是还有可能锁超时。 如果要保证一段业务在分布式环境中只有一个线程执行,就是保持原子性,还是有问题,其他解决方案。 用一个redisson框架,生产级别使用的成熟的分布式锁框架,RLock lock = redisson.getLock(lockkey); lock.lock();,try执行业务逻辑 finally lock.unlock(); redission的原理图:  多个线程,将Redis线程里面写一个锁的值lockkey,redisson也是往里面写值,默认超时时间30s,底层掉setnx很长参数的那个方法,设置太长如果程序挂掉会锁1个小时,超时时间不好估量,redisson设置完这个锁,会开一个锁延时线程,会定期的检查线程还有没有锁,如果持有锁就会延长锁的时间。第一个线程拿到所,其他线程想拿所会自旋锁,一个尝试获取锁,会影响一点性能,CAP问题。 Redis使用主从架构,或者集群,说单机说明你没有分布式经验。集群原理要能讲明白
如果redisson设置一个锁到主节点,主节点没同步锁到从节点,挂掉了,主从切换时,新的节点去操作新的redis主节点没发现锁,线程1还在执行业务逻辑,分布式锁又失效了。 可以用zookeeper解决,zookeeper设计理念与分布式锁很符合,zookeeper可以保证数据一致性,zookeeper也是主从架构,使用zookeeper集群架构。 
为什么没有丢失问题,zookeeper集群也有一个leader节点和多个follower节点,如果有一个请求creat一个值,zookeeper不是马上返回ok,会分为两阶段操作之后,再返回ok, 第一阶段先写到日志文件中,同时转发follower,让follower也写到日志文件中,leader如果收到半数以上follower日志文件写入成功回复之后,leader才去做commit操作,其它线程就可以看到这条信息,leader做commit的同时把commit消息同步到其他follower。 如果写的过程中leader挂了,第一阶段挂了主从切换,消息没提交,没问题,第二阶段commit客户端收到ok,进行别的操作,主节点挂了,会选其他节点做主节点,会优先选择日志消息最多的节点,至少有一个节点有日志消息,选择它作为主节点。不会100%解决,但是消息一致性高,paxos算法(少数服从多数)的变种。 zk加锁设置临时节点,会不会有死锁,有异常用finally解决,被kill了临时节点会被删掉。 还可以解决自旋锁问题,调用create命令,谁先创建成功谁拿锁,用监听的方式可以解决自旋问题,watch机制,很多线程过来加锁,创建临时有序节点,第一个线程加锁成功,拿到节点,在root根节点下面,谁创建临时节点,序号最小谁就拿到那把锁,线程1执行完删掉锁,挂掉了锁也会消失,对根节点进行监听,有监听回调机制,释放锁,删掉001节点后,所有线程都会收到删除事件,还是谁的序号最小,谁拿到这把锁,就去主动在回调的事件里执行自己的业务逻辑,就不会使用while循环持续尝试加锁。 
四、分布式事务


五、分布式Session
 tomcat弊端需要依赖tomcat容器,可以用spring-session 不依赖容器。
总结
普及微服务架构,缓存和异步消息,如何用分布式锁解决问题。
|