BBS论坛项目相关-8:Redis相关

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结构,内存优化,用于保存顿消的字符串,内存分配只分一次,分一块连续区域

redisObjectsdshdr
type;encoding;ptrfree;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";
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值