目录
- 根据数据不断调整架构
- [安装elasticsearch 版本8.12.2](#安装elasticsearch 版本8.12.2)
- kibana安装
- ik分词
- springboot实战
随着物联网平台的不断发展,平台要求接入的模块会越来越多,对应的数据存储也会有不同的变化,对应的架构也会不断地变化。
- 存在问题,数据量大后,性能很慢
- 单表 表字段过多
根据数据不断调整架构
第一版:
数据量不大,采用mysql单表,所有的数据都存放到一个表中,操作简单
第二版:
随着业务的发展,单个表可能达到几个百字段,某个通讯模块解析的时候,实际上只有十几个字段,但是,因为表有几百个字段,会造成空间的一个浪费。查询的性能能明显下降。
- 采用shardingsphere分表方式, 1个月一个表,减少单表的大小
- 实际上,空间浪费的问题还没有解决
第三版:
第二个版本运行1年后,增加的不同的物联网通讯模块(解析的场景)越来越多,例如,解析天气,解析大气、解析充电桩、解析气候、解析环保等等,导致我们不得不直面空间浪费的问题。
- 当前版本的缺陷: 空间浪费过多,如果需要增加字段,所有的按月分的表,都需要新增对应模块的新增字段,因为数据偏多,导致新增字段的花费时长过多,再一个就是,无法达到我们物联网设计的最初的一个初衷,就是想尽量少的改动的前提下,兼容所有的通讯数据的接入。
遇到问题,那我们就要解决问题
- 1、mysql行转列,逻辑太复杂,没有什么用,排除
- 2、mysql只存基本的数据,设备id、时间、动态的字段都用json存储(mysql也支持按这里面的数据过滤),好像是5.7以后的版本才支持,经过测试,因为存储的是json数据,导致空间比正常的存储,还大个10倍左右,剔除
- 3、 还有一个同事提议,不同的通讯模块定义一个表,这样就可以减少表的一个浪费,首先,多表数据查询就是一个问题,不同模块不同表分表也是一个问题,随着模块的越来越多,表维护也是一个问题,给用户的权限太大,会影响历史数据的结构,风险过大。所有该方案剔除
- 4、mysql只存基础数据,其他的详细数据存es, 我身边认识的大部分人同样类似的业务都是采取该方案,下面就得测试一下
安装elasticsearch 版本8.12.2
D:\system\es\8.12.2\elasticsearch-8.12.2\config\jvm.options
增加一句,解决控制台打印乱码的问题
java
-Dfile.encoding=GBK
- 记住这个账号和密码后面有用 账号elastic 密码6*X_gLqvJ3sK=Wbx=bsd
修改D:\system\es\8.12.2\elasticsearch-8.12.2\config\elasticsearch.yml
- 设置为false
/bin/elasticsearch.bat 双击这个文件启动,启动后最后这个是账户和密码。
- 设置为false后,需要去掉https的s进行访问
kibana安装
- 注意需要跟es的版本一样,不然会有奇奇怪怪的问题
创建kibana用户
java
elasticsearch-reset-password -u kibana_system
- 因为elastic是 超级用户,无法用这个用户来启动,所以得新创建一个用户
- 账号kibana_system 密码c2zJWwm20-0TxiiHAsEG
配置
修改配置文件"D:\env\kibana-8.15.0\config\kibana.yml"
将下面对应几个注释取消掉,如果修改的话就顺便改一下~
java
server.port: 5601
server.host: "0.0.0.0"
# 国际化中文
i18n.locale: "zh-CN"
# 配置es集群url
elasticsearch.hosts: ["http://localhost:9200"]
# 创建连接用户的账号和密码
elasticsearch.username: "kibana_system"
elasticsearch.password: "c2zJWwm20-0TxiiHAsEG"
启动bin下面的kibana.bat脚本
访问localhost:5601
- 输入上面设置的账号elastic 密码6*X_gLqvJ3sK=Wbx=bsd登录,用新创建的用户会提示权限不够
- 写DSL发请求
ik分词
- 在es的plugins目录下 ,新创建一个ik目录,把ik分词的压缩包放到该目录下面
进入Kibana界面
java
POST _analyze
{
"text":"华为手机",
"analyzer":"ik_smart"
}
- 不用分词,会一个汉字一个词,不太友好
java
普通
im_smart
最小颗粒
ik_max_word
分词的拓展以及停用
- 分词的过程中,会存在一些词汇,例如"的",这个无意义,考虑剔除,可以用来做黑名单,防止一些敏感词出现
- "欧力给"因为是新的词汇,无法失败,得自己添加
- D:\system\es\8.12.2\elasticsearch-8.12.2\plugins\ik\config 分词插件目录下IKAnalyzer.cfg.xml
- ext.dic直接输入"欧力给" 如果存在多个,一直按回车
- stopword.dic 是停用词的文件,增加一个的
记得重启es的服务,不然不生效
- 欧力给变成一个词汇,"得"也被剔除
springboot实战
pom.xml
java
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
application.yml
java
elasticsearch:
port: 9200
ip: 127.0.0.1
username: elastic
password: 6*X_gLqvJ3sK=Wbx=bsd
- 注意使用自己的es的账号和密码
相关配置
java
package com.zyee.iopace.web.entity.es;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Document(indexName = "shopping", shards = 3, replicas = 1)
public class Product {
//必须有 id,这里的 id 是全局唯一的标识,等同于 es 中的"_id"
@Id
private Long id;//商品唯一标识
/**
* type : 字段数据类型
* analyzer : 分词器类型
* index : 是否索引(默认:true)
* Keyword : 短语,不进行分词
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title;//商品名称
@Field(type = FieldType.Keyword)
private String category;//分类名称
@Field(type = FieldType.Double)
private Double price;//商品价格
@Field(type = FieldType.Keyword, index = false)
private String images;//图片地址
}
package com.zyee.iopace.web.config.es;
import lombok.Data;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
@ConfigurationProperties(prefix = "elasticsearch")
@Configuration
@Data
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration{
private String ip ;
private Integer port ;
private String username;
private String password;
//重写父类方法
@Override
public RestHighLevelClient elasticsearchClient() {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
RestClientBuilder builder = RestClient.builder(new HttpHost(ip, port));
builder.setHttpClientConfigCallback(httpClientBuilder -> {
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
return httpClientBuilder;
});
RestHighLevelClient restHighLevelClient = new
RestHighLevelClient(builder);
return restHighLevelClient;
}
}
//controller类
@ApiOperation(value = "测试")
@GetMapping("/test123")
public ResponseResult test123() {
Product product = new Product();
product.setId(3L);
product.setTitle("华为手机");
product.setCategory("手机");
product.setPrice(2999.0);
product.setImages("http://www.atguigu/hw.jpg");
productDao.save(product);
return ResponseResult.SUCCESS();
}
- 插入成功,但是控制台打印报错
注意重启项目后,再去搜Version,能看到我们部署的版本是8.12.2,但是java引入的是7.12.1所以报错,注意不要直接再外面try catch来判断,这样代码不太友好,要从根本上解决问题,大概率是版本的问题,考虑升级版本
- 再次确定,注意版本一致性,不然有各种各样的问题
有点尴尬这里版本不一致,只能降到7.12.1版本
###降低版本7.12.1
java
下载7.12.1 es kibara ik
###D:\system\es\7.12.1\elasticsearch-7.12.1\config\elasticsearch.yml 增加如下
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: false
###es得bin下面执行 修改elastic的初始化密码为123456,一直输入123456
/elasticsearch-setup-passwords interactive
###新增kibana用户kibana_system c2zJWwm20-0TxiiHAsEG
###修改D:\system\es\7.12.1\kibana-7.12.1\config
server.port: 5601
server.host: "0.0.0.0"
# 国际化中文
i18n.locale: "zh-CN"
# 配置es集群url
elasticsearch.hosts: ["http://localhost:9200"]
# 创建连接用户的账号和密码
elasticsearch.username: "elastic"
elasticsearch.password: "123456"
#####项目的application.yml配置
elasticsearch:
port: 9200
ip: 127.0.0.1
username: elastic
password: 123456
框架集成-SpringData-集成测试-文档操作
java
import com.lun.dao.ProductDao;
import com.lun.model.Product;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataESProductDaoTest {
@Autowired
private ProductDao productDao;
/**
* 新增
*/
@Test
public void save(){
Product product = new Product();
product.setId(2L);
product.setTitle("华为手机");
product.setCategory("手机");
product.setPrice(2999.0);
product.setImages("http://www.atguigu/hw.jpg");
productDao.save(product);
}
//POSTMAN, GET http://localhost:9200/product/_doc/2
//修改
@Test
public void update(){
Product product = new Product();
product.setId(2L);
product.setTitle("小米 2 手机");
product.setCategory("手机");
product.setPrice(9999.0);
product.setImages("http://www.atguigu/xm.jpg");
productDao.save(product);
}
//POSTMAN, GET http://localhost:9200/product/_doc/2
//根据 id 查询
@Test
public void findById(){
Product product = productDao.findById(2L).get();
System.out.println(product);
}
@Test
public void findAll(){
Iterable<Product> products = productDao.findAll();
for (Product product : products) {
System.out.println(product);
}
}
//删除
@Test
public void delete(){
Product product = new Product();
product.setId(2L);
productDao.delete(product);
}
//POSTMAN, GET http://localhost:9200/product/_doc/2
//批量新增
@Test
public void saveAll(){
List<Product> productList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Product product = new Product();
product.setId(Long.valueOf(i));
product.setTitle("["+i+"]小米手机");
product.setCategory("手机");
product.setPrice(1999.0 + i);
product.setImages("http://www.atguigu/xm.jpg");
productList.add(product);
}
productDao.saveAll(productList);
}
//分页查询
@Test
public void findByPageable(){
//设置排序(排序方式,正序还是倒序,排序的 id)
Sort sort = Sort.by(Sort.Direction.DESC,"id");
int currentPage=0;//当前页,第一页从 0 开始, 1 表示第二页
int pageSize = 5;//每页显示多少条
//设置查询分页
PageRequest pageRequest = PageRequest.of(currentPage, pageSize,sort);
//分页查询
Page<Product> productPage = productDao.findAll(pageRequest);
for (Product Product : productPage.getContent()) {
System.out.println(Product);
}
}
}
java
import com.lun.dao.ProductDao;
import com.lun.model.Product;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataESSearchTest {
@Autowired
private ProductDao productDao;
/**
* term 查询
* search(termQueryBuilder) 调用搜索方法,参数查询构建器对象
*/
@Test
public void termQuery(){
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "小米");
Iterable<Product> products = productDao.search(termQueryBuilder);
for (Product product : products) {
System.out.println(product);
}
}
/**
* term 查询加分页
*/
@Test
public void termQueryByPage(){
int currentPage= 0 ;
int pageSize = 5;
//设置查询分页
PageRequest pageRequest = PageRequest.of(currentPage, pageSize);
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "小米");
Iterable<Product> products =
productDao.search(termQueryBuilder,pageRequest);
for (Product product : products) {
System.out.println(product);
}
}
}
相关代码调整
java
package com.zyee.iopace.web.entity.es;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Document(indexName = "test", shards = 3, replicas = 1)
public class Product {
//必须有 id,这里的 id 是全局唯一的标识,等同于 es 中的"_id"
@JsonSerialize(using = ToStringSerializer.class)
@Id
private Long id;//商品唯一标识
@Field(type = FieldType.Integer)
private Integer stationInfoId;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
@Field(type = FieldType.Date)
private Date reportTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Field(type = FieldType.Date)
private Date createTime;
//里面是json格式 动态的
@Field(type = FieldType.Keyword,index = false)
private String detail;
}
- 之前mysql的数据以json格式存到detail字段中
- 经过分析,mysql存6w多条数据大约是60M左右,约大了4倍
到目前为止,已经实现了es动态列的功能