引用
本文主要介绍redis的string类型的命令。主要包括以下几个
set key value [ex second | px mills] [nx | xx]
get key
del key
expire key second
strlen key
setrange key index value
getrange key low high
mset key value key2 value2
msetnx key value key2 value2
append key value
incr key
incrby key value
decr key
decrby key value
getset key resetvalue
挨个介绍
记住redis的key都是string类型
设置一个string类型的value
set key value [ex second | px mills] [nx | xx]
- ex是设置过期时间 单位是s
- px也是过期时间 单位是ms
- nx是不存在才执行命令
- xx是存在才执行命令
获取一个string类型的值
get key
删除一个string类型的值
del key
对所有类型的值都有效
设置一个键的过期时间,单位是s
expire key second
获取string类型值的长度
我试了下一个中文字符的长度3
strlen key
对一个string值从index开始填充value字符串。
setrange key index value`
获取一个string值的某一段 low和high是下标,获取的范围是[low,high]。会返回范围内出string值,不存在就返回空
getrange key low high
一次性设置多个key value对。这是一个原子操作
mset key value key2 value2
一次性设置多个key value,且只在key不存在的情况下。这也是一个原子操作。当有一个key设置失败时,本次所有key的设置都不生效
msetnx key value key2 value2
对一个string类型的number自增1,如果key不存在那么先设置一个0的key字符串。再自增1.这是原子操作
incr key
与incr相同,但是自增的步长可以自己指定
incrby key step
与incr类型,对自减1
decr key
与incrby类型, 值是自减的
decrby key step
对指定key的字符串末尾添加一个字符串。如果key不存在就相当于set命令
append key value
获取一个key的值,并且覆盖这个key的值
getset key resetvalue
案例一 分布式锁
redis的CAP模型中满足的是CP模型,但是本身分布式锁要求的是强一致性是一个CP模型。所有使用redis做分布式锁是不推荐的,但是小规模、不要求强一致性的情况下也是能快速实现分布式锁的一种方式。下面使用java代码实现一个简单版的分布式锁。
乞丐版
通过sexnx这个命令获取锁。
释放锁的时候就比较讲究了。因为线程执行完后释放锁的时候,可能锁已经过期了,此时需要使用lua脚本删除锁,保证只删掉自己的锁。lua脚本是为了保证原子性。
此版本存在的问题有:
- 直接把uuid返回了,这样不符合设计模式,需要使用面对对象的法则,返回锁对象。提供release方法
- 不支持重入,多次获取自己的锁会失败
- 没有控制锁释放的权限,其他线程知道uuid也能把锁释放了。
- 锁的释放也需要支持重复,且需要返回正确的结果。
@Component
public class RedisDistributeLock {
private static RedisTemplate<String, String> redisTemplate;
public static String getLock(String key,int expire) throws RuntimeException {
String uniq = UUID.randomUUID().toString();
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, uniq, expire, TimeUnit.SECONDS);
if (null == result || !result) {
throw new RuntimeException();
}
return uniq;
}
public static Boolean release(String key, String uniq) {
return redisTemplate.execute(
(RedisCallback<Boolean>) connection -> connection.scriptingCommands()
.eval(("if redis.call('get',KEYS[1]) == "
+ "ARGV[1] "
+ "then "
+ "return redis.call('del',KEYS[1]) "
+ "else return 0 end").getBytes(), ReturnType.BOOLEAN,
1,
key.getBytes() , uniq.getBytes()));
}
@Autowired
public void setRedisTemplate(
RedisTemplate<String, String> redisTemplate) {
RedisDistributeLock.redisTemplate = redisTemplate;
}
}
平民版
- 把uuid存入了线程本地变量,解决了直接返回给用户这一不合理的设计
- 支持了重入
- 控制锁释放的权限
这个版本的问题在于没有实现等待且超时的机制,没有满足客户端期望尝试一段时间内去获取锁
/**
* @author djcao
* @workcode BG389966
* @date 2020/6/4
*/
@Component
public class RedisDLock implements DistributeLock
{
ThreadLocal<Map<String,String>> threadLocal = ThreadLocal.withInitial(HashMap::new);
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Boolean lock(String key, Integer timeout, TimeUnit timeUnit) {
String lockedValue = threadLocal.get().get(key);
if (!StringUtils.isEmpty(lockedValue)) {
return true;
}
String value = UUID.randomUUID().toString();
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, timeUnit);
if (result != null && result) {
threadLocal.get().put(key, value);
}
return result;
}
@Override
public Boolean unlock(String key) {
String value = threadLocal.get().get(key);
if (value == null) {
return true;
}
redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.scriptingCommands().eval(("if redis.call('get', KEYS[1] ) == "
+ "ARGV[1] then return redis.call('del',KEYS[1]) else 0 end").getBytes(),
ReturnType.BOOLEAN,1,
key.getBytes(),threadLocal.get().get(key).getBytes()));
threadLocal.get().remove(key);
return true;
}
}
土豪版
需要使用到后面的watch机制。
思路如下
如果设置不成功,那么对一个key进行watch,当key被删除后再尝试获取。
案例二 限速器
我们可以使用限速器有效解决恶意用户和爬虫频繁调用接口的问题。
/**
* @author djcao
* @workcode BG389966
* @date 2020/6/6
*/
@Component
public class RedisLimit {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 限制QPS
*
* @param ip key
* @param limit limit
* @return
*/
public Boolean entry(String ip, Long limit) {
if (BooleanUtils.isTrue(redisTemplate.hasKey(ip))) {
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(ip, "1", 1, TimeUnit.SECONDS);
if (result == null || !result) {
return incr(ip) > limit;
}
return result;
}
return incr(ip) > limit;
}
private Long incr(String key) {
return redisTemplate.opsForValue().increment(key);
}
}