内存型缓存框架redis
1. 什么是redis
概念: redis是一款高性能的NOSQL系列的非关系型数据库
1.1 什么是NOSQL
NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。
随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
1.2 NOSQL和关系型数据库比较
- 优点
1)成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。
2)查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。
3)存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
4)扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。
- 缺点
1)维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。
2)不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。
3)不提供关系型数据库对事务的处理。
1.3 非关系型数据库的优势
1)性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
2)可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
2.为什么要使用redis
- 性能高
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求
- redis支持的存储数据类型
1) 字符串类型 string
2) 哈希类型 hash
3) 列表类型 list
4) 集合类型 set
5) 有序集合类型 sortedset
- 应用场景
缓存(数据查询、短连接、新闻内容、商品内容等等)
• 聊天室的在线好友列表
• 任务队列。(秒杀、抢购、12306等等)
• 应用排行榜
• 网站访问统计
• 数据过期处理(可以精确到毫秒
• 分布式集群架构中的session分离
- 小结
- 读写性能非常好
- 单线程的缓存型数据库
- 内存型数据库
3.redis与spring整合
3.1 导入坐标
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
3.2 配置redis连接池及操作模板
- 在resources目录下,创建配置文件spring-redis.xml
- 配置文件的头信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
- redis连接池配置
<context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true" ></context:property-placeholder>
<!-- redis 相关配置 -->
<bean id="poolConfigs" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
</bean>
<bean id="JedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.hostname}" p:port="${redis.port}" p:password="${redis.password}" p:pool-config-ref="poolConfigs"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="JedisConnectionFactory"/>
</bean>
- 配置redis连接的参数 redis.properties
redis.hostname=127.0.0.1
redis.port=6379
redis.password=
redis.maxIdle=300
redis.maxWaitMillis=3000
redis.testOnBorrow=true
3.3 启动redis服务
redis根目录/bin/redis-server.exe
4.redis的基本用法
4.0 准备测试环境
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-redis.xml")
public class RedisTest {
}
4.1 基本数据类型添加和获取
- 添加和获取基本数据类型
@Test
public void testSetValue(){
redisTemplate.boundValueOps("salary").set("1000");
redisTemplate.boundValueOps("salary").set("2000");
redisTemplate.boundValueOps("salary").set("3000");
}
- 添加有时效性的变量
@Test
public void testSetValueByTime(){
redisTemplate.boundValueOps("salary").set("2000",10, TimeUnit.SECONDS);
}
- 获取数据
@Test
public void testGetvalue(){
String salary = (String) redisTemplate.boundValueOps("salary").get();
System.out.println(salary);
}
4.2 set类型操作(无序)
- 添加数据
@Test
public void testSetSet(){
redisTemplate.boundSetOps("emp").add("aaa");
redisTemplate.boundSetOps("emp").add("bbb");
redisTemplate.boundSetOps("emp").add("ccc");
}
- 获取数据
@Test
public void testGetSet(){
Set<String> emp = redisTemplate.boundSetOps("emp").members();
for (String str : emp){
System.out.println(str);
}
}
4.3 list类型操作(有序)
- 添加数据
@Test
public void testSetList(){//本身存储的数据是有序的,redis提供多种存储方式,可以将数据插入时,可以在当前数组中插入到前端或后面
redisTemplate.boundListOps("yanfa").leftPush(1);//添加到集合的左边
redisTemplate.boundListOps("yanfa").leftPush(2);
redisTemplate.boundListOps("yanfa").leftPush(3);
redisTemplate.boundListOps("yanfa").rightPush(4);//添加到集合的右边
}
- 获取数据
@Test
public void testGetList(){
Long size = redisTemplate.boundListOps("yanfa").size();
for (int i = 0 ; i < size ;i++){
//获取指定索引位置的值
Object obj = redisTemplate.boundListOps("yanfa").index(i);
System.out.println(obj);
}
}
@Test
public void testGetList2(){
List<Integer> list = redisTemplate.boundListOps("yanfa").range(0,1);
for (Integer i : list){
System.out.println(i);
}
}
4.4 Hash类型操作
- 添加数据
@Test
public void testSaveHash(){
redisTemplate.boundHashOps("componey").put("教研部","zhangsan");
redisTemplate.boundHashOps("componey").put("管理部","lisi");
redisTemplate.boundHashOps("componey").put("研发部","wangwu");
}
- 获取数据
@Test
public void testGetHash(){
String guanli = (String) redisTemplate.boundHashOps("componey").get("管理部");
System.out.println(guanli);
}
小结
1.关于使用RedisTemplate操作数据存取,有4种常用方式:
value/hash/set/list
2.在数据存储中,可以对每个存储的值,设置 有效时长
3. 使用模板进行存储时,提供了两种存储方式:
OpsForxxx()
boundXXXOps()
5. 案例演示
模拟高并发效果,通过redis解决并发中产生的现场问题:
安全:
模拟秒杀功能
5.1 在不使用redis的情况下,来实现创建购买订单的效果
- service层的核心代码
package com.qianfeng.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@Service
public class OrderService {
//库存的数量
private static HashMap<String, Integer> kucunMap;
// 订单的数量
private static HashMap<String, Integer> orderMap;
//进行订单的库存数据的初始化
static {
kucunMap = new HashMap();
kucunMap.put("kucun", 10000);
//初始化订单量为0
orderMap = new HashMap();
orderMap.put("kucun", 0);
}
//获取当前库存量和订单的数量
public String getOrderInfo() {
return "当前库存剩余量为:" + kucunMap.get("kucun").intValue() + ",当前订单的数量:" + orderMap.size();
}
// 编写下单操作
/*
synchronized : 同步锁:
每次进入到该 方法,只允许一个线程。
*/
public void publishOrder() {
int currKucun = kucunMap.get("kucun").intValue();
// 从库存中 -1
kucunMap.put("kucun", currKucun - 1);
//向订单中 +1
orderMap.put("kucun", orderMap.get("kucun").intValue() + 1);
//假设每次请求,需要0.1秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 控制层代码
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/order")
public HashMap publishOrder() {
orderService.publishOrder();//下订单
String info = orderService.getOrderInfo();
HashMap map = new HashMap();
map.put("result", info);
return map;
}
5.2 使用redis的分布式锁解决超买或超卖
5.2.1 redis的锁的执行方法
对某个key-value添加锁:
redisTemplate.opsForValue().setIfAbsent(key, value)
释放锁:
redisTemplate.opsForValue().getOperations().delete(key)
5.2.2 锁控制库存的逻辑
- 将锁的创建和释放抽离为一个工具类
@Component
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean lock1(String key, String value) {
//setIfAbsent(key, value) 给这个key-value加锁,如果加锁成功,是返回true,否则 ,返回false
if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
//第一次,即:这个key还没有被赋值的时候
return true;
}
return false;
}
public void unlock(String key, String value) {
try {
if (MyStringUtils.Object2String(redisTemplate.opsForValue().get(key)).equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 在业务层调用
package com.qianfeng.cw.service;
import com.qianfeng.cw.util.MyStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.UUID;
@Service
public class OrderService {
@Autowired
private RedisLock redisLock;
//商品详情
private static HashMap<String, Integer> product = new HashMap();
//订单表
private static HashMap<String, String> orders = new HashMap();
//库存表
private static HashMap<String, Integer> stock = new HashMap();
static {
product.put("123", 10000);
stock.put("123", 10000);
}
public String buyInfo(String productId) {
return "限量抢购商品XXX共" + product.get(productId) + ",现在成功下单" + orders.size()
+ ",剩余库存" + stock.get(productId) + "件";
}
public String placeOrderToRedis(String productId) {
/**
* redis加锁
*/
String value = System.currentTimeMillis() + 10000 + "";
//还没有卖完
try {
//模拟操作数据库
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!redisLock.lock1(productId, value)) {
//系统繁忙,请稍后再试
throw new RuntimeException("系统繁忙,请稍后再试");
}
//##############################业务逻辑#################################//
if (stock.get(productId) == 0) {
return "活动已经结束了";
//已近买完了
} else {
orders.put(UUID.randomUUID().toString(), productId);//将抢到的商品 保存到 订单容器列表中
stock.put(productId, stock.get(productId) - 1); // 将当前被抢到并下单成功后的商品,从库存中减1
}
//##############################业务逻辑#################################//
/**
* redis解锁
*/
redisLock.unlock(productId, value);
return buyInfo(productId);
}
}
5.2.3 控制层调用代码
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/placeOrderToRedis")
public HashMap placeOrderToRedis(String productId) {
return putKeyAndValue("抢购结果", orderService.placeOrderToRedis(productId));
}
private HashMap putKeyAndValue(String key, String value) {
HashMap hashMap = new HashMap();
hashMap.put(key, value);
return hashMap;
}
}