4-11 CS后台项目-4 及 Redis缓存数据
使用Redis缓存数据
使用Redis可以提高查询效率,一定程度上可以减轻数据库服务器的压力,从而保护了数据库。
通常,应用Redis的场景有:
- 高频查询,例如:热搜列表、秒杀
- 改变频率低的数据,例如:商品类别
一旦使用Redis,就会导致Redis和数据库中都存在同样的数据,当数据发生变化时,可能出现不一致的问题!
所以,还有某些数据在特定的场景中不能使用Redis:
- 要求数据必须是准确的:下单购买时要求库存是准确的
- 如果每次库存发生变化时都更新了Redis中的库存值,保证了Redis中的数据是准确的,也可以使用
- 数据的修改频率很高,且对数据准确性有一定要求
需要学会评估是否要求数据一定保持一致!
要使用Redis缓存数据,至少需要:
- 开发新的组件,实现对Redis中的数据访问
- 此组件并不是必须的,因为访问Redis数据的API都非常简单,自定义组件时,组件中的每个方法可能也只有少量代码,甚至只有1行代码
- 如果直接将访问Redis的代码写在Service中,首次开发时会更省事,但不利于维护
- 【推荐】如果将访问Redis的代码写的新的组件中,首次开发时会更麻烦,但利于维护
- 在Service中调用新的组件,在Service中决定何时访问MySQL,何时访问Redis
在使用Redis之前,还必须明确一些问题:
- 哪些查询功能改为从Redis中获取数据
- Redis中的数据从哪里来
暂定目标:
- 根据类别的id查询类别详情,改为从Redis中获取数据
- 优先从Redis中获取数据,如果Redis中没有,则从MySQL中获取,且获取到数据后,将数据存入到Redis中,所以,经过首次查询后,Redis中将存在此数据,后续每一次都可以直接从Redis中拿到必要的数据
在cn.tedu.csmall.product.webapi.repository创建ICategoryRedisRepository接口,并在接口中添加抽象方法:
public interface ICategoryRedisRepository {
String KEY_CATEGORY_ITEM_PREFIX = "categories:item:";
// 将类别详情存入到Redis中
void save(CategoryDetailsVO category);
// 根据类别id获取类别详情
CategoryDetailsVO getDetailsById(Long id);
}
然后在cn.tedu.csmall.product.webapi.repository.impl创建CategoryRedisRepositoryImpl(接口的实现类),实现以上接口:
@Repository
public class CategoryRedisRepositoryImpl implements ICategoryRedisRepository {
@Autowired
private RedisTemplate<String, Serilizalbe> redisTemplate;
@Override
public void save(CategoryDetailsVO category) {
String key = KEY_CATEGORY_ITEM_PREFIX + category.getId();
redisTemplate.opsForValue().set(key, category);
}
@Override
public CategoryDetailsVO getDetailsById(Long id) {
String key = KEY_CATEGORY_ITEM_PREFIX + id;
Serializable result = redisTemplate.opsForValue().get(key);
if (result == null) {
return null;
} else {
CategoryDetailsVO category = (CategoryDetailsVO) result;
return category;
}
}
}
完成后,测试:
package cn.tedu.csmall.product.webapi.repository;
import cn.tedu.csmall.pojo.vo.CategoryDetailsVO;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class CategoryRedisRepositoryTests {
@Autowired
ICategoryRedisRepository repository;
@Test
void testGetDetailsByIdSuccessfully() {
testSave();
Long id = 10L;
CategoryDetailsVO category = repository.getDetailsById(id);
Assertions.assertNotNull(category);
}
@Test
void testGetDetailsByIdReturnNull() {
Long id = -1L;
CategoryDetailsVO category = repository.getDetailsById(id);
Assertions.assertNull(category);
}
private void testSave() {
CategoryDetailsVO category = new CategoryDetailsVO();
category.setId(10L);
category.setName("家用电器");
Assertions.assertDoesNotThrow(() -> {
repository.save(category);
});
}
}
然后,需要修改CategoryServiceImpl中的实现:
@Autowired
private ICategoryRedisRepository categoryRedisRepository;
@Override
public CategoryDetailsVO getDetailsById(Long id) {
// ===== 以下是原有代码,只从数据库中获取数据 =====
// CategoryDetailsVO category = categoryMapper.getDetailsById(id);
// if (category == null) {
// throw new ServiceException(State.ERR_CATEGORY_NOT_FOUND,
// "获取类别详情失败,尝试访问的数据不存在!");
// }
// return category;
// ===== 以下是新的业务,将从Redis中获取数据 =====
// 从repsotiroy中调用方法,根据id获取缓存的数据
// 判断缓存中是否存在与此id对应的key
// 有:表示明确的存入过某数据,此数据可能是有效数据,也可能是null
// -- 判断此key对应的数据是否为null
// -- 是:表示明确的存入了null值,则此id对应的数据确实不存在,则抛出异常
// -- 否:表示明确的存入了有效数据,则返回此数据即可
// 无:表示从未向缓存中写入此id对应的数据,在数据库中,此id可能存在数据,也可能不存在
// 从mapper中调用方法,根据id获取数据库的数据
// 判断从数据库中获取的结果是否为null
// 是:数据库也没有此数据,先向缓存中写入错误数据(null),再抛出异常
// 将从数据库中查询到的结果存入到缓存中
// 返回查询结果
}
为了避免缓存穿透,需要在ICategoryRedisRepository中添加2个抽象方法:
/**
* 判断是否存在id对应的缓存数据
*
* @param id 类别id
* @return 存在则返回true,否则返回false
*/
boolean exists(Long id);
/**
* 向缓存中写入某id对应的空数据(null),此方法主要用于解决缓存穿透问题
*
* @param id 类别id
*/
void saveEmptyValue(Long id);
并在CategoryRedisRepositoryImpl中补充实现:
@Override
public boolean exists(Long id) {
String key = KEY_CATEGORY_ITEM_PREFIX + id;
return redisTemplate.hasKey(key);
}
@Override
public void saveEmptyValue(Long id) {
String key = KEY_CATEGORY_ITEM_PREFIX + id;
redisTemplate.opsForValue().set(key, null);
}
业务中的具体实现为:
@Override
public CategoryDetailsVO getDetailsById(Long id) {
// ===== 以下是原有代码,只从数据库中获取数据 =====
// CategoryDetailsVO category = categoryMapper.getDetailsById(id);
// if (category == null) {
// throw new ServiceException(State.ERR_CATEGORY_NOT_FOUND,
// "获取类别详情失败,尝试访问的数据不存在!");
// }
// return category;
// ===== 以下是新的业务,将从Redis中获取数据 =====
log.debug("根据id({})获取类别详情……", id);
// 从repository中调用方法,根据id获取缓存的数据
// 判断缓存中是否存在与此id对应的key
boolean exists = categoryRedisRepository.exists(id);
if (exists) {
// 有:表示明确的存入过某数据,此数据可能是有效数据,也可能是null
// -- 判断此key对应的数据是否为null
CategoryDetailsVO cacheResult = categoryRedisRepository.getDetailsById(id);
if (cacheResult == null) {
// -- 是:表示明确的存入了null值,则此id对应的数据确实不存在,则抛出异常
log.warn("在缓存中存在此id()对应的Key,却是null值,则抛出异常", id);
throw new ServiceException(State.ERR_CATEGORY_NOT_FOUND,
"获取类别详情失败,尝试访问的数据不存在!");
} else {
// -- 否:表示明确的存入了有效数据,则返回此数据即可
return cacheResult;
}
}
// 缓存中没有此id匹配的数据
// 从mapper中调用方法,根据id获取数据库的数据
log.debug("没有命中缓存,则从数据库查询数据……");
CategoryDetailsVO dbResult = categoryMapper.getDetailsById(id);
// 判断从数据库中获取的结果是否为null
if (dbResult == null) {
// 是:数据库也没有此数据,先向缓存中写入错误数据,再抛出异常
log.warn("数据库中也无此数据(id={}),先向缓存中写入错误数据", id);
categoryRedisRepository.saveEmptyValue(id);
log.warn("抛出异常");
throw new ServiceException(State.ERR_CATEGORY_NOT_FOUND,
"获取类别详情失败,尝试访问的数据不存在!");
}
// 将从数据库中查询到的结果存入到缓存中
log.debug("已经从数据库查询到匹配的数据,将数据存入缓存……");
categoryRedisRepository.save(dbResult);
// 返回查询结果
log.debug("返回查询到数据:{}", dbResult);
return dbResult;
}
许多缓存数据应该是服务器刚刚启动就直接写入到Redis中的,当后续客户端访问时,缓存中已经存在的数据可以直接响应,避免获取数据时缓存中还没有对应的数据,还需要从数据库中查询。
在服务器刚刚启动时就加载需要缓存的数据并写入到Redis中,这种做法称之为缓存预热。
需要解决的问题有:
- 需要实现开机启动时自动执行某个任务
- 哪些数据需要写入到缓存中,例如全部“类别”数据
在Spring Boot中,可以自定义某个组件类,实现ApplicationRunner即可,例如:
package cn.tedu.csmall.product.webapi.app;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class CachePreLoad implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("CachePreLoad.run()");
}
}
为了将全部“类别”写入到缓存中,首先,需要能够从数据库中查询到全部数据,则需要:
- 在
CategoryMapper接口中添加:List<CategoryDetailsVO> list(); - 在
CategoryMapper.xml中配置以上抽象方法映射的SQL语句
然后,还需要实现将查询到的List<CategoryDetailsVO>写入到Redis中,则需要:
- 在
ICategoryRedisRepository接口中添加:void save(List<CategoryDetailsVO> categories); - 在
CategoryRedisRepositoryImpl中实现以上方法- 存入时,Key值可以是:
categories:list
- 存入时,Key值可以是:
由于向Redis中存入列表数据始终是“追加”的,且Redis中的数据并不会因为项目重启而消失,所以,如果反复启动项目,会在Redis的列表中反复追加重复的数据!为了避免此问题,应该在每次缓存预热之间先删除现有数据,所以,还需要:
- 在
ICategoryRedisRepository接口中添加:Boolean deleteList(); - 在
CategoryRedisRepositoryImpl中实现以上方法
从设计的角度,Service是可以调用数据访问层的组件的,即可以调用Mapper或其它Repository组件,换言之,Mapper和其它Repository组件应该只被Service调用!
所以,应该在ICategoryService中定义“预热类别数据的缓存”的抽象方法:
void preloadCache();
另外,在Redis中存入了整个“类别”的列表后,也只能一次性拿到整个列表,不便于根据“类别”的id获取指定的数据,反之,如果每个“类别”数据都独立的存入到Redis中,当需要获取整个列表时,也只能把每个数据都找出来,然后再在Java程序中存入到List集合中,操作也是不方便的,所以,当需要更加关注效率时,应该将类别数据存2份到Redis中,一份是整个列表,另一份是若干个独立的类别数据。
目前,在缓存中存入独立的各个类别数据,在预热时并没有清除这些数据,如果在数据库中删除了数据,但缓存中的数据仍存在,为了避免这样的错误,应该在预热时,补充“删除所有类别”的功能!
则在ICategoryRedisRepository中添加void deleteAllItem();方法,用于删除所有独立的类别数据。
相关代码:ICategoryRedisRepository:
package cn.tedu.csmall.product.webapi.repository;
import cn.tedu.csmall.pojo.vo.CategoryDetailsVO;
import java.util.List;
public interface ICategoryRedisRepository {
/**
* 类别数据的KEY的前缀
*/
String KEY_CATEGORY_ITEM_PREFIX = "categories:item:";
/**
* 类别列表的KEY
*/
String KEY_CATEGORY_LIST = "categories:list";
/**
* 判断是否存在id对应的缓存数据
*
* @param id 类别id
* @return 存在则返回true,否则返回false
*/
Boolean exists(Long id);
/**
* 向缓存中写入某id对应的空数据(null),此方法主要用于解决缓存穿透问题
*
* @param id 类别id
*/
void saveEmptyValue(Long id);
/**
* 将类别详情存入到Redis中
*
* @param category 类别详情
*/
void save(CategoryDetailsVO category);
/**
* 将类别的列表存入到Redis中
*
* @param categories 类别列表
*/
void save(List<CategoryDetailsVO> categories);
/**
* 删除Redis中各独立存储的类别数据
*/
void deleteAllItem();
/**
* 删除Redis中的类别列表
* @return 如果成功删除,则返回true,否则返回false
*/
Boolean deleteList();
/**
* 根据类别id获取类别详情
*
* @param id 类别id
* @return 匹配的类别详情,如果没有匹配的数据,则返回null
*/
CategoryDetailsVO getDetailsById(Long id);
}
相关代码:CategoryRedisRepositoryImpl:
package cn.tedu.csmall.product.webapi.repository.impl;
import cn.tedu.csmall.pojo.vo.CategoryDetailsVO;
import cn.tedu.csmall.product.webapi.repository.ICategoryRedisRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Repository
public class CategoryRedisRepositoryImpl implements ICategoryRedisRepository {
@Autowired
private RedisTemplate<String, Serializable> redisTemplate;
@Override
public Boolean exists(Long id) {
String key = KEY_CATEGORY_ITEM_PREFIX + id;
return redisTemplate.hasKey(key);
}
@Override
public void saveEmptyValue(Long id) {
String key = KEY_CATEGORY_ITEM_PREFIX + id;
redisTemplate.opsForValue().set(key, null, 30, TimeUnit.SECONDS);
}
@Override
public void save(CategoryDetailsVO category) {
String key = KEY_CATEGORY_ITEM_PREFIX + category.getId();
redisTemplate.opsForValue().set(key, category);
}
@Override
public void save(List<CategoryDetailsVO> categories) {
for (CategoryDetailsVO category : categories) {
redisTemplate.opsForList().rightPush(KEY_CATEGORY_LIST, category);
}
}
@Override
public void deleteAllItem() {
Set<String> keys = redisTemplate.keys(KEY_CATEGORY_ITEM_PREFIX + "*");
redisTemplate.delete(keys);
}
@Override
public Boolean deleteList() {
return redisTemplate.delete(KEY_CATEGORY_LIST);
}
@Override
public CategoryDetailsVO getDetailsById(Long id) {
String key = KEY_CATEGORY_ITEM_PREFIX + id;
Serializable result = redisTemplate.opsForValue().get(key);
if (result == null) {
return null;
} else {
CategoryDetailsVO category = (CategoryDetailsVO) result;
return category;
}
}
}
相关代码:缓存预热的业务代码(以下方法的声明在ICategoryService接口中,以下代码是CategoryServiceImpl中重写的方法):
@Override
public void preloadCache() {
log.debug("删除缓存中的类别列表……");
categoryRedisRepository.deleteList();
log.debug("删除缓存中的各独立的类别数据……");
categoryRedisRepository.deleteAllItem();
log.debug("从数据库查询类别列表……");
List<CategoryDetailsVO> list = categoryMapper.list();
for (CategoryDetailsVO category : list) {
log.debug("查询结果:{}", category);
log.debug("将当前类别存入到Redis:{}", category);
categoryRedisRepository.save(category);
}
log.debug("将类别列表写入到Redis……");
categoryRedisRepository.save(list);
log.debug("将类别列表写入到Redis完成!");
}
相关代码:缓存预热类(CachePreLoad):
package cn.tedu.csmall.product.webapi.app;
import cn.tedu.csmall.product.service.ICategoryService;
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.stereotype.Component;
@Component
@Slf4j
public class CachePreLoad implements ApplicationRunner {
@Autowired
private ICategoryService categoryService;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("CachePreLoad.run()");
log.debug("准备执行缓存预热……");
categoryService.preloadCache();
log.debug("缓存预热完成!");
}
}
管理员相关数据表
管理员及权限的管理,涉及的数据表有:
-- 数据库:mall_ams
-- 权限表:创建数据表
drop table if exists ams_permission;
create table ams_permission (
id bigint unsigned auto_increment,
name varchar(50) default null comment '名称',
value varchar(255) default null comment '值',
description varchar(255) default null comment '描述',
sort tinyint unsigned default 0 comment '自定义排序序号',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '权限表' charset utf8mb4;
-- 权限表:插入测试数据
insert into ams_permission (name, value, description) values
('商品-商品管理-读取', '/pms/product/read', '读取商品数据,含列表、详情、查询等'),
('商品-商品管理-编辑', '/pms/product/update', '修改商品数据'),
('商品-商品管理-删除', '/pms/product/delete', '删除商品数据'),
('后台管理-管理员-读取', '/ams/admin/read', '读取管理员数据,含列表、详情、查询等'),
('后台管理-管理员-编辑', '/ams/admin/update', '编辑管理员数据'),
('后台管理-管理员-删除', '/ams/admin/delete', '删除管理员数据');
-- 角色表:创建数据表
drop table if exists ams_role;
create table ams_role (
id bigint unsigned auto_increment,
name varchar(50) default null comment '名称',
description varchar(255) default null comment '描述',
sort tinyint unsigned default 0 comment '自定义排序序号',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '角色表' charset utf8mb4;
-- 角色表:插入测试数据
insert into ams_role (name) values
('超级管理员'), ('系统管理员'), ('商品管理员'), ('订单管理员');
-- 角色权限关联表:创建数据表
drop table if exists ams_role_permission;
create table ams_role_permission (
id bigint unsigned auto_increment,
role_id bigint unsigned default null comment '角色id',
permission_id bigint unsigned default null comment '权限id',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '角色权限关联表' charset utf8mb4;
-- 角色权限关联表:插入测试数据
insert into ams_role_permission (role_id, permission_id) values
(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6),
(2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6),
(3, 1), (3, 2), (3, 3);
-- 管理员表:创建数据表
drop table if exists ams_admin;
create table ams_admin (
id bigint unsigned auto_increment,
username varchar(50) default null unique comment '用户名',
password char(64) default null comment '密码(密文)',
nickname varchar(50) default null comment '昵称',
avatar varchar(255) default null comment '头像URL',
phone varchar(50) default null unique comment '手机号码',
email varchar(50) default null unique comment '电子邮箱',
description varchar(255) default null comment '描述',
is_enable tinyint unsigned default 0 comment '是否启用,1=启用,0=未启用',
last_login_ip varchar(50) default null comment '最后登录IP地址(冗余)',
login_count int unsigned default 0 comment '累计登录次数(冗余)',
gmt_last_login datetime default null comment '最后登录时间(冗余)',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '管理员表' charset utf8mb4;
-- 管理员表:插入测试数据
insert into ams_admin (username, password, nickname, email, description, is_enable) values
('root', '1234', 'root', 'root@tedu.cn', '最高管理员', 1),
('super_admin', '1234', 'administrator', 'admin@tedu.cn', '超级管理员', 1),
('nobody', '1234', '无名', 'liucs@tedu.cn', null, 0);
-- 管理员角色关联表:创建数据表
drop table if exists ams_admin_role;
create table ams_admin_role (
id bigint unsigned auto_increment,
admin_id bigint unsigned default null comment '管理员id',
role_id bigint unsigned default null comment '角色id',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '管理员角色关联表' charset utf8mb4;
-- 管理员角色关联表:插入测试数据
insert into ams_admin_role (admin_id, role_id) values
(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (2, 4), (3, 3);
-- 查询示例:查询id=1的管理员的权限
select distinct ams_permission.value from ams_permission
left join ams_role_permission on ams_role_permission.permission_id=ams_permission.id
left join ams_role on ams_role_permission.role_id=ams_role.id
left join ams_admin_role on ams_admin_role.role_id=ams_role.id
left join ams_admin on ams_admin_role.admin_id=ams_admin.id
where ams_admin.id=1
order by ams_permission.value;
-- 管理员登录日志表:创建数据表
drop table if exists ams_login_log;
create table ams_login_log (
id bigint unsigned auto_increment,
admin_id bigint unsigned default null comment '管理员id',
username varchar(50) default null comment '管理员用户名(冗余)',
nickname varchar(50) default null comment '管理员昵称(冗余)',
ip varchar(50) default null comment '登录IP地址',
user_agent varchar(255) default null comment '浏览器内核',
gmt_login datetime default null comment '登录时间',
gmt_create datetime default null comment '数据创建时间',
gmt_modified datetime default null comment '数据最后修改时间',
primary key (id)
) comment '管理员登录日志表' charset utf8mb4;
-- 管理员登录日志表:插入测试数据
insert into ams_login_log (admin_id, username, nickname, ip, user_agent, gmt_login) values
(1, 'root', 'root', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15', DATE_SUB(NOW(), interval 1 day)),
(2, 'root', 'root', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15', DATE_SUB(NOW(), interval 12 hour)),
(3, 'root', 'root', '127.0.0.1', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15', NOW());
-- 查看数据表结构
desc ams_permission; desc ams_role; desc ams_role_permission; desc ams_admin; desc ams_admin_role; desc ams_login_log;
当某个管理员尝试登录时,必须实现”根据用户名查询此管理员的信息,至少包括id、密码、权限“,需要执行的SQL语句大致是:
-- 管理员表 admin
-- 角色表 role
-- 管理员与角色关联表 admin_role (admin_id, role_id)
-- 权限表 permission
-- 角色与权限关联表 role_permission (role_id, permission_id)
-- 【根据用户名查询管理员,且必须查出对应的权限】
select
ams_admin.id,
ams_admin.username,
ams_admin.password,
ams_admin.is_enable,
ams_permission.value
from ams_admin
left join ams_admin_role on ams_admin.id = ams_admin_role.admin_id
left join ams_role_permission on ams_admin_role.role_id = ams_role_permission.role_id
left join ams_permission on ams_role_permission.permission_id = ams_permission.id
where username='root';
接下来,在根项目中创建csmall-admin模块(与csmall-product类似),并在其下创建csmall-admin-service和csmall-admin-webapi这2个子模块(与csmall-product的2个子模块类似),然后,尽量在csmall-admin-webapi中实现以上查询功能:
public interface AdminMapper {
AdminLoginVO findByUsername(String username);
}
4-11 CS后台项目-4 及 Redis缓存数据的更多相关文章
- 4-10 CS后台项目练习-3 || Redis
13. 类别管理--根据id查询类别详情--持久层 13.1. 规划SQL语句 本次需要执行的SQL语句大致是: select * from pms_category where id=? 关于字段列 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 使用Redis缓存数据
上一篇文章(https://www.cnblogs.com/meowv/p/12943699.html)完成了项目的全局异常处理和日志记录. 在日志记录中使用的静态方法有人指出写法不是很优雅,遂优化一 ...
- SpringBoot微服务电商项目开发实战 --- Redis缓存雪崩、缓存穿透、缓存击穿防范
最近已经推出了好几篇SpringBoot+Dubbo+Redis+Kafka实现电商的文章,今天再次回到分布式微服务项目中来,在开始写今天的系列五文章之前,我先回顾下前面的内容. 系列(一):主要说了 ...
- 13.在项目中部署redis企业级数据备份方案以及各种踩坑的数据恢复容灾演练
到这里为止,其实还是停留在简单学习知识的程度,学会了redis的持久化的原理和操作,但是在企业中,持久化到底是怎么去用得呢? 企业级的数据备份和各种灾难下的数据恢复,是怎么做得呢? 1.企业级的持久化 ...
- Java项目中使用Redis缓存案例
缓存的目的是为了提高系统的性能,缓存中的数据主要有两种: 1.热点数据.我们将经常访问到的数据放在缓存中,降低数据库I/O,同时因为缓存的数据的高速查询,加快整个系统的响应速度,也在一定程度上提高并发 ...
- 使用redis缓存数据需要注意的问题以及个人的一些思考和理解
之前我有博客也尝试过使用redis,在实际的项目中确实作用挺大的.至少对于数据的频繁读取来说都起着至关重要的作用. 但是随着技术的学习,慢慢的业务要复杂起来,以后也许会用到redis集群,所以在这边查 ...
- 在NodeJS中使用Redis缓存数据
Redis数据库采用极简的设计思想,最新版的源码包还不到2Mb.其在使用上也有别于一般的数据库. node_redis redis驱动程序多使用 node_redis 此模块可搭载官方的 hiredi ...
- PHP Redis 缓存数据
// 注:只是在此做下记录,有兴趣的可以参考,不做实际教程文档// 配置文件define('CONFIG', [ 'redis-server' => '127.0.0.1', 'redis-po ...
- 4-7 CS后台项目练习-1
1. 关于此项目 此项目是一个自营性质电商类型的项目. 当前目标是设计后台管理相关功能. 2. 关于项目的开发流程 开发项目的标准流程应该有:需求分析.可行性分析.总体设计.详细设计等. 建议课后学习 ...
随机推荐
- 论文解读(DAGNN)《Towards Deeper Graph Neural Networks》
论文信息 论文标题:Towards Deeper Graph Neural Networks论文作者:Meng Liu, Hongyang Gao, Shuiwang Ji论文来源:2020, KDD ...
- 详解计算miou的代码以及混淆矩阵的意义
详解计算miou的代码以及混淆矩阵的意义 miou的定义 ''' Mean Intersection over Union(MIoU,均交并比):为语义分割的标准度量.其计算两个集合的交集和并集之比. ...
- PTA 7-4 堆栈操作合法性 (20 分)
假设以S和X分别表示入栈和出栈操作.如果根据一个仅由S和X构成的序列,对一个空堆栈进行操作,相应操作均可行(如没有出现删除时栈空)且最后状态也是栈空,则称该序列是合法的堆栈操作序列.请编写程序,输入S ...
- ajax 请求登录超时跳转登录页解决方法
在Filter里判断是否登录,如果未登录返回401状态 public class SelfOnlyAttribute : ActionFilterAttribute { public override ...
- Java异常处理最佳实践
总结一些Java异常的处理原则 Java异常处理最佳实践 不要忘记关闭资源 在finally里关闭资源 public void readFile() { FileInputStream fileInp ...
- Linux下MySQL表名区分大小写
问题:MySQL一个数据库的表名统一小写,在Windows上安装的MySQL没有问题,但是把数据库部署到Linux上,应用启动的时候报表不存在错误. 解决:修改my.cnf lower_case_ta ...
- monit 配置详解(monitrc)
monitrc是Monit的主配置文件(控制文件). monitrc的内容主要分为全局(golbal)和服务(services)两个部分. 默认情况下monitrc文件在/etc/monit目录下. ...
- Spring Ioc源码分析系列--Bean实例化过程(二)
Spring Ioc源码分析系列--Bean实例化过程(二) 前言 上篇文章Spring Ioc源码分析系列--Bean实例化过程(一)简单分析了getBean()方法,还记得分析了什么吗?不记得了才 ...
- 第一次的ssm整合
数据库表 导入依赖 <dependencies> <dependency> <groupId>javax.servlet</groupId> <a ...
- docker引起服务器磁盘爆满
服务器异常 又是开开心心打开我心爱的服务器一天: 吔!这是嘛啊?我的服务器域名访问不了了,一直转圈圈超时了,好,打开ssh远程看看,吔!!!还是访问不了,宕机了?怀着一颗憋大便的心情打开了阿里云控制面 ...