作为一个后端开发,那必要的存储肯定是要的。
随着业务的不同,数据类型也是各种各样,接口需要也是各种各样,存储的方式也就慢慢的多了起来。
本文主要记录工作中不同的业务场景下,不同的存储方式。
1.内存存储
内存存储应该是最轻便也是最简单的存储了,一起来看看代码的实现。
static Map<String, Object> map = new HashMap<>();
map.put("name", "张三" + new Date().getTime());
map.put("age", 19);
map.put("crad", new Date().getTime());
在当前服务中存在一个静态的Map容器,对一些不是太重要的数据进行延迟刷库等操作。
优点:轻便,读取速度快
缺点:声明周期跟随服务,服务重启容器也重置
2.磁盘存储
数据最终还是要落入磁盘的,不然就要考虑丢失问题,一起来看看磁盘怎么存储的。
public static void main(String[] args) throws Exception {
File file = new File("/test/123.txt");
//写入流 写入数据
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write("123456".getBytes());
outputStream.write("\n78901".getBytes());
outputStream.write("\nasdfdf".getBytes());
outputStream.flush();
outputStream.close();
StringBuilder builder = new StringBuilder();
int len;
byte[] bytes = new byte[1024];
//读取文本中数据
FileInputStream inputStream = new FileInputStream(file);
while ((len = inputStream.read(bytes)) != -1) {
builder.append(new String(bytes, 0, len));
}
inputStream.close();
System.out.println(builder.toString());
}
这种存储方式在单机模式下还是挺实用的,至少数据比较稳妥,不会丢。
缺点:磁盘io和内存io在读写速度上有这天差地别,高频率下磁盘读写肯定有瓶颈
3.redis存储
前面两种都是基于本机的存储,第一种甚至做不到多服务间交互,随着服务的演变,分布式的存储肯定必不可少的
先来说说中间件redis存储
Jedis jedis = new Jedis("192.168.0.100", 6379, 10000000);
jedis .select(14);
jedis .set("test", "test_value");
redis作为一个特殊的多路复用网络模型,在读写上多线程读,单线程写,在性能上正常机器能达到秒Qps 10W加,基本上是项目中必备的了。
缺点:纯内存的存储,但无法做关系型数据搜索
4.数据库存储
数据库的种类繁多,也就不一一介绍了,只要贴下mysql的代码
@Autowried
UserMapper userMapper;
Map<String,Object> map = new HashMap();
map.put("user_id","ud112");
map.put("name","张三");
//insert into user_info(`user_id`,`name`) values (#{user_id},#{name})
userMapper.insert(map);
关系型的数据库能够快速的查询出想要的数据,建立合适的索引可以快速的扫描数据。
5.文件型数据库存储
首先看下MongoDb存储的代码
//实体类上添加@Document("student") 其他和JPA一样的操作
Student student = new Student();
student.setUid(new Date().getTime());
student.setUsername("张三");
student.setAge(19);
student.setCreateTime(new Date());
Student insert = mongoTemplate.insert(student);
log.info("插入数据成功:{}", insert);
mongoDb单个的key/value存储和redis类似,但mongoDb可以存储文件,这个是redis做不到的
6.elasticsearch搜索引擎存储
在微服务时间一直有一个痛点,在订单服务要对用户做条件搜索,在没有es之前这真是一个痛点,但是在有了es这样一个分布式的搜索引擎之后,这个问题也迎刃而解了。
一起来看看代码
//在索引test_index上存储一条数据
User user = new User();
user.setName("世界上最好的语言php.java");
user.setAge(19);
user.setAddr("老北京");
user.setInfo("老北京信息");
IndexResponse response = elasticsearchClient.index(a -> a
.index("test_index")
.id("0").document(user));
log.info("response:{}", response.seqNo());
//查询and多条件匹配数据 name包含语言 and age=17
SearchResponse<JSONObject> response1 = elasticsearchClient.search(s -> s
.index("test_index")
.query(q -> q.bool(t ->
t.must(m -> {
m.match(a -> a.field("name").query("语言"));
m.match(a -> a.field("age").query(117));
return m;
})
)),
JSONObject.class);
Iterator<Hit<JSONObject>> iterator = response1.hits().hits().iterator();
while (iterator.hasNext()) {
Hit<JSONObject> decodeBeanHit = iterator.next();
JSONObject docJson = decodeBeanHit.source();
log.info("response_all:{}", docJson.toJSONString());
}
elasticsearch的api使用起来比较复杂的,下一篇文章会专门记录一下
7.zookeeper存储
zookeeper的特色,节点唯一,临时节点和过期回调
public static void init(){
try {
//连接到zk
zooKeeper = new ZooKeeper(address, 60000,
watchedEvent -> {
});
while (true) {
synchronized (object) {
for (int i = 0; i < nodeNum; i++) {
String path = defPath + i;
try {
//创建一个临时节点 如果不成功则添加监听 过期后会回调到监听
String s = zooKeeper.create(path, "1".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
//取消监听
zooKeeper.exists(path, false);
log.info("创建节点成功:{}", s);
return;
} catch (KeeperException ex) {
log.error("节点已存在,添加监听并等待节点事件:{}", path);
addZkWatch(path, zooKeeper);
} catch (Exception e) {
log.error("执行异常:", e);
throw new RuntimeException("zk初始化失败");
}
}
object.wait();
}
}
} catch (Exception e) {
log.error("初始化zk异常!");
throw new RuntimeException(e);
}
}
private static void addZkWatch(String path, ZooKeeper zooKeeper) {
log.info("添加监听:{}", path);
try {
zooKeeper.exists(path, watchedEvent -> {
synchronized (object) {
String eventPath = watchedEvent.getPath();
Watcher.Event.EventType eventType = watchedEvent.getType();
log.info("收到节点事件======{}", eventPath, eventType);
if (Watcher.Event.EventType.NodeDeleted.equals(eventType)) {
log.info("发现节点删除,唤醒服务抢占");
object.notifyAll();
} else {
//原生包由map存储事件只会调用一次,处理过事件之后需要重新监听
addZkWatch(path, zooKeeper);
}
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
上述代码则是在已有项目中用zookeeper做备用节点的案例,如果服务宕机则会唤醒备用节点。
存储方式各有差异,也各有千秋,这里主要是挑了比较常见的存储方式。
至于合适主要还是要跟业务相关联,才能最大的力度的使用。
如果还有其他的方式实现的,欢迎评论区留言哦
以上就是本章的全部内容了。
上一篇:随手记录第二话 – 高并发情况下秒杀、抢红包都有哪些实现方式?
下一篇:随手记录第四话 – elasticsearch基于docker安装以及SpringBoot集成使用
问渠那得清如许,为有源头活水来