使用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

由于向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-servicecsmall-admin-webapi这2个子模块(与csmall-product的2个子模块类似),然后,尽量在csmall-admin-webapi中实现以上查询功能:

public interface AdminMapper {
AdminLoginVO findByUsername(String username);
}

4-11 CS后台项目-4 及 Redis缓存数据的更多相关文章

  1. 4-10 CS后台项目练习-3 || Redis

    13. 类别管理--根据id查询类别详情--持久层 13.1. 规划SQL语句 本次需要执行的SQL语句大致是: select * from pms_category where id=? 关于字段列 ...

  2. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用Redis缓存数据

    上一篇文章(https://www.cnblogs.com/meowv/p/12943699.html)完成了项目的全局异常处理和日志记录. 在日志记录中使用的静态方法有人指出写法不是很优雅,遂优化一 ...

  3. SpringBoot微服务电商项目开发实战 --- Redis缓存雪崩、缓存穿透、缓存击穿防范

    最近已经推出了好几篇SpringBoot+Dubbo+Redis+Kafka实现电商的文章,今天再次回到分布式微服务项目中来,在开始写今天的系列五文章之前,我先回顾下前面的内容. 系列(一):主要说了 ...

  4. 13.在项目中部署redis企业级数据备份方案以及各种踩坑的数据恢复容灾演练

    到这里为止,其实还是停留在简单学习知识的程度,学会了redis的持久化的原理和操作,但是在企业中,持久化到底是怎么去用得呢? 企业级的数据备份和各种灾难下的数据恢复,是怎么做得呢? 1.企业级的持久化 ...

  5. Java项目中使用Redis缓存案例

    缓存的目的是为了提高系统的性能,缓存中的数据主要有两种: 1.热点数据.我们将经常访问到的数据放在缓存中,降低数据库I/O,同时因为缓存的数据的高速查询,加快整个系统的响应速度,也在一定程度上提高并发 ...

  6. 使用redis缓存数据需要注意的问题以及个人的一些思考和理解

    之前我有博客也尝试过使用redis,在实际的项目中确实作用挺大的.至少对于数据的频繁读取来说都起着至关重要的作用. 但是随着技术的学习,慢慢的业务要复杂起来,以后也许会用到redis集群,所以在这边查 ...

  7. 在NodeJS中使用Redis缓存数据

    Redis数据库采用极简的设计思想,最新版的源码包还不到2Mb.其在使用上也有别于一般的数据库. node_redis redis驱动程序多使用 node_redis 此模块可搭载官方的 hiredi ...

  8. PHP Redis 缓存数据

    // 注:只是在此做下记录,有兴趣的可以参考,不做实际教程文档// 配置文件define('CONFIG', [ 'redis-server' => '127.0.0.1', 'redis-po ...

  9. 4-7 CS后台项目练习-1

    1. 关于此项目 此项目是一个自营性质电商类型的项目. 当前目标是设计后台管理相关功能. 2. 关于项目的开发流程 开发项目的标准流程应该有:需求分析.可行性分析.总体设计.详细设计等. 建议课后学习 ...

随机推荐

  1. C#自定义配置文件(一)

    C#自定义配置文件 .NET程序中,经常使用Config文件来配置应用程序中经常使用的值,比如数据库连接字符串.最近项目遇到一个需要配置好多节点在配置文件中的需求.为了使配置节点整洁易维护,在代码调用 ...

  2. 基于.Net C# 通信开发-网络调试助手

    基于.Net C# 通信开发-网络调试助手1.概述 网络调试助手是集TCP/UDP服务端客户端一体的网络调试工具,可以帮助网络应用设计.开发.测试人员检查所开发的网络应用软硬件的数据收发状况,提高开发 ...

  3. 蓝桥杯Web:【功能实现】菜单树检索

    [功能实现]菜单树检索 背景介绍 实际工作中很多前端攻城狮都会遇到这样一个需求:在多级菜单树中模糊搜索匹配的菜单项,并显示出来. 本题需要在已提供的基础项目中使用 Vue.js 知识,实现对已提供的二 ...

  4. .NET混合开发解决方案11 WebView2加载的网页中JS调用C#方法

    系列目录     [已更新最新开发文章,点击查看详细] WebView2控件应用详解系列博客 .NET桌面程序集成Web网页开发的十种解决方案 .NET混合开发解决方案1 WebView2简介 .NE ...

  5. 栈在go语言中实现,及解决388.文件的最长绝对路径的思路

    今天在LeetCode刷每日一题,遇到了388. 文件的最长绝对路径的思路,这道题让我想到了系统的目录是栈结构,果然在题解中找到了栈的解法(暴力半天没出来,跑去看题解了QWQ). 所以我就捎带复习了一 ...

  6. 用js给闺女做了一个加减乘除的html

    下班回家用二十分钟给闺女做了一个加减乘除的页面,顺便记录下代码,时间仓促,后期再来修改吧 目录结构 -yq --menu.html --yq.html --yq50.html --yq70.html ...

  7. 如何生成一个java文档

    如何生成一个java文档 众所周知,一个程序给别人看可能可以看懂,几万行程序就不一定了.在更多的时候,我们并不需要让别人知道我们的程序是怎么写的,只需要告诉他们怎么用的.那么,api文档就发挥了它的作 ...

  8. arts-week11

    Algorithm 69. Sqrt(x) - LeetCode Review Building a network attached storage device with a Raspberry ...

  9. 771. Jewels and Stones - LeetCode

    Question 771. Jewels and Stones Solution 题目大意:两个字符串J和S,其中J中每个字符不同,求S中包含有J中字符的个数,重复的也算 思路:Set记录字符串J中的 ...

  10. 好客租房16-jsx中的列表渲染

    如果要渲染一组数组 应该使用数组的map方法 注意:渲染列表时候添加key属性 key属性的值要保持唯一 原则:map()遍历谁 就给谁添加key属性 尽量避免索引号作为key //导入react i ...