Redis- 技术专题 -Redis 知识体系
1.基本文件说明
2.基础命令
3.字符串命令
4.哈希(Hash)命令
编码: field value 值由 ziplist 及 hashtable 两种编码格式
字段较少的时候采用ziplist,字段较多的时候会变成hashtable编码
5.列表(List)命令
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)
容量 -> 集合,有序集合也是如此。
6.集合(Set)命令
Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据
7.有序集合(sorted set)命令
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
8.发布订阅
开启两个客户端
A客户端订阅频道:subscribe redisChat (频道名字为 redisChat)
B客户端发布内容:publish redisChat "Hello, this is my wor" (内容是 hello....)
A客户端即为自动收到内容, 原理图如下:
9.Redis 事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
批量操作在发送 EXEC 命令前被放入队列缓存
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中
一个事务从开始到执行会经历以下三个阶段:
开始事务
命令入队
执行事务
注意:redis事务和数据库事务不同,redis事务出错后最大的特点是,一剩下的命令会继续执行,二出错的数据不会回滚
10.Redis 服务器命令
11.Redis 数据备份与恢复
Redis SAVE 命令用于创建当前数据库的备份
如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。获取 redis 目录可以使用 CONFIG 命令
12.Redis 性能测试
redis 性能测试的基本命令如下:
redis目录执行:redis-benchmark [option] [option value]// 会返回各种操作的性能报告(100连接,10000请求)redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000// 100个字节作为value值进行压测redis-benchmark -h 127.0.0.1 -p 6379 -q -d 100
Jedis
<!-- jedis --><dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.2</version></dependency>
Jedis配置
############# redis Config ############## Redis数据库索引(默认为0)spring.redis.database=0# Redis服务器地址spring.redis.host=120.79.88.17# Redis服务器连接端口spring.redis.port=6379# Redis服务器连接密码(默认为空)spring.redis.password=123456# 连接池中的最大空闲连接spring.redis.jedis.pool.max-idle=8# 连接池中的最小空闲连接spring.redis.jedis.pool.min-idle=0
JedisConfig
@Configurationpublic class JedisConfig extends CachingConfigurerSupport { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.max-idle}") private Integer maxIdle; @Value("${spring.redis.min-idle}") private Integer minIdle; @Bean public JedisPool redisPoolFactory(){ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxIdle(maxIdle); jedisPoolConfig.setMinIdle(minIdle); jedisPoolConfig.setMaxWaitMillis(3000L); int timeOut = 3; return new JedisPool(jedisPoolConfig, host, port, timeOut, password); }}
基础使用
@RunWith(SpringRunner.class)@SpringBootTest(classes = KerwinBootsApplication.class)public class ApplicationTests { @Resource JedisPool jedisPool; @Test public void testJedis () { Jedis jedis = jedisPool.getResource(); jedis.set("year", String.valueOf(24)); }}
SpringBoot RedisTemplate
<!-- redis --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- redis 2.X 更换为commons-pool2 连接池 --><dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId></dependency>
############# redis Config ############## Redis数据库索引(默认为0)spring.redis.database=0# Redis服务器地址spring.redis.host=120.79.88.17# Redis服务器连接端口spring.redis.port=6379# Redis服务器连接密码(默认为空)spring.redis.password=123456# 连接池最大连接数(使用负值表示没有限制)spring.redis.jedis.pool.max-active=200# 连接池最大阻塞等待时间(使用负值表示没有限制)spring.redis.jedis.pool.max-wait=1000ms# 连接池中的最大空闲连接spring.redis.jedis.pool.max-idle=8# 连接池中的最小空闲连接spring.redis.jedis.pool.min-idle=0# 连接超时时间(毫秒)spring.redis.timeout=1000ms
// Cache注解配置类@Configurationpublic class RedisCacheConfig { @Bean public KeyGenerator simpleKeyGenerator() { return (o, method, objects) -> { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(o.getClass().getSimpleName()); stringBuilder.append("."); stringBuilder.append(method.getName()); stringBuilder.append("["); for (Object obj : objects) { stringBuilder.append(obj.toString()); } stringBuilder.append("]"); return stringBuilder.toString(); }; } @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { return new RedisCacheManager( RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), // 默认策略,未配置的 key 会使用这个 this.getRedisCacheConfigurationWithTtl(15), // 指定 key 策略 this.getRedisCacheConfigurationMap() ); } private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() { Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(16); redisCacheConfigurationMap.put("redisTest", this.getRedisCacheConfigurationWithTtl(15)); return redisCacheConfigurationMap; } private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) { Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith( RedisSerializationContext .SerializationPair .fromSerializer(jackson2JsonRedisSerializer) ).entryTtl(Duration.ofSeconds(seconds)); return redisCacheConfiguration; }}
// RedisAutoConfiguration@Configuration@EnableCachingpublic class RedisConfig { @Bean @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; }}
// 基础使用@ResourceRedisTemplate<String,Object> redisTemplate;redisTemplate.opsForList().rightPush("user:1:order", dataList.get(3).get("key").toString());// 注解使用@Cacheable(value = "redisTest")public TestBean testBeanAnnotation () {}
13.Redis使用场景
或者简单消息队列,发布订阅实施消息系统等等
String - 缓存
// 1.Cacheable 注解// controller 调用 service 时自动判断有没有缓存,如果有就走redis缓存直接返回,如果没有则数据库然后自动放入redis中// 可以设置过期时间,KEY生成规则 (KEY生成规则基于 参数的toString方法)@Cacheable(value = "yearScore", key = "#yearScore")@Overridepublic List<YearScore> findBy (YearScore yearScore) {}// 2.手动用缓存if (redis.hasKey(???) { return ....} redis.set(find from DB)...
String - 限流 | 计数器
// 注:这只是一个最简单的Demo 效率低,耗时旧,但核心就是这个意思// 计数器也是利用单线程incr...等等@RequestMapping("/redisLimit")public String testRedisLimit(String uuid) { if (jedis.get(uuid) != null) { Long incr = jedis.incr(uuid); if (incr > MAX_LIMITTIME) { return "Failure Request"; } else { return "Success Request"; } } // 设置Key 起始请求为1,10秒过期 -> 实际写法肯定封装过,这里就是随便一写 jedis.set(uuid, "1"); jedis.expire(uuid, 10); return "Success Request";}
String - 分布式锁 (重点)
/*** * 核心思路: * 分布式服务调用时setnx,返回1证明拿到,用完了删除,返回0就证明被锁,等... * SET KEY value [EX seconds] [PX milliseconds] [NX|XX] * EX second:设置键的过期时间为second秒 * PX millisecond:设置键的过期时间为millisecond毫秒 * NX:只在键不存在时,才对键进行设置操作 * XX:只在键已经存在时,才对键进行设置操作 * * 1.设置锁 * A. 分布式业务统一Key * B. 设置Key过期时间 * C. 设置随机value,利用ThreadLocal 线程私有存储随机value * * 2.业务处理 * ... * * 3.解锁 * A. 无论如何必须解锁 - finally (超时时间和finally 双保证) * B. 要对比是否是本线程上的锁,所以要对比线程私有value和存储的value是否一致(避免把别人加锁的东西删除了) */@RequestMapping("/redisLock")public String testRedisLock () { try { for(;;){ RedisContextHolder.clear(); String uuid = UUID.randomUUID().toString(); String set = jedis.set(KEY, uuid, "NX", "EX", 1000); RedisContextHolder.setValue(uuid); if (!"OK".equals(set)) { // 进入循环-可以短时间休眠 } else { // 获取锁成功 Do Somethings.... break; } } } finally { // 解锁 -> 保证获取数据,判断一致以及删除数据三个操作是原子的, 因此如下写法是不符合的 /*if (RedisContextHolder.getValue() != null && jedis.get(KEY) != null && RedisContextHolder.getValue().equals(jedis.get(KEY))) { jedis.del(KEY); }*/ // 正确姿势 -> 使用Lua脚本,保证原子性 String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; Object eval = jedis.eval(luaScript, Collections.singletonList(KEY), Collections.singletonList(RedisContextHolder.getValue())); } return "锁创建成功-业务处理成功";}
String - 分布式Session(重点)
// 1.首先明白为什么需要分布式session -> nginx负载均衡 分发到不同的Tomcat,即使利用IP分发,可以利用request获取session,但是其中一个挂了,怎么办?? 所以需要分布式session注意理解其中的区别 A服务-用户校验服务 B服务-业务层情况A:A,B 服务单机部署:cookie:登录成功后,存储信息到cookie,A服务自身通过request设置session,获取session,B服务通过唯一key或者userid 查询数据库获取用户信息cookie+redis:登录成功后,存储信息到cookie,A服务自身通过request设置session,获取session,B服务通过唯一key或者userid 查询redis获取用户信息情况B:A服务多节点部署,B服务多节点部署B服务获取用户信息的方式其实是不重要的,必然要查,要么从数据库,要么从cookieA服务:登录成功后,存储唯一key到cookie, 与此同时,A服务需要把session(KEY-UserInfo)同步到redis中,不能存在单纯的request(否则nginx分发到另一个服务器就完犊子了)官方实现:spring-session-data-redis有一个内置拦截器,拦截request,session通过redis交互,普通使用代码依然是request.getSession.... 但是实际上这个session的值已经被该组件拦截,通过redis进行同步了
List 简单队列-栈
// 说白了利用redis - list数据结构 支持从左从右push,从左从右pop@Componentpublic class RedisStack { @Resource Jedis jedis; private final static String KEY = "Stack"; /** push **/ public void push (String value) { jedis.lpush(KEY, value); } /** pop **/ public String pop () { return jedis.lpop(KEY); }}
@Componentpublic class RedisQueue { @Resource JedisPool jedisPool; private final static String KEY = "Queue"; /** push **/ public void push (String value) { Jedis jedis = jedisPool.getResource(); jedis.lpush(KEY, value); } /** pop **/ public String pop () { Jedis jedis = jedisPool.getResource(); return jedis.rpop(KEY); }}
List 社交类APP - 好友列表
根据时间显示好友,多个好友列表,求交集,并集 显示共同好友等等...疑问:难道大厂真的用redis存这些数据吗???多大的量啊... 我个人认为实际是数据库存用户id,然后用算法去处理,更省空间
Set 好友关系(合,并,交集)
// 插入key 及用户idsadd cat:1 001 002 003 004 005 006// 返回抽奖参与人数scard cat:1// 随机抽取一个srandmember cat:1// 随机抽取一人,并移除spop cat:1
Zset 排行榜
根据分数实现有序列表微博热搜:每点击一次 分数+1 即可--- 不用数据库目的是因为避免order by 进行全表扫描
李浩宇/Alex
我们始于迷惘,终于更高的迷惘. 2020.03.25 加入
一个酷爱计算机技术、健身运动、悬疑推理的极客狂人,大力推荐安利Java官方文档:https://docs.oracle.com/javase/specs/index.html
评论