点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人。
文章不定期同步公众号,还有各种一线大厂面试原题、我的学习系列笔记。
nacos | eureka | |
---|---|---|
应用 | nacos是阿里巴巴的开源中间件,可以直接启动jar即可用 | eureka需要连着springboot项目一起启动才可用 |
负载均衡 | nacos默认提供权重设置功能,调整承载流量压力 | 无 |
心跳机制 | nacos支持由客户端或服务端发起的健康检查 | Eureka是由客户端发起心跳 |
负载均衡策略 | 用Ribion | 用Ribion |
dubbo和k8s的集成 | 支持 | 不支持 |
选型建议 | 希望引入alibaba生态圈;希望在线对服务上下线&在线流量管理 | 希望引入spring clound生态圈 |
一致性协议 | 支持AP+CP任一种实现 | AP |
动态配置 | 支持(方便管理所有环境的服务配置) | 不支持 |
幂等性:多次重复请求/多次重复操作某一资源,产生的结果是一样的;对于数据库而言,幂等性就是多次重复地对数据库进行某一操作,得到的结果的一样的;对于接口而言,在设计的时候需要考虑幂等性,就是多次重复请求某一个接口,从接口处得到的结果是一样的
操作 | 是否幂等 | 示例 |
---|---|---|
查询 | 是 | select * from user where name='afei' |
新增 | 是 | insert into user(userid,name) values(1,'afei');若userid是主键,那这个sql就是幂等性的,因为只有第一次数据可以被插入,对数据库产生的结果是一样的;若userid不是主键,那这个sql就不是幂等性的,因为可以重复插入,对数据库产生的结果是不一样的 |
由上可知分布式锁主要用于解决CAP中的‘C’数据一致性问题:分布式环境中,可能存在多个进程竞争同一个资源,这就需要实现多进程间的“互斥锁”,在java中自带有实现线程间的互斥锁(Synchronized,Reentranlock),但是分布式环境下进程间的互斥需要自己实现,需要把这个“互斥锁”存在公共的地方被多个进程访问到,这样同一时刻只有一个进程能拿到“互斥锁”,进而保证了数据的一致性=‘C’,一般可用redis/zookeeper/数据库来实现分布式锁
又分为两种方式:表锁、版本号机制
CREATE TABLE `order` ( //实现分布式锁的表 `id` int(11) NOT NULL AUTO_INCREMENT, `order_no` int(11) DEFAULT NULL comment `锁住的订单号资源`, PRIMARY KEY (`id`), unique key `unique_order_no`(`order_no`) )ENGINE = INNODB
可知order_no为唯一性约束,当想锁住某个orderNo时->先把它插入表中->当有多个相同的order_no提交到数据库,只有一个能成功->想释放锁时,删除该条记录。可以先检查某个order_no是否在表中,不存在则插入,“检查+插入”应该放到同一个事务中:
@Transactional //“检查+插入”应该放到同一个事务中 public boolean addOrder(int orderNo) { if(orderMapper.selectOrder(orderNo)==null){ //检查 //order表不存在该条记录则插入,表示orderNo订单号被锁定 int result = orderMapper.addOrder(orderNo); if(result>0){ return true; } } return false; } public void fun(int orderNo){ if(addOrder(orderNo)) {//拿到分布式锁 //业务处理 ... //处理完删除order表的orderNo记录,表示释放分布式锁 orderMapper.delete(orderNo); } }
CREATE TABLE `order` ( //实现分布式锁的表 `version` int(11) NOT NULL, //版本号、 `order_no` int(11) DEFAULT NULL comment `锁住的订单号资源`, )ENGINE = INNODB
假如已存在version=1,orderNo='N1'的记录,则:
//先获取锁:
select version from order where order_no='N1';
//再占用锁:
udpate order ser version='2' where order_no='N1' and version='1';//更新成功则拿到锁
redis可以用【setnx(key,value)+设置expire】实现分布式锁,这是目前比较优的一种解决方案
public boolean fun(Jedis jedis,String key,String value,int expireTime){ Long r=jedis.setnx(key,value);//若key不存在则保存(key,value)并返回1,代表拿到分布式锁;若key已存在则设置失败并返回0,代表分布式锁已被占用 if(r==1){ //拿到分布式锁 //设置锁的过期时间:从当前时点开始经过expireTime(s)之后该key失效,key-value会被删除,代表分布式锁被释放; jedis.expire(key,expireTime); return true;//拿到true可以向下执行业务操作 } return false; }
产生的问题:
//redis2.6.12之后:setnx提供了expireTime参数,可以一步设置过期时间 public boolean fun(Jedis jedis,String key,String value,int expireTime){ String r=jedis.setnx(key,value,"NX","PX",expireTime);//分布式锁已被占用 if("OK".equals(e)){ //拿到分布式锁 //设置过期时间释放分布式锁 jedis.expire(key,expireTime); return true; } }
先创建一个持久型父节点,每当客户端们想竞争访问共享资源时,都会在该父节点下新建一个临时有序的子节点->
不断地会有新的临时有序子节点被创建->
后面来的客户端在访问资源时,会检查自己创建的节点是否序号最小,若最小则获取锁,否则阻塞等待->
被阻塞等待的节点均会获取到上一个节点,并为上一节点注册watch事件监听节点是否还存在->
等到上一节点使用完共享资源,则会删除自身,进而触发watch事件被下一节点监听到,下一节点重复上面步骤:检查自己序号是否最小......
OK,如果文章哪里有错误或不足,欢迎各位留言。
创作不易,各位的「三连」是二少创作的最大动力!我们下期见!
分布式锁的三种实现方式