淘客网站怎么做代理,常见的网络营销手段,品牌建设不足的原因,营销型网站的功能在日常业务开发的过程中#xff0c;我们经常会遇到存在高并发的场景#xff0c;这个时候都会选择使用redis来实现一个锁#xff0c;来防止并发。
但是很多时候#xff0c;我们可能业务完成后#xff0c;就需要把锁释放掉#xff0c;给下一个线程用#xff0c;但是如果我…在日常业务开发的过程中我们经常会遇到存在高并发的场景这个时候都会选择使用redis来实现一个锁来防止并发。
但是很多时候我们可能业务完成后就需要把锁释放掉给下一个线程用但是如果我们忘记了释放锁可能就会存在死锁的问题。对于使用锁不太熟练的话这种情况时常发生虽然很多时候我们的锁是有过期时间的但是如果忘记了释放那么在这个过期时间内还是会存在大的损失。
还有一点就是在我们使用redis实现一个锁的时候我们需要导入redisClient设置key设置过期时间设置是否锁等等一些重复的操作。前面的哪些步骤很多都是重复的所以我们可以想一个方法来把重复的东西都抽象出来做成统一的处理同时哪些变化的值提供一个设置的入口。
抽出来的东西我们还可以封装成一个spring-boot-stater,这样我们只需要写一份就可以在不同的项目中使用了。说干就干下面我们使用redisson完成一个自动锁的starter。
实现
首先我们分析一下哪些东西是我们需要进行合并哪些又是需要提供给使用方的。得到下面的一些问题 加锁、释放锁过程 我们需要合并起来 锁key加锁时间......这些需要给使用方注入 锁的key该怎么去生成很多时候我们需要根据业务字段去构造一个key比如 user:{userId},那么这个userId该怎么获取
我们从上面需要解决的问题去思考需要怎么去实现。我们需要封装一些公共的逻辑又需要提供一些配置的入库这样的话我们可以尝试一种方法使用 注解AOP,通过注解的方式完成加锁、解锁。很多时候如果需要抽出一些公共的方法会用到注解AOP去实现
定义注解
AutoLock 注解
一个锁需要有的信息有key加锁的时间时间单位是否尝试加锁加锁等待时间 等等。如果还有其他的业务需要可以添加一个扩展内容自己去解析处理 那么这个注解的属性就可以知道有哪些了
/*** 锁的基本信息*/
Target({ElementType.METHOD})
Documented
Retention(RetentionPolicy.RUNTIME)
public interface AutoLock {/*** 锁前缀*/String prefix() default anoxia:lock;/*** 加锁时间*/long lockTime() default 30;/*** 是否尝试加锁*/boolean tryLock() default true;/*** 等待时间-1 不等待*/long waitTime() default -1;/*** 锁时间类型*/TimeUnit timeUnit() default TimeUnit.MILLISECONDS;}LockField 注解
这个注解添加到参数属性上面用来解决上面提到获取不同的业务参数内容构造key的问题。所以我们需要提供一个获取哪些字段来构造这个key配置这里需要考虑两个问题: 1、参数是基本类型 2、参数是引用类型 - 这种类型需要从对象中拿到对象的属性值
/*** 构建锁的业务数据* author huangle* date 2023/5/5 15:01*/
Target({ElementType.PARAMETER})
Documented
Retention(RetentionPolicy.RUNTIME)
public interface LockField {String[] fieldNames() default {};}定义切面
重点就在这个切面里面我们需要在这里完成key的合成锁的获取与释放。整个过程可以分为以下几步 获取锁的基本信息构建key 加锁执行业务 业务完成释放锁
/*** 自动锁切面* 处理加锁解锁逻辑** author huangle* date 2023/5/5 14:50*/
Aspect
Component
public class AutoLockAspect {private final static Logger LOGGER LoggerFactory.getLogger(AutoLockAspect.class);Resourceprivate RedissonClient redissonClient;private static final String REDIS_LOCK_PREFIX anoxiaLock;private static final String SEPARATOR :;/*** 定义切点*/Pointcut(annotation(cn.anoxia.lock.annotation.AutoLock))public void lockPoincut() {}/*** 定义拦截处理方式** return*/Around(lockPoincut())public Object doLock(ProceedingJoinPoint joinPoint) throws Throwable {// 获取需要加锁的方法MethodSignature methodSignature (MethodSignature) joinPoint.getSignature();Method method methodSignature.getMethod();// 获取锁注解AutoLock autoLock method.getAnnotation(AutoLock.class);// 获取锁前缀String prefix autoLock.prefix();// 获取方法参数Parameter[] parameters method.getParameters();StringBuilder lockKeyStr new StringBuilder(prefix);Object[] args joinPoint.getArgs();// 遍历参数int index -1;LockField lockField;// 构建keyfor (Parameter parameter : parameters) {Object arg args[index];lockField parameter.getAnnotation(LockField.class);if (lockField null) {continue;}String[] fieldNames lockField.fieldNames();if (fieldNames null || fieldNames.length 0) {lockKeyStr.append(SEPARATOR).append(arg);} else {ListObject filedValues ReflectionUtil.getFiledValues(parameter.getType(), arg, fieldNames);for (Object value : filedValues) {lockKeyStr.append(SEPARATOR).append(value);}}}String lockKey REDIS_LOCK_PREFIX SEPARATOR lockKeyStr;RLock lock redissonClient.getLock(lockKey);// 加锁标志位boolean lockFlag false;try {long lockTime autoLock.lockTime();long waitTime autoLock.waitTime();TimeUnit timeUnit autoLock.timeUnit();boolean tryLock autoLock.tryLock();try {if (tryLock) {lockFlag lock.tryLock(waitTime, lockTime, timeUnit);} else {lock.lock(lockTime, timeUnit);lockFlag true;}}catch (Exception e){LOGGER.error(加锁失败错误信息, e);throw new RuntimeException(加锁失败);}if (!lockFlag) {throw new RuntimeException(加锁失败);}// 执行业务return joinPoint.proceed();} finally {// 释放锁if (lockFlag) {lock.unlock();LOGGER.info(释放锁完成key{},lockKey);}}}}获取业务属性
这个是一个获取对象中字段的工具类在一些常用的工具类里面也有实现可以直接使用也可以自己实现一个
/*** author huangle* date 2023/5/5 15:17*/
public class ReflectionUtil {public static ListObject getFiledValues(Class? type, Object target, String[] fieldNames) throws IllegalAccessException {ListField fields getFields(type, fieldNames);ListObject valueList new ArrayList();Iterator fieldIterator fields.iterator();while(fieldIterator.hasNext()) {Field field (Field)fieldIterator.next();if (!field.isAccessible()) {field.setAccessible(true);}Object value field.get(target);valueList.add(value);}return valueList;}public static ListField getFields(Class? claszz, String[] fieldNames) {if (fieldNames ! null fieldNames.length ! 0) {ListString needFieldList Arrays.asList(fieldNames);ListField matchFieldList new ArrayList();ListField fields getAllField(claszz);Iterator fieldIterator fields.iterator();while(fieldIterator.hasNext()) {Field field (Field)fieldIterator.next();if (needFieldList.contains(field.getName())) {matchFieldList.add(field);}}return matchFieldList;} else {return Collections.EMPTY_LIST;}}public static ListField getAllField(Class? claszz) {if (claszz null) {return Collections.EMPTY_LIST;} else {ListField list new ArrayList();do {Field[] array claszz.getDeclaredFields();list.addAll(Arrays.asList(array));claszz claszz.getSuperclass();} while(claszz ! null claszz ! Object.class);return list;}}
}配置自动注入
在我们使用 starter 的时候都是通过这种方式来告诉spring在加载的时候完成这个bean的初始化。这个过程基本是定死的。就是编写配置类如果通过springBoot的EnableAutoConfiguration来完成注入。注入后我们就可以直接去使用这个封装好的锁了。
/*** author huangle* date 2023/5/5 14:50*/
Configuration
public class LockAutoConfig {Beanpublic AutoLockAspect autoLockAspect(){return new AutoLockAspect();}}// spring.factories 中内容
org.springframework.boot.autoconfigure.EnableAutoConfigurationcn.anoxia.lock.config.LockAutoConfig测试
我们先打包这个sarter然后导入到一个项目里面打包导入的过程就不说了自己去看一下就可以 直接上测试类下面执行后可以看到锁已经完成了释放。如果业务抛出异常导致中断也不用担心锁不会释放的问题因为我们是在 finally 中释放锁的
/*** author huangle* date 2023/5/5 14:28*/
RestController
RequestMapping(/v1/user)
public class UserController {AutoLock(lockTime 3, timeUnit TimeUnit.MINUTES)GetMapping(/getUser)public String getUser(RequestParam LockField String name) {return hello:name;}PostMapping(/userInfo)AutoLock(lockTime 1, timeUnit TimeUnit.MINUTES)public String userInfo(RequestBody LockField(fieldNames {id, name}) UserDto userDto){return userDto.getId():userDto.getName();}}