一.整合JDBC
对于数据访问层,无论是sql还是nosql,springboot都是用spring data的方式统一处理,spring data是和springboot,springcloud齐名的项目
https://spring.io/projects/spring-data
1.新建项目
2.yml配置文件配置数据库信息等
3.测试观察默认的数据源
4.查看自动装配数据源的源码
xxxProperties
xxxAutoConfiguration
5.使用jdbcTemplate完成crud
通过数据源获取数据库连接,从而操作jdbc原生操作
观察源码发现,数据源和配置文件都有了,所以可以直接拿来使用
二.整合Druid数据源
1.介绍
阿里的数据库连接池实现,加入了日志监控
2.如何获取
导入druid和log4j的依赖
3.IDEA中观察源码
4.配置指定的druid数据源并测试
5.druid数据源的自定义配置
<1>自定义配置后台监控功能
<2>看源码
<3>测试
三.整合Mybatis
1.获取springboot整合mybatis的依赖
2.新建项目,导入依赖
3.配置连接数据库 并测试连接
4.编写实体类和mapper以及mapper.xml
5.配置yml整合mybaits
6.编写controller
7.测试
四.SpringSecurity(安全)
1.是什么
是针对spring项目的安全框架,可定制化;也是springboot底层安全模块默认的技术选型,可以实现web安全控制
2.为什么用
web开发中,安全是第一位,web阶段可以用过滤器,拦截器做;
框架阶段,有springsecurity,shiro,
由于web阶段用原生代码写拦截器等需要大量配置才能完成认证授权,所以用框架简化了配置
3.怎么用
两大功能:认证(用户的用户名和密码能不能对应上),授权(用户有哪些权限)
可以用来做:
功能权限
访问权限
菜单权限
<1>springsecurity环境搭建
I.导入依赖
①使用spring-security-web
①使用spring-boot-starter-security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
使用这个依赖,启动项目时,控制台会输出Using generated security password: 17708027-39f3-44cc-9623-7843038d0af9的信息,密码是随机生成的
4)禁用spring security两种方法:
@EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class})
或
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class })
5)除此之外,spring-boot-starter-security 还会默认启用一些必要的 Web 安全防护,比如针对 XSS、CSRF 等常见针对 Web 应用的攻击,同时,也会将一些常见的静态资源路径排除在安全防护之外。
③总结
security 在后来的版本中不在支持自动配置,支持自定义 WebSecurityConfigurer bean,org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 是 WebSecurityConfigurer 的实现之一,新建一个配置类 WebSecurityConfig 继承抽象类 WebSecurityConfigurerAdapter
<2>编写controller层
<3>测试访问
<4>用户认证和授权
以aop横切的思想,不改变原来代码的情况下,实现像拦截器一样的功能
I.编写配置类
i.配置授权,权限控制以及用户注销
①.导包
②.配置授权与用户注销与记住我与登录页面定制
③.配置权限控制
ii.配置认证
II.测试
使用接口时会跳出登录信息(security内置用户, usename: user,密码为启动后台log显示的随机数):
III.自定义用户密码
如果我们希望对 HTTP Basic 认证的用户名和密码进行定制,可以通过如下配置项进行:
security.user.name={个人希望设置的用户名}
security.user.password={个人希望使用的访问密码}
五.shiro
1.是什么
Apache Shiro 是一个Java 的安全(权限)框架。
Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等
官网:http://shiro.apache.org/
2.为什么用
3.怎么用
<1>10min快速入门
I.导依赖
shiro依赖
日志依赖
官方默认用的是commons-logging;
我们用log4j
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<!--日志门面,通过下面两个可以调用很多日志的框架 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependencies/>
II.写配置
log4j.properties
shiro.ini
III.hello
<2>shiro核心三大对象
subject :管理当前用户
securitymanager : 管理所有用户
realm :连接数据
4.springboot集成shiro
I.测试springboot环境
i.导入依赖
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
ii.编写主页面和controller
iii.测试
II.整合shiro
i.导入springboot整合shiro的依赖——三大对象
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.8.0</version>
</dependency>
ii.编写配置类
流程:创建对象->接管对象->连到前端
六.springboot整合redis
1.为什么用
springboot操作数据:spring-data框架
比如jdbc,redis
2.怎么用
lettuce替换了Jredis
springboot2.x之后,lettuce替换了Jredis
Jredis(像NIO模式):底层用的是直连,多个线程操作不安全,需要用jredis pool连接池解决
lettuce(像BIO模式):底层用的是netty,实例可以在多个线程共享,不存在线程不安全的情况,就可以减少线程的数量,也就是不需要线程池
I.导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
II.yml配置redis
spring:
redis:
host: 127.0.0.1
port: 6379
lettuce:
shutdown-timeout: 0ms
III.使用redisTemplate类测试
IV.自定义配置类
i.自定义配置类(编写自己的redisTemplate)
为什么用自定义配置类:
举例:保存一个对象的时候,对象需要序列化,RedisTemplate默认用的是jdk的序列化方式,如果不要jdk的方式,想用别的方式就需要自定义配置类实现
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
//为了开发方便,一般都用<String, Object>
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String,
Object>();
template.setConnectionFactory(factory);
//json序列化的配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//string序列化配置
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
ii.编写pojo实体类
③yml配置
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 0ms
④测试
⑤编写redis-utils工具类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键 * @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time,TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map===============================
/**
* HashGet
* @param key 键 不能为null * @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值 * @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值 *
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list===============================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值 * @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存 *
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
ren true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count,value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
⑥.测试自己写的redis-utils
七.springboot整合Dubbo+Zookeeper
1.分布式
2.RPC
<1>是什么
两个核心模块,通讯和序列化,同步处理
<2>为什么用
序列化原因:方便数据传输
<3>怎么用
Dubbo和+Zookeeper
Dubbo就是一个高可用的RPC框架
3.Dubbo+Zookeeper
<1>是什么
Dubbo就是一个高可用的RPC框架
三大核心功能:
面向接口的远程方法调用
负载均衡
服务注册与发现
<2>为什么用
<3>怎么用
dubbo使用spring的配置方式,只需要spring加载dubbo的配置即可
I.注册中心zookeeper
i.官网下载
https://mirror.bit.edu.cn/apache/zookeeper
ii.解压
alex@zhaozhigangdeMacBook-Pro zookeeper.tar.gz % tar -zxvf zookeeper.tar.gz
iii.复制配置文件
iv.启动服务端与客户端
alex@zhaozhigangdeMacBook-Pro bin % ./zkServer.sh start
alex@zhaozhigangdeMacBook-Pro bin % zkCli.sh -server localhost
#停止服务端
alex@zhaozhigangdeMacBook-Pro bin % ./zkServer.sh stop
II.安装dubbo-admin
是一个监控管理后台网站
<4>dubbo的运行原理
4.服务注册与发现实战(springboot整合Dubbo和Zookeeper)
5.谈谈对zookeeper的理解
对于zookeeper的理解,我觉得可以从分布式系统的三种典型的应用场景来说,
第一种是集群管理,在多个节点组成的集群中,为了保证集群的HA的特性,每个节点都回去冗余一份数据副本,那么这种情况下,需要保证客户端访问集群中任意一个节点都是最新的数据;
第二种,分布式锁,如何保证跨进程的共享资源的并发安全性,对于分布式系统来说,也是一个比较大的挑战,为了达到这个目的,必须要使用跨进程的锁,也就是分布锁来实现,
第三种,master选举,在多个节点组成的集群中,为了降低集群数据同步的一个复杂度,一般会存下master和slave两种角色的节点,master负责去做事务和非事务的请求处理,slave专门负责非事务的请求处理,但是在分布式系统中,如何去确定某个节点是master还是slave也是需要考虑的;
基于这三种常见场景的需求,产生了zookeeper这样一个中间件,它是一个分布式开源的协调组件,专门负责协调和解决分布式系统中的分类问题,比如针对上述描述的问题,zook都可以用来解决,
第一个,集群管理,zookeeper提供了CP模型,来保证集群中每个节点的数据一致性,当然ZK本身的集群并不是一个强一致性模型,而是一个顺序一致性模型,如果我们需要去保证CP特性的话,我们需要调用sync方法进行同步;
第二个,分布式锁,zookeeper提供了多种不同的节点类型,比如持久化节点,临时节点,有序节点和容器节点,其中对于分布式锁这个场景来说,zookeeper可以利用有序节点这样一个特性来实现;
第三个,master选举,zookeeper可以利用持久化节点来存储和管理其他集群节点的一些信息,从而去进行master选取的一种机制,或者还可以利用集中的一些有序节点的特性来实现master选举,目前主流的kafka,hbase,hadoop都是通过zookeeper来实现集群中节点的主从选举;
总的来说,zookeeper是一个经典的分布式数据一致性解决方案,它主要致力于分布式应用中的一些高性能,高可用的,并且有严格访问顺序控制的能力模型,实现分布式的协调服务, 它底层的数据一致性算法,是基于Paxos算法演进而来的ZAB协议实现的;
以上就是我对zookeeper的全部理解
6.ZAB协议是什么
7.zk怎么保证数据一致性(数据同步原理)
8.zk的分布式锁原理
9.分布式锁的理解和实现
分布式锁是一种跨进程,跨机器节点的互斥锁,他可以用来去保证多个机器节点对于共享资源访问的一个排他性,我觉得分布式锁和线程锁本质上一样的,线程锁的生命周期是单进程多线程,而分布式锁的生命周期是多进程多机器节点,在本质上,他们都需要满足锁的几个基本特性,首先是排他性,也就是说在同一时刻,只能有一个节点去访问共享资源,其次就是可重入性,它允许一个已经获得锁的线程,在没有释放锁之前去重新获得锁,第三个是锁的获取和释放的方法,第四个是锁的失效机制,也就是避免死锁的问题;
所以我认为只要能满足这些特性的技术组件,都能去实现分布式锁,
首先第一个,关系型数据库,他可以使用唯一的约束来实现锁的排他性,如果要针对某个方法加锁,就可以创建个表,去包含方法名称的一个字段,并且把方法名称的字段设置成唯一约束,那么抢占锁的逻辑就变成了往表里面插入一条数据,如果已经有了其他的线程获得了某个方法的锁,那么这个时候再去插入数据的时候,一定会失败,所以从而去保证了锁的互斥性,这种方式去实现完整的分布式锁,还需要考虑重入性,锁的失效机制,没有抢占到锁的线程要阻塞等待等待都会比较麻烦;
所以第二个就是redis,它里面提供了setnx命令去实现锁的排他性,但key不存在时返回1,存在就返回0,还可以使用expire命令去设置锁的失效时间去避免死锁的问题,当然有可能存在锁过期,但是业务逻辑还没有执行完的时候,可以写个定时任务对指定的key进行续期,Redission这个开源组件提供了一个分布式锁的封装实现,并且内置了一个叫watch dog的机制对key去做续期,所以我认为redis里面这种分布式锁的设计已经能解决大部分问题了,当然如果在redis搭建了高可用集群的情况下,出现主从切换导致key失效的问题,也可能会导致多个线程抢占同一个锁资源的情况,所以redis官方也提供了一个叫Redlock的解决办法,但实现上会相对复杂一些;
所以在我看来呢,分布式锁它应该是一个CP模型,而redis是一个AP模型,所以再集群架构下,由于数据一致性问题导致极端情况下出现的多个线程抢占锁的情况很难避免;那么基于CP模型,又能实现分布式锁的组件,可以选择zookeeper,首先在数据一致性方面,zookeeper用到了ZAB协议去保证数据的一致性;第二个是在锁的互斥方面,zooke他可以基于有序节点,再结合watch机制来实现互斥和唤醒;
以上就是我对分布式锁的理解