概述
Redis:远程字典服务Remote dictionary server
- 一、安装
- 1、安装步骤
- 2、其他命令
- 3、redis的类型
- 4、redis的监控功能watch
- 5、redis的订阅发布功能
- 6、关于缓存带来的一系列问题(缓存穿透等)
- 二、主从复制之哨兵模式的搭建
- 三、springboot集成redis之单机版
- 四、springboot集成redis之哨兵模式集群版
- 五、redis相关面试题
- 六、redis分布式锁的实现
- 七、redisson整合SpringCache,简化缓存开发
- 八、下面是自己用的,工作中的某些关于redis的
一、安装
1、安装步骤
1.上传redis的安装包,redis-4.0.14.tar.gz
2.将安装包进行解压缩
tar -zxf redis-4.0.14.tar.gz
3.进入redis的安装包
cd redis-4.0.14
4.使用make和install进行编译和安装
make PREFIX=/usr/local/redis/redis01 install #PREFIX参数是指安装在哪个目录下
5.拷贝redis配置文件redis.conf
到/usr/local/redis/redis01/bin目录中
cp redis.conf /usr/local/redis/redis01/bin
6.修改redis.conf配置文件
port 6379 #redis的端口
daemonize yes #后台启动redis
注意,按照以上步骤可能会出现以下错误:
- 问题一:如果在使用
make
命令的时候出现了gcc 命令未找到
错误(gcc:C程序的编译工具)
解决:使用命令yum install gcc-c++
去安装gcc - 问题二:再次
make
,出现了Jemalloc/jemalloc.h:没有那个文件或目录
解决:使用命令make distclean
之后再make
- 问题三:启动之前先设置一下后台启动,redis默认不是后台启动的。
启动redis服务命令:进入/usr/local/bin,输入命令redis-server redisconfig/redis.conf
,连接redis客户端命令:redis-cli -p 6379
测试redis性能:使用redis官方自带的redis-benchmark,模拟100个并发100000个请求
2、其他命令
redis命令查询官网地址:redis命令
3、redis的类型
- String,
- List(所有List命令都是以L开头的),
- Set(无序不重复集合,所有Set命令都是以S开头的),
- Hash(其实就是java的map,所有Hash命令都是以H开头的),
- Zset(有序集合,需要带个序号标识例如命令:
zadd myzset 1 onezset 2 twozset
redis会自动根据自己的算法对命令中的1、2进行排序), - Geospatial(地理经纬度,添加
GEOADD hubei:city 13.361389 38.115556 "武汉" 15.087269 37.502669 "襄阳"
查询:GEOPOS hubei:city "武汉"
) - Hyperloglog(基数统计:一批数据中的不重复元素的个数)
应用场景: - Bitmaps(位存储)
应用场景:
4、redis的监控功能watch
5、redis的订阅发布功能
(了解即可,一般使用)
6、关于缓存带来的一系列问题(缓存穿透等)
1、缓存穿透
2、缓存雪崩
3、缓存击穿
二、主从复制之哨兵模式的搭建
1、搭建主从复制,搭建成功后从机只能读,不能写。
补充说明:
1、不能写是指不能在从机上直接执行写操作,而是由主机发送写操作命令给从机,从机再写。
2、仅仅是单纯的主从复制模式,主机宕机后从机就会一直等着主机复活,在此期间无法进行写操作。如果不想再等主机复活,想手动重新选举主机,可在某从机中使用命令slaveof no one
让自己变为主机,但其他从机还得手动配置新主机,所以后来2.8版本后有了哨兵模式,完全实现自动化选举,无需手动。
3、在非哨兵模式的主从复制下,如果使用的是在redis里敲命令slaveof ip port
,一旦此从机宕机,再次启动时就会成为主机,slave
就会失效,要想持久化此配置,需要去此从机的redis.conf
里,搜索slaveof
,配置后保存。
启动三台端口分别为6379、6380、6381的redis。具体步骤如下:
2、在主从复制的基础上搭建哨兵模式。下面只建了一个哨兵,如果需要多个,直接拷贝多个sentinel.conf,分别启动即可
补充:上面配置哨兵模式时使用的sentinel.conf只配置了一个监控当前主机的功能,其实sentinel.conf还有些其他配置,如下图:
三、springboot集成redis之单机版
springboot2.x以上使用的是Lettuce,不再使用jedis,可参考:spring boot整合 lettuce 、 spring boot整合 lettuce
1、集成步骤
2、细节补充
-
(1)如果需要往磁盘写入,持久化某实体类,必须将此类实现序列化接口
-
(2)redisTemplate方法对应Redis五种数据类型:string、hash、list、set、zset,但是一般使用字符串类型opsForValue()即可,如下图
-
(3)在使用redis图形化工具时,我们查看的key 和value都是没有序列化的编码格式,不直观,这时我们可以设置redisTemplate的序列化编码格式
-
(4)最好是不使用自带的RedisTemplate,自己写一个封装的配置类,封装好存储的序列化步骤,代码如下:
package com.ws.wssf.business.config;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean(name="lockRedisTemplate")
public RedisTemplate<String, Object> lockRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(stringRedisSerializer);
//连接Redis
template.setConnectionFactory(factory);
template.afterPropertiesSet();
return template;
}
@Bean(name = "redisTemplate")
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = getJackson2JsonRedisSerializer();
template.setConnectionFactory(factory);
// key序列化方式
template.setKeySerializer(redisSerializer);
// value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
// value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
/**配合cache注解使用
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
// 对每个缓存空间应用不同的配置
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("access", getRedisCacheConfiguration().entryTtl(Duration.ofSeconds(7180L)));//access_token这个缓存空间7180秒
return RedisCacheManager.builder(factory)
.cacheDefaults(getRedisCacheConfiguration())//默认缓存
.withInitialCacheConfigurations(configMap)//缓存策略
.build();
}
/**RedisCacheConfiguration默认状态
* @return
*/
private RedisCacheConfiguration getRedisCacheConfiguration() {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(getJackson2JsonRedisSerializer()))
.disableCachingNullValues();
return config;
}
/**Jackson2JsonRedisSerializer默认配置
* @return
*/
private Jackson2JsonRedisSerializer<Object> getJackson2JsonRedisSerializer(){
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
return jackson2JsonRedisSerializer;
}
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
- (5)对于一些java操作redis基本的命令,也建议封装一下,比如String的设置值、获取值,数字的自增自减,代码如下:
package com.ws.wssf.business.util;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import com.google.gson.Gson;
@Component
public class RedisUtil {
@Resource(name="redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
/*** 字符串类型redis命令封装 ******/
/**
* 基本数据类型赋值
*
* @param key
* 键
* @param value
* 值
*/
public boolean setKeyValue(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
return false;
}
}
/**删除key
* @param key
* @return
*/
public boolean deleteKeyValue(String key) {
try {
redisTemplate.delete(key);
return true;
} catch (Exception e) {
return false;
}
}
/**基本数据类型赋值
* @param key 键
* @param value 值
* @param timeout 超时时间
* @param timeType 时间类型:时、分、秒
* @return
*/
public boolean setKeyValue(String key, Object value,int timeout,TimeUnit timeType) {
try {
redisTemplate.opsForValue().set(key, value);
redisTemplate.expire(key, timeout, timeType);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 基本数据获取值
*
* @param key
* @return
*/
public Object getValueByKey(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 递增key(实现id的递增、统计文章的访问量等)
*
* @param key
* @param delta 歩长
* @param timeout 超时时间
* @param timeType 时间类型
* @return
*/
public long incrKey(String key, long delta, int timeout, TimeUnit timeType) {
long num = redisTemplate.opsForValue().increment(key, delta);
if(num==1)redisTemplate.expire(key, timeout, timeType);//第一次设置超时时间为一天
return num;
}
/**
* 递增key(实现id的递增、统计文章的访问量等)
*
* @param key
* @param delta
* 歩长
* @return
*/
public long incrKey(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key
* 键
* @param by
* 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/**
* 向尾部追加值
*
* @param key
* @param append
* 追加的值
*/
public void appendKeyValue(String key, String append) {
redisTemplate.opsForValue().append(key, append);
}
/**
* 位操作,根据偏移量获取值
*
* @param key
* @param offset
* @return true:当前二进制位是1,false:0 如果需要获取的二进制位的索引超出了键值的二进制位的实际长度则默认位值是0:
*/
public boolean getBit(String key, Integer offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
/**
* 位操作,根据偏移量设置值
*
* @param key
* @param offset
* @param value
* @return如果要设置的位置超过了键值的二进制位的长度,SETBIT命令会自动将中间的二进制位 设置为0,同理设置一个不存在的键的指定二进制位的值会自动将其前面的位赋值为0
*/
public boolean setBit(String key, Integer offset, boolean value) {
return redisTemplate.opsForValue().setBit(key, offset, value);
}
/**BITCOUNT命令可以获得字符串类型键中值是1的二进制位个数
* @param key
* @return
*/
public long bitCount(String key) {
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
}
/**可以通过参数来限制统计的字节范围,如我们只希望统计前两个字节
* bitCountRange key(值为aar) 0 1(即"aa"):6
* @param key
* @param start
* @param end
* @return
*/
public Long bitCountRange(String key,long start,long end) {
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes(), start, end));
}
/**BITOP命令可以对多个字符串类型键进行位运算,并将结果存储在destkey参数指定的键
中。BITOP命令支持的运算操作有AND、OR、XOR 和NOT
* @param op :AND, OR, XOR, NOT;
* @param saveKey
* @param desKey
* @return
*/
public long bitOp(RedisStringCommands.BitOperation op, String saveKey, String... desKey) {
byte[][] bytes = new byte[desKey.length][];
for (int i = 0; i < desKey.length; i++) {
bytes[i] = desKey[i].getBytes();
}
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitOp(op, saveKey.getBytes(), bytes));
}
/****************************字符串类型命令结束*******************************/
/****************************散列类型命令开始********************************/
/**散列根据key插入某个属性的值
* @param key 键
* @param field 属性
* @param value 值
* @return 当执行的是插入操作时(即之前字段不存在)HSET命令会返回1,当执行的是更新操作时(即之前字段已经存在)HSET命令会返回0
*/
public void hSet(String key,Object field,Object value){
//putIfAbsent命令存在则返回不覆盖
redisTemplate.opsForHash().put(key, field, value);
}
/**散列根据key获取某个属性的值
* @param key
* @param field
* @param value
* @return
*/
public Object hGet(String key,Object field){
return redisTemplate.opsForHash().get(key,field);
}
/**散列设置map:多属性多值
* @param key
* @param dataMap
*/
public void hMSet(String key, Map<String, Object> dataMap){
redisTemplate.opsForHash().putAll(key,dataMap);
}
/**获取散列多个传入的属性的值
* @param key
* @param hashKeys
*/
public void hMGet(String key, Collection<Object> hashKeys){
redisTemplate.opsForHash().multiGet(key, hashKeys);
}
/**根据key来获取散列
* @param key
* @return
* @author julong
* @date 2017-12-8 上午10:24:03
*/
public Map<Object, Object> hGetAll(String key) {
return this.redisTemplate.opsForHash().entries(key);
}
/**判断散列中该字段是否存在
* @param key redis散列的key
* @param hashKey map的key
* @return
*/
public boolean hExists(String key,Object hashKey) {
return this.redisTemplate.opsForHash().hasKey(key, hashKey);
}
/**删除散列的属性(一个或者多个)
* @param key
* @param mapKey
* @return 返回值是被删除的字段个数
*/
public long hDel(String key,Object... mapKey){
return redisTemplate.opsForHash().delete(key, mapKey);
}
/**获取散列的key集合
* @param key
* @return
*/
public Set<Object> hKeys(String key){
return redisTemplate.opsForHash().keys(key);
}
/**HVALS命令与HKEYS命令相对应,HVALS命令用来获得键中所有字段值
* @param key
* @return
*/
public List<Object> hVals(String key){
return redisTemplate.opsForHash().values(key);
}
/**获得字段数量
* @param key
* @return
*/
public long hLen(String key){
return redisTemplate.opsForHash().size(key);
}
/*******************************散列类型命令结束******************************/
/*******************************列表类型命令开始******************************/
/**向列表左插入一个元素
* @param key
* @param value
* @return 返回的是列表的长度
*/
public long lPush(String key,Object value){
return redisTemplate.opsForList().leftPush(key, value);
}
/**向列表右插入一个元素
* @param key
* @param value
* @return 返回的是列表的长度
*/
public long rPush(String key,Object value){
return redisTemplate.opsForList().rightPush(key, value);
}
/**向列表左插入多个元素
* @param key
* @param values
* @return 返回的是列表的长度
*/
public long lPushAll(String key,Collection<Object> values){
return redisTemplate.opsForList().leftPushAll(key, values);
}
/**向列表右插入多个元素
* @param key
* @param values
* @return 返回的是列表的长度
*/
public long rPushAll(String key,Collection<Object> values){
return redisTemplate.opsForList().rightPushAll(key, values);
}
/**向列表左移除一个元素
* @param key
* @param value
* @return 返回被移除的元素值
*/
public Object lPop(String key){
return redisTemplate.opsForList().leftPop(key);
}
/**向列表右移除一个元素
* @param key
* @param value
* @return 返回被移除的元素值
*/
public Object rPop(String key){
return redisTemplate.opsForList().rightPop(key);
}
/**获取列表中元素的个数
* @param key
* @return 当键不存在时LLEN会返回0
*/
public long lLen(String key){
return redisTemplate.opsForList().size(key);
}
/**获得列表中的某一片段
* @param key
* @param start
* @param end
* @return (1)返回索引从start到stop之间的所有元素(包含两端的元素),起始索引为0
* (2)LRANGE命令也支持负索引,表示从右边开始计算序数,如 LRANGE key -2 -1:"-1"表示最右边第一个元
* 素,"-2"表示最右边第二个元素,依次类推
* (3) 显然,LRANGE numbers 0 -1可以获取列表中的所有元素。另外一些特殊情况如下。
*****(a)如果start的索引位置比stop的索引位置靠后,则会返回空列表。
*****(b)如果stop大于实际的索引范围,则会返回到列表最右边的元素:
*/
public List<Object> lRange(String key,long start,long end){
return redisTemplate.opsForList().range(key, start, end);
}
/**LREM命令会删除列表中前count个值为value的元素,返回值是实际删除的元素个数。根
*据count值的不同,LREM命令的执行方式会略有差异:
*●当count>0时LREM命令会从列表左边开始删除前count个值为value的元素;
*●当count<0时LREM 命令会从列表右边开始删除前|count|个值为value的元素;
*●当count=0是LREM命令会删除所有值为value的元素
* @param key
* @param count
* @param value
* @return 返回值是实际删除的元素个数
*/
public long lRem(String key,long count,Object value){
return redisTemplate.opsForList().remove(key, count, value);
}
/**将列表类型当作数组来用
* @param key
* @param index 索引
* @return 索引返回的元素,索引从0开始;如果index是负数则表示从右边开始计算的索引,最右边元素的索引是-1
*/
public Object lIndex(String key,long index){
return redisTemplate.opsForList().index(key, index);
}
/** LSET是另一个通过索引操作列表的命令,它会将索引为index的元素赋值为value
* @param key
* @param index
* @param value
*/
public void lSet(String key,long index,Object value){
redisTemplate.opsForList().set(key, index, value);
}
/**LTRIM命令可以删除指定索引范围之外的所有元素,其指定列表范围的方法和LRANGE命令相同
* @param key
* @param start
* @param end
*/
public void lTrim(String key,long start,long end){
redisTemplate.opsForList().trim(key, start, end);
}
/**LINSERT命令首先会在列表中从左到右查找值为pivot的元素,然后根据第二个参数是
*BEFORE还是AFTER来决定将value插入到该元素的前面还是后面
* @param key
* @param where
* @param pivot
* @param value
* @return 返回值是插入后列表的元素个数
*/
public long lInsert(String key,RedisListCommands.Position where,Object pivot,Object value){
return redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
Gson gson=new Gson();
return connection.lInsert(key.getBytes(), where, gson.toJson(pivot).getBytes(), gson.toJson(value).getBytes());
}
});
}
/**RPOPLPUSH命令会先从source列表类型键的右边弹出一个元素,然后将其加入
到destination列表类型键的左边,并返回这个元素的值,整个过程是原子的
* @param sourceKey
* @param destinationKey
* @return 返回source右边弹出元素的值
*/
public Object popLPush(String sourceKey,String destinationKey){
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);
}
/*******************************列表类型命令结束******************************/
/*******************************集合类型命令开始******************************/
/**SADD命令用来向集合中增加一个或多个元素
* @param key
* @param values
* @param return 返回值是成功加入的元素数量
*/
public long sAdd(String key,Object... values){
return redisTemplate.opsForSet().add(key, values);
}
/**SREM命令用来从集合中删除一个或多个元素
* @param key
* @param values
* @return 返回删除成功的个数
*/
public long sRem(String key,Object... values){
return redisTemplate.opsForSet().remove(key, values);
}
/**SMEMBERS命令会返回集合中的所有元素
* @param key
* @return
*/
public Set<Object> sMembers(String key){
return redisTemplate.opsForSet().members(key);
}
/**判断一个元素是否在集合中,无论集合中有多少个元素,SISMEMBER命令始终可以极快地返回结果
* @param key
* @param value
* @return
*/
public boolean sisMember(String key,Object value){
return redisTemplate.opsForSet().isMember(key, value);
}
/**SDIFF命令用来对多个集合执行差集运算。集合A与集合B的差集表示为A-B
* @param key
* @param otherKeys 其他key,一个或多个
* return 返回差集元素
*/
public Set<Object> sDiff(String key,Collection<String> otherKeys){
return redisTemplate.opsForSet().difference(key, otherKeys);
}
/**SINTER命令用来对多个集合执行交集运算
* @param key
* @param otherKeys
* @return
*/
public Set<Object> sInter(String key,Collection<String> otherKeys){
return redisTemplate.opsForSet().intersect(key, otherKeys);
}
/**SUNION命令用来对多个集合执行并集运算
* @param key
* @param otherKeys
* @return
*/
public Set<Object> sUnion(String key,Collection<String> otherKeys){
return redisTemplate.opsForSet().union(key, otherKeys);
}
/**SCARD命令用来获得集合中的元素个数
* @param key
* @return
*/
public long sCard(String key){
return redisTemplate.opsForSet().size(key);
}
/**SDIFFSTORE命令和SDIFF命令功能一样,唯一的区别就是前者不会直接返回运算结果,而是将结果存储在destination键中
* @param key
* @param otherKeys 其他key,一个或多个
* return
*/
public long sDiffStore(String key,Collection<String> otherKeys,String destKey){
return redisTemplate.opsForSet().differenceAndStore(key, otherKeys, destKey);
}
/**SINTERSTORE命令用来对多个集合执行交集运算,将结果存储在destination键中
* @param key
* @param otherKeys
* @return
*/
public long sInterStore(String key,Collection<String> otherKeys,String destKey){
return redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey);
}
/**sUnionStore命令用来对多个集合执行并集运算,将结果存储在destination键中
* @param key
* @param otherKeys
* @return
*/
public long sUnionStore(String key,Collection<String> otherKeys,String destKey){
return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
}
/**SRANDMEMBER命令用来随机从集合中获取多个元素,可以重复
* @param key
* @param count
* @return
*/
public List<Object> sRandMember(String key,long count){
return redisTemplate.opsForSet().randomMembers(key, count);
}
/**SPOP命令会从集合中随机选择多个元素弹出。
* @param key
* @param count
* @return
*/
public List<Object> sPop(String key,long count){
return redisTemplate.opsForSet().pop(key, count);
}
/*******************************集合类型命令结束******************************/
/*******************************有序集合类型命令结束***************************/
/**ZADD命令用来向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数
* @param key
* @param tuples
* @return 返回值是新加入到集合中的元素个数(不包含之前已经存在的元素)
*/
public boolean zAdd(String key,Object value,double score){
return redisTemplate.opsForZSet().add(key, value, score);
}
/*public long zAddAll(String key,Set<TypedTuple<?>> tuples){
return redisTemplate.opsForZSet().add(key, tuples);
}*/
/**获得元素的分数
* @param key
* @param value
*/
public double zScore(String key,Object value){
return redisTemplate.opsForZSet().score(key, value);
}
/**ZRANGE命令会按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素
* 索引都是从0开始,负数代表从后向前查找(-1表示最后一个元素);rangeWithScores命令带上分数
* @param key
* @param start
* @param end
* @return
*/
public Set<Object> zRange(String key,long start,long end){
return redisTemplate.opsForZSet().range(key, start, end);
}
/**ZREVRANGE命令是按照元素分数从大到小的顺序给出结果的
* @param key
* @param start
* @param end
* @return
*/
public Set<Object> zRevRange(String key,long start,long end){
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**该命令按照元素分数从小到大的顺序返回分数在min和max之间(包含min和max)的元素
* @param key
* @param min
* @param max
* @return
*/
public Set<Object> zRangeByScore(String key,double min,double max){
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
/**该命令按照元素分数从大到小的顺序返回分数在min和max之间(包含min和max)的元素
* @param key
* @param min
* @param max
* @return
*/
public Set<Object> zRevRangeByScore(String key,double min,double max){
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
/**该命令按照元素分数从小到大的顺序返回分数在min和max之间(包含min和max)指定位置的元素
* @param key
* @param min
* @param max
* @param offset 偏移量
* @param count 返回的个数
* @return
*/
public Set<Object> zRangeByScore(String key,double min,double max,long offset,long count){
return redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
}
/**该命令按照元素分数从大到小的顺序返回分数在min和max之间(包含min和max)指定位置的元素
* @param key
* @param min
* @param max
* @param offset 偏移量
* @param count 返回的个数
* @return
*/
public Set<Object> zRevRangeByScore(String key,double min,double max,long offset,long count){
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, offset, count);
}
/**ZINCRBY命令可以增加一个元素的分数
* @param key
* @param value 元素
* @param delta 增加的分数,负数为减分
* @return 返回增加后的分数
*/
public double zIncrby(String key,Object value,double delta){
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
/**获得集合中元素的数量
* @param key
* @return
*/
public long zCard(String key){
return redisTemplate.opsForZSet().size(key);
}
/**获得指定分数范围內的元素个数
* @param key
* @param min
* @param max
* @return
*/
public long zCount(String key,double min,double max){
return redisTemplate.opsForZSet().count(key, min, max);
}
/**删除一个或多个元素
* @param key
* @param values
* @return 返回值是成功删除的元素数量(不包含本来就不存在的元素)
*/
public long zRem(String key,Object... values){
return redisTemplate.opsForZSet().remove(key, values);
}
/**ZREMRANGEBYRANK命令按照元素分数从小到大的顺序(即索引0表示最小的值)删除
*处在指定排名范围内的所有元素
* @param key
* @param start
* @param end
* @return 返回删除的元素数量
*/
public long zRemRangeByRank(String key,long start,long end){
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
/**ZREMRANGEBYSCORE命令会删除指定分数范围内的所有元素
* @param key
* @param min
* @param max
* @return 返回删除的元素数量
*/
public long zRemRangeByScore(String key,double min,double max){
return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
}
/**ZRANK命令会按照元素分数从小到大的顺序获得指定的元素的排名(从0开始,即分数最
*小的元素排名为0
* @param key
* @param value
* @return
*/
public long zRank(String key,Object value){
return redisTemplate.opsForZSet().rank(key, value);
}
/**ZREVRANK命令则相反(分数最大的元素排名为0)
* @param key
* @param value
* @return
*/
public long zRevRank(String key,Object value){
return redisTemplate.opsForZSet().reverseRank(key, value);
}
/**ZINTERSTORE命令用来计算多个有序集合的交集并将结果存储在destination键中(同样
*以有序集合类型存储),返回值为destination键中的元素个数
* @param key
* @param otherKeys
* @param destKey
* @param aggregate
SUM:集合中该元素分数的和;
MIN:destination键中元素的分数是每个参与计算的集合中该元素分数的最小值
MAX:destination键中元素的分数是每个参与计算的集合中该元素分数的最大值
* @return
*/
public long zInterStore(String key,Collection<String> otherKeys,
String destKey,RedisZSetCommands.Aggregate aggregate){
return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, destKey,aggregate);
}
/**计算多个有序集合的并集并将结果存储在destination键中(同样
*以有序集合类型存储),返回值为destination键中的元素个数
* @param key
* @param otherKeys
* @param destKey
* @param aggregate
* @return
*/
public long zUnionStore(String key,Collection<String> otherKeys,
String destKey,RedisZSetCommands.Aggregate aggregate){
return redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey, aggregate);
}
/*******************************有序集合类型命令结束***************************/
/*******************************分布式锁及分布式id***************************/
private Logger logger = LoggerFactory.getLogger(getClass());
//加锁lua脚本
private String lock = "if redis.call("setnx",KEYS[1],ARGV[1]) == 1 "
+ " then return redis.call("expire",KEYS[1],ARGV[2])"
+ " else return 0 end";
//解锁lua脚本
private String unLock = "if redis.call("get",KEYS[1]) == ARGV[1] "
+ " then return redis.call("del",KEYS[1])"
+ " else return 0 "
+ " end";
//步长默认为1
private int delta = 1;
@Resource(name="lockRedisTemplate")
private RedisTemplate<String, Object> lockRedisTemplate;
//获取分布式自增id
public int generateId(String key) {
String requestId = UUID.randomUUID().toString();
int seq = 1;
boolean isReleaseLock = true;//是否释放锁
String lockKey = key + "_lock";
try {
boolean mLock = lock(requestId, lockKey, 1*10);//锁的有效时间10秒
if(mLock){
isReleaseLock = false;
seq = (int)incrKey(key, delta);
releaseLock(requestId, lockKey);//释放锁
isReleaseLock = true;
}
} catch (Exception e) {
logger.error( "获取自增id失败",e);
}finally{
if(!isReleaseLock){
releaseLock(requestId, lockKey);
}
}
return seq;
}
public boolean lock(String requestId, String key, int expiresTime) {
try {
while(true){
DefaultRedisScript<Integer> defaultRedisScript = new DefaultRedisScript<Integer>(lock, Integer.class);
Integer result = lockRedisTemplate.execute(defaultRedisScript,Collections.singletonList(key),requestId,String.valueOf(expiresTime));
if(null != result && result == 1){
return true;
}
Thread.sleep(40);
}
} catch (InterruptedException e) {
logger.info("获取分布式锁失败");
return false;
}
}
public boolean releaseLock(String requestId, String key) {
DefaultRedisScript<Integer> defaultRedisScript = new DefaultRedisScript<Integer>(unLock,Integer.class);
long result = lockRedisTemplate.execute(defaultRedisScript,Collections.singletonList(key),requestId);
return result == 1;
}
/*******************************分布式锁及分布式id结束***************************/
}
3、案例之双重检验(高并发情况下造成缓存穿透)
-
(1)案例需求:每次有查询请求,先去redis里取,没有再去查询数据库
-
(2)造成缓存穿透的原因:
-
(3)解决缓存穿透一:整个方法加锁,只允许一个线程进入
-
(4)解决缓存穿透二,部分代码加锁,配合双重校验
四、springboot集成redis之哨兵模式集群版
1、集成步骤:
-
(1)如果已经完成了哨兵模式的搭建,仅此一步即可使用,直接修改下面配置即可,其他所有都不用动
-
(2)如果没有搭建哨兵模式,请按照上面的二、主从复制之哨兵模式的搭建。
-
(2.1)如果连接哨兵时不用密码,则忽略这一步:
redis6379.conf、redis6380.conf、redis6381.conf
都需要添加密码配置,如下图:
修改sentinel.conf,加上下面三行配置:
五、redis相关面试题
1、redis单线程为什么还这么快?
对于内存系统,如果没有上下文切换效率就是最高的(cpu>内存>硬盘),而redis就
是将所有数据都放在内存中的,而多线程会导致cpu会上下文切换,会消耗一定时
间,所以单线程操作效率就是最高的
2、redis的事务:
事务就是一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行的过程
中会按照顺序执行,一次性、顺序性、排他性执行一系列的命令。redis单条命令
是保证原子性的,但是事务不保证原子性!redis的命令在事务中没有直接被执行,
只有发起执行命令时才会执行!所以redis事务实现分为三步:开启事(multi)、
命令入队(。。。)、执行事务(exec),若需要取消事务,在执行事务之前使用
(discard)。redis事务异常包括编译时异常(执行事务时所有命令都执行失败)
和运行时异常(错误的命令执行失败比如给一个字符串实现自增,正确命令执行成功)
3、redis持久化之rdb和aof的区别
rdb是将数据都写入一个叫dump.rdb的文件,能处理大量数据的持久化,但是如果突然宕机会导致数据丢失。
aof是以日志的形式将每个写操作记录到appendonly.aof文件,只追加不修改,redis启动之初都会读取该文件重新构建数据。因为是默认每秒同步一次,也可以设
置每次写操作都同步,所以数据几乎不会丢失,但是处理大量数据的话就比较耗时。当命令被破坏时,可以使用`redis-check-aof --fix appendonly.aof`来修复
4、Redis缓存穿透和雪崩的相关问题
缓存穿透:大量请求在reids里查不到,直接去查询mysql
缓存击穿:当某些数据的redis缓存刚好过期,大量请求又访问它。可以使用分布式锁进行解决,只有一个线程获取锁后能去mysql查询,其他的等待查询结果重新写入redis,去redis读取
缓存雪崩:redis宕机,所有数据没有了二级缓存
5、redis分布式锁参考另一篇博客第三阶段第6条:Redis之分布式锁
六、redis分布式锁的实现
工作中推荐使用Redisson
- V1.0版:创建一个redis节点
REDIS_LOCK
,没有则创建,即相当于获取锁,业务处理完后删除此节点,相当于解锁
- V2.0版:bug:当代码创建完REDIS_LOCK后,突然宕机了,没有走进finally里面。解决:给节点加入过期时间,10s后删除
- V3.0版:bug:创建节点和设置节点过期删除时间是非原子操作。解决:使用带过期参数的setIfAbsent()
- V4.0版:bug:过期删除时间是固定的,一旦小于业务执行时间,就会提前删除节点。解决:在finally里加一次判断,是自己的锁才能删除
- V5.0版:bug:finally的判断和删除是两个动作,不是原子性的。解决:1、lua脚本判断并删除节点(公司会用的)。2、redis自身的事务,先监听节点,再提交事务,若监听中间被修改过,则事务提交失败,继续循环(面试会问的)
- V6.0版:使用Redisson实现分布式锁(工作中推荐此方法)
参考Redis的三个客户端框架比较:Jedis,Redisson,Lettuce 、 Redission使用方式总结 、 springboot 集成redission 以及分布式锁的使用
七、redisson整合SpringCache,简化缓存开发
1.在properties里面已经配置好redis的前提下配置缓存类型
2.在启动类上加上@EnableCaching
3.只使用注解即可@Cacheable
4.关于注解@Cacheable自定义属性值的使用
- 可以指定缓存key值(@Cacheable(value={“category”},key=“‘level1Category’”)),注意,这里还可以使用#root来取到方法的名称等信息,如:(@Cacheable(value={“category”},key=“#root.method.name”))
- 可以指定数据存活时间(需要在配置文件中设置ttl:spring.cache.redis.time-to-live=)
- 可以指定数据存放格式为json(默认为JDK的序列化)
也就是说需要新建一个配置类
数据存入redis的格式如下图
当缓存数据有更新时,需要设置缓存失效模式
@CacheEvict
如果要批量操作,比如某数据需要存多个分区,更新时对多个分区的多个key进行清空
方式一:分别指定
方式二:删除整个分区
5.SpringCache的缺点
八、下面是自己用的,工作中的某些关于redis的
关于redis的application.yml的配置,说明下区别
redis的application配置:随着 Spring Boot2.x 的到来,支持的组件越来越丰富,也越来越成熟,其中对 Redis 的支持不仅仅是丰富了它的API,更是替换掉底层 Jedis 的依赖,取而代之换成了 Lettuce。
虽然 Lettuce 和 Jedis 的都是连接 Redis Server 的客户端程序,但是 Jedis 在实现上是直连 redis server,多线程环境下非线程安全,除非使用连接池,为每个Jedis实例增加物理连接。而 Lettuce 基于 Netty 的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问,同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
概念:
Jedis:是Redis的Java实现客户端,提供了比较全面的Redis命令的支持,
Redisson:实现了分布式和可扩展的Java数据结构。
Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
优点:
Jedis:比较全面的提供了Redis的操作特性
Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列
Lettuce:主要在一些分布式缓存框架上使用比较多
可伸缩:
Jedis:使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
Redisson:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作
Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作
结论:
建议使用:Jedis + Redisson
控制台基本命令
- 进入redis目录。cmd打开redis后启动redis-cil:
redis-cli.exe
- 进入某个库,select index
三、redis的配置文件参数说明
#是否在后台运行;no:不是后台运行 daemonize yes #是否开启保护模式,默认开启。要是配置里没有指定bind和密码。开启该参数后,redis只会本地进行访问,拒绝外部访问。 protected-mode yes #redis的进程文件 pidfile /var/run/redis/redis-server.pid #redis监听的端口号。 port 6379 #此参数确定了TCP连接中已完成队列(完成三次握手之后)的长度, 当然此值必须不大于Linux系统定义的/proc/sys/net/core/somaxconn值,默认是511,而Linux的默认参数值是128。当系统并发量大并且客户端速度缓慢的时候,可以将这二个参数一起参考设定。该内核参数默认值一般是128,对于负载很大的服务程序来说大大的不够。一般会将它修改为2048或者更大。在/etc/sysctl.conf中添加:net.core.somaxconn = 2048,然后在终端中执行sysctl -p。 tcp-backlog 511 #指定 redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求 #bind 127.0.0.1 #bind 0.0.0.0 #配置unix socket来让redis支持监听本地连接。 # unixsocket /var/run/redis/redis.sock #配置unix socket使用文件的权限 # unixsocketperm 700 # 此参数为设置客户端空闲超过timeout,服务端会断开连接,为0则服务端不会主动断开连接,不能小于0。 timeout 0 #tcp keepalive参数。如果设置不为0,就使用配置tcp的SO_KEEPALIVE值,使用keepalive有两个好处:检测挂掉的对端。降低中间设备出问题而导致网络看似连接却已经与对端端口的问题。在Linux内核中,设置了keepalive,redis会定时给对端发送ack。检测到对端关闭需要两倍的设置值。 tcp-keepalive 0 #指定了服务端日志的级别。级别包括:debug(很多信息,方便开发、测试),verbose(许多有用的信息,但是没有debug级别信息多),notice(适当的日志级别,适合生产环境),warn(只有非常重要的信息) loglevel notice #指定了记录日志的文件。空字符串的话,日志会打印到标准输出设备。后台运行的redis标准输出是/dev/null。 logfile /var/log/redis/redis-server.log #是否打开记录syslog功能 # syslog-enabled no #syslog的标识符。 # syslog-ident redis #日志的来源、设备 # syslog-facility local0 #数据库的数量,默认使用的数据库是DB 0。可以通过SELECT命令选择一个db databases 16 # redis是基于内存的数据库,可以通过设置该值定期写入磁盘。 # 注释掉“save”这一行配置项就可以让保存数据库功能失效 # 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) # 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) # 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化) save 900 1 save 300 10 save 60 10000 #当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作,可以通过info中的rdb_last_bgsave_status了解RDB持久化是否有错误 stop-writes-on-bgsave-error yes #使用压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间 rdbcompression yes #是否校验rdb文件。从rdb格式的第五个版本开始,在rdb文件的末尾会带上CRC64的校验和。这跟有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗,所以如果你追求高性能,可以关闭该配置。 rdbchecksum yes #rdb文件的名称 dbfilename dump.rdb #数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录 dir /data ############### 主从复制 ############### #复制选项,slave复制对应的master。 # slaveof #如果master设置了requirepass,那么slave要连上master,需要有master的密码才行。masterauth就是用来配置master的密码,这样可以在连上master后进行认证。 # masterauth #当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。2) 如果slave-serve-stale-data设置为no,除去INFO和SLAVOF命令之外的任何请求都会返回一个错误”SYNC with master in progress”。 slave-serve-stale-data yes #作为从服务器,默认情况下是只读的(yes),可以修改成NO,用于写(不建议)。 slave-read-only yes #是否使用socket方式复制数据。目前redis复制提供两种方式,disk和socket。如果新的slave连上来或者重连的slave无法部分同步,就会执行全量同步,master会生成rdb文件。有2种方式:disk方式是master创建一个新的进程把rdb文件保存到磁盘,再把磁盘上的rdb文件传递给slave。socket是master创建一个新的进程,直接把rdb文件以socket的方式发给slave。disk方式的时候,当一个rdb保存的过程中,多个slave都能共享这个rdb文件。socket的方式就的一个个slave顺序复制。在磁盘速度缓慢,网速快的情况下推荐用socket方式。 repl-diskless-sync no #diskless复制的延迟时间,防止设置为0。一旦复制开始,节点不会再接收新slave的复制请求直到下一个rdb传输。所以最好等待一段时间,等更多的slave连上来。 repl-diskless-sync-delay 5 #slave根据指定的时间间隔向服务器发送ping请求。时间间隔可以通过 repl_ping_slave_period 来设置,默认10秒。 # repl-ping-slave-period 10 #复制连接超时时间。master和slave都有超时时间的设置。master检测到slave上次发送的时间超过repl-timeout,即认为slave离线,清除该slave信息。slave检测到上次和master交互的时间超过repl-timeout,则认为master离线。需要注意的是repl-timeout需要设置一个比repl-ping-slave-period更大的值,不然会经常检测到超时。 # repl-timeout 60 #是否禁止复制tcp链接的tcp nodelay参数,可传递yes或者no。默认是no,即使用tcp nodelay。如果master设置了yes来禁止tcp nodelay设置,在把数据复制给slave的时候,会减少包的数量和更小的网络带宽。但是这也可能带来数据的延迟。默认我们推荐更小的延迟,但是在数据量传输很大的场景下,建议选择yes。 repl-disable-tcp-nodelay no #复制缓冲区大小,这是一个环形复制缓冲区,用来保存最新复制的命令。这样在slave离线的时候,不需要完全复制master的数据,如果可以执行部分同步,只需要把缓冲区的部分数据复制给slave,就能恢复正常复制状态。缓冲区的大小越大,slave离线的时间可以更长,复制缓冲区只有在有slave连接的时候才分配内存。没有slave的一段时间,内存会被释放出来,默认1m。 # repl-backlog-size 5mb #master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。单位为秒。 # repl-backlog-ttl 3600 #当master不可用,Sentinel会根据slave的优先级选举一个master。最低的优先级的slave,当选master。而配置成0,永远不会被选举。 slave-priority 100 #redis提供了可以让master停止写入的方式,如果配置了min-slaves-to-write,健康的slave的个数小于N,mater就禁止写入。master最少得有多少个健康的slave存活才能执行写命令。这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。设置为0是关闭该功能。 # min-slaves-to-write 3 #延迟小于min-slaves-max-lag秒的slave才认为是健康的slave。 # min-slaves-max-lag 10 # 设置1或另一个设置为0禁用这个特性。 # Setting one or the other to 0 disables the feature. # By default min-slaves-to-write is set to 0 (feature disabled) and # min-slaves-max-lag is set to 10. ############### 安全相关 ############### #requirepass配置可以让用户使用AUTH命令来认证密码,才能使用其他命令。这让redis可以使用在不受信任的网络中。为了保持向后的兼容性,可以注释该命令,因为大部分用户也不需要认证。使用requirepass的时候需要注意,因为redis太快了,每秒可以认证15w次密码,简单的密码很容易被攻破,所以最好使用一个更复杂的密码。注意只有密码没有用户名。 # requirepass foobared #把危险的命令给修改成其他名称。比如CONFIG命令可以重命名为一个很难被猜到的命令,这样用户不能使用,而内部工具还能接着使用。 # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 #设置成一个空的值,可以禁止一个命令 # rename-command CONFIG "" ############### 进程限制相关 ############### # 设置能连上redis的最大客户端连接数量。默认是10000个客户端连接。由于redis不区分连接是客户端连接还是内部打开文件或者和slave连接等,所以maxclients最小建议设置到32。如果超过了maxclients,redis会给新的连接发送’max number of clients reached’,并关闭连接。 # maxclients 10000 #redis配置的最大内存容量。当内存满了,需要配合maxmemory-policy策略进行处理。注意slave的输出缓冲区是不计算在maxmemory内的。所以为了防止主机内存使用完,建议设置的maxmemory需要更小一些。 # maxmemory #内存容量超过maxmemory后的处理策略。 #volatile-lru:利用LRU算法移除设置过过期时间的key。 #volatile-random:随机移除设置过过期时间的key。 #volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL) #allkeys-lru:利用LRU算法移除任何key。 #allkeys-random:随机移除任何key。 #noeviction:不移除任何key,只是返回一个写错误。 #上面的这些驱逐策略,如果redis没有合适的key驱逐,对于写命令,还是会返回错误。redis将不再接收写请求,只接收get请求。写命令包括:set setnx setex append incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby getset mset msetnx exec sort。 # maxmemory-policy noeviction #lru检测的样本数。使用lru或者ttl淘汰算法,从需要淘汰的列表中随机选择sample个key,选出闲置时间最长的key移除。 # maxmemory-samples 5 ############### APPEND ONLY 持久化方式 ############### #默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了。但是redis如果中途宕机,会导致可能有几分钟的数据丢失,根据save来策略进行持久化,Append Only File是另一种持久化方式,可以提供更好的持久化特性。Redis会把每次写入的数据在接收后都写入 appendonly.aof 文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。 appendonly no #aof文件名 appendfilename "appendonly.aof" #aof持久化策略的配置 #no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。 #always表示每次写入都执行fsync,以保证数据同步到磁盘。 #everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。 appendfsync everysec # 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no,是最安全的方式,不会丢失数据,但是要忍受阻塞的问题。如果对延迟要求很高的应用,这个字段可以设置为yes,,设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,不会造成阻塞的问题(因为没有磁盘竞争),等rewrite完成后再写入,这个时候redis会丢失数据。Linux的默认fsync策略是30秒。可能丢失30秒数据。因此,如果应用系统无法忍受延迟,而可以容忍少量的数据丢失,则设置为yes。如果应用系统无法忍受数据丢失,则设置为no。 no-appendfsync-on-rewrite no #aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。 auto-aof-rewrite-percentage 100 #设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写 auto-aof-rewrite-min-size 64mb #aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项(redis宕机或者异常终止不会造成尾部不完整现象。)出现这种现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。 aof-load-truncated yes ############### LUA SCRIPTING ############### # 如果达到最大时间限制(毫秒),redis会记个log,然后返回error。当一个脚本超过了最大时限。只有SCRIPT KILL和SHUTDOWN NOSAVE可以用。第一个可以杀没有调write命令的东西。要是已经调用了write,只能用第二个命令杀。 lua-time-limit 5000 ############### 集群相关 ############### #集群开关,默认是不开启集群模式。 # cluster-enabled yes #集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。这个文件并不需要手动配置,这个配置文件有Redis生成并更新,每个Redis集群节点需要一个单独的配置文件,请确保与实例运行的系统中配置文件名称不冲突 # cluster-config-file nodes-6379.conf #节点互连超时的阀值。集群节点超时毫秒数 # cluster-node-timeout 15000 #在进行故障转移的时候,全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间了,导致数据过于陈旧,这样的slave不应该被提升为master。该参数就是用来判断slave节点与master断线的时间是否过长。判断方法是: #比较slave断开连接的时间和(node-timeout * slave-validity-factor) + repl-ping-slave-period #如果节点超时时间为三十秒, 并且slave-validity-factor为10,假设默认的repl-ping-slave-period是10秒,即如果超过310秒slave将不会尝试进行故障转移 # cluster-slave-validity-factor 10 #master的slave数量大于该值,slave才能迁移到其他孤立master上,如这个参数若被设为2,那么只有当一个主节点拥有2 个可工作的从节点时,它的一个从节点会尝试迁移。 # cluster-migration-barrier 1 #默认情况下,集群全部的slot有节点负责,集群状态才为ok,才能提供服务。设置为no,可以在slot没有全部分配的时候提供服务。不建议打开该配置。 # cluster-require-full-coverage yes ############### SLOW LOG 慢查询日志 ############### ###slog log是用来记录redis运行中执行比较慢的命令耗时。当命令的执行超过了指定时间,就记录在slow log中,slog log保存在内存中,所以没有IO操作。 #执行时间比slowlog-log-slower-than大的请求记录到slowlog里面,单位是微秒,所以1000000就是1秒。注意,负数时间会禁用慢查询日志,而0则会强制记录所有命令。 slowlog-log-slower-than 10000 #慢查询日志长度。当一个新的命令被写进日志的时候,最老的那个记录会被删掉。这个长度没有限制。只要有足够的内存就行。你可以通过 SLOWLOG RESET 来释放内存。 slowlog-max-len 128 ############### 延迟监控 ############### #延迟监控功能是用来监控redis中执行比较缓慢的一些操作,用LATENCY打印redis实例在跑命令时的耗时图表。只记录大于等于下边设置的值的操作。0的话,就是关闭监视。默认延迟监控功能是关闭的,如果你需要打开,也可以通过CONFIG SET命令动态设置。 latency-monitor-threshold 0 ############### EVENT NOTIFICATION 订阅通知 ############### #键空间通知使得客户端可以通过订阅频道或模式,来接收那些以某种方式改动了 Redis 数据集的事件。因为开启键空间通知功能需要消耗一些 CPU ,所以在默认配置下,该功能处于关闭状态。 #notify-keyspace-events 的参数可以是以下字符的任意组合,它指定了服务器该发送哪些类型的通知: ##K 键空间通知,所有通知以 __keyspace@__ 为前缀 ##E 键事件通知,所有通知以 __keyevent@__ 为前缀 ##g DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知 ##$ 字符串命令的通知 ##l 列表命令的通知 ##s 集合命令的通知 ##h 哈希命令的通知 ##z 有序集合命令的通知 ##x 过期事件:每当有过期键被删除时发送 ##e 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送 ##A 参数 g$lshzxe 的别名 #输入的参数中至少要有一个 K 或者 E,否则的话,不管其余的参数是什么,都不会有任何 通知被分发。详细使用可以参考http://redis.io/topics/notifications notify-keyspace-events "" ############### ADVANCED CONFIG 高级配置 ############### #数据量小于等于hash-max-ziplist-entries的用ziplist,大于hash-max-ziplist-entries用hash hash-max-ziplist-entries 512 #value大小小于等于hash-max-ziplist-value的用ziplist,大于hash-max-ziplist-value用hash。 hash-max-ziplist-value 64 #数据量小于等于list-max-ziplist-entries用ziplist,大于list-max-ziplist-entries用list。 list-max-ziplist-entries 512 #value大小小于等于list-max-ziplist-value的用ziplist,大于list-max-ziplist-value用list。 list-max-ziplist-value 64 #数据量小于等于set-max-intset-entries用iniset,大于set-max-intset-entries用set。 set-max-intset-entries 512 #数据量小于等于zset-max-ziplist-entries用ziplist,大于zset-max-ziplist-entries用zset。 zset-max-ziplist-entries 128 #value大小小于等于zset-max-ziplist-value用ziplist,大于zset-max-ziplist-value用zset。 zset-max-ziplist-value 64 #value大小小于等于hll-sparse-max-bytes使用稀疏数据结构(sparse),大于hll-sparse-max-bytes使用稠密的数据结构(dense)。一个比16000大的value是几乎没用的,建议的value大概为3000。如果对CPU要求不高,对空间要求较高的,建议设置到10000左右。 hll-sparse-max-bytes 3000 #Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用。当你的使用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no。如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存。 activerehashing yes ##对客户端输出缓冲进行限制可以强迫那些不从服务器读取数据的客户端断开连接,用来强制关闭传输缓慢的客户端。 #对于normal client,第一个0表示取消hard limit,第二个0和第三个0表示取消soft limit,normal client默认取消限制,因为如果没有寻问,他们是不会接收数据的。 client-output-buffer-limit normal 0 0 0 #对于slave client和MONITER client,如果client-output-buffer一旦超过256mb,又或者超过64mb持续60秒,那么服务器就会立即断开客户端连接。 client-output-buffer-limit slave 256mb 64mb 60 #对于pubsub client,如果client-output-buffer一旦超过32mb,又或者超过8mb持续60秒,那么服务器就会立即断开客户端连接。 client-output-buffer-limit pubsub 32mb 8mb 60 #redis执行任务的频率为1s除以hz。 hz 10 #在aof重写的时候,如果打开了aof-rewrite-incremental-fsync开关,系统会每32MB执行一次fsync。这对于把文件写入磁盘是有帮助的,可以避免过大的延迟峰值。 aof-rewrite-incremental-fsync yes
笔记参考链接
redis配置类:
package com.ws.wssf.business.config;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean(name="lockRedisTemplate")
public RedisTemplate<String, Object> lockRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(stringRedisSerializer);
//连接Redis
template.setConnectionFactory(factory);
template.afterPropertiesSet();
return template;
}
@Bean(name = "redisTemplate")
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = getJackson2JsonRedisSerializer();
template.setConnectionFactory(factory);
// key序列化方式
template.setKeySerializer(redisSerializer);
// value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
// value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
/**配合cache注解使用
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
// 对每个缓存空间应用不同的配置
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("access", getRedisCacheConfiguration().entryTtl(Duration.ofSeconds(7180L)));//access_token这个缓存空间7180秒
return RedisCacheManager.builder(factory)
.cacheDefaults(getRedisCacheConfiguration())//默认缓存
.withInitialCacheConfigurations(configMap)//缓存策略
.build();
}
/**RedisCacheConfiguration默认状态
* @return
*/
private RedisCacheConfiguration getRedisCacheConfiguration() {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(getJackson2JsonRedisSerializer()))
.disableCachingNullValues();
return config;
}
/**Jackson2JsonRedisSerializer默认配置
* @return
*/
private Jackson2JsonRedisSerializer<Object> getJackson2JsonRedisSerializer(){
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
return jackson2JsonRedisSerializer;
}
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
redis工具类:含分布式锁生成唯一id方法
package com.ws.wssf.business.util;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import com.google.gson.Gson;
@Component
public class RedisUtil {
@Resource(name="redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
/*** 字符串类型redis命令封装 ******/
/**
* 基本数据类型赋值
*
* @param key
* 键
* @param value
* 值
*/
public boolean setKeyValue(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
return false;
}
}
/**删除key
* @param key
* @return
*/
public boolean deleteKeyValue(String key) {
try {
redisTemplate.delete(key);
return true;
} catch (Exception e) {
return false;
}
}
/**基本数据类型赋值
* @param key 键
* @param value 值
* @param timeout 超时时间
* @param timeType 时间类型:时、分、秒
* @return
*/
public boolean setKeyValue(String key, Object value,int timeout,TimeUnit timeType) {
try {
redisTemplate.opsForValue().set(key, value);
redisTemplate.expire(key, timeout, timeType);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 基本数据获取值
*
* @param key
* @return
*/
public Object getValueByKey(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 递增key(实现id的递增、统计文章的访问量等)
*
* @param key
* @param delta 歩长
* @param timeout 超时时间
* @param timeType 时间类型
* @return
*/
public long incrKey(String key, long delta, int timeout, TimeUnit timeType) {
long num = redisTemplate.opsForValue().increment(key, delta);
if(num==1)redisTemplate.expire(key, timeout, timeType);//第一次设置超时时间为一天
return num;
}
/**
* 递增key(实现id的递增、统计文章的访问量等)
*
* @param key
* @param delta
* 歩长
* @return
*/
public long incrKey(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key
* 键
* @param by
* 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/**
* 向尾部追加值
*
* @param key
* @param append
* 追加的值
*/
public void appendKeyValue(String key, String append) {
redisTemplate.opsForValue().append(key, append);
}
/**
* 位操作,根据偏移量获取值
*
* @param key
* @param offset
* @return true:当前二进制位是1,false:0 如果需要获取的二进制位的索引超出了键值的二进制位的实际长度则默认位值是0:
*/
public boolean getBit(String key, Integer offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
/**
* 位操作,根据偏移量设置值
*
* @param key
* @param offset
* @param value
* @return如果要设置的位置超过了键值的二进制位的长度,SETBIT命令会自动将中间的二进制位 设置为0,同理设置一个不存在的键的指定二进制位的值会自动将其前面的位赋值为0
*/
public boolean setBit(String key, Integer offset, boolean value) {
return redisTemplate.opsForValue().setBit(key, offset, value);
}
/**BITCOUNT命令可以获得字符串类型键中值是1的二进制位个数
* @param key
* @return
*/
public long bitCount(String key) {
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
}
/**可以通过参数来限制统计的字节范围,如我们只希望统计前两个字节
* bitCountRange key(值为aar) 0 1(即"aa"):6
* @param key
* @param start
* @param end
* @return
*/
public Long bitCountRange(String key,long start,long end) {
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes(), start, end));
}
/**BITOP命令可以对多个字符串类型键进行位运算,并将结果存储在destkey参数指定的键
中。BITOP命令支持的运算操作有AND、OR、XOR 和NOT
* @param op :AND, OR, XOR, NOT;
* @param saveKey
* @param desKey
* @return
*/
public long bitOp(RedisStringCommands.BitOperation op, String saveKey, String... desKey) {
byte[][] bytes = new byte[desKey.length][];
for (int i = 0; i < desKey.length; i++) {
bytes[i] = desKey[i].getBytes();
}
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitOp(op, saveKey.getBytes(), bytes));
}
/****************************字符串类型命令结束*******************************/
/****************************散列类型命令开始********************************/
/**散列根据key插入某个属性的值
* @param key 键
* @param field 属性
* @param value 值
* @return 当执行的是插入操作时(即之前字段不存在)HSET命令会返回1,当执行的是更新操作时(即之前字段已经存在)HSET命令会返回0
*/
public void hSet(String key,Object field,Object value){
//putIfAbsent命令存在则返回不覆盖
redisTemplate.opsForHash().put(key, field, value);
}
/**散列根据key获取某个属性的值
* @param key
* @param field
* @param value
* @return
*/
public Object hGet(String key,Object field){
return redisTemplate.opsForHash().get(key,field);
}
/**散列设置map:多属性多值
* @param key
* @param dataMap
*/
public void hMSet(String key, Map<String, Object> dataMap){
redisTemplate.opsForHash().putAll(key,dataMap);
}
/**获取散列多个传入的属性的值
* @param key
* @param hashKeys
*/
public void hMGet(String key, Collection<Object> hashKeys){
redisTemplate.opsForHash().multiGet(key, hashKeys);
}
/**根据key来获取散列
* @param key
* @return
* @author julong
* @date 2017-12-8 上午10:24:03
*/
public Map<Object, Object> hGetAll(String key) {
return this.redisTemplate.opsForHash().entries(key);
}
/**判断散列中该字段是否存在
* @param key redis散列的key
* @param hashKey map的key
* @return
*/
public boolean hExists(String key,Object hashKey) {
return this.redisTemplate.opsForHash().hasKey(key, hashKey);
}
/**删除散列的属性(一个或者多个)
* @param key
* @param mapKey
* @return 返回值是被删除的字段个数
*/
public long hDel(String key,Object... mapKey){
return redisTemplate.opsForHash().delete(key, mapKey);
}
/**获取散列的key集合
* @param key
* @return
*/
public Set<Object> hKeys(String key){
return redisTemplate.opsForHash().keys(key);
}
/**HVALS命令与HKEYS命令相对应,HVALS命令用来获得键中所有字段值
* @param key
* @return
*/
public List<Object> hVals(String key){
return redisTemplate.opsForHash().values(key);
}
/**获得字段数量
* @param key
* @return
*/
public long hLen(String key){
return redisTemplate.opsForHash().size(key);
}
/*******************************散列类型命令结束******************************/
/*******************************列表类型命令开始******************************/
/**向列表左插入一个元素
* @param key
* @param value
* @return 返回的是列表的长度
*/
public long lPush(String key,Object value){
return redisTemplate.opsForList().leftPush(key, value);
}
/**向列表右插入一个元素
* @param key
* @param value
* @return 返回的是列表的长度
*/
public long rPush(String key,Object value){
return redisTemplate.opsForList().rightPush(key, value);
}
/**向列表左插入多个元素
* @param key
* @param values
* @return 返回的是列表的长度
*/
public long lPushAll(String key,Collection<Object> values){
return redisTemplate.opsForList().leftPushAll(key, values);
}
/**向列表右插入多个元素
* @param key
* @param values
* @return 返回的是列表的长度
*/
public long rPushAll(String key,Collection<Object> values){
return redisTemplate.opsForList().rightPushAll(key, values);
}
/**向列表左移除一个元素
* @param key
* @param value
* @return 返回被移除的元素值
*/
public Object lPop(String key){
return redisTemplate.opsForList().leftPop(key);
}
/**向列表右移除一个元素
* @param key
* @param value
* @return 返回被移除的元素值
*/
public Object rPop(String key){
return redisTemplate.opsForList().rightPop(key);
}
/**获取列表中元素的个数
* @param key
* @return 当键不存在时LLEN会返回0
*/
public long lLen(String key){
return redisTemplate.opsForList().size(key);
}
/**获得列表中的某一片段
* @param key
* @param start
* @param end
* @return (1)返回索引从start到stop之间的所有元素(包含两端的元素),起始索引为0
* (2)LRANGE命令也支持负索引,表示从右边开始计算序数,如 LRANGE key -2 -1:"-1"表示最右边第一个元
* 素,"-2"表示最右边第二个元素,依次类推
* (3) 显然,LRANGE numbers 0 -1可以获取列表中的所有元素。另外一些特殊情况如下。
*****(a)如果start的索引位置比stop的索引位置靠后,则会返回空列表。
*****(b)如果stop大于实际的索引范围,则会返回到列表最右边的元素:
*/
public List<Object> lRange(String key,long start,long end){
return redisTemplate.opsForList().range(key, start, end);
}
/**LREM命令会删除列表中前count个值为value的元素,返回值是实际删除的元素个数。根
*据count值的不同,LREM命令的执行方式会略有差异:
*●当count>0时LREM命令会从列表左边开始删除前count个值为value的元素;
*●当count<0时LREM 命令会从列表右边开始删除前|count|个值为value的元素;
*●当count=0是LREM命令会删除所有值为value的元素
* @param key
* @param count
* @param value
* @return 返回值是实际删除的元素个数
*/
public long lRem(String key,long count,Object value){
return redisTemplate.opsForList().remove(key, count, value);
}
/**将列表类型当作数组来用
* @param key
* @param index 索引
* @return 索引返回的元素,索引从0开始;如果index是负数则表示从右边开始计算的索引,最右边元素的索引是-1
*/
public Object lIndex(String key,long index){
return redisTemplate.opsForList().index(key, index);
}
/** LSET是另一个通过索引操作列表的命令,它会将索引为index的元素赋值为value
* @param key
* @param index
* @param value
*/
public void lSet(String key,long index,Object value){
redisTemplate.opsForList().set(key, index, value);
}
/**LTRIM命令可以删除指定索引范围之外的所有元素,其指定列表范围的方法和LRANGE命令相同
* @param key
* @param start
* @param end
*/
public void lTrim(String key,long start,long end){
redisTemplate.opsForList().trim(key, start, end);
}
/**LINSERT命令首先会在列表中从左到右查找值为pivot的元素,然后根据第二个参数是
*BEFORE还是AFTER来决定将value插入到该元素的前面还是后面
* @param key
* @param where
* @param pivot
* @param value
* @return 返回值是插入后列表的元素个数
*/
public long lInsert(String key,RedisListCommands.Position where,Object pivot,Object value){
return redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
Gson gson=new Gson();
return connection.lInsert(key.getBytes(), where, gson.toJson(pivot).getBytes(), gson.toJson(value).getBytes());
}
});
}
/**RPOPLPUSH命令会先从source列表类型键的右边弹出一个元素,然后将其加入
到destination列表类型键的左边,并返回这个元素的值,整个过程是原子的
* @param sourceKey
* @param destinationKey
* @return 返回source右边弹出元素的值
*/
public Object popLPush(String sourceKey,String destinationKey){
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);
}
/*******************************列表类型命令结束******************************/
/*******************************集合类型命令开始******************************/
/**SADD命令用来向集合中增加一个或多个元素
* @param key
* @param values
* @param return 返回值是成功加入的元素数量
*/
public long sAdd(String key,Object... values){
return redisTemplate.opsForSet().add(key, values);
}
/**SREM命令用来从集合中删除一个或多个元素
* @param key
* @param values
* @return 返回删除成功的个数
*/
public long sRem(String key,Object... values){
return redisTemplate.opsForSet().remove(key, values);
}
/**SMEMBERS命令会返回集合中的所有元素
* @param key
* @return
*/
public Set<Object> sMembers(String key){
return redisTemplate.opsForSet().members(key);
}
/**判断一个元素是否在集合中,无论集合中有多少个元素,SISMEMBER命令始终可以极快地返回结果
* @param key
* @param value
* @return
*/
public boolean sisMember(String key,Object value){
return redisTemplate.opsForSet().isMember(key, value);
}
/**SDIFF命令用来对多个集合执行差集运算。集合A与集合B的差集表示为A-B
* @param key
* @param otherKeys 其他key,一个或多个
* return 返回差集元素
*/
public Set<Object> sDiff(String key,Collection<String> otherKeys){
return redisTemplate.opsForSet().difference(key, otherKeys);
}
/**SINTER命令用来对多个集合执行交集运算
* @param key
* @param otherKeys
* @return
*/
public Set<Object> sInter(String key,Collection<String> otherKeys){
return redisTemplate.opsForSet().intersect(key, otherKeys);
}
/**SUNION命令用来对多个集合执行并集运算
* @param key
* @param otherKeys
* @return
*/
public Set<Object> sUnion(String key,Collection<String> otherKeys){
return redisTemplate.opsForSet().union(key, otherKeys);
}
/**SCARD命令用来获得集合中的元素个数
* @param key
* @return
*/
public long sCard(String key){
return redisTemplate.opsForSet().size(key);
}
/**SDIFFSTORE命令和SDIFF命令功能一样,唯一的区别就是前者不会直接返回运算结果,而是将结果存储在destination键中
* @param key
* @param otherKeys 其他key,一个或多个
* return
*/
public long sDiffStore(String key,Collection<String> otherKeys,String destKey){
return redisTemplate.opsForSet().differenceAndStore(key, otherKeys, destKey);
}
/**SINTERSTORE命令用来对多个集合执行交集运算,将结果存储在destination键中
* @param key
* @param otherKeys
* @return
*/
public long sInterStore(String key,Collection<String> otherKeys,String destKey){
return redisTemplate.opsForSet().intersectAndStore(key, otherKeys, destKey);
}
/**sUnionStore命令用来对多个集合执行并集运算,将结果存储在destination键中
* @param key
* @param otherKeys
* @return
*/
public long sUnionStore(String key,Collection<String> otherKeys,String destKey){
return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
}
/**SRANDMEMBER命令用来随机从集合中获取多个元素,可以重复
* @param key
* @param count
* @return
*/
public List<Object> sRandMember(String key,long count){
return redisTemplate.opsForSet().randomMembers(key, count);
}
/**SPOP命令会从集合中随机选择多个元素弹出。
* @param key
* @param count
* @return
*/
public List<Object> sPop(String key,long count){
return redisTemplate.opsForSet().pop(key, count);
}
/*******************************集合类型命令结束******************************/
/*******************************有序集合类型命令结束***************************/
/**ZADD命令用来向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数
* @param key
* @param tuples
* @return 返回值是新加入到集合中的元素个数(不包含之前已经存在的元素)
*/
public boolean zAdd(String key,Object value,double score){
return redisTemplate.opsForZSet().add(key, value, score);
}
/*public long zAddAll(String key,Set<TypedTuple<?>> tuples){
return redisTemplate.opsForZSet().add(key, tuples);
}*/
/**获得元素的分数
* @param key
* @param value
*/
public double zScore(String key,Object value){
return redisTemplate.opsForZSet().score(key, value);
}
/**ZRANGE命令会按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素
* 索引都是从0开始,负数代表从后向前查找(-1表示最后一个元素);rangeWithScores命令带上分数
* @param key
* @param start
* @param end
* @return
*/
public Set<Object> zRange(String key,long start,long end){
return redisTemplate.opsForZSet().range(key, start, end);
}
/**ZREVRANGE命令是按照元素分数从大到小的顺序给出结果的
* @param key
* @param start
* @param end
* @return
*/
public Set<Object> zRevRange(String key,long start,long end){
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**该命令按照元素分数从小到大的顺序返回分数在min和max之间(包含min和max)的元素
* @param key
* @param min
* @param max
* @return
*/
public Set<Object> zRangeByScore(String key,double min,double max){
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
/**该命令按照元素分数从大到小的顺序返回分数在min和max之间(包含min和max)的元素
* @param key
* @param min
* @param max
* @return
*/
public Set<Object> zRevRangeByScore(String key,double min,double max){
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
/**该命令按照元素分数从小到大的顺序返回分数在min和max之间(包含min和max)指定位置的元素
* @param key
* @param min
* @param max
* @param offset 偏移量
* @param count 返回的个数
* @return
*/
public Set<Object> zRangeByScore(String key,double min,double max,long offset,long count){
return redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
}
/**该命令按照元素分数从大到小的顺序返回分数在min和max之间(包含min和max)指定位置的元素
* @param key
* @param min
* @param max
* @param offset 偏移量
* @param count 返回的个数
* @return
*/
public Set<Object> zRevRangeByScore(String key,double min,double max,long offset,long count){
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max, offset, count);
}
/**ZINCRBY命令可以增加一个元素的分数
* @param key
* @param value 元素
* @param delta 增加的分数,负数为减分
* @return 返回增加后的分数
*/
public double zIncrby(String key,Object value,double delta){
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
/**获得集合中元素的数量
* @param key
* @return
*/
public long zCard(String key){
return redisTemplate.opsForZSet().size(key);
}
/**获得指定分数范围內的元素个数
* @param key
* @param min
* @param max
* @return
*/
public long zCount(String key,double min,double max){
return redisTemplate.opsForZSet().count(key, min, max);
}
/**删除一个或多个元素
* @param key
* @param values
* @return 返回值是成功删除的元素数量(不包含本来就不存在的元素)
*/
public long zRem(String key,Object... values){
return redisTemplate.opsForZSet().remove(key, values);
}
/**ZREMRANGEBYRANK命令按照元素分数从小到大的顺序(即索引0表示最小的值)删除
*处在指定排名范围内的所有元素
* @param key
* @param start
* @param end
* @return 返回删除的元素数量
*/
public long zRemRangeByRank(String key,long start,long end){
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
/**ZREMRANGEBYSCORE命令会删除指定分数范围内的所有元素
* @param key
* @param min
* @param max
* @return 返回删除的元素数量
*/
public long zRemRangeByScore(String key,double min,double max){
return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
}
/**ZRANK命令会按照元素分数从小到大的顺序获得指定的元素的排名(从0开始,即分数最
*小的元素排名为0
* @param key
* @param value
* @return
*/
public long zRank(String key,Object value){
return redisTemplate.opsForZSet().rank(key, value);
}
/**ZREVRANK命令则相反(分数最大的元素排名为0)
* @param key
* @param value
* @return
*/
public long zRevRank(String key,Object value){
return redisTemplate.opsForZSet().reverseRank(key, value);
}
/**ZINTERSTORE命令用来计算多个有序集合的交集并将结果存储在destination键中(同样
*以有序集合类型存储),返回值为destination键中的元素个数
* @param key
* @param otherKeys
* @param destKey
* @param aggregate
SUM:集合中该元素分数的和;
MIN:destination键中元素的分数是每个参与计算的集合中该元素分数的最小值
MAX:destination键中元素的分数是每个参与计算的集合中该元素分数的最大值
* @return
*/
public long zInterStore(String key,Collection<String> otherKeys,
String destKey,RedisZSetCommands.Aggregate aggregate){
return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys, destKey,aggregate);
}
/**计算多个有序集合的并集并将结果存储在destination键中(同样
*以有序集合类型存储),返回值为destination键中的元素个数
* @param key
* @param otherKeys
* @param destKey
* @param aggregate
* @return
*/
public long zUnionStore(String key,Collection<String> otherKeys,
String destKey,RedisZSetCommands.Aggregate aggregate){
return redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey, aggregate);
}
/*******************************有序集合类型命令结束***************************/
/*******************************分布式锁及分布式id***************************/
private Logger logger = LoggerFactory.getLogger(getClass());
//加锁lua脚本
private String lock = "if redis.call("setnx",KEYS[1],ARGV[1]) == 1 "
+ " then return redis.call("expire",KEYS[1],ARGV[2])"
+ " else return 0 end";
//解锁lua脚本
private String unLock = "if redis.call("get",KEYS[1]) == ARGV[1] "
+ " then return redis.call("del",KEYS[1])"
+ " else return 0 "
+ " end";
//步长默认为1
private int delta = 1;
@Resource(name="lockRedisTemplate")
private RedisTemplate<String, Object> lockRedisTemplate;
//获取分布式自增id
public int generateId(String key) {
String requestId = UUID.randomUUID().toString();
int seq = 1;
boolean isReleaseLock = true;//是否释放锁
String lockKey = key + "_lock";
try {
boolean mLock = lock(requestId, lockKey, 1*10);//锁的有效时间10秒
if(mLock){
isReleaseLock = false;
seq = (int)incrKey(key, delta);
releaseLock(requestId, lockKey);//释放锁
isReleaseLock = true;
}
} catch (Exception e) {
logger.error( "获取自增id失败",e);
}finally{
if(!isReleaseLock){
releaseLock(requestId, lockKey);
}
}
return seq;
}
public boolean lock(String requestId, String key, int expiresTime) {
try {
while(true){
DefaultRedisScript<Integer> defaultRedisScript = new DefaultRedisScript<Integer>(lock, Integer.class);
Integer result = lockRedisTemplate.execute(defaultRedisScript,Collections.singletonList(key),requestId,String.valueOf(expiresTime));
if(null != result && result == 1){
return true;
}
Thread.sleep(40);
}
} catch (InterruptedException e) {
logger.info("获取分布式锁失败");
return false;
}
}
public boolean releaseLock(String requestId, String key) {
DefaultRedisScript<Integer> defaultRedisScript = new DefaultRedisScript<Integer>(unLock,Integer.class);
long result = lockRedisTemplate.execute(defaultRedisScript,Collections.singletonList(key),requestId);
return result == 1;
}
/*******************************分布式锁及分布式id结束***************************/
}
server:
port: 10033
spring:
datasource:
url: jdbc:mysql://193.168.1.241:3307/wssf_business?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
username: root
password: wellsun_2020
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: 192.168.88.41
port: 6379
password: wellsun_2018
database: 0
timeout: 5000
#jedis:
# pool:
# max-active: 50
# max-wait: 3000
# max-idle: 20
# min-idle: 2
# 建议不使用jedis了,配置文件中添加 lettuce.pool 相关配置,则会使用到lettuce连接池
lettuce:
pool:
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: 60s
# 连接池中的最大空闲连接 默认 8
max-idle: 10
# 连接池中的最小空闲连接 默认 0
min-idle: 10
# 连接池最大连接数(使用负值表示没有限制) 默认 8
max-activ: 8
session:
store-type: redis
timeout: 1800
redis:
namespace: wssf
application:
name: wssf-business
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
main:
allow-bean-definition-overriding: true
mybatis:
mapper-locations:
- classpath:mapper/*Mapper.xml
type-aliases-package: com.ws.wssf.business.model
eureka:
client:
service-url:
defaultZone: http://localhost:10030/eureka/ # 服务中心地址
feign:
httpclient:
connection-timeout: 30000
hystrix:
enabled: true
ribbon:
ReadTimeout: 5000
SocketTimeout: 5000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 12000
timeout:
enabled: false
最后
以上就是专一玫瑰为你收集整理的Redis学习使用一、安装二、主从复制之哨兵模式的搭建三、springboot集成redis之单机版四、springboot集成redis之哨兵模式集群版五、redis相关面试题六、redis分布式锁的实现七、redisson整合SpringCache,简化缓存开发八、下面是自己用的,工作中的某些关于redis的的全部内容,希望文章能够帮你解决Redis学习使用一、安装二、主从复制之哨兵模式的搭建三、springboot集成redis之单机版四、springboot集成redis之哨兵模式集群版五、redis相关面试题六、redis分布式锁的实现七、redisson整合SpringCache,简化缓存开发八、下面是自己用的,工作中的某些关于redis的所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复