前言

在平时的开发中我们都需要处理重复提交的问题,避免业务出错或者产生脏数据,虽然可以通过前端控制但这并不是可以完全避免,最好的方式还是前后端均进行控制,这样的话就可以更有效,尽可能全面的去减少错误的发生。

一、比如我们注册的时候需要发送验证码

如果用户频繁点击或者恶意攻击的话就会造成不断的请求对服务器产生很大的压力,为了避免这种情况我们需要做处理,传统的模式中是在数据库中记录手机号、验证码已经发送时间,再次请求的时候呢去数据库查询是否有该手机号记录,并校验是否超过间隔时间,如果超过则重新发送并更新时间,否组不予发送,这样有一个缺点就是如果同时又很多人在做相同的业务同时查询就会对数据库造成很大的压力。

根据此种情况我们可以使用Redis incrde 原子性递增,来解决这种高并发的秒杀或者分布式序列号生成等场景。鉴于本场景我们只用他来做计数实现间隔时间内只接收一次请求。

实现逻辑:在发送短信之后使用Redis的incr设置一个递增的KEY(根据自己的需要设定但是要保证每一个人的唯一),来判断该KEY的数值,如果等于1说明这是第一次请求,发送短信记录日志,并设置有效期,如果不等于的话说明是间隔时间内多次请求,就提示请求频繁,稍后重试。

 String redisKey = "SMS_SEND_" + smsPhone;
long count = redisTemplate.opsForValue().increment(redisKey, 1);
if (count == 1) {
//设置有效期一分钟
redisTemplate.expire(redisKey, 60, TimeUnit.SECONDS);
}
if (count > 1) {
resultMap.put("retCode", "-1");
resultMap.put("retMsg", "每分钟只能发送一次短信");
outPrintJson(resultMap);
return;
}
/** 发送短信 */
......
/** 记录发送日志 */
......

二、上述方式可以解决特定的问题,当需要处理的情况多的话我们可以考虑使用切面来解决

代码如下:

 package com.slp.annotation;

 public class RedisLockBean {
private String key;
private int timeInSecond;
private String codeName;
private String msgName;
private String code;
private String msg;
private boolean isAtController;
private boolean isAtService;
private boolean isAtParameter;
private String returnType;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public int getTimeInSecond() {
return timeInSecond;
}
public void setTimeInSecond(int timeInSecond) {
this.timeInSecond = timeInSecond;
}
public String getCodeName() {
return codeName;
}
public void setCodeName(String codeName) {
this.codeName = codeName;
}
public String getMsgName() {
return msgName;
}
public void setMsgName(String msgName) {
this.msgName = msgName;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
} public boolean isAtController() {
return isAtController;
}
public void setAtController(boolean isAtController) {
this.isAtController = isAtController;
}
public boolean isAtService() {
return isAtService;
}
public void setAtService(boolean isAtService) {
this.isAtService = isAtService;
}
public boolean isAtParameter() {
return isAtParameter;
}
public void setAtParameter(boolean isAtParameter) {
this.isAtParameter = isAtParameter;
}
public String getReturnType() {
return returnType;
}
public void setReturnType(String returnType) {
this.returnType = returnType;
}
@Override
public String toString() {
return "RedisLockBean [key=" + key + ", timeInSecond=" + timeInSecond
+ ", codeName=" + codeName + ", msgName=" + msgName + ", code="
+ code + ", msg=" + msg + ", isAtController=" + isAtController
+ ", isAtService=" + isAtService + ", isAtParameter="
+ isAtParameter + ", returnType=" + returnType + "]";
}
}
 package com.slp.annotation;

 import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 解决的问题:<br>
* 1.数据库加锁性能较差<br>
* 2.数据库加锁,若相应线程异常,所无法释放<br>
* 注意事项:<br>
* 方法的返回值对象必须包含错误码,错误信息属性及其的get方法
*
*/
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
/**
* 若加注解的入参时基本数据类型(int,long)或String时,fieldName无效<br>
* 若注解的参数是自定义对象时,请注意一下几点:<br>
* 1.确保定义有相应属性public修饰的get方法<br>
* 2.get方法的返回参数是基本的数据类型或String<br>
* 3.get方法的返回值不为空<br>
* 否则,加锁失败.
* @return
*/
String[] fieldName() default {};
/**
* 锁的有效时间,单位为秒,默认值为1
* @return
*/
int timeInSecond() default 1;
/**
* 加锁,锁已被其它请求获取时,直接返回重复提交,codeName指定返回对象的返回码对应的属性,默认值'code'
* @return
*/
String codeName() default "code";
/**
* 加锁,锁已被其它请求获取时,直接返回重复提交,msgName指定返回对象的返回信息对应的属性,默认值'msg'
* @return
*/
String msgName() default "msg";
/**
* 加锁,锁已被其它请求获取时,直接返回重复提交,code指定返回对象的返回码对应的值,默认值'09'
* @return
*/
String code() default "09";
/**
* 加锁,锁已被其它请求获取时,直接返回重复提交,msg指定返回对象的返回码对应的值,默认值'重复提交'
* @return
*/
String msg() default "重复提交";
/**
* 注解作用与方法时,指定参数在参数列表中的索引
*/
int paramIndex() default 0;
}
 package com.slp.annotation;

 import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import com.alibaba.fastjson.JSONObject;
import com.cul.culsite.common.RedisKeyConstants;
import com.cul.culsite.service.RedisService;
import com.cul.culsite.util.DateUtil;
import com.cul.culsite.util.OxmHelper; @Component
public class RedisLockAspect {
private final static Logger logger = LoggerFactory.getLogger(RedisLockAspect.class); protected static final String XML_TYPE = "xml";
protected static final String JSON_TYPE = "json";
protected static final String ILLEGAL_TYPE = "illegal type";
@Autowired
private RedisService redisService; public Object redisLockParse(ProceedingJoinPoint p) throws Throwable{
Signature signature = p.getSignature();
boolean isRepetition = false;
RedisLockBean redisLockBean = null;
String value = System.nanoTime()+"";
if(signature instanceof MethodSignature){
//获得接口中定义的方法的Method,但注解时加载实现类中方法的参数上
MethodSignature methodSignature = (MethodSignature)signature;
Method serviceMethod = methodSignature.getMethod(); try {
Method serviceImpMethod = p.getTarget().getClass().getMethod(serviceMethod.getName(), serviceMethod.getParameterTypes());
//获取key值
redisLockBean = getRedisLockKey(p.getTarget(),serviceImpMethod,p.getArgs());
//成功获取key值,在redis中加锁
if(redisLockBean!=null){
logger.info("redis lock value is :{}",value);
boolean isPutSuccess =redisService.setIfAbsent(redisLockBean.getKey(), value, redisLockBean.getTimeInSecond());
//加锁失败,直接返回
if(!isPutSuccess){
logger.info("get redis lock fail for {}",redisLockBean.getKey());
if(redisLockBean.isAtParameter()||redisLockBean.isAtService()){
Class<?> returnType = serviceImpMethod.getReturnType();
//加锁方法有返回值
if(!returnType.getName().equals(java.lang.Void.class.getName())){
//实例化返回值对象
try {
Object result = returnType.newInstance();
//设置返回码
returnType.getMethod(getSetMethodNameByFieldName(redisLockBean.getCodeName()), java.lang.String.class).invoke(result, redisLockBean.getCode());
//设置返回信息
returnType.getMethod(getSetMethodNameByFieldName(redisLockBean.getMsgName()), java.lang.String.class).invoke(result, redisLockBean.getMsg());
return result;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}else{
throw new RuntimeException("@RedisLock作用的方法没有返回参数");
}
}else if(redisLockBean.isAtController()){
Map<String,String> result = new HashMap<String,String>();
result.put(redisLockBean.getCodeName(), redisLockBean.getCode());
result.put(redisLockBean.getMsgName(), redisLockBean.getMsg());
return response(redisLockBean.getReturnType()==null?"json":redisLockBean.getReturnType(), result);
}
}else{
logger.info("get redis lock success for {}",redisLockBean.getKey());
isRepetition = true;
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
Object result = null;
try {
result = p.proceed();
} catch (Throwable e) {
throw e;
}finally{
if(redisLockBean!=null){
if(isRepetition&&value.equals(redisService.get(redisLockBean.getKey()))){
logger.info("lock has released :{}",redisLockBean.getKey());
redisService.delete(redisLockBean.getKey());
} }
}
return result;
} private RedisLockBean getRedisLockKey(Object target,Method method,Object... object){
if(target == null){
throw new RuntimeException("get redis lock key error,target is null");
}
if(method==null){
throw new RuntimeException("get redis lock key error,method is null");
}
List<String> fieldValueList = new ArrayList<String>();
RedisLockBean redisLockBean = new RedisLockBean();
RedisLock redisLock = null;
//类上有@Controller说明@RedisLock是放在请求方法上,使用HttpServletRequest获取请求参数
if(method.isAnnotationPresent(RedisLock.class)&&target.getClass().isAnnotationPresent(Controller.class)){
//controller层方法时对外开放的接口
if(method.isAnnotationPresent(RequestMapping.class)){
redisLock = method.getAnnotation(RedisLock.class);
//获取方法中的HttpServletRequest类型的参数
HttpServletRequest request = null;
for(Object para:object){
if(para instanceof HttpServletRequest){
request = (HttpServletRequest)para;
break;
}
}
if(request==null){
throw new RuntimeException("@RedisLock作用于controller层方法时,方法需要包含HttpServletRequest类型的参数");
}
//未定义加锁参数时,默认使用mac
String[] paraName = redisLock.fieldName();
if(paraName==null||paraName.length==0){
paraName=new String[]{"mac"};
}
for(String para:paraName){
fieldValueList.add(request.getParameter(para));
}
if(fieldValueList.isEmpty()){
throw new RuntimeException("@RedisLock作用于controller层方法时,生成key失败,请求中没有mac签名");
}
//标示注解作用在controller成方法上
redisLockBean.setAtController(true);
}else{
throw new RuntimeException("@RedisLock作用于controller层的方法时,该方法上需要使用@RequestMapping注解");
}
//注解作用于非controller层方法上
}else if(method.isAnnotationPresent(RedisLock.class)){
redisLock = method.getAnnotation(RedisLock.class);
//参数的索引位置
int index = redisLock.paramIndex();
String[] fieldName = redisLock.fieldName();
String[] values = getFieldValue(object[index],fieldName);
//注解的参数时基本的数据类型或String,不需要传入属性名称,否则设置的属性,都必须获得该属性值
if(values==null || values.length!=fieldName.length && fieldName.length>0){
return null;
}
fieldValueList.addAll(Arrays.asList(values));
redisLockBean.setAtService(true);
}else{
Annotation[][] annotations;
annotations = method.getParameterAnnotations();
for(int i=0;i<annotations.length;i++){
for(Annotation annotation:annotations[i]){
if(annotation instanceof RedisLock){
RedisLock redisLockTmp = (RedisLock)annotation;
if(redisLock==null){
redisLock = redisLockTmp;
}
String[] fieldName = redisLockTmp.fieldName();
String[] values = getFieldValue(object[i],fieldName);
//注解的参数时基本的数据类型或String,不需要传入属性名称,否则设置的属性,都必须获得该属性值
if(values==null || values.length!=fieldName.length && fieldName.length>0){
return null;
}
fieldValueList.addAll(Arrays.asList(values));
redisLockBean.setAtParameter(true);
}
}
}
}
//未使用注解
if(fieldValueList.isEmpty()){
return null;
} //设置其它参数值
if(redisLockBean.getTimeInSecond()==0){
redisLockBean.setTimeInSecond(redisLock.timeInSecond());
}
if(StringUtils.isEmpty(redisLockBean.getCodeName())){
redisLockBean.setCodeName(redisLock.codeName());
}
if(StringUtils.isEmpty(redisLockBean.getCode())){
redisLockBean.setCode(redisLock.code());
}
if(StringUtils.isEmpty(redisLockBean.getMsgName())){
redisLockBean.setMsgName(redisLock.msgName());
}
if(StringUtils.isEmpty(redisLockBean.getMsg())){
redisLockBean.setMsg(redisLock.msg());
} Collections.sort(fieldValueList);
logger.info("all value of fieldName is {}",fieldValueList);
//生成key值
StringBuilder builder = new StringBuilder();
builder.append(target.getClass().getName())
.append("-")
.append(method.getName())
.append("-")
.append(Arrays.asList(method.getParameterTypes()))
.append("-")
.append(fieldValueList);
String lockKey = RedisKeyConstants.REDIS_LOCK + builder.toString();
logger.info("redis lock key is :{}",builder.toString());
redisLockBean.setKey(lockKey);
logger.info("redisLockBean :{}",redisLockBean.toString());
return redisLockBean;
}
private String[] getFieldValue(Object argObj,String...fieldName){
if(fieldName ==null || fieldName.length == 0){
return new String[]{getBaseClassValue(argObj)};
}
List<String> fieldsValue = new ArrayList<String>();
for(String field:fieldName){
String value = getFieldValue(argObj,field);
logger.info("value of fieldName '{}' is :{}",fieldName,value);
if(value!=null){
fieldsValue.add(value);
}
}
return fieldsValue.toArray(new String[0]);
}
private String getFieldValue(Object argObj,String fieldName){
if(argObj==null){
throw new RuntimeException("argObj is null,cannot get field value of fieldName");
}
String value = getBaseClassValue(argObj);
if(!StringUtils.isEmpty(value)){
return value;
}
String methodName = getGetMethodValueByFieldName(fieldName);
Object result = null;
try {
Method method = argObj.getClass().getMethod(methodName);
result = method.invoke(argObj);
} catch (NoSuchMethodException e) {
logger.error("method {} without parameter is not exists!",methodName);
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
logger.error("method {} without parameter is not public!",methodName);
e.printStackTrace();
} catch (IllegalArgumentException e) {
logger.error("method {} has parameter!",methodName);
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
if(result==null){
logger.warn("method {} does not have returnValue",methodName);
return null;
}
return getBaseClassValue(result);
}
private String getBaseClassValue(Object object){
if(object==null){
throw new RuntimeException("argObj is null,cannot get field value ");
}
if(object instanceof String){
return object.toString();
}
if(object instanceof Integer){
int i = (Integer)object;
//剔除成员变量的默认值
if(i!=0){
return i+"";
}
}
if(object instanceof Long){
long i = (Long)object;
if(i!=0){
return i+"";
}
}
return null;
} private String getGetMethodValueByFieldName(String fieldName){
return getMethodNameByFieldNameAndPrefix("get",fieldName);
}
private String getSetMethodNameByFieldName(String fieldName){
return getMethodNameByFieldNameAndPrefix("set",fieldName);
}
private String getMethodNameByFieldNameAndPrefix(String prefix,String fieldName){
if(StringUtils.isEmpty(fieldName)){
throw new RuntimeException("cannot get Get method by null or length is 0");
}
if(StringUtils.isEmpty(prefix)){
throw new RuntimeException("cannot get Get method by null without prefix");
}
String getMethodName = prefix+fieldName.substring(0, 1).toUpperCase();
//fieldName 的长度大于一时,索引大于一的字符不改变大小写
if(fieldName.length()>1){
getMethodName = getMethodName + fieldName.substring(1);
}
return getMethodName;
} private String response(String type, Object obj) {
if (XML_TYPE.equalsIgnoreCase(type)) {
String ret = OxmHelper.marshal(obj);
logger.info("response:{}",ret);
return ret;
}
if (JSON_TYPE.equalsIgnoreCase(type)) {
String ret = JSONObject.toJSONString(obj);
logger.info("response:{}",ret);
return ret;
}
return ILLEGAL_TYPE + ":" + type;
}
}
 package com.slp.service.impl;

 import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert; import com.cul.culsite.service.RedisService; @Service
public class RedisServiceImpl implements RedisService {
private static final Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class);
@Autowired
private RedisTemplate<String, String> redisTemplate; /**
* 获取redis值
*
* @param key
* 键
* @return 值
*/
public String get(String key) {
Assert.hasText(key, "redis get key cannot null"); return redisTemplate.opsForValue().get(key);
} /**
* 删除redis键
*
* @param key
* 键
*/
@Override
public void delete(String key) {
Assert.hasText(key, "redis delete key cannot null");
redisTemplate.delete(key);
} /**
* 设置redis值
*
* @param key
* 键
* @param value
* 值
* @param time
* 时间(分钟) 如果小于0 默认为1分钟
*/
@Override
public boolean setIfAbsent(final String key,final String value,int time) {
Assert.hasText(key, "redis set key cannot null");
Assert.hasText(value, "redis set value cannot null"); if(time<=0){
time = 1;
}
final int timeInSecond = time;
try{ @SuppressWarnings("unchecked")
Object isSetSuccess = redisTemplate.execute(new SessionCallback() { @Override
public Object execute(RedisOperations arg0)
throws DataAccessException {
try{
//开始事务
List<Object> result=new ArrayList<Object>();
arg0.multi();
arg0.opsForValue().setIfAbsent(key, value);
// arg0.expireAt(key,DateUtils.addSeconds(new Date(),timeInSecond));
arg0.expire(key, timeInSecond, TimeUnit.SECONDS);
//提交事务
result= arg0.exec(); logger.info("redis mutil for get lock result is :{}",result);
//执行了两次redis操作,应该有两个返回值,否则防止key永久有效,执行删除操作
if(result == null||result.size()!=2){
redisTemplate.delete(key);
return false;
}
//获取加锁操作的返回结果
boolean setIfAbsentResult = false;
if(result.get(0) instanceof Boolean){
setIfAbsentResult =(Boolean)result.get(0);
}
//获取设置key有效时间返回结果
boolean expireAtResult = false;
if(result.get(1) instanceof Boolean){
expireAtResult = (Boolean)result.get(1);
}
if(setIfAbsentResult&&expireAtResult){
logger.info("加锁成功.......");
return true;
}
}catch(Exception e){
e.printStackTrace();
}
return false;
} });
if(isSetSuccess instanceof Boolean){
return (Boolean) isSetSuccess;
}
return false;
}catch(Exception e){
e.printStackTrace();
return false;
}
} @Override
public Set<String> keys(String keyPattern) {
Assert.hasText(keyPattern, "keys pattern is null"); return redisTemplate.keys(keyPattern);
} @Override
public long incr(String key) {
Assert.hasText(key, "key is null"); return redisTemplate.opsForValue().increment(key, 1L);
}
@Override
public long decr(String key) {
Assert.hasText(key, "key is null"); return redisTemplate.opsForValue().increment(key, -1L);
} @Override
public void set(String key, String value) {
Assert.hasText(key, "key is null");
Assert.hasText(value, "value is null");
redisTemplate.opsForValue().set(key, value);
} @Override
public boolean set(final String key, final long value, final int timeInSecond) {
Assert.hasText(key, "key is null");
Assert.hasText(value + "", "value is null");
Assert.hasText(timeInSecond + "", "timeInSecond is null"); try{ @SuppressWarnings("unchecked")
Object isSetSuccess = redisTemplate.execute(new SessionCallback() { @Override
public Object execute(RedisOperations arg0)
throws DataAccessException {
try{
//开始事务
List<Object> result=new ArrayList<Object>();
arg0.multi();
arg0.opsForValue().increment(key, value);
arg0.expire(key, timeInSecond, TimeUnit.SECONDS);
//提交事务
result= arg0.exec(); logger.info("result of redis set long value is :{}",result);
//执行了两次redis操作,应该有两个返回值,否则防止key永久有效,执行删除操作
if(result == null || result.size() != 2){
redisTemplate.opsForValue().increment(key, (0 - value));
return false;
}
//获取加锁操作的返回结果
long incrementResult = 0;
if(result.get(0) instanceof Long){
incrementResult =(Long)result.get(0);
}
//获取设置key有效时间返回结果
boolean expireAtResult = false;
if(result.get(1) instanceof Boolean){
expireAtResult = (Boolean)result.get(1);
}
if((incrementResult == value) && expireAtResult){
return true;
}
}catch(Exception e){
e.printStackTrace();
}
redisTemplate.opsForValue().increment(key, (0 - value));
return false;
} });
if(isSetSuccess instanceof Boolean){
return (Boolean) isSetSuccess;
}
return false;
}catch(Exception e){
e.printStackTrace();
return false;
} } public Long getLong(String key) {
try{
Set<String> keys = redisTemplate.keys(key);
//key指定的数据不存在
if (keys == null || keys.isEmpty()) {
return null;
}
return redisTemplate.opsForValue().increment(key, 0);
} catch (DataAccessException e) {
logger.info("error :{}", e);
logger.info("{}指定的数据不是数值类型", key);
throw new RuntimeException(key + "指定的数据不是数值类型");
}
} public Long getLongNoKeys(String key) {
try {
long keys = redisTemplate.opsForValue().increment(key, 0);
return keys;
} catch (DataAccessException e) {
logger.info("error :{}", e);
logger.info("{}指定的数据不是数值类型", key);
throw new RuntimeException(key + "指定的数据不是数值类型");
}
} /**
* 删除set集合中的对象
* @param key
* @param value
*/
@Override
public void srem(String key, String value) {
redisTemplate.boundSetOps(key).remove(value);
}
}

使用方式:

/**
*
* @Description:
* @param @param request
* @param @return
* @param @throws Exception
* @return String
* @throws
* @author liping.sang
* @date 2017-8-8
*/
@RedisLock(fieldName={"reqNo"},timeInSecond=3)
@RequestMapping(method = { RequestMethod.GET, RequestMethod.POST }, value = "/test2")
@ResponseBody
public String test2(HttpServletRequest request)
throws Exception {
 <aop:pointcut expression="execution(* com.slp.controller.Test.test(..))" id="test"/>

             <aop:around method="redisLockParse" pointcut-ref="test"/>            

【Redis使用系列】使用Redis做防止重复提交的更多相关文章

  1. 【Redis使用系列】redis设置登陆密码

    找到安装redis的配置文件,找到redis.comf文件找到#requirepass foobared 新建一行 requirepass  xxxx 你的密码 ,然后重启.再登录的时候可以登录,但是 ...

  2. 【Redis使用系列】Redis常用操作

    一.string类型的常用命令 set key value   #一个key对应一个value.多次赋值,会覆盖前面. setnx key value  #如果key存在则创建key1,并返回1,如果 ...

  3. resubmit 渐进式防重复提交框架简介

    resubmit resubmit 是一款为 java 设计的渐进式防止重复提交框架. 推荐阅读: 面试官:你们的项目中是怎么做防止重复提交的? resubmit 渐进式防重复提交框架简介 创作目的 ...

  4. 分布式缓存技术redis学习系列(四)——redis高级应用(集群搭建、集群分区原理、集群操作)

    本文是redis学习系列的第四篇,前面我们学习了redis的数据结构和一些高级特性,点击下面链接可回看 <详细讲解redis数据结构(内存模型)以及常用命令> <redis高级应用( ...

  5. [ 搭建Redis本地服务器实践系列三 ] :图解Redis客户端工具连接Redis服务器

    上一章 [ 搭建Redis本地服务器实践系列二 ] :图解CentOS7配置Redis  介绍了Redis的初始化脚本文件及启动配置文件,并图解如何以服务的形式来启动.终止Redis服务,可以说我们的 ...

  6. Python操作redis学习系列之(集合)set,redis set详解 (六)

    # -*- coding: utf-8 -*- import redis r = redis.Redis(host=") 1. Sadd 命令将一个或多个成员元素加入到集合中,已经存在于集合 ...

  7. Redis系列(二):Redis的数据类型及命令操作

    原文链接(转载请注明出处):Redis系列(二):Redis的数据类型及命令操作 Redis 中常用命令 Redis 官方的文档是英文版的,当然网上也有大量的中文翻译版,例如:Redis 命令参考.这 ...

  8. redis系列:redis介绍与安装

    前言 这个redis系列的文章将会记录博主学习redis的过程.基本上现在的互联网公司都会用到redis,所以学习这门技术于你于我都是有帮助的. 博主在写这个系列是用的是目前最新版本4.0.10,虚拟 ...

  9. 吊打面试官系列:Redis 性能优化的 13 条军规大全

    1.缩短键值对的存储长度 键值对的长度是和性能成反比的,比如我们来做一组写入数据的性能测试,执行结果如下: 从以上数据可以看出,在 key 不变的情况下,value 值越大操作效率越慢,因为 Redi ...

随机推荐

  1. UnityEditor--------------之Selection类的变量解析

    UnityEditor的Selection类 Unity官方文档:https://docs.unity3d.com/ScriptReference/Selection.html Selection S ...

  2. 嵌入式开发之hi3519---网络不通问题rmii

    http://www.ebaina.com/bbs/forum.php?mod=viewthread&tid=18092&extra=page%3D1%26filter%3Dtypei ...

  3. Android Notification和权限机制探讨

    近期为了在部门内做一次小型的技术分享.深入了解了一下Notification的实现原理.以及android的权限机制.在此做个记录.文章可能比較长,没耐心的话就直接看题纲吧. 先看一下以下两张图 图一 ...

  4. Windows环境下ELK(5.X)平台的搭建

    一.Windows环境下ELK平台的搭建(2.*) 1.安装配置Java环境 在Oracle官网获取最新版的Java版本,由于只是运行不是开发,所以也可以只下载JRE.官网:http://www.or ...

  5. C# int转string 每三位加一个逗号

    ; Console.WriteLine(aaaa.ToString("N0")); Console.WriteLine()); Console.WriteLine("架构 ...

  6. php foreach 传值还是传引用

    From: http://my.oschina.net/guomingliang/blog/215457 php 中遍历一个array时可以使用for或foreach,foreach的语法为:fore ...

  7. MacOS下MySQL配置

    先去官网下载一个 MySQL for mac http://www.cnblogs.com/xiaobo-Linux/ 命令行运行终端,运行下面两条命令: 1 2 alias mysql=/usr/l ...

  8. JSTL时间格式化项目小试

    我在之前的博客中虽然详尽的介绍了JSTL在各个载体中的用法,也介绍了它和EL的共同使用的好处,但是只是顺便提了一下JSTL的格式化. 今天在项目中遇到了一个小问题,也就想到这,马上就开始实践了一下,效 ...

  9. ubuntu-12.04.5-desktop-i386.iso:ubuntu-12.04.5-desktop-i386:安装Oracle11gR2

    ubuntu 桌面版的安装不介绍. 如何安装oracle:核心步骤和关键点. ln -sf /bin/bash /bin/sh ln -sf /usr/bin/basename /bin/basena ...

  10. 自动换行后缩进怎么做(CSS)?(可用于 Li y 元素的排版)

    <style type="text/css">li{ width:100px; border:1px solid #ccc; padding-left:25px; te ...