点赞
点赞功能如果单从业务逻辑的角度讲其实没啥难的,无非就是给每个用户设计一张表来记录该用户的被点赞数,该用户id,以及点赞者id
这个时候我们就要思考了点赞这个操作是不是一个数据量非常大操作特别频繁的呀,所以这个时候我们应该采用一个更加高效的数据库来存储这些数据,而这个数据库就是Redis数据库
Redis
Redis是一款基于键值对的NoSQL 数据库,它的值支持多种数据结构:字符串(strings)、哈希(hashes)、 列表(lists)、 集合(sets)、 有序集合(sorted sets)等。
Redis将所有的数据都存放在内存中,所以它的读写性能十分惊人。同时,Redis还可以将内存中的数据以快照或日志的形式保存到硬盘上,以保证数据的安全性。
Redis典型的应用场景包括:缓存、排行榜、计数器、社交网络、消息队列等。
感觉redis有点像全局的Map以Key:value的形式存储数据,只不过他是一个全局的map并且效率更高,提供了更高效的CRUD,更多的类型
Redis的事务机制
redis的事务是将操作放入一个队列中,所以不要在事务中进行查找操作
//事务
@Test
public void tx(){
Object obj = redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
String key = "test:tx";
//开启事务
operations.multi();
ValueOperations valueOperations = operations.opsForValue();
valueOperations.set(key,1);
valueOperations.set(key,2);
System.out.println(valueOperations.get(key));
//提交事务
return operations.exec();
}
});
System.out.println(obj);
}
Spring 整合 Redis
步骤
-
引入依赖
-
配置数据库参数
-
编写配置类
-
构造RedisTemplate
访问Redis
-
redisTemplate. opsForValue() strings类型
-
redisTemplate. opsForHash() hashset类型
-
redisTemplate. opsForList () lists类型
-
redisTemplate. opsForSet () sets类型
-
redisTemplate. opsForzSet () sorted set类型
配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String,Object> template = new RedisTemplate<>();
// 设置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;
}
}
Redis的高级数据类型
-
HyperLogLog
-
采用一种基数算法,用于完成独立总数的统计。
-
占据空间小,无论统计多少个数据,只占12K的内存空间。
-
不精确的统计算法,标准误差为0.81%。
-
相当于set,用于去重,统计数据
//统计20W个重复数据的独立总数
@Test
public void HyperLogLogTest() {
String key = "test:hll:01";
for (int i = 0; i < 100000; i++) {
HyperLogLogOperations hyperLogLogOperations = redisTemplate.opsForHyperLogLog();
hyperLogLogOperations.add(key,i);
}
for (int i = 0; i < 100000; i++) {
HyperLogLogOperations hyperLogLogOperations = redisTemplate.opsForHyperLogLog();
hyperLogLogOperations.add(key,(int)(Math.random() * 10000 + 1));
}
System.out.println(redisTemplate.opsForHyperLogLog().size(key));
}
//将三组数据合并,再统计合并后重复数据的独立总数
@Test
public void HyperLogLogUnionTest() {
String key = "test:hll:02";
for (int i = 0; i < 10000; i++) {
HyperLogLogOperations hyperLogLogOperations = redisTemplate.opsForHyperLogLog();
hyperLogLogOperations.add(key,i);
}
String key2 = "test:hll:03";
for (int i = 0; i < 10000; i++) {
HyperLogLogOperations hyperLogLogOperations = redisTemplate.opsForHyperLogLog();
hyperLogLogOperations.add(key2,i + 5000);
}
String key3 = "test:hll:04";
for (int i = 0; i < 10000; i++) {
HyperLogLogOperations hyperLogLogOperations = redisTemplate.opsForHyperLogLog();
hyperLogLogOperations.add(key3,i + 10000);
}
String unionKey = "test:hll:union";
System.out.println(redisTemplate.opsForHyperLogLog().union(unionKey,key,key2,key3));
}
-
Bitmap
-
不是一种独立的数据结构,实际上就是字符串。
-
支持按位存取数据,可以将其看成是byte数组。
-
适合存储索大量的连续的数据的布尔值。
-
相当于string byte[] 内容只能有0 1
@Test
public void BitmapTest() {
String key = "test:bm:01";
// 记录
redisTemplate.opsForValue().setBit(key,1,true);
redisTemplate.opsForValue().setBit(key,4,false);
redisTemplate.opsForValue().setBit(key,7,true);
// 查询
System.out.println(redisTemplate.opsForValue().getBit(key,1));
System.out.println(redisTemplate.opsForValue().getBit(key,2));
System.out.println(redisTemplate.opsForValue().getBit(key,3));
// 统计
Object obj = redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
return connection.bitCount(key.getBytes());
}
});
System.out.println(obj);
}
// 或运算OR
@Test
public void BitmapOperationTest() {
String key2 = "test:bm:02";
// 记录
redisTemplate.opsForValue().setBit(key2,0,true);
redisTemplate.opsForValue().setBit(key2,1,true);
redisTemplate.opsForValue().setBit(key2,2,true);
String key3 = "test:bm:03";
// 记录
redisTemplate.opsForValue().setBit(key3,2,true);
redisTemplate.opsForValue().setBit(key3,3,true);
redisTemplate.opsForValue().setBit(key3,4,true);
String key4 = "test:bm:04";
// 记录
redisTemplate.opsForValue().setBit(key4,4,true);
redisTemplate.opsForValue().setBit(key4,5,true);
redisTemplate.opsForValue().setBit(key4,6,true);
String key = "test:bm:or";
Object obj = redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
connection.bitOp(RedisStringCommands.BitOperation.OR,key.getBytes(),key2.getBytes(),key3.getBytes(),key4.getBytes());
return connection.bitCount(key.getBytes());
}
});
System.out.println(obj);
for (int i = 0; i < 7; i++) {
System.out.println(redisTemplate.opsForValue().getBit(key,i));
}
}
Redis的技术讲解完了接下来就是开始业务逻辑的处理了
使用Redis最难的并不是操作而是如何设计一个复用性高的表也就是key!!!
要思考怎么样设计一个key可以表示所有要表示的数据
以点赞为例:
点赞操作要分为两种一种是实体的点赞一种是用户的点赞
首先这是个实体点赞操作部署关注操作对吧所以key里面要有like字段,然后点赞操作我们需要知道这是个帖子啊还是评论啊,还是回复啊所以我们还需要实体的类型是吧,再者就是这个实体的id了。然后这个点赞是我点的吧需要我的id对吧。所以key为like:entity:entityType:entityId这样设计还能提供拓展性如果以后提供了更多的点赞种类都能使用比如我还想增加一个给用户直接点赞的需求那么只需要type+1就好了
然后巧妙的地方来了value我选用的是set(userid)因为我们需要考虑到这个用户有可能是点错了啦要取消点赞哒所以存储set可以判断这个用户有没有点过赞然后才能进行操作,而且set还能获得点赞的数量这样岂不美哉。
接着就是用户点赞key的设计了这个没啥就是一个用户id就好了存储的value也是数量就好了like:user:userId -> int这个操作的设计是为了后面发送系统消息的需求提供的之后再讲
业务逻辑
后端需要从前端获取这个点赞的操作是给帖子还是回复还是评论也就是实体类型,然后是实体id然后是作为被点赞的人的id,至于点赞者我的id可以直接从ThreadLocal中获取,然后将我的id存到实体点赞的set中,然后用户点赞++反之移出再--。如果是点赞的操作那么还需要将这条消息添加到消息队列中
关注
关注和点赞操作一模一样所以我就只讲一下key的设计好了
key设计要分为两个一个是某个实体(用户)关注的实体(用户)列表,一个是一个实体(用户)被关注的实体(用户)列表,说白了就是我的关注和我的粉丝
// 某个用户关注的实体
// followee:userId:entityType -> zset(entityId,now)
// userId -> 某个关注别人的关注者id
// entityType -> 关注的类型,可以是帖子可以是人等待
// value是zset(entityId,now) 是一个有序集合以时间为score,以实体id为值
// 某个实体拥有的粉丝
// follower:entityType:entityId -> zset(userId,now)
// entityType -> 实体类型可以是用户可以是帖子等等
// entityId -> 这个实体的id
// 有了这两个参数可以唯一的表示某个实体,比如说如果这个实体是人那么entityType就是人entityId就是userId如果这个实体是帖子那么entityType就是帖子entityId就是帖子id
// value是zset(userId,now)是一个有序集合,集合中值是userId表示是哪个用户关注了这个实体