BBS论坛项目相关-8:Redis相关
Redis
Redis是一种基于键值对的NoSQL数据库,值支持多种数据结构:6379
字符串,哈希,列表,集合,有序集合
将所有数据都存在内存中,读写性能好。
还可以将内存中的数据以快照或日志的形式保存到硬盘中。
【
持久化:rdb快照当前数据原原本本保存,耗时,存储时会产生阻塞,适合几小时做一次,做数据备份,AOF日志,追加的形式,占空间,实时存储,把命令从头到尾跑一遍
】
Redis事务中不会立即执行查询命令,而是将命令放入队列中,稍后统一执行。
应用场景:缓存,排行榜,计数器,社交网络,消息队列等
Redis的五种数据类型
String:
应用:单值缓存,对象缓存,分布式锁,WEB集群session共享
计数器:INCR article:readcount:{文章id},GET article:readcount:{文章id}
分布式系统全局序列号:INCR orderID 1000 redis批量生成序列号提升性能
【
String底层结构:
Redis用六大结构构建了一个对象系统redisObject,包含了五大数据对象
redisObject:
typedef Struct redisObject{
unsigned type:4;//类型
unsigned encoding:4;//编码,对应底层使用的数据结构
void *ptr;//指向底层实现数据结构的指针
}
字符串底层是简单动态字符串SDS和直接存储,编码:int,row,embstr区别于内存结构不同
1)int:整数值,直接保存在redisObject的ptr属性
2)raw:大于32字节的字符串值,用SDS结构,内存分配两次,创建redisObject和sdshdr
redisObject |
---|
type;encoding;ptr |
sdshdr |
---|
free;len;buf |
3)embstr:小于等于32字节的字符串值,用SDS结构,内存优化,用于保存顿消的字符串,内存分配只分一次,分一块连续区域
redisObject | sdshdr |
---|---|
type;encoding;ptr | free;len;buf |
总结:
1)在redis中存储long和double的浮点数都是先转为字符串再存储
2)raw和embstr效果相同,不同于内存分配和释放
3)embstr内存连续,,能更好利用缓存优势
4)int和embstr如果追加字符串,满足条件会转为raw,embstr是只读的,一旦修改就转为raw
】
hash:
应用:
购物车:用户id为key,商品id为field,商品数量为value
HMSET userid entityId 1 userid2 entityId2 1
常用指令:HINCRBY增加 HLEN长度 HDEL删除 HGETALL获取全部
【
hash优点:
1)同类数据归类整合存储,方便数据管理
2)比string消耗内存cpu小
3)比string节省空间
缺点:
1)过期功能不能用在field上,只能用在key上
2)redis集群下不适合大规模使用
】
list:
stack:LPUSH+LPOP
queue:LPUSH+RPOP
blocking MQ:LPUSH+BRPOP
应用:
消息流: LPUSH msg:{userId} 消息id;LRANGE msg:{userId} 04 查看最新的4条消息
set
应用:
抽奖:SADD key {userid};SMEMBERS key;SRANDMEMBER key [count]
如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合。
如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。
点赞:SADD like{消息id} userid;SREM like{消息id} userid 移除值;
商品筛选:SISMEMBER like{消息id} userid SMEMBERS like{消息id} ;SCARD like{消息id}返回key的set的元素个数
集合操作应用:关注模型,共同关注,也关注了他 SINTER SUNION SDIFF :SINTER set1 set2 set3
sorted set
应用:
ZADD key score member [[score member] …]
ZREM 删除 ZSCORE key member有序集合key中member的分
ZINCRBY 加分 ZCARD 返回个数 ZRANGE key start stop [withScores] 正序 start~top
ZREVRANGE 倒序
集合: ZUNIONSTORE
排行榜
Spring整合Redis
配置数据库参数
redis的数据库个数是可以配置的,默认为16个,见redis.windows.conf/redis.conf的databases 16。
对应数据库的索引值为0 - (databases -1),即16个数据库,索引值为0-15。默认存储的数据库为0。
spring.redis.database=11
spring.redis.host=localhost
spring.redis.port=6379
编写配置类,构造RedisTemplate
设置key的序列方式注意
Template.setKeySerializer(RedisSerializer.string())
如果本身value是hash值,需要对其进行hash的序列化
Template.setHashKeySerializer(RedisSerializer.string())
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 设置key的序列化方式
template.setKeySerializer(RedisSerializer.string());
// 设置value的序列化方式
template.setValueSerializer(RedisSerializer.json());
// 设置hash的key的序列化方式
template.setHashKeySerializer(RedisSerializer.string());
// 设置hash的value的序列化方式
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();
return template;
}
}
序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。
本质上存储和网络传输 都需要经过 把一个对象状态保存成一种跨平台识别的字节格式,然后其他的平台才可以通过字节信息解析还原对象信息。
redis序列化方式对比:
redis的默认方式是JdkSerializationRedisSerializer
JdkSerializationRedisSerializer: 使用JDK提供的序列化功能。
优点是反序列化时不需要提供类型信息(class),但缺点是需要实现Serializable接口,还有序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。
Jackson2JsonRedisSerializer: 使用Jackson库将对象序列化为JSON字符串。
优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。
但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码,发现其只在反序列化过程中用到了类型信息。
问题:使用默认的JDK序列化方式,在RDM工具中查看k-v值时会出现“乱码”,不方便查看。
解决:自定义系列化方式,使用Jackson2JsonRedisSerializer
访问Redis
redisTemplate.opsForValue();
redisTemplate.opsForHash();
redisTemplate.opsForList();
redisTemplate.opsForSet();
redisTemplate.opsForZSet();
redis事务
本质是一组命令的集合,事务支持一次执行多个命令,一个事务中的所有命令都被序列化,在事务执行过程中按照顺序串行执行。
没有隔离级别的概念,批量操作在发送EXEC命令之前放在队列缓存中,不会立即执行。不要在事务中进行查询,因为它不会马上执行,所以不会查询出结果
Redis事务不保证原子性,单条命令具有原子性,但事务不保证原子性,且没有回滚,一条命令出错,其他命令仍会执行。
muti开启事务
exec执行事务
discard取消事务
watch key1 key2…监视一个或多个key,被监视的key被其他命令改动时,事务被打断,相当于乐观锁
unwatch 取消对所有key的监控
可绑定后对次访问同一个key,对其进行操作,相当于事务
public void testBoundOperations() {
String redisKey = "test:count";
BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);
operations.increment();
operations.increment();
operations.increment();
operations.increment();
operations.increment();
System.out.println(operations.get());
}
编程式事务
public void testTransactional() {
Object obj = redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String redisKey = "test:tx";
operations.multi();
operations.opsForSet().add(redisKey, "zhangsan");
operations.opsForSet().add(redisKey, "lisi");
operations.opsForSet().add(redisKey, "wangwu");
System.out.println(operations.opsForSet().members(redisKey));//[]
return operations.exec();
}
});
System.out.println(obj);
}
其他
由于redis的key最好根据业务有规律的设计,可以单独写一个redisKey工具类,用于生成各业务所需的key值,方便统一管理
public class RedisKeyUtil {
private static final String SPLIT = ":";
private static final String PREFIX_ENTITY_LIKE = "like:entity";
private static final String PREFIX_USER_LIKE = "like:user";
private static final String PREFIX_FOLLOWEE = "followee";
private static final String PREFIX_FOLLOWER = "follower";
private static final String PREFIX_KAPTCHA = "kaptcha";
private static final String PREFIX_TICKET = "ticket";
private static final String PREFIX_USER = "user";
private static final String PREFIX_UV = "uv";
private static final String PREFIX_DAU = "dau";
private static final String PREFIX_POST = "post";
// 某个实体的赞
// like:entity:entityType:entityId -> set(userId)
public static String getEntityLikeKey(int entityType, int entityId) {
return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
}
// 某个用户的赞
// like:user:userId -> int
public static String getUserLikeKey(int userId) {
return PREFIX_USER_LIKE + SPLIT + userId;
}
// 某个用户关注的实体
// followee:userId:entityType -> zset(entityId,now)
public static String getFolloweeKey(int userId, int entityType) {
return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
}
// 某个实体拥有的粉丝
// follower:entityType:entityId -> zset(userId,now)
public static String getFollowerKey(int entityType, int entityId) {
return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
}
// 登录验证码
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}
// 登录的凭证
public static String getTicketKey(String ticket) {
return PREFIX_TICKET + SPLIT + ticket;
}
// 用户
public static String getUserKey(int userId) {
return PREFIX_USER + SPLIT + userId;
}
// 单日UV
public static String getUVKey(String date) {
return PREFIX_UV + SPLIT + date;
}
// 区间UV
public static String getUVKey(String startDate, String endDate) {
return PREFIX_UV + SPLIT + startDate + SPLIT + endDate;
}
// 单日活跃用户
public static String getDAUKey(String date) {
return PREFIX_DAU + SPLIT + date;
}
// 区间活跃用户
public static String getDAUKey(String startDate, String endDate) {
return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;
}
// 帖子分数
public static String getPostScoreKey() {
return PREFIX_POST + SPLIT + "score";
}
}