mongodb
需求
要记录日志,并且要根据日志的多个字段来查询(复杂查询),保存30天以上的日志要删除,日志的准确性和完整性要求低。
选型
目前项目使用了redis和mysql,由于日志数量太多,写入和读取操作频繁,所以mysql首先排除了,其次需要复杂的查询操作,用redis操作复杂度略高,并且mongodb是存入硬盘而redis则大部分都在内存里(这句可能是错误的),所以综合考虑选用mongodb来存储日志。
安装
参考官方文档 Install on Red Hat
[root@centos]# vim /etc/yum.repos.d/mongodb-org-4.0.repo
复制以下内容
[mongodb-org-4.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.0.asc
保存之后安装
[root@centos]# yum install -y mongodb-org
状态、重启、停止
systemctl status mongod.service
systemctl restart mongod.service
systemctl stop mongod.service
安装填坑
总之要借助systemctl status -l mongod.service
指令和/var/log/mongodb/mongod.log
日志文件来排查错误。
1、由于安装时先写的repo然后又执行了yum makecache,最后总是提示找不到版本,所以要执行
[root@centos]# yum clean all
2、千万不要直接[root@centos]# yum install -y mongodb
这样会直接安装centos自带的repo版本,版本是2.×的老版本,后面还要卸载,按照官方文档执行
[root@centos]# yum install -y mongodb-org
3、selinux权限放行,这个查看了mongo日志,按照操作日志提示执行
2019-05-14T19:56:02.967+0800 E STORAGE [initandlisten] Failed to set up listener: # SocketException: Cannot assign requested address
ausearch -c 'mongod' --raw | audit2allow -M my-mongod
semodule -i my-mongod.pp
[root@centos]# ausearch -c 'mongod' --raw | audit2allow -M my-mongod
[root@centos]# semodule -i my-mongod.pp
4、启动不了,因为删除了整个数据库文件,然后用的是root权限启动mogod,这就导致了重新创建的storage.bson
文件权限太高,以后如果用systemctl来启动,无权访问的问题。
2019-05-14T20:44:40.366+0800 E STORAGE [initandlisten] Unable to read the storage engine metadata file: FileNotOpen: Failed to read metadata from /var/lib/mongo/storage.bson
2019-05-14T20:44:40.366+0800 F - [initandlisten] Fatal Assertion 28661 at src/mongo/db/storage/storage_engine_metadata.cpp 93
根据提示修改整个数据库文件归属
[root@centos]# chown mongod -R mongo
[root@centos]# chgrp mongod -R mongo
5、还是启动不了,因为刚安装好就启动了一下,然后就kill -9杀掉了进程,不了解要先删除mongod.lock
和WiredTiger.lock
文件,这里就不贴日志了,说一下除了systemctl stop之外的正确停止mongodb的做法
关闭方法一
在mongo的shell里执行,需要先切换到admin,如果开了权限认证需要先认证db.auth("admin", "password" )
才能成功
db.shutdownServer()
关闭方法二
[root@centos]# killall mongod
创建管理员和数据库账号
进入mongodb管理shell
[root@centos]# mongo --port 27017
查看所有数据库
show dbs
切换到admin数据库
use admin
创建管理员账号
db.createUser(
{
user: "admin",
pwd: "adminpassword",
roles: [
{ role: "root", db: "admin" },
{ role: "userAdminAnyDatabase", db: "admin" }
]
}
)
切换到logdb数据库
use logdb
创建数据库账号,就是程序里连接时用的账号密码
db.createUser(
{
user: "loguser",
pwd: "logpassword",
roles: [
{ role: "dbOwner", db: "logdb" }
]
}
)
查找所有用户
db.system.users.find().pretty()
开启账户认证和远程访问
主要在配置文件mongod.conf
里进行
[root@centos]# vim /etc/mongod.conf
设置mongodb远程访问
主要在net模块
net:
port: 27017
bindIp: 127.0.0.1
port就是端口号,如果修改了端口号,比如修改为17017,那么进入mongo的时候就要执行[root@centos]# mongo --port 17017
bindIp和redis的bind配置的坑一样,都是又大又深,网上的各种解释和不负责任的随手粘帖复制,真让我无f可k说!!
网络错误说法一
编辑mongod.conf注释bindIp,并重启mongodb.(这句配置代表只能本机使用,所以需注释)。
正确操作
查看官方文档就知道,即使注释掉也没卵用,因为人家默认的就是本机访问,必须手动改为bindIp: 0.0.0.0
。
网络错误说法二
绑定多个IP只需要在后面追加新IP用逗号隔开即可,如果还是报错
Failed to set up listener: SocketException: Cannot assign requested address
则需要把所有IP用中括号括起来, bindIp: [127.0.0.1,115.159.159.129],尝试了之后会给你这样一个报错
Scalar option 'net.bindIp' must be a single value
正确操作
经过多次修改试验得出结论,
1、IP只能配置1个,配置多个IP均启动不了
2、IP只能是127.0.0.1或者0.0.0.0,配置局域网的其他IP或者广域网IP都启动不了
3、配置127.0.0.1,局域网内不能访问,0.0.0.0则可以访问
结论
在看了官方文档之后其实还是一头雾水没搞清楚,就以4.0.9版本的实际操作来说,如果不限制IP访问,既不能注释,也无法配置多个,只能修改为
net:
port: 17017
bindIp: 0.0.0.0
开启账号密码认证
在执行这一步之前,需要先创建管理员和数据库账号,
方法很简单,在mongod.conf
追加如下内容
security:
authorization: enabled
千万记得,配置文件所有的冒号后面要跟一个空格!
第二行千万不要顶格写,加两个空格
保存之后可以直接在本机访问
[root@centos]# mongo --port 17017 -u "admin" -p "adminpassword" --authenticationDatabase "admin"
或者通过远程连接可视化工具,免费版的Robo 3T来连接。
其他优化项,关闭THP
这些优化项实在查阅资料时看到的,但并没有实际操作。关闭方法参考官方文档
Transparent Huge Pages (THP),通过使用更大的内存页面,可以减少具有大量内存的机器上的缓冲区(TLB)查找的开销。
但是,数据库工作负载通常对THP表现不佳,因为它们往往具有稀疏而不是连续的内存访问模式。您应该在Linux机器上禁用THP,以确保MongoDB的最佳性能。
集成spring
本文在spring4.3.9基础上集成,请注意版本号和适用性。
maven依赖
这里有个地方需要注意一下,如果spring4.X的话,那么spring-data-mongodb就必须用1.X,如果用最新的2.X,会在运行时报找不到此函数的错误
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoSuchMethodError: org.springframework.util.Assert.isInstanceOf(Ljava/lang/Class;Ljava/lang/Object;Ljava/util/function/Supplier;)V
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.10.22.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.10.2</version>
</dependency>
配置spring的mongoTemplate
首先在spring配置文件里加上
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
在schema里添加http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
然后配置了mongodb的数据源,去掉了插入数据时生成的_class
字段,最后配置mongoTemplate。
完整配置如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/mongo
http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
http://www.springframework.org/schema/data/repository
http://www.springframework.org/schema/data/repository/spring-repository-1.5.xsd
">
<!-- 引入jdbc配置文件 -->
<context:property-placeholder location="classpath:properties/*.properties"/>
<!-- 自动扫描注解的bean -->
<context:component-scan
base-package="com.lucien.service"/>
<!-- mongodb -->
<mongo:mongo-client id="mongo" host="${mongo.host}" port="${mongo.port}"
credentials="${mongo.username}:${mongo.password}@${mongo.dbname}">
<mongo:client-options
connections-per-host="${mongo.connectionsPerHost}"
threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
connect-timeout="${mongo.connectTimeout}"
max-wait-time="${mongo.maxWaitTime}"
socket-keep-alive="${mongo.socketKeepAlive}"
socket-timeout="${mongo.socketTimeout}"
/>
</mongo:mongo-client>
<!-- 设置使用的数据库名-->
<mongo:db-factory id="mongoDbFactory" dbname="${mongo.dbname}" mongo-ref="mongo"/>
<!-- 去掉_class字段配置 -->
<bean id="mappingContext"
class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
<bean id="defaultMongoTypeMapper"
class="org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper">
<constructor-arg name="typeKey"><null/></constructor-arg>
</bean>
<bean id="mappingMongoConverter"
class="org.springframework.data.mongodb.core.convert.MappingMongoConverter">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
<constructor-arg name="mappingContext" ref="mappingContext" />
<property name="typeMapper" ref="defaultMongoTypeMapper" />
</bean>
<!-- mongodb的模板 -->
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongoDbFactory"/>
<constructor-arg ref="mappingMongoConverter"/>
</bean>
</beans>
编写实体bean
@id的注解是为了接收mongodb自动生成的id
@Indexed(expireAfterSeconds = 30)则是该条数据在30秒后自动删除,所以添加的索引,这里有个地方需要注意,数据类型必须是Date型,如果是long型,存的是时间戳,则时间到期后不会自动删除
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
@Document(collection = "log")
public class LogBean {
@Id
public String id;
public String name;
@Indexed(expireAfterSeconds = 30)
public Date createTime;
}
测试用例代码片段
@Autowired
MongoTemplate mongoTemplate;
//插入10条数据
List<LogBean> list = new ArrayList<>();
for(int i=0;i<10;i++) {
LogBean data = new LogBean();
data.name=i+"";
data.createTime = new Date(System.currentTimeMillis());
list.add(data);
}
mongoTemplate.insert(list,"log");
查询
Criteria criteria = new Criteria("name").is("3");
List<LogBean> listres = mongoTemplate.find(new Query(criteria), LogBean.class);
至此,算是把demo跑通了。