目录
- 根据数据不断调整架构
- 安装elasticsearch 版本8.12.2
- kibana安装
- ik分词
- 分词的拓展以及停用
- springboot实战
- pom.xml
- application.yml
- 相关配置
- 框架集成-SpringData-集成测试-文档操作
- 相关代码调整
随着物联网平台的不断发展,平台要求接入的模块会越来越多,对应的数据存储也会有不同的变化,对应的架构也会不断地变化。
- 存在问题,数据量大后,性能很慢
- 单表 表字段过多
根据数据不断调整架构
第一版:
数据量不大,采用mysql单表,所有的数据都存放到一个表中,操作简单
第二版:
随着业务的发展,单个表可能达到几个百字段,某个通讯模块解析的时候,实际上只有十几个字段,但是,因为表有几百个字段,会造成空间的一个浪费。查询的性能能明显下降。
- 采用shardingsphere分表方式, 1个月一个表,减少单表的大小
- 实际上,空间浪费的问题还没有解决
第三版:
第二个版本运行1年后,增加的不同的物联网通讯模块(解析的场景)越来越多,例如,解析天气,解析大气、解析充电桩、解析气候、解析环保等等,导致我们不得不直面空间浪费的问题。
- 当前版本的缺陷: 空间浪费过多,如果需要增加字段,所有的按月分的表,都需要新增对应模块的新增字段,因为数据偏多,导致新增字段的花费时长过多,再一个就是,无法达到我们物联网设计的最初的一个初衷,就是想尽量少的改动的前提下,兼容所有的通讯数据的接入。
遇到问题,那我们就要解决问题
- 1、mysql行转列,逻辑太复杂,没有什么用,排除
- 2、mysql只存基本的数据,设备id、时间、动态的字段都用json存储(mysql也支持按这里面的数据过滤),好像是5.7以后的版本才支持,经过测试,因为存储的是json数据,导致空间比正常的存储,还大个10倍左右,剔除
- 3、 还有一个同事提议,不同的通讯模块定义一个表,这样就可以减少表的一个浪费,首先,多表数据查询就是一个问题,不同模块不同表分表也是一个问题,随着模块的越来越多,表维护也是一个问题,给用户的权限太大,会影响历史数据的结构,风险过大。所有该方案剔除
- 4、mysql只存基础数据,其他的详细数据存es, 我身边认识的大部分人同样类似的业务都是采取该方案,下面就得测试一下
安装elasticsearch 版本8.12.2
下载es
D:\system\es\8.12.2\elasticsearch-8.12.2\config\jvm.options
增加一句,解决控制台打印乱码的问题
-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 双击这个文件启动,启动后最后这个是账户和密码。
输入https://127.0.0.1:9200/
- 设置为false后,需要去掉https的s进行访问
kibana安装
kibana下载
- 注意需要跟es的版本一样,不然会有奇奇怪怪的问题
创建kibana用户
elasticsearch-reset-password -u kibana_system
- 因为elastic是 超级用户,无法用这个用户来启动,所以得新创建一个用户
- 账号kibana_system 密码c2zJWwm20-0TxiiHAsEG
配置
修改配置文件"D:\env\kibana-8.15.0\config\kibana.yml"
将下面对应几个注释取消掉,如果修改的话就顺便改一下~
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分词
8.12.2下载
- 在es的plugins目录下 ,新创建一个ik目录,把ik分词的压缩包放到该目录下面
进入Kibana界面
POST _analyze
{"text":"华为手机","analyzer":"ik_smart"
}
- 不用分词,会一个汉字一个词,不太友好
普通
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
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency>
application.yml
elasticsearch:port: 9200ip: 127.0.0.1username: elasticpassword: 6*X_gLqvJ3sK=Wbx=bsd
- 注意使用自己的es的账号和密码
相关配置
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"@Idprivate 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;//重写父类方法@Overridepublic 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 = newRestHighLevelClient(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
下载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: 9200ip: 127.0.0.1username: elasticpassword: 123456
框架集成-SpringData-集成测试-文档操作
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 {@Autowiredprivate ProductDao productDao;/*** 新增*/@Testpublic 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//修改@Testpublic 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 查询@Testpublic void findById(){Product product = productDao.findById(2L).get();System.out.println(product);}@Testpublic void findAll(){Iterable<Product> products = productDao.findAll();for (Product product : products) {System.out.println(product);}}//删除@Testpublic void delete(){Product product = new Product();product.setId(2L);productDao.delete(product);}//POSTMAN, GET http://localhost:9200/product/_doc/2//批量新增@Testpublic 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);}//分页查询@Testpublic 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);}}
}
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 {@Autowiredprivate ProductDao productDao;/*** term 查询* search(termQueryBuilder) 调用搜索方法,参数查询构建器对象*/@Testpublic void termQuery(){TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "小米");Iterable<Product> products = productDao.search(termQueryBuilder);for (Product product : products) {System.out.println(product);}}/*** term 查询加分页*/@Testpublic 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);}}}
相关代码调整
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)@Idprivate 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动态列的功能