目录
Redis常规配置
tcp-keepalive
security
redis.conf中设置密码,永久设置
用户名默认是default,可以不写
Jedis
创建Maven项目,引入依赖
需要防火墙打开Redis的端口
将bind 127.0.0.1注释掉,支持远程连接
protected_mode保护模式设为no,支持远程连接
如果Redis配置了密码,则需要进行身份校验
jedis.auth("密码");
RedisTemplate
引入依赖
application.properties
#Redis 服务器地址
spring.redis.host=192.168.102.130
#Redis 服务器连接端口
spring.redis.port=6379
#Redis 如果有密码,需要配置, 没有密码就不要写
#spring.redis.password=123
#Redis 数据库索引(默认为0)
spring.redis.database=0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
redis配置类
默认配置存在的问题:
redisTemplate 模糊查找 keys(*) 数据为空
使用Java程序读取客户端写入的数据,转换异常,是因为没有使用配置类进行序列化,除非都是数据都是通过Java程序读和写
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template =
new RedisTemplate<>();
System.out.println("template=>" + template);
RedisSerializer<String> redisSerializer =
new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key 序列化方式
template.setKeySerializer(redisSerializer);
//value 序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap 序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer =
new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间 600 秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config)
.build();
return cacheManager;
}
}
Controller
连接池技术
使用连接池来获取Redis连接
volatile的作用:
1、线程的可见性:当一个线程去修改一个共享变量时,另一个线程可以读取修改的值
2、顺序的一致性:禁止指令重排
保证每次调用返回的 jedisPool 是单例,构造器私有化
使用双重校验,保证 jedisPool 只创建一次,可以解决超卖问题
public class JedisPoolUtil {
private static volatile JedisPool jedisPool = null;
private JedisPoolUtil() {
}
public static JedisPool getJedisPoolInstance() {
if (null == jedisPool) {
synchronized (JedisPoolUtil.class) {
if (null == jedisPool) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
//对连接池进行配置
poolConfig.setMaxTotal(200);
poolConfig.setMaxIdle(32);
poolConfig.setMaxWaitMillis(100 * 1000);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig, "192.168.102.130", 6379, 60000);
}
}
}
return jedisPool;
}
//释放回连接池
public static void release(RedisProperties.Jedis jedis) {
if (null != jedis) {
jedis.close();
}
}
}
Lua脚本
使用Lua脚本可以解决超卖和库存遗留问题
可以直接代替连接池的代码,现在只需要从连接池获取连接
public class SecKillRedisByLua {
static String secKillScript = "local userid=KEYS[1];\r\n" +
"local ticketno=KEYS[2];\r\n" +
"local stockKey='sk:'..ticketno..\":ticket\";\r\n" +
"local usersKey='sk:'..ticketno..\":user\";\r\n" +
"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
"if tonumber(userExists)==1 then \r\n" +
" return 2;\r\n" +
"end\r\n" +
"local num= redis.call(\"get\" ,stockKey);\r\n" +
"if tonumber(num)<=0 then \r\n" +
" return 0;\r\n" +
"else \r\n" +
" redis.call(\"decr\",stockKey);\r\n" +
" redis.call(\"sadd\",usersKey,userid);\r\n" +
"end\r\n" +
"return 1";
//使用lua脚本完成秒杀的核心方法
public static boolean doSecKill(String uid,String ticketNo) {
//先从redis连接池,获取连接
JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = jedisPoolInstance.getResource();
//就是将lua脚本进行加载
String sha1 = jedis.scriptLoad(secKillScript);
//evalsha是根据指定的 sha1校验码, 执行缓存在服务器的脚本
Object result = jedis.evalsha(sha1, 2, uid, ticketNo);
String resString = String.valueOf(result);
//根据lua脚本执行返回的结果,做相应的处理
if("0".equals(resString)) {
System.out.println("票已经卖光了..");
jedis.close();
return false;
}
if("2".equals(resString)) {
System.out.println("不能重复购买..");
jedis.close();
return false;
}
if("1".equals(resString)) {
System.out.println("抢购成功");
jedis.close();
return true;
} else {
System.out.println("购票失败..");
jedis.close();
return false;
}
}
}
Jedis集群
引入依赖
1、防火墙打开相关端口
2、创建set集合,保存集群信息
3、创建集群操作对象
配置文件
spring.redis.lettuce.cluster.refresh.adaptive=true,java程序感知主从切换
spring.redis.lettuce.cluster.refresh.period=2000,设置定时刷新时间
public class Main {
public static void main(String[] args) {
Set set = new HashSet<HostAndPort>();
set.add(new HostAndPort("192.168.102.130",6381));
set.add(new HostAndPort("192.168.102.130",6382));
set.add(new HostAndPort("192.168.102.131",6383));
set.add(new HostAndPort("192.168.102.131",6384));
set.add(new HostAndPort("192.168.102.132",6385));
set.add(new HostAndPort("192.168.102.132",6386));
JedisCluster jedisCluster = new JedisCluster(set);
jedisCluster.set("name","tom");
String demo12 = jedisCluster.get("tom");
System.out.println(demo12);
jedisCluster.close();
}
}
Redis应用问题&解决方案
缓存穿透
缓存击穿
缓存雪崩
分布式锁
Redis实现分布式锁
基本实现
1、setnx key value,理解为上锁,在key没有删除前,不能执行相同key的上锁命令
2、del key,理解为释放锁
3、expire key seconds,给锁设置过期时间,防止死锁
4、ttl key,查看某个锁过期时间,没有过期执行相同ikey会失败,-2是已过期,-1是永不过期
5、set key value nx ex seconds,设置锁的同时,指定过期时间,防止死锁
代码实现
Lua脚本保证删除原子性
Redis新功能
ACL
给jack增加set权限
删除用户