什么是redis的字典缓存?
Redis的缓存是Redis内部用于存储键值对数据结构的一种基础数据结构。在Redis中,所有的键值对都是通过字典这种数据结构来存储的。字典在Redis中扮演着核心角色,因为它不仅用于数据库中的键值对存储,还用于实现其他如哈希、集合等复杂数据结构。
以下是关于Redis字典缓存的一些关键点:
- 数据结构:Redis的字典使用哈希表作为底层实现,这样可以提供快速的查找、添加和删除操作。哈希表通常是一个数组,数组的每个元素是一个指向键值对结构的指针。
- 哈希冲突解决:当不同的键通过哈希函数映射到同一个位置时,Redis使用链表法来解决冲突。如果一个位置有多个键值对,它们会形成一个链表。
- rehash:随着键值对数量的增加或减少,为了维持哈希表的性能,Redis会进行rehash操作,即重新计算所有键的哈希值,并将它们重新分布到新的哈希表中。
- 渐进式rehash:为了避免rehash操作带来的性能问题,Redis使用渐进式rehash。它将rehash操作分散到对字典的每个添加、删除、查找和更新操作中,从而避免了一次性rehash可能导致的长时间延迟。
- 缓存作用:由于字典的高效访问特性,Redis可以快速读写数据,这使得Redis非常适合作为缓存系统使用。在字典中存储的数据可以直接从内存中访问,大大减少了数据读取的时间。
- 持久化:虽然字典是内存中的数据结构,但Redis支持将字典中的数据持久化到硬盘上,以保证在系统故障时数据不会丢失。
- 类型特定字典:Redis支持多种数据类型,如字符串、列表、集合、哈希、有序集合等,每种数据类型在内部都可能使用到字典结构来存储元数据或数据本身。
Redis的字典缓存是支撑其高性能的一个关键因素,它使得Redis能够以极快的速度处理大量的数据。
项目目录
代码实践
entity层
package com.wyl.redis.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
/**
* @Description
* @Author wuyilong
* @Date 2024-07-03
*/
@Data
@TableName("full_city")
@Entity
@Table(name="full_city")
public class FullCity extends Model<FullCity> {
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@TableId(value = "id", type = IdType.AUTO)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 名称
*/
@TableField("name")
private String name;
/**
* 行政编码
*/
@TableField("code")
private String code;
/**
* 全名称
*/
@TableField("full_name")
private String fullName;
/**
* 级别,1省,2市,3区,4街道
*/
@TableField("level")
private Integer level;
/**
* 创建时间
*/
@TableField("create_time")
private Date createTime;
/**
* 中心点
*/
@TableField("center")
private String center;
/**
* 是否被撤销,0否,1是
*/
@TableField("is_revoke")
private Integer isRevoke;
/**
* 父级编码
*/
private String parentCode;
@Override
public Serializable pkVal() {
return this.id;
}
}
service层
package com.wyl.redis.service.impl;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.map.MapUtil;
import com.wyl.redis.bean.DictionaryBean;
import com.wyl.redis.constant.DictionaryConst;
import com.wyl.redis.entity.FullCity;
import com.wyl.redis.service.DictionaryOperate;
import com.wyl.redis.service.FullCityService;
import com.wyl.redis.vo.FullCityVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @Description
* @Author WuYiLong
* @Date 2024/7/3 17:36
*/
@Slf4j
@Service
public class FullCityOperate implements DictionaryOperate {
@Autowired
private FullCityService fullCityService;
@Autowired
private RedisTemplate redisTemplate;
@Override
public List list(String key) {
if(!redisTemplate.hasKey(key)) {
List<FullCity> list = fullCityService.list();
List<DictionaryBean> dictionaryBeans = list.stream().map(m -> {
DictionaryBean dictionaryBean = new DictionaryBean();
dictionaryBean.setCode(m.getCode());
dictionaryBean.setName(m.getName());
dictionaryBean.setLevel(m.getLevel());
dictionaryBean.setParentCode(m.getParentCode());
return dictionaryBean;
}).collect(Collectors.toList());
redisTemplate.opsForValue().set(key,dictionaryBeans);
return dictionaryBeans;
}
List<DictionaryBean> list = (List<DictionaryBean>)redisTemplate.opsForValue().get(key);
return list;
}
@Override
public List<Tree<String>> tree(String key) {
if(!redisTemplate.hasKey(key)) {
List<FullCity> list = fullCityService.list();
List<Tree<String>> build = TreeUtil.build(list, "0", (t1, t2) -> {
t2.setId(t1.getCode());
t2.setName(t1.getName());
t2.setParentId(t1.getParentCode());
});
redisTemplate.opsForValue().set(key,build);
return build;
}
List<Tree<String>> trees = (List<Tree<String>>)redisTemplate.opsForValue().get(key);
return trees;
}
@Override
public String codeNameMap(String key, String code) {
if(!redisTemplate.opsForHash().hasKey(key,code)) {
FullCityVo fullCityVo = fullCityService.getByCode(code);
if(fullCityVo != null) {
redisTemplate.opsForHash().putIfAbsent(key,fullCityVo.getCode(),fullCityVo.getName());
return fullCityVo.getName();
}
return null;
}
String name = (String)redisTemplate.opsForHash().get(key, code);
return name;
}
@Override
public String nameCodeMap(String key, String name) {
if(!redisTemplate.opsForHash().hasKey(key,name)) {
FullCityVo fullCityVo = fullCityService.getByFullName(name);
if(fullCityVo != null) {
redisTemplate.opsForHash().putIfAbsent(key,fullCityVo.getFullName(),fullCityVo.getCode());
return fullCityVo.getCode();
}
return null;
}
String code = (String)redisTemplate.opsForHash().get(key, name);
return code;
}
@Override
public String supportType() {
return DictionaryConst.FULL_CITY;
}
}
package com.wyl.redis.service.impl;
import cn.hutool.core.lang.tree.Tree;
import com.wyl.redis.constant.DictionaryConst;
import com.wyl.redis.exception.BusinessException;
import com.wyl.redis.service.DictionaryOperate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @Description
* @Author WuYiLong
* @Date 2024/7/3 17:23
*/
@Slf4j
@Component
public class DictionaryService implements ApplicationContextAware {
private Map<String,DictionaryOperate> dictionaryMaps = new HashMap<>();
@Autowired
private RedisTemplate redisTemplate;
public DictionaryOperate buildDictionaryOperate(String key) {
DictionaryOperate dictionaryOperate = dictionaryMaps.get(key);
if(dictionaryOperate == null) {
throw new BusinessException("字典的key不存在");
}
return dictionaryOperate;
}
public List list(String key) {
String listKey = DictionaryConst.DIC+key+DictionaryConst.LIST;
if(key.contains(":")) {
String[] split = key.split(":");
key = split[0];
listKey = DictionaryConst.DIC+key+DictionaryConst.LIST+":"+split[1];
}
List list = buildDictionaryOperate(key).list(listKey);
return list;
}
public List<Tree<String>> tree(String key) {
String listKey = DictionaryConst.DIC+key+DictionaryConst.TREE;
if(key.contains(":")) {
String[] split = key.split(":");
key = split[0];
listKey = DictionaryConst.DIC+key+DictionaryConst.TREE+":"+split[1];
}
List<Tree<String>> tree =buildDictionaryOperate(key).tree(listKey);
return tree;
}
public String codeNameMap(String key, String code) {
String name = buildDictionaryOperate(key).codeNameMap(DictionaryConst.DIC+key+":codeNameMap", code);
return name;
}
public String nameCodeMap(String key, String name) {
String code = buildDictionaryOperate(key).nameCodeMap(DictionaryConst.DIC+key+":nameCodeMap", name);
return code;
}
public void refresh() {
Set keys = redisTemplate.keys("dic*");
keys.forEach(v->{
redisTemplate.delete(v);
});
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, DictionaryOperate> dictionaryOperateMap = applicationContext.getBeansOfType(DictionaryOperate.class);
dictionaryOperateMap.forEach((k,v)->{
dictionaryMaps.put(v.supportType(),v);
});
}
}
controller层
package com.wyl.redis.controller;
import com.wyl.common.bean.ResponseData;
import com.wyl.redis.service.impl.DictionaryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
*
* @Description
* @Author WuYiLong
* @Date 2024/7/8 10:21
*/
@Api(tags = "字典api")
@RestController
@RequestMapping(value = "dictionary")
public class DictionaryController {
@Autowired
private DictionaryService dictionaryService;
@ApiOperation(value = "字典刷新")
@GetMapping(value = "refresh")
public ResponseData refresh() {
dictionaryService.refresh();
return ResponseData.success();
}
@ApiOperation(value = "字典列表")
@GetMapping(value = "list")
public ResponseData list(String key) {
return ResponseData.successInstance(dictionaryService.list(key));
}
@ApiOperation(value = "字典树")
@GetMapping(value = "tree")
public ResponseData tree(String key) {
return ResponseData.successInstance(dictionaryService.tree(key));
}
@ApiOperation(value = "根据code获取名称")
@GetMapping(value = "codeNameMap")
public ResponseData codeNameMap(String key, String code) {
return ResponseData.successInstance(dictionaryService.codeNameMap(key,code));
}
@ApiOperation(value = "根据名称获取code")
@GetMapping(value = "nameCodeMap")
public ResponseData nameCodeMap(String key, String name) {
return ResponseData.successInstance(dictionaryService.nameCodeMap(key, name));
}
}
测试
根据code获取名称
字典列表
字典树
字典在redis客户端的存储
项目说明
只需要配置好本地的数据库,连接上自己本地的redis,启动项目,就会自动初始化数据库脚本到本地数据库。
package com.wyl.redis.config;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.dynamic.datasource.support.ScriptRunner;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.Map;
/**
* @Description 公共初始化配置
* @Author WuYiLong
* @Date 2024/7/8 9:38
*/
@Slf4j
@ConditionalOnProperty(prefix = "init",value = "enabled",havingValue = "true")
@Component
public class InitConfig implements ApplicationRunner {
@Autowired
private DynamicDataSourceProperties dynamicDataSourceProperties;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("****************初始化数据库脚本开始*************");
Map<String, DataSourceProperty> datasource = dynamicDataSourceProperties.getDatasource();
DataSourceProperty master = datasource.get("master");
DataSource build = DataSourceBuilder
.create()
.url(master.getUrl())
.driverClassName(master.getDriverClassName())
.password(master.getPassword())
.type(master.getType())
.username(master.getUsername())
.build();
ScriptRunner scriptRunner = new ScriptRunner(true, ";");
scriptRunner.runScript(build,"classpath:/db/**");
log.info("****************初始化数据库脚本结束*************");
}
}
在配置文件那里配置,设置init.enabled=true
init:
enabled: false
项目地址
github