续Redis基本使用
Redis的用途
上次课我们使用了Redis来保存一个字符串
但是实际开发中,基本上保存的都是集合或对象
我们可以保存json格式的字符串来实现
在实际开发中,我们可能会将标签\分类\秒杀这样或类似的数据保存在Redis中,以应对频繁的访问
实际上,还有一种比较多的使用的缓存就是某一条信息的浏览量\评论数\点赞数等,以及秒杀商品时的库存数等,都可以利用Redis提高并发量,高效访问
我们就来使用一下加减数字的命令
127.0.0.1:6379> set num "2"
OK
127.0.0.1:6379> get num
"2"
127.0.0.1:6379> incr num
(integer) 3
127.0.0.1:6379> get num
"3"
127.0.0.1:6379> decr num
(integer) 2
笔记末尾有Redis操作其它类型的演示操作,同学们可以自己运行测试
Redis线程安全问题
Redis底层操作数据的线程只有一条,即使有并发请求,也不会有线程安全问题,因为足够快,所以一条线程也能快速处理数据,请求不会有明显的等待
SpringBoot操作Redis
添加依赖
转到knows-faq模块
在pom.xml文件中添加如下依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
Redis也是数据库,java操作Redis和java操作mysql有很多相似
java是通过jdbc来操作mysql数据库的
java是通过jedis来操作redis数据库的
jdbc和jedis都是比较繁琐的操作数据库的代码
所以我们使用mybatis操作mysql更简单
我们使用Spring-data-redis也可以更简单的操作redis
在操作之前我们还要配置一下application.properties文件,指定Redis的位置
knows-faq模块的application.properties文件
# 配置Redis的ip地址和端口号,以便SpringDataRedis连接
spring.redis.host=localhost
spring.redis.port=6379
基本操作测试
我们配置完SpringDataRedis
最好先在测试类中进行一下测试,保证运行状况良好
测试代码如下
// 下面代码时要连接Redis来进行新增和获取的操作
// 注解标记的对象可以操作Redis
// 是SpringDataRedis框架自动向Spring容器中保存的对象
// RedisTemplate<[key类型],[value的类型]>
@Resource
RedisTemplate<String,String> redisTemplate;
// 新增数据
@Test
public void add(){
redisTemplate.opsForValue().set("myname","诸葛亮");
System.out.println("ok");
}
// 获取数据
@Test
public void get(){
String name=redisTemplate.opsForValue().get("myname");
System.out.println(name);
}
Redis缓存标签列表
继续在knows-faq模块
我们的目标是TagServiceImpl类中查询出所有标签保存在Redis中
而现在我们在使用List<Tag>和Map<String,Tag>类型的属性来充当缓存需要进行改进,所有要删除之前的缓存属性和实现的代码
TagServiceImpl代码修改后结果如下
@Service
public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements ITagService {
@Autowired
private TagMapper tagMapper;
// 获得操作Redis的对象
@Resource
private RedisTemplate<String,List<Tag>> redisTemplate;
@Override
public List<Tag> getTags() {
// 先编写代码,从Redis中获得所有标签
List<Tag> tags=redisTemplate.opsForValue().get("tags");
// 判断从Redis中获得的标签是否为空
if(tags==null){
// 如果为空,证明是第一次访问,需要连接数据库查询所有标签
tags=tagMapper.selectList(null);
// 将查询出的所有标签保存在Redis中,以便下次获取
redisTemplate.opsForValue().set("tags",tags);
System.out.println("Redis加载所有标签完毕");
}
// 返回所有标签
return tags;
}
@Override
public Map<String, Tag> getTagMap() {
// 实例化一个map对象
Map<String,Tag> tagMap=new HashMap<>();
// 遍历上面获得List的方法,将所有标签保存在map中
for(Tag t: getTags()){
tagMap.put(t.getName(),t);
}
// 返回tagMap
return tagMap;
}
}
重启faq项目
启动Nacos\gateway\knows-client
再访问学生首页,第一次访问时控制台会输出"Redis加载所有标签完毕",但是之后的刷新就不会再输出这个信息了,甚至重启faq模块之后访问学生首页也不会输出这个信息了,原因是Redis中保存着这个信息,只要Redis不重启,就不会出现这个信息!
Ribbon实现微服务间调用
什么是Ribbon
Ribbon也是SpringCloud提供的组件
它的功能是实现微服务之间的相互调用的
因为微服务项目每个业务都是项目整体的多个分支
分支之间一定会有交互,微服务之间的互相调用是普遍存在的
由此可知,Ribbon的使用时非常频繁和普遍呃,所以我们添加的
spring-cloud-starter-alibaba-nacos-discovery依赖已经包含了Ribbon,
也就是说Ribbon不需要单独添加配置和依赖,直接使用即可
Ribbon使用示例
在使用Ribbon之前
我们需要先明确在指定的业务流程中,Ribbon作用的多个微服务项目中哪个是调用的发起者,哪个是被调用的
被调用的一方称之为服务的提供者,也叫"生产者"
发起调用的一方称之为服务的调用者,也叫"消费者"
Ribbon可以调用什么样的方法
Ribbon本质上就是向目标服务器发送了一次请求,它能调用到的方法就是目标服务器编写的控制器的方法,调用的依据就是控制器方法的url
我们将用于Ribbon调用的控制器方法称之为"Rest接口"
-
定义一个生产者的方法(Controller类的方法)
-
添加Ribbon调用的支持(每个微服务项目编写一次)
-
消费者发起调用(一般在业务逻辑层中发起调用)
步骤1:
定义生产者
任何已经定义好的控制器方法,都可以被Ribbon调用
我们现在将knows-sys模块的AuthController类中的demo方法作为生产者,也就是调用目标
调用它的路径就是:/v1/auth/demo
步骤2:
添加Ribbon的支持
我们需要在将要发起Ribbon请求的项目中添加Ribbon的支持
我们一般会在SpringBoot启动类中将能够调用Ribbon请求的对象保存在Spring容器中,以备项目使用
我们本次测试使用faq模块调用sys模块中的方法
knows-faq模块的SpringBoot启动类中添加支持Ribbon的配置
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("cn.tedu.knows.faq.mapper")
public class KnowsFaqApplication {
public static void main(String[] args) {
SpringApplication.run(KnowsFaqApplication.class, args);
}
// @Bean表示会将下面方法的返回值保存在Spring容器中
@Bean
// LoadBalanced是负载均衡的意思
// 微服务模块间的调用是不经过网关的,所以网关中设置的负载均衡无效
// 导致Ribbon请求需要单独的配置负载均衡的注解,完成高效调用
@LoadBalanced
// 向Spring容器中保存一个RestTemplate类型的对象,支持Ribbon调用
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
RestTemplate就是能够执行Ribbon请求的对象
这个步骤只需要配置一次,如果在其他业务中knows-faq项目又需要调用其他项目的方法,就不需要再次配置了
步骤3:
发起调用
我们应该已经在发起调用的一方中配置了RestTemplate对象
实际开发中,都是在业务逻辑层中发起Ribbon调用
现在我们是测试,写在测试类中即可
调用目标:/v1/auth/demo
测试代码如下
// 从Spring容器中获得发起Ribbon请求的对象
@Resource
RestTemplate restTemplate;
@Test
public void ribbon(){
// 发送ribbon请求,先定义url
// url=[协议]+[服务器名称]+[控制器路径]
// 服务器名称必须是Nacos服务列表中存在的名称
// 控制器路径就是从/v1开始的路径
String url="http://sys-service/v1/auth/demo";
// 发起Ribbon请求
String str=restTemplate.getForObject(url,String.class);
System.out.println(str);
}
测试必须在Nacos和sys启动的情况下运行
faq模块会在测试运行时启动
Ribbon调用示意图
Ribbon请求用户信息
上面章节完成了第一个Ribbon程序
在实际开发中,需要更有意义的控制器方法作为调用目标,而不是返回一个"helloworld"
下面我们就来实现根据用户名获得用户对象的实际业务
这个业务在faq模块中,很多方法都需要使用
sys模块仍然是生产者
faq模块仍然是消费者
sys模块要定义一个根据用户名返回用户对象的控制层方法
因为没有这个方法,所以我们要
转到knows-sys模块,编写出这个方法
先编写业务逻辑层IUserService添加方法如下
// 根据用户名获得用户对象
User getUserByUsername(String username);
UserServiceImpl实现类方法
@Override
public User getUserByUsername(String username) {
return userMapper.findUserByUsername(username);
}
AuthController类中编写方法调用上面业务逻辑层
@Resource
private IUserService userService;
// 当前控制器方法用于Ribbon请求,路径设计为/v1/auth/user
// 参数是用户名,返回值为用户对象,Ribbon请求对应GetMapping
@GetMapping("/user")
public User getUser(String username){
return userService.getUserByUsername(username);
}
sys模块Rest接口定义完成
也就是生产者定义完成下面要开始编写消费者的Ribbon的支持
上次课已经配置完毕,直接跳过
所以直接开始在knows-faq模块编写Ribbon调用即可
仍然使用测试类来进行测试
// 根据用户名获得用户对象的Ribbon调用
@Test
public void getUser(){
// url路径中?之后的内容就是Ribbon请求的参数
// 参数名称必须和控制器方法参数名称一致
// 参数的值不直接赋值,使用{1}来占位
// 调用时有既定的赋值方式
String url="http://sys-service/v1/auth/user?username={1}";
// 调用时,前两个参数意义不变,第三参数向{1}中复制
User user=restTemplate
.getForObject(url,User.class,"st2");
System.out.println(user);
}
启动Nacos\重启Sys后再运行测试
测试结果中包含我们查询的用户信息,表示一切正常
微服务的会话保持
会话和会话保持
会话就是Session
指多次请求和响应的过程,只要浏览器打开之后不关闭,不超时就是同一次会话,在本次会话中登录成功时,会将用户信息保存在会话中,只要是同一次会话,当前登录的用户信息,就能一直保存在当前服务器内存中
我们达内知道单体项目portal实际上也是有会话支持的,只是所有会话的操作都封装在了Spring-Security框架,Spring-Security底层也是依靠session实现会话保持的
单体项目只需要一个服务器来保存当前登录用户信息,就可以实现会话保持
但是微服务项目是由多个服务器构成的,我们登录之后如何让所有微服务都知道我们的身份就成为了问题
微服务中的会话保持
每个服务器都有自己的内存,它们的内存中的数据不会自动共享
当一个用户登录一台服务器后,相当于把自己的用户信息保存在了这台服务器的内存中,当访问其它服务器时,新的服务器并没有保存当前用户的信息,所以会话保持是失败的
所有微服务项目都面临这个问题
那么想要在微服务架构下实现登录后还能会话保持,就需要特殊的解决方案
这个方案的名称叫"单点登录"
单点登录的实现思路
现在业界实现单点登录的思路主要有两种,都能够实现微服务的会话保持
1.Session共享
2.Token(令牌)
方案一:Session共享
核心思想就是将登录成功的用户信息共享给所有模块
实现思路
用户在登录服务器(sys)模块登录成功,同时会将用户信息保存在Redis中(保存的key为当前用户的sessionId)
该用户访问其它模块时,这个模块会到Redis中寻找这个用户的信息(依据也是当前用户的sessionId),这样就能实现会话保持
优点:
- 支持Session共享功能的框架比较成熟,仅需要简单配置就可以实现功能
- 结构相对简单,不需要大范围修改代码和更改程序结构,成本比较低
缺点:
- 因为用户信息要共享到Redis中,各个微服务模块需要时还需要从Redis中获取,内存使用开销较大,影响服务器性能
- 只能在当前微服务项目中实现会话保持,比较难以实现跨项目的信息共享
方案二:Token令牌
登录成功时,由登录服务器向客户端响应一个Token(令牌),客户端来保存这个Token,这个Token就是一个加密的字符串,其中包含当前登录用户的信息
当前项目的所有微服务都可以解析这个Token
最终客户端需要表名自己用户的身份时,只需要将Token和自己的请求信息一起发送给服务器,任何模块的微服务都可以知道这个用户的身份信息
优点:
- 服务器内存不需要再保存用户信息,减少内存开销,运行效率更高
- 客户端保存令牌,只要是可以解析这个令牌的项目都可以知道用户身份,方便跨应用(app)登录,也方便功能的扩展
缺点:
- 一般需要一个单独的授权服务器来生成和解析令牌,配置内容较多,开发成本高
- 因为加密和解密需要CPU的参与,所以需要CPU的算力,消耗的额外的算力,CPU的运行效率可能受影响
达内知道项目使用Token令牌的方式实现微服务架构的单点登录功能
Oauth2概述
什么是Oauth2
实现Token单点登录的解决方案现在业界是有明确标准的
Oauth(Open Auth:开放授权)是一套授权标准,是业界都在使用的一套完整的授权解决方案的格式
我们所使用的Oauth2.0就是Oauth1.0的升级.但是Oauth1.0基本没有被使用
现在很多大公司和企业都使用Oauth2作为开发授权标准
Oauth2支持的部分常用授权模式
-
扫码登录(专业名称:授权码登录)
-
用户名密码登录
-
客户端登录(手机内部的app授权)
-
…
Spring Cloud Security
Spring Cloud Security就是微服务版的Spring-Security框架
在原有的基础上添加了微服务结构下用户的登录和权限管理的支持
最终我们要使用Spring Cloud Security和Oauth2结合实现微服务架构下的Token单点登录
最终实现项目结构如下图
微服务项目结构从功能上分为两大类
- 授权服务器:接收用户名和密码,验证登录,返回令牌
- 资源服务器:当用户请求当前服务器时,解析令牌,获得用户信息
Oauth2标准主要使用在授权服务器中
我们会创建授权服务器项目,这个项目会添加Oauth2的支持
这个依赖中包含了很多标准的方法
主要是包含了很多控制器方法,也就是说,我们创建的auth模块是不需要编写控制器方法的,都是Oauth2提供的,我们需要做的就是对这个项目进行各种配置
创建Auth授权服务器项目
创建knows-auth项目
父子相认
<module>knows-auth</module>
knows-auth的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>knows</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>knows-auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>knows-auth</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>knows-commons</artifactId>
</dependency>
<!-- Spring Cloud Security结合Oauth2会在数据库中
保存一些临时信息,需要jdbc依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--
SpringCloudSecurity 依赖
让我们的项目支持微服务结构下的用户授权和管理
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<!--
Oauth2依赖
让我们的项目支持Oauth2标准,这个依赖中自带很多控制器方法
令牌的生成和解析等
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--
JWT依赖
让我们的项目执行对用户信息的JWT加密
以保存到客户端
-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
</dependencies>
</project>
application.properties文件配置如下
spring.datasource.url=jdbc:mysql://localhost:3306/knows?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
spring.datasource.username=root
spring.datasource.password=root
logging.level.cn.tedu.knows.auth=debug
server.port=8010
spring.application.name=auth-service
spring.cloud.nacos.discovery.server-addr=localhost:8848
# 下面的配置是允许Spring容器中已经存在的对象被新对象覆盖
# 意思就是两个相同id的对象保存到Spring容器时会不会报错
# 设置完true之后,相同id的后一个出现的对象会覆盖掉之前的对象
# 当前我们的auth项目内部,会有我们注入的对象覆盖系统原有对象的情况
spring.main.allow-bean-definition-overriding=true
SpringBoot启动类
@SpringBootApplication
@EnableDiscoveryClient
public class KnowsAuthApplication {
public static void main(String[] args) {
SpringApplication.run(KnowsAuthApplication.class, args);
}
// Ribbon的支持
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
授权服务器配置准备
auth项目会配置很多信息
我们先按步骤进行准备工作
先配置Spring-Security放行
创建security包,包中创建SecurityConfig
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 当前auth项目也是设置Spring-Security全部放行
// 因为登录验证交给了Oauth2,Spring-Security不在负责验证
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // 关闭防跨域攻击
.authorizeRequests() // 设计访问权限
.anyRequest().permitAll() // 任何请求全部放行
.and().formLogin(); // 支持表单登录
}
// 我们在Spring容器中保存一个加密对象
// 之后有配置需要加密,就可以取出使用
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// 我们在后面的配置中,需要Spring-Security框架中的授权管理器
// 授权管理器是登录功能的重要组成部分,现在是Oauth2需要它
// 我们需要将这个授权管理器保存到Spring容器中,以便Oauth2使用它
@Bean
public AuthenticationManager
authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
在授权过程中,令牌的生成方式和保存方式也是非常重要的部分
我们要编写一个类,专门配置令牌的保存
我们最终的目标效果是生成一个令牌保存到客户端
但是分步骤完成,先做一个简单的版本,暂时还是保存在内存中
创建TokenConfig类,来编写保存令牌的配置信息
// 只要是Spring的配置,就需要添加这个注解
@Configuration
public class TokenConfig {
// 配置保存令牌的策略对象到Spring容器
// 1.保存在内存中
// 2.生成令牌保存在客户端
// 先暂时保存在内存中
@Bean
public TokenStore tokenStore(){
return new InMemoryTokenStore();
}
}
我们当前微服务版本的登录,仍然是SpringSecurity的实现思路,只是搭配了Oauth2的标准使用
就是portal项目中UserDetailsServiceImpl类中的登录配置类似
根据UserDetailsServiceImpl类中的业务,我们需要下面3个方法
1.根据用户名获得用户对象
2.根据用户id获得用户的所有角色
3.根据用户id获得用户的所有权限
因为这些方法都是应该有sys用户管理模块提供的
所以转到knows-sys模块编写上面3个方法
我们编写过1方法
2.3方法没有编写
从业务逻辑层开始
IUserService接口添加方法如下
// 根据用户id获得所有权限
List<Permission> getPermissionsById(Integer id);
// 根据用户id获得所有角色
List<Role> getRolesById(Integer id);
UserServiceImpl实现类
@Override
public List<Permission> getPermissionsById(Integer id) {
return userMapper.findUserPermissionsById(id);
}
@Override
public List<Role> getRolesById(Integer id) {
return userMapper.findUserRolesById(id);
}
AuthController类添加Rest接口
// 根据用户id获得所有权限
@GetMapping("/permissions")
public List<Permission> permissions(Integer id){
return userService.getPermissionsById(id);
}
// 根据用户id获得所有角色
@GetMapping("/roles")
public List<Role> roles(Integer id){
return userService.getRolesById(id);
}
英文
incr\increment:增长
随笔
Redis 其它类型操作参考
List 列表
常用命令: lpush,rpush,lpop,rpop,lrange等
Redis的list在底层实现上并不是数组而是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。
Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。
lists的常用操作包括LPUSH、RPUSH、LRANGE、RPOP等。可以用LPUSH在lists的左侧插入一个新元素,用RPUSH在lists的右侧插入一个新元素,用LRANGE命令从lists中指定一个范围来提取元素,RPOP从右侧弹出数据。来看几个例子::
//新建一个list叫做mylist,并在列表头部插入元素"Tom"
127.0.0.1:6379> lpush mylist "Tom"
//返回当前mylist中的元素个数
(integer) 1
//在mylist右侧插入元素"Jerry"
127.0.0.1:6379> rpush mylist "Jerry"
(integer) 2
//在mylist左侧插入元素"Andy"
127.0.0.1:6379> lpush mylist "Andy"
(integer) 3
//列出mylist中从编号0到编号1的元素
127.0.0.1:6379> lrange mylist 0 1
1) "Andy"
2) "Tom"
//列出mylist中从编号0到倒数第一个元素
127.0.0.1:6379> lrange mylist 0 -1
1) "Andy"
2) "Tom"
3) "Jerry"
//从右侧取出最后一个数据
127.0.0.1:6379> rpop mylist
"Jerry"
//再次列出mylist中从编号0到倒数第一个元素
127.0.0.1:6379> lrange mylist 0 -1
1) "Andy"
2) "Tom"
Set 集合
常用命令: sadd,smembers,sunion 等
set 是无序不重复集合,list是有序可以重复集合,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要功能,这个也是list所不能提供的。
可以基于 set 轻易实现交集、并集、差集的操作。比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能,也就是求交集的过程。set具体命令如下:
//向集合myset中加入一个新元素"Tom"
127.0.0.1:6379> sadd myset "Tom"
(integer) 1
127.0.0.1:6379> sadd myset "Jerry"
(integer) 1
//列出集合myset中的所有元素
127.0.0.1:6379> smembers myset
1) "Jerry"
2) "Tom"
//判断元素Tom是否在集合myset中,返回1表示存在
127.0.0.1:6379> sismember myset "Tom"
(integer) 1
//判断元素3是否在集合myset中,返回0表示不存在
127.0.0.1:6379> sismember myset "Andy"
(integer) 0
//新建一个新的集合yourset
127.0.0.1:6379> sadd yourset "Tom"
(integer) 1
127.0.0.1:6379> sadd yourset "John"
(integer) 1
127.0.0.1:6379> smembers yourset
1) "Tom"
2) "John"
//对两个集合求并集
127.0.0.1:6379> sunion myset yourset
1) "Tom"
2) "Jerry"
3) "John"
Sorted Set 有序集合
常用命令: zadd,zrange,zrem,zcard等
和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。
很多时候,我们都将redis中的有序集合叫做zsets,这是因为在redis中,有序集合相关的操作指令都是以z开头的,比如zrange、zadd、zrevrange、zrangebyscore等等
来看几个生动的例子:
//新增一个有序集合hostset,加入一个元素baidu.com,给它赋予score:1
127.0.0.1:6379> zadd hostset 1 baidu.com
(integer) 1
//向hostset中新增一个元素bing.com,赋予它的score是30
127.0.0.1:6379> zadd hostset 3 bing.com
(integer) 1
//向hostset中新增一个元素google.com,赋予它的score是22
127.0.0.1:6379> zadd hostset 22 google.com
(integer) 1
//列出hostset的所有元素,同时列出其score,可以看出myzset已经是有序的了。
127.0.0.1:6379> zrange hostset 0 -1 with scores
1) "baidu.com"
2) "1"
3) "google.com"
4) "22"
5) "bing.com"
6) "30"
//只列出hostset的元素
127.0.0.1:6379> zrange hostset 0 -1
1) "baidu.com"
2) "google.com"
3) "bing.com"
Hash
常用命令: hget,hset,hgetall 等。
Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息:
//建立哈希,并赋值
127.0.0.1:6379> HMSET user:001 username antirez password P1pp0 age 34
OK
//列出哈希的内容
127.0.0.1:6379> HGETALL user:001
1) "username"
2) "antirez"
3) "password"
4) "P1pp0"
5) "age"
6) "34"
//更改哈希中的某一个值
127.0.0.1:6379> HSET user:001 password 12345
(integer) 0
//再次列出哈希的内容
127.0.0.1:6379> HGETALL user:001
1) "username"
2) "antirez"
3) "password"
4) "12345"
5) "age"
6) "34"