数据类型应用场景
类型 | 简介 | 特性 | 场景 |
String(字符串) | 二进制安全 | 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M | --- |
Hash(字典) | 键值对集合,即编程语言中的Map类型 | 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
List(列表) | 链表(双向链表) | 增删快,提供了操作某一段元素的API | 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 |
Set(集合) | 哈希表实现,元素不重复 | 1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 | 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐 |
Sorted Set(有序集合) | 将Set中的元素增加一个权重参数score,元素按score有序排列 | 数据插入集合时,已经进行天然排序 | 1、排行榜 2、带权重的消息队列 |
订阅和发布
什么是发布和订阅
发布订阅是一种应用程序(系统)之间通讯,传递数据的技术手段。特别是在异构(不同语言)系统之间作用非常明显。发布订阅可以是实现应用(系统)之间的解耦合。
发布订阅:类似微信中关注公众号/订阅号,公众号/订阅号发布的文章,信息。订阅者能及时获取到最新的内容。微博的订阅也是类似的。日常生活中听广播,看电视。都需要有信息的发布者,收听的人需要订阅(广播、电视需要调动某个频道)。发布订阅是一对多的关系。
订阅:对某个内容感兴趣,需要实时获取新的内容。只要关注的内容有变化就能立即得到通知。多的一方。
发布:提供某个内容,把内容信息发送给多个对此内容感兴趣的订阅者。是有主动权,是一的一方。
发布订阅应用在即时通信应用中较多,比如网络聊天室,实时广播、实时提醒等。滴滴打车软件的抢单;外卖的抢单;在微信群发红包,抢红包都可以使用发布订阅实现。
Redis的发布和订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(publish)发送消息,订阅者(subscribe)接收消息。发布订阅也叫生产者消费者模式,是实现消息队列的一种方式。Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
命令行模式举例
关于redis的订阅和发布,重点关注三个要点
- psubscribe:订阅一个或多个符合给定模式的频道。返回值:接收到的信息
- publish:publish channel message:将信息 message 发送到指定的频道 channel
返回值:接收到信息 message 的订阅者数量。
- unsubscribe:指示客户端退订给定的频道
主要的操作命令
redis发布与订阅的缺陷
虽然redis实现了发布订阅(publish/subscribe)的功能,但是在通常的情况下是不推荐使用的,如果想使用消息队列这种功能,最好还是使用专业的各种MQ中间件,例如rabbitMQ(阿里云开源消息队列),rockedMQ,activitedMQ,kafka(apache 开源的多消息队列,大数据生态中应用广泛)等。
代码方式举例
package main
import (
"fmt"
"github.com/my/repo/redisConn"
"log"
"sync/atomic"
"time"
)
var conn = redisConn.ConnectRedis()
var ctx = redisConn.CTX
func main() {
go subscribe()// 订阅
publish(6)// 信息发布
time.Sleep(1 * time.Second)
defer Reset()
}
func subscribe() {
sub := conn.Subscribe(ctx, "channel")
defer sub.Close()
var count int32 = 0
for item := range sub.Channel() { // 通过遍历Channel的执行结果来监听订阅消息
fmt.Println(item.String()) // 打印接收到的每条消息
atomic.AddInt32(&count, 1) // 接收一条订阅反馈消息
fmt.Println(count) // 3条发布者发送的消息
switch count {
case 4: // 执行退出操作,停止监听新消息
if err := sub.Unsubscribe(ctx, "channel"); err != nil {
log.Println("unsubscribe faile, err:", err)
} else {
fmt.Println("unsubscribe success")
}
}
}
}
func publish(n int) {
time.Sleep(1 * time.Second) // 休眠,让订阅者有足够的时间连接服务器并监听消息
for n > 0 {
var url = "hello=%d"
msg := fmt.Sprintf(url,n )
conn.Publish(ctx, "channel", msg)
n--
}
}
//Redis Flushdb 命令用于清空当前数据库中的所有 key。
func Reset() {
conn.FlushDB(ctx)
}
pipeline
https://www.cnblogs.com/-wenli/p/12922089.html
为什么使用 Pipeline
Redis客户端执行一条命令分为如下四个过程:
发送命令
命令排队
命令执行
返回结果
Redis的pipeline功能的原理是 客户端通过一次性将多条redis命令发往Redis 服务端,减少了每条命令分别传输的IO开销。同时减少了系统调用的次数,因此提升了整体的吞吐能力。
未使用pipeline命令
使用了pipeline命令
用户基础画像(hash应用)
业务需求
在推荐系统中,用户基础画像时非常重要的,例如:我们可以根据用户的性别/年龄的特征,来选择那些该用户喜欢的商品,为提高系统性能,一般采用通过缓存系统来存储(例如:Redis)
数据结构设计
key(用户ID) | 特征名称 | 特征数值 |
user:234 | 用户名称 | vx-aiwen |
联系方式 | 1121025735 | |
博客 | wenjie.blog.csdn.net | |
性别 | male | |
年龄 | 30 |
任务实现
通过代码实战方式,完成用户基础画像方案
1、用户基础画像存储
2、针对用户ID,获取用户画像信息
key := "user:234"
conn.HSet(ctx, key, "username", "vx-aiwen")
conn.HSet(ctx, key, "password", "1121025735")
conn.HSet(ctx, key, "blog", "wenjie.blog.csdn.net")
conn.HSet(ctx, key, "sex", "male")
conn.HSet(ctx, key, "age", 30)
微博热榜列表(zset 应用)
业务需求
热榜任务设计
zset 的 key,把文章ID 作为 member ,点击数评论数等作为 score,当 score 发生变化时更新 score。利用 ZREVRANGE
或者 ZRANGE
查到对应数量的记录。
https://blog.csdn.net/weichi7549/article/details/107836536
https://blog.csdn.net/weichi7549/article/details/107836536
- 离线任务存储
贴子ID 作为 member ,点击数评论数等作为 score
- 在线热榜推荐
热榜任务实现
通过实战代码,最终实现rank列表,然后在界面上进行展示
rank | 得分 | 文章ID | 文章标题 |
0 | 265.2 | articleID_1000 | .. |
1 | 166.7 | articleID_3000 | .... |
2 | 166.5 | articleID_2000 | ..... |
3 | 166.2 | articleID_4000 | ....... |
package main
import (
"fmt"
"github.com/aiwen/aiwein-go-redis/redisConn"
"github.com/go-redis/redis/v8"
)
// key 设计
var (
key = "weibo_hot"
)
type WeiboHotAPI struct {
rdb *redis.Client
}
var api = WeiboHotAPI{rdb: redisConn.ConnectRedis()}
func main() {
//离线任务存储
api.store()
//人榜列表获取
api.hotList()
}
func (api *WeiboHotAPI) store() error {
client := api.rdb
client.ZAdd(api.rdb.Context(), key, &redis.Z{
Score: 265.2, // 点击或者阅读综合得分
Member: "articleID_1000", // 文章ID
})
client.ZAdd(api.rdb.Context(), key, &redis.Z{
Score: 166.5,
Member: "articleID_2000",
})
client.ZAdd(api.rdb.Context(), key, &redis.Z{
Score: 166.7,
Member: "articleID_3000",
})
client.ZAdd(api.rdb.Context(), key, &redis.Z{
Score: 166.2,
Member: "articleID_4000",
})
client.ZAdd(api.rdb.Context(), key, &redis.Z{
Score: 162.5,
Member: "articleID_5000",
})
return nil
}
func (api *WeiboHotAPI) hotList() {
client := api.rdb
// 获取排行榜
// 前三列表
rank_list := client.ZRevRangeWithScores(api.rdb.Context(), key, 0, 3).Val()
fmt.Println("rank_list = ", rank_list)
for rank, data := range rank_list {
fmt.Println("rank = ", rank, ",articleID = ", data.Member, "score = ", data.Score)
}
}