DB缓存,减轻DB服务器压力
一般情况下数据存在数据库中,应用程序直接操作数据库。
当访问量上万,数据库压力增大,可以采取的方案有:
读写分离,分库分表
当访问量达到10万、百万,需要引入缓存。
将已经访问过的内容或数据存储起来,当再次访问时先找缓存,缓存命中返回数据。
不命中再找数据库,并回填缓存。
提高系统响应
数据库的数据是存在文件里,也就是硬盘。与内存做交换(swap)
在大量瞬间访问时(高并发)MySQL单机会因为频繁IO而造成无法响应。MySQL的InnoDB是有行锁
将数据缓存在Redis中,也就是存在了内存中。
内存天然支持高并发访问。可以瞬间处理大量请求。
qps到达11万/S读请求 8万写/S
做Session分离
传统的session是由tomcat自己进行维护和管理。
集群或分布式环境,不同的tomcat管理各自的session。
只能在各个tomcat之间,通过网络和Io进行session的复制,极大的影响了系统的性能。
1、各个Tomcat间复制session,性能损耗
2、不能保证各个Tomcat的Session数据同步
将登录成功后的Session信息,存放在Redis中,这样多个服务器(Tomcat)可以共享Session信息。
Redis的作用是数据的临时存储
做分布式锁(Redis)
一般讲锁是多线程的锁,是在一个进程中的
多个进程(JVM)在并发时也会产生问题,也要控制时序性
可以采用分布式锁。使用Redis实现 setNX
做乐观锁(Redis)
同步锁和数据库中的行锁、表锁都是悲观锁
悲观锁的性能是比较低的,响应性比较差
高性能、高响应(秒杀)采用乐观锁
Redis可以实现乐观锁 watch + incr
缓存原指CPU上的一种高速存储器,它先于内存与CPU交换数据,速度很快
现在泛指存储在计算机上的原始数据的复制集,便于快速访问。
在互联网技术中,缓存是系统快速响应的关键技术之一
以空间换时间的一种技术(艺术)
大型网站中缓存的使用
单机架构LAMP(Linux+apache+MySQL+PHP)、JavaEE(SSM)
访问量越大,响应力越差,用户体验越差
引入缓存、示意图如下:
在大型网站中从浏览器到网络,再到应用服务器,再到数据库,通过在各个层面应用缓存技术,大大提升了系统性能和用户体验。
传统互联网:页面缓存和浏览器缓存
移动互联网:APP缓存
页面缓存
页面缓存:页面自身对某些元素或全部元素进行存储,并保存成文件。
html5:Cookie、WebStorage(SessionStorage和LocalStorage)、WebSql、indexDB、Application
Cache等
开启步骤:
1、设置manifest描述文件
CACHE MANIFEST
#comment
js/index.js
img/bg.png
2、html关联manifest属性
<html lang="en" manifest="demo.appcache">
使用LocalStorage进行本地的数据存储,示例代码:
localStorage.setItem("Name","张飞")
localStorage.getItem("Name")
localStorage.removeItem("Name")
localStorage.clear()
浏览器缓存
当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接
从浏览器缓存中提取而不是从原始服务器中提取这个资源。
浏览器缓存可分为强制缓存和协商缓存。
强制缓存:直接使用浏览器的缓存数据
条件:Cache-Control的max-age没有过期或者Expires的缓存时间没有过期
<meta http-equiv="Cache-Control" content="max-age=7200" />
<meta http-equiv="Expires" content="Mon, 20 Aug 2010 23:00:00 GMT" />
协商缓存:服务器资源未修改,使用浏览器的缓存(304);反之,使用服务器资源(200)
<meta http-equiv="cache-control" content="no-cache">
APP缓存
原生APP中把数据缓存在内存、文件或本地数据库(SQLite)中。比如图片文件。
网络端缓存
通过代理的方式响应客户端请求,对重复的请求返回缓存中的数据资源。
Web代理缓存
可以缓存原生服务器的静态资源,比如样式、图片等。
常见的反向代理服务器比如大名鼎鼎的Nginx。
边缘缓存
边缘缓存中典型的商业化服务就是CDN了。
CDN的全称是Content Delivery Network,即内容分发网络。
CDN通过部署在各地的边缘服务器,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
CDN的关键技术主要有内容存储和分发技术。现在一般的公有云服务商都提供CDN服务。
服务端缓存
服务器端缓存是整个缓存体系的核心。包括数据库级缓存、平台级缓存和应用级缓存。
数据库级缓存
数据库是用来存储和管理数据的。
MySQL在Server层使用查询缓存机制。将查询后的数据缓存起来。
K-V结构,Key:select语句的hash值,Value:查询结果
InnoDB存储引擎中的buffer-pool用于缓存InnoDB索引及数据块。
平台级缓存
平台级缓存指的是带有缓存特性的应用框架。
比如:GuavaCache 、EhCache(二级缓存,硬盘)、OSCache(页面缓存)等。
部署在应用服务器上,也称为服务器本地缓存。
应用级缓存(重点)
具有缓存功能的中间件:Redis、Memcached、EVCache(AWS)、Tair(阿里 、美团)等。
采用K-V形式存储。
利用集群支持高可用、高性能、高并发、高扩展。
分布式缓存
提升用户体验
用户体验(User Experience):用户在使用产品过程中建立起来的一种纯主观感受。
缓存的使用可以提升系统的响应能力,大大提升了用户体验。
减轻服务器压力
客户端缓存、网络端缓存减轻应用服务器压力。
服务端缓存减轻数据库服务器的压力。
提升系统性能
系统性能指标:响应时间、延迟时间、吞吐量、并发用户数和资源利用率等。
缓存技术可以:
缩短系统的响应时间
减少网络传输时间和应用延迟时间
提高系统的吞吐量
增加系统的并发用户数
提高了数据库资源的利用率
额外的硬件支出
缓存是一种软件系统中以空间换时间的技术
需要额外的磁盘空间和内存空间来存储数据
搭建缓存服务器集群需要额外的服务器
采用云服务器的缓存服务就不用额外的服务器了
阿里云(Tair、Redis),百度云(Redis),提供缓存服务
AWS亚马逊云服务:EVCache
高并发缓存失效
在高并发场景下会出现缓存失效(缓存穿透、缓存雪崩、缓存击穿)
造成瞬间数据库访问量增大,甚至崩溃
缓存与数据库数据同步
缓存与数据库无法做到数据的时时同步
Redis无法做到主从时时数据同步
缓存并发竞争
多个redis的客户端同时对一个key进行set值得时候由于执行顺序引起的并发问题
缓存有三种读写模式
Cache Aside Pattern(常用)
Cache Aside Pattern(旁路缓存),是最经典的缓存+数据库读写模式。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存。
为什么是删除缓存,而不是更新缓存呢?
1、缓存的值是一个结构:hash、list,更新数据需要遍历
先遍历(耗时)后修改
2、懒加载,使用的时候才更新缓存
使用的时候才从DB中加载
也可以采用异步的方式填充缓存
开启一个线程 定时将DB的数据刷到缓存中
高并发脏读的三种情况
1、先更新数据库,再更新缓存
update与commit之间,更新缓存,commit失败
则DB与缓存数据不一致
2、先删除缓存,再更新数据库
update与commit之间,有新的读,缓存空,读DB数据到缓存 数据是旧的数据
commit后 DB为新数据
则DB与缓存数据不一致
3、先更新数据库,再删除缓存(推荐)
update与commit之间,有新的读,缓存空,读DB数据到缓存 数据是旧的数据
commit后 DB为新数据
则DB与缓存数据不一致
采用延时双删策略
Read/Write Through Pattern
应用程序只操作缓存,缓存操作数据库。
Read-Through(穿透读模式/直读模式):应用程序读缓存,缓存没有,由缓存回源到数据库,并写入缓存。(guavacache)
Write-Through(穿透写模式/直写模式):应用程序写缓存,缓存写数据库。
该种模式需要提供数据库的handler,开发较为复杂。
Write Behind Caching Pattern
应用程序只更新缓存。
缓存通过异步的方式将数据批量或合并后更新到DB中
不能时时同步,甚至会丢数据
缓存的整体设计思路包括:
1、多层次
分布式缓存宕机,本地缓存还可以使用
2、数据类型
简单数据类型
Value是字符串或整数或二进制
Value的值比较大(大于100K)
只进行setter和getter
可采用Memcached
Memcached纯内存缓存,多线程 K-V
复杂数据类型
Value是hash、set、list、zset
需要存储关系,聚合,计算
可采用Redis
3、要做集群
分布式缓存集群方案(Redis)
codis
哨兵+主从
RedisCluster
4、缓存的数据结构设计
1、与数据库表一致
数据库表和缓存是一一对应的
缓存的字段会比数据库表少一些
缓存的数据是经常访问的
用户表,商品表
2、与数据库表不一致
需要存储关系,聚合,计算等
比如某个用户的帖子、用户的评论。
以用户评论为例,DB结构如下:
ID | UUID | PostTime | Content |
---|---|---|---|
1 | 1000 | 1547342000 | 备注1 |
2 | 1001 | 1547342000 | 备注2 |
3 | 1004 | 1547342000 | 备注3 |
如果要取出UID为1000的用户的评论,原始的表的数据结构显然是不行的。
我们应做如下设计:
key:UID+时间戳(精确到天) 评论一般以天为计算单位
value:Redis的Hash类型。field为 id和content
expire:设置为一天
拉勾网(www.lagou.com),是国内的招聘门户网站,亿万级PV,单机响应性能QPS万级。
首页分析:
职位时时变化,不能使用静态html
采用模板技术,数据在服务端拿出,不能为空
数据不一定时时
架构图如下:
1、静态文件
在nginx中,放置静态文件,比如css,js, 图片等
server {
listen 80 default_server;
server_name localhost;
root /mnt/blog/;
location / {
}
#要缓存文件的后缀,可以在以下设置。
location ~ .*\.(gif|jpg|png|css|js)(.*) {
proxy_pass http://ip地址:90;
proxy_redirect off;
proxy_set_header Host $host;
proxy_cache cache_one;
proxy_cache_valid 200 302 24h;
proxy_cache_valid 301 30d;
proxy_cache_valid any 5m;
expires 90d;
add_header wall "hello lagou.";
}
}
2、职位列表
数据特点:
固定数据,一次性读取
方案:
在服务器开启时一次性初始化(从xml)到服务器本地缓存
采用Guava Cache,Guava Cache用于存储频繁使用的少量数据,支持高并发访问
也可以使用JDK的CurrentHashMap,需要自行实现
3、热门职位
数据特点:
频繁变化,不必时时同步
但一定要有数据,不能为空
方案:
数据从服务层读取(dubbo),然后放到本地缓存中(Guava),如果出现超时或读取为空,则返回原
来本地缓存的数据。
注意:不同的客户端看到的数据有可能不一样。
4、数据回填
从Dubbo中读取数据时,先读取Redis集群的缓存,如果缓存命中则直接返回。
如果缓存不命中则返回本地缓存,不能直接读取数据库。
采用异步的形式从数据库刷入到缓存中。
5、热点策略
对于热点数据我们采用本地缓存策略,而不采用服务熔断策略,因为首页数据可以不准确,但不能不响
应。
什么是Redis
Redis (Remote Dictionary Server)远程字典服务器,是用C语言开发的一个开源的高性能键值对( key-value )内存数据库。
它提供了五种数据类型来存储值:字符串类型、散列类型、列表类型、集合类型、有序集合类型它是一种NoSQL 数据存储。
Redis应用场景
缓存使用,减轻DB压力
DB使用,用于临时存储数据(字典表,购买记录)
解决分布式场景下Session分离问题(登录信息)
任务队列(秒杀、抢红包等等) 乐观锁
应用排行榜 zset
签到 bitmap
分布式锁
冷热数据交换
Redis安装环境
Redis 没有官方的Windows 版本,所以建议在Linux 系统上安装运行。
我们使用CentOS 7 作为安装环境。
Redis安装
第一步:安装C 语言需要的GCC 环境
yum install -y gcc-c++
yum install -y wget
第二步:下载并解压缩Redis 源码压缩包
wget http://download.redis.io/releases/redis-5.0.5.tar.gz
tar -zxf redis-5.0.5.tar.gz
第三步:编译Redis 源码,进入redis-5.0.5 目录,执行编译命令
cd redis-5.0.5/src
make
第四步:安装Redis ,需要通过PREFIX 指定安装路径
mkdir /usr/redis -p
make install PREFIX=/usr/redis
Redis启动
前端启动
启动命令: redis-server ,直接运行bin/redis-server 将以前端模式启动
./redis-server
后端启动(守护进程启动)
cp redis.conf /usr/redis/bin/
vi /usr/redis/bin/redis.conf
vi redis.conf
# 将`daemonize`由`no`改为`yes`
daemonize yes
# 默认绑定的是回环地址,默认不能被其他机器访问
# bind 127.0.0.1
# 是否开启保护模式,由yes该为no
protected-mode no
启动服务
./redis-server redis.conf
命令说明
redis-server :启动redis 服务
redis-cli :进入redis 命令客户端
redis-benchmark : 性能测试的工具
redis-check-aof : aof 文件进行检查的工具
redis-check-dump : rdb 文件进行检查的工具
redis-sentinel : 启动哨兵监控服务
命令格式
./redis-cli -h 127.0.0.1 -p 6379
参数说明
-h:redis服务器的ip地址
-p:redis实例的端口号
默认方式
如果不指定主机和端口也可以
默认主机地址是127.0.0.1
默认端口是6379
1、新建maven项目后导入Jedis包
pom.xml
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.9.0version>
dependency>
2、写程序
@Test
public void testConn(){
//与Redis建立连接 IP+port
Jedis redis = new Jedis("192.168.127.128", 6379);
//在Redis中写字符串 key value
redis.set("jedis:name:1","jd-zhangfei");
//获得Redis中字符串的值
System.out.println(redis.get("jedis:name:1"));
//在Redis中写list
redis.lpush("jedis:list:1","1","2","3","4","5");
//获得list的长度
System.out.println(redis.llen("jedis:list:1"));
}
1. 新建Spring项目
maven中添加依赖
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>5.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>5.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-redisartifactId>
<version>1.0.3.RELEASEversion>
dependency>
dependencies>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigur
er">
<property name="locations">
<list>
<value>classpath:redis.propertiesvalue>
list>
property>
bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxActive" value="${redis.pool.maxActive}" />
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<property name="maxWait" value="${redis.pool.maxWait}" />
<property name="testOnBorrow" value="${redis.pool.testOnBorrow}" />
bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactor
y">
<property name="hostName" value="${redis.server}"/>
<property name="port" value="${redis.port}"/>
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="jedisPoolConfig" />
bean>
<bean id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
<property name="KeySerializer">
<bean
class="org.springframework.data.redis.serializer.StringRedisSerializer">
bean>
property>
<property name="ValueSerializer">
<bean
class="org.springframework.data.redis.serializer.StringRedisSerializer">
bean>
property>
bean>
beans>
redis.pool.maxActive=100
redis.pool.maxIdle=50
redis.pool.maxWait=1000
redis.pool.testOnBorrow=true
redis.timeout=50000
redis.server=192.168.72.128
redis.port=6379
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import
org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import java.io.Serializable;
@ContextConfiguration({ "classpath:redis.xml" })
public class RedisTest extends AbstractJUnit4SpringContextTests {
@Autowired
private RedisTemplate<Serializable, Serializable> rt;
@Test
public void testConn() {
rt.opsForValue().set("name","zhangfei");
System.out.println(rt.opsForValue().get("name"));
}
}
1.添加redis依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
2.application.yml中添加配置属性
spring:
redis:
host: 192.168.72.128
port: 6379
jedis:
pool:
min-idle: 0
max-idle: 8
max-active: 80
max-wait: 30000
timeout: 3000
3.添加配置类RedisConfig
package com.lagou.sbr.cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
package com.lagou.sbr.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping(value = "/redis")
public class RedisController {
@Autowired
RedisTemplate redisTemplate;
@GetMapping("/put")
public String put(@RequestParam(required = true) String key,
@RequestParam(required = true) String value) {
//设置过期时间为20秒
redisTemplate.opsForValue().set(key,value,20, TimeUnit.SECONDS);
return "Success";
}
@GetMapping("/get")
public String get(@RequestParam(required = true) String key){
return (String) redisTemplate.opsForValue().get(key);
}
}
package com.lagou.sbr;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class SpringbootRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedisApplication.class, args);
}
}
Redis是一个Key-Value的存储系统,使用ANSI C语言编写。
key的类型是字符串。
value的数据类型有:
常用的:string字符串类型、list列表类型、set集合类型、sortedset(zset)有序集合类型、hash类
型。
不常见的:bitmap位图类型、geo地理位置类型。
Redis5.0新增一种:stream类型
注意:Redis中命令是忽略大小写,(set SET),key是不忽略大小写的 (NAME name)
Redis的String能表达3种值的类型:字符串、整数、浮点数 100.01 是个六位的串
常见操作命令如下表:
命令名称 | 命令格式 | 命令描述 |
---|---|---|
set | set key value | 赋值 |
get | get key | 取值 |
getset | getset key value | 取值并赋值 |
setnx | setnx key value | 当key不存在时才用 赋值 set key value NX PX 3000 原子操作,px 设置毫秒数 |
append | append key value | 向尾部追加值 |
strlen | strlen key | 获取字符串长度 |
incr | incr key | 递增数字 |
incrby | incrby key increment | 增加指定的整数 |
decr | decr key | 递减数字 |
decrby | decrby key decrement | 减少指定的整数 |
应用场景:
1、key和命令是字符串
2、普通的赋值
3、incr用于乐观锁
incr:递增数字,可用于实现乐观锁 watch(事务)
4、setnx用于分布式锁
当value不存在时采用赋值,可用于实现分布式锁
举例:
setnx:
127.0.0.1:6379> setnx name zhangf #如果name不存在赋值
(integer) 1
127.0.0.1:6379> setnx name zhaoyun #再次赋值失败
(integer) 0
127.0.0.1:6379> get name
"zhangf"
set
127.0.0.1:6379> set age 18 NX PX 10000 #如果不存在赋值 有效期10秒
OK
127.0.0.1:6379> set age 20 NX #赋值失败
(nil)
127.0.0.1:6379> get age #age失效
(nil)
127.0.0.1:6379> set age 30 NX PX 10000 #赋值成功
OK
127.0.0.1:6379> get age
"30"
list列表类型可以存储有序、可重复的元素
获取头部或尾部附近的记录是极快的
list的元素个数最多为2^32-1个(40亿)
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
lpush | lpush key v1 v2 v3 … | 从左侧插入列表 |
lpop | lpop key | 从列表左侧取出 |
rpush | rpush key v1 v2 v3 … | 从右侧插入列表 |
rpop | rpop key | 从列表右侧取出 |
lpushx | lpushx key value | 将值插入到列表头部 |
rpushx | rpushx key value | 将值插入到列表尾部 |
blpop | blpop key timeout | 从列表左侧取出,当列表为空时阻塞,可以设置最大阻塞时间,单位为秒 |
brpop | brpop key timeout | 从列表右侧取出,当列表为空时阻塞,可以设置最大阻塞时间,单位为秒 |
llen | llen key | 获得列表获得列表中元素个数 |
lindex | lindex key index | 获得列表中下标为index的元素 index从0开始 |
lrange | lrange key start end | 返回列表中指定区间的元素,区间通过start和end指定 |
lrem | lrem key count value | 删除列表中与value相等的元素当count>0时, lrem会从列表左开始删除;当count<0时,lrem会从列表后边开始删除;当count=0时, lrem删除所有值为value的元素 |
lset | lset key index value | 将列表index位置的元素设置成value的值 |
ltrim | ltrim key start end | 对列表进行修剪,只保留start到end区间 |
rpoplpush | rpoplpush key1 key2 | 从key1列表右侧弹出并插入到key2列表左侧 |
brpoplpush | brpoplpush key1 key2 | 从key1列表右侧弹出并插入到key2列表左侧,会阻塞 |
linsert | linsert key BEFORE/AFTER pivot value | 将value插入到列表,且位于值pivot之前或之后 |
应用场景:
1、作为栈或队列使用
列表有序可以作为栈和队列使用
2、可用于各种列表,比如用户列表、商品列表、评论列表等。
举例:
127.0.0.1:6379> lpush list:1 1 2 3 4 5 3
(integer) 5
127.0.0.1:6379> lrange list:1 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> lpop list:1 # 从0开始
"5"
127.0.0.1:6379> rpop list:1
"1"
127.0.0.1:6379> lindex list:1 1
"3"
127.0.0.1:6379> lrange list:1 0 -1
1) "4"
2) "3"
3) "2"
127.0.0.1:6379> lindex list:1 1
"3"
127.0.0.1:6379> rpoplpush list:1 list:2
"2"
127.0.0.1:6379> lrange list:2 0 -1
1) "2"
127.0.0.1:6379> lrange list:1 0 -1
1) "4"
2) "3"
Set:无序、唯一元素
集合中最大的成员数为 2^32 - 1
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
sadd | sadd key mem1 mem2 … | 为集合添加新成员 |
srem | srem key mem1 mem2 … | 删除集合中指定成员 |
smembers | smembers key | 获得集合中所有元素 |
spop | spop key | 返回集合中一个随机元素,并将该元素删除 |
srandmember | srandmember key | 返回集合中一个随机元素,不会删除该元素 |
scard | scard key | 获得集合中元素的数量 |
sismember | sismember key member | 判断元素是否在集合内 |
sinter | sinter key1 key2 key3 | 求多集合的交集 |
sdiff | sdiff key1 key2 key3 | 求多集合的交集 |
sunion | sunion key1 key2 key3 | 求多集合的并集 |
应用场景:
适用于不能重复的且不需要顺序的数据结构
比如:关注的用户,还可以通过spop进行随机抽奖
举例:
127.0.0.1:6379> sadd set:1 a b c d
(integer) 4
127.0.0.1:6379> smembers set:1
1) "d"
2) "b"
3) "a"
4) "c"
127.0.0.1:6379> srandmember set:1
"c"
127.0.0.1:6379> srandmember set:1
"b"
127.0.0.1:6379> sadd set:2 b c r f
(integer) 4
127.0.0.1:6379> sinter set:1 set:2
1) "b"
2) "c"
127.0.0.1:6379> spop set:1
"d"
127.0.0.1:6379> smembers set:1
1) "b"
2) "a"
3) "c"
SortedSet(ZSet) 有序集合: 元素本身是无序不重复的
每个元素关联一个分数(score)
可按分数排序,分数可重复
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
zadd | zadd key score1 member1 score2 | 为有序集合添加新成员 |
zrem | zrem key mem1 mem2 … | 删除有序集合中指定成员 |
zcard | zcard key | 获得有序集合中的元素数量 |
zcount | zcount key min max | 返回集合中score值在[min,max]区间的元素数量 |
zincrby | zincrby key increment member | 在集合的member分值上加increment |
zscore | zscore key member | 获得集合中member的分值 |
zrank | zrank key member | 获得集合中member的排名(按分值从小到大) |
zrevrank | zrevrank key member | 获得集合中member的排名(按分值从大到小) |
zrange | zrange key start end | 获得集合中指定区间成员,按分数递增排序 |
zrevrange | zrevrange key start end | 获得集合中指定区间成员,按分数递减排序 |
应用场景:
由于可以按照分值排序,所以适用于各种排行榜。比如:点击排行榜、销量排行榜、关注排行榜等。
举例:
127.0.0.1:6379> zadd hit:1 100 item1 20 item2 45 item3
(integer) 3
127.0.0.1:6379> zcard hit:1
(integer) 3
127.0.0.1:6379> zscore hit:1 item3
"45"
127.0.0.1:6379> zrevrange hit:1 0 -1
1) "item1"
2) "item3"
3) "item2"
127.0.0.1:6379>
Redis hash 是一个 string 类型的 field 和 value 的映射表,它提供了字段和字段值的映射。
每个 hash 可以存储 2^32 - 1 键值对(40多亿)。
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
hset | hset key field value | 赋值,不区别新增或修改 |
hmset | hmset key field1 value1 field2 value2 | 批量赋值 |
hsetnx | hsetnx key field value | 赋值,如果filed存在则不操作 |
hexists | hexists key filed | 查看某个field是否存在 |
hget | hget key field | 获取一个字段值 |
hmget | hmget key field1 field2 … | 获取多个字段值 |
hgetall | hgetall key | 获取哈希表中所有的字段和值。 |
hdel | hdel key field1 field2 … | 删除指定字段 |
huncrby | hincrby key field increment | 指定字段自增increment |
hlen | hlen key | 获得字段数量 |
应用场景:
对象的存储 ,表数据的映射
举例:
127.0.0.1:6379> hmset user:001 username zhangfei password 111 age 23 sex M
OK
127.0.0.1:6379> hgetall user:001
1) "username"
2) "zhangfei"
3) "password"
4) "111"
5) "age"
6) "23"
7) "sex"
8) "M"
127.0.0.1:6379> hget user:001 username
"zhangfei"
127.0.0.1:6379> hincrby user:001 age 1
(integer) 24
127.0.0.1:6379> hlen user:001
(integer) 4
bitmap是进行位操作的
通过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身。
bitmap本身会极大的节省储存空间。
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
setbit | setbit key offset value | 设置key在offset处的bit值(只能是0或者1) |
getbit | getbit key offset | 获得key在offset处的bit值 |
bitcount | bitcount key | 获得key的bit位为1的个数 |
bitpos | bitpos key value | 返回第一个被设置为bit值的索引值 |
bitop | bitop and[or/xor/not] destkey key [key …] | 对多个key 进行逻辑运算后存入destkey中 |
应用场景:
1、用户每月签到,用户id为key , 日期作为偏移量 1表示签到
2、统计活跃用户, 日期为key,用户id为偏移量 1表示活跃
3、查询用户在线状态, 日期为key,用户id为偏移量 1表示在线
举例:
127.0.0.1:6379> setbit user:sign:1000 20200101 1 #id为1000的用户20200101签到
(integer) 0
127.0.0.1:6379> setbit user:sign:1000 20200103 1 #id为1000的用户20200103签到
(integer) 0
127.0.0.1:6379> getbit user:sign:1000 20200101 #获得id为1000的用户20200101签到状态
1 表示签到
(integer) 1
127.0.0.1:6379> getbit user:sign:1000 20200102 #获得id为1000的用户20200102签到状态
0表示未签到
(integer) 0
127.0.0.1:6379> bitcount user:sign:1000 # 获得id为1000的用户签到次数
(integer) 2
127.0.0.1:6379> bitpos user:sign:1000 1 #id为1000的用户第一次签到的日期
(integer) 20200101
127.0.0.1:6379> setbit 20200201 1000 1 #20200201的1000号用户上线
(integer) 0
127.0.0.1:6379> setbit 20200202 1001 1 #20200202的1000号用户上线
(integer) 0
127.0.0.1:6379> setbit 20200201 1002 1 #20200201的1002号用户上线
(integer) 0
127.0.0.1:6379> bitcount 20200201 #20200201的上线用户有2个
(integer) 2
127.0.0.1:6379> bitop or desk1 20200201 20200202 #合并20200201的用户和20200202上线
了的用户
(integer) 126
127.0.0.1:6379> bitcount desk1 #统计20200201和20200202都上线的用
户个数
(integer) 3
geo是Redis用来处理位置信息的。在Redis3.2中正式使用。主要是利用了Z阶曲线、Base32编码和
geohash算法
Z阶曲线
在x轴和y轴上将十进制数转化为二进制数,采用x轴和y轴对应的二进制数依次交叉后得到一个六位数编码。把数字从小到大依次连起来的曲线称为Z阶曲线,Z阶曲线是把多维转换成一维的一种方法。
Base32编码
Base32这种数据编码机制,主要用来把二进制数据编码成可见的字符串,其编码规则是:任意给定一
个二进制数据,以5个位(bit)为一组进行切分(base64以6个位(bit)为一组),对切分而成的每个组进行编
码得到1个可见字符。Base32编码表字符集中的字符总数为32个(0-9、b-z去掉a、i、l、o),这也是
Base32名字的由来。
geohash算法
Gustavo在2008年2月上线了geohash.org网站。Geohash是一种地理位置信息编码方法。 经过
geohash映射后,地球上任意位置的经纬度坐标可以表示成一个较短的字符串。可以方便的存储在数据库中,附在邮件上,以及方便的使用在其他服务中。以北京的坐标举例,[39.928167,116.389550]可以转换成wx4g0s8q3jf9 。
Redis中经纬度使用52位的整数进行编码,放进zset中,zset的value元素是key,score是GeoHash的
52位整数值。在使用Redis进行Geo查询时,其内部对应的操作其实只是zset(skiplist)的操作。通过zset的score进行排序就可以得到坐标附近的其它元素,通过将score还原成坐标值就可以得到元素的原始坐标。
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
geoadd | geoadd key 经度 纬度 成员名称1 经度1 纬度1 成员名称2 精度2 纬度2 … | 添加地理坐标 |
geohash | geohash key 成员名称1 成员名称2… | 返回标准的geohash串 |
geopos | geopos key 成员名称1 成员名称2… | 返回成员经纬度 |
geodist | geodist key 成员1 成员2 单位 | 计算成员间距离 |
georadiusbymember | georadiusbymember key 成员 值单位 count 数 asc[desc] | 根据成员查找附近的成员 |
应用场景:
1、记录地理位置
2、计算距离
3、查找"附近的人"
举例:
127.0.0.1:6379> geoadd user:addr 116.31 40.05 zhangf 116.38 39.88 zhaoyun 116.47
40.00 diaochan #添加用户地址 zhangf、zhaoyun、diaochan的经纬度
(integer) 3
127.0.0.1:6379> geohash user:addr zhangf diaochan #获得zhangf和diaochan的geohash
码
1) "wx4eydyk5m0"
2) "wx4gd3fbgs0"
127.0.0.1:6379> geopos user:addr zhaoyun #获得zhaoyun的经纬度
1) 1) "116.38000041246414185"
2) "39.88000114172373145"
127.0.0.1:6379> geodist user:addr zhangf diaochan #计算zhangf到diaochan的距离,单
位是m
"14718.6972"
127.0.0.1:6379> geodist user:addr zhangf diaochan km #计算zhangf到diaochan的距离,
单位是km
"14.7187"
127.0.0.1:6379> geodist user:addr zhangf zhaoyun km
"19.8276"
127.0.0.1:6379> georadiusbymember user:addr zhangf 20 km withcoord withdist
count 3 asc
# 获得距离zhangf20km以内的按由近到远的顺序排出前三名的成员名称、距离及经纬度
#withcoord : 获得经纬度 withdist:获得距离 withhash:获得geohash码
1) 1) "zhangf"
2) "0.0000"
3) 1) "116.31000012159347534"
2) "40.04999982043828055"
2) 1) "diaochan"
2) "14.7187"
3) 1) "116.46999925374984741"
2) "39.99999991084916218"
3) 1) "zhaoyun"
2) "19.8276"
3) 1) "116.38000041246414185"
2) "39.88000114172373145"
stream是Redis5.0后新增的数据结构,用于可持久化的消息队列。
几乎满足了消息队列具备的全部内容,包括:
消息ID的序列化生成
消息遍历
消息的阻塞和非阻塞读取
消息的分组消费
未完成消息的处理
消息队列监控
每个Stream都有唯一的名称,它就是Redis的key,首次使用xadd 指令追加消息时自动创建。
常见操作命令如下表:
命令名称 | 命令格式 | 描述 |
---|---|---|
xadd | xadd key id <*> field1 value1… | 将指定消息数据追加到指定队列(key)中,*表示最新生成的id(当前时间+序列号) |
xread | xread [COUNT count] [BLOCK milliseconds] STREAMS key [key …] ID [ID …] | 从消息队列中读取,COUNT:读取条数,BLOCK:阻塞读(默认不阻塞)key:队列名称 id:消息id |
xrange | xrange key start end [COUNT] | 读取队列中给定ID范围的消息 COUNT:返回消息条数(消息id从小到大) |
xrevrange | xrevrange key start end [COUNT] | 读取队列中给定ID范围的消息 COUNT:返回消息条数(消息id从大到小) |
xdel | xdel key id | 删除队列的消息 |
xgroup | xgroup create key groupname id | 创建一个新的消费组 |
xgroup | xgroup delconsumer key groupname | 删除指定消费组 |
xgroup | xgroup delconsumer key groupname cname | 删除指定消费组中的某个消费者 |
xgroup | xgroup setid key id | 修改指定消息的最大id |
xreadgroup | xreadgroup setid key id | 修改指定消息的最大id |
xreadgroup | xreadgroup group groupname consumer COUNT streams key | 从队列中的消费组中创建消费者并消费数据(consumer不存在则创建) |
应用场景:
消息队列的使用
127.0.0.1:6379> xadd topic:001 * name zhangfei age 23
"1591151905088-0"
127.0.0.1:6379> xadd topic:001 * name zhaoyun age 24 name diaochan age 16
"1591151912113-0"
127.0.0.1:6379> xrange topic:001 - +
1) 1) "1591151905088-0"
2) 1) "name"
2) "zhangfei"
3) "age"
4) "23"
2) 1) "1591151912113-0"
2) 1) "name"
2) "zhaoyun"
3) "age"
4) "24"
5) "name"
6) "diaochan"
7) "age"
8) "16"
127.0.0.1:6379> xread COUNT 1 streams topic:001 0
1) 1) "topic:001"
2) 1) 1) "1591151905088-0"
2) 1) "name"
2) "zhangfei"
3) "age"
4) "23"
#创建的group1
127.0.0.1:6379> xgroup create topic:001 group1 0
OK
# 创建cus1加入到group1 消费 没有被消费过的消息 消费第一条
127.0.0.1:6379> xreadgroup group group1 cus1 count 1 streams topic:001 >
1) 1) "topic:001"
2) 1) 1) "1591151905088-0"
2) 1) "name"
2) "zhangfei"
3) "age"
4) "23"
#继续消费 第二条
127.0.0.1:6379> xreadgroup group group1 cus1 count 1 streams topic:001 >
1) 1) "topic:001"
2) 1) 1) "1591151912113-0"
2) 1) "name"
2) "zhaoyun"
3) "age"
4) "24"
5) "name"
6) "diaochan"
7) "age"
8) "16"
#没有可消费
127.0.0.1:6379> xreadgroup group group1 cus1 count 1 streams topic:001 >
(nil)