微服务之存储

引言

在微服务架构下,推荐每个服务都有自己独立的数据库、缓存、搜索等,这样做的优点是能够让服务之间的耦合度降低,同时可以让不同的服务根据不同的业务需求选择自己合适的存储方式。搜索服务可以用 Elasticsearch,日志服务可以用 Mongodb,业务数据可以用 MYSQL 。缺点就是对于事务的处理比较麻。所以我们尽量避免分布式事务,采用合理的设计。

——《spring cloud 微服务 入门、进阶与实战》

存储选型

关于数据库的选择每个公司都不太一样,说说我的选择吧。业务数据肯定是用 MYSQL,如果资金允许也可以用 Oracle;搜索服务用 Elasticsearch来构建;大数据量的基础数据,采用 Mongodb存储,缓存用 Redis 即可。一个中小型的互联网公司用这些组件基本上就足够了。微服务的好处在这里就体现出来了,每个服务都可以根据自己的业务选择最合适的存储方式。

——《spring cloud 微服务 入门、进阶与实战》

Mongodb

集成 Spring Data Mongodb

  • 在 spring boot 中集成 mongodb 只需要加入对应的 starter 就可以了,非常方便。
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
  • 配置数据源
1
2
3
4
5
6
# mongodb
spring.data.mongodb.database=test
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
#spring.data.mongodb.username=xxx
#spring.data.mongodb.password=xxx

添加数据操作

  • 准备实体对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Document 注解标识是一个文档,等同于Mysql 中的表,collection 值表示 Mongodb
* 中的集合名称,不写的话默认为实体类名
**/
@Document(collection = "article_info")
public class Article {
/**
*
*/
@Field("id")
private Long id;
/**
* 字段标识,可自定义存入数据库的 字段名(也就是 key 名)
*/
@Field("title")
private String title;

@Field("url")
private String url;

@Field("author")
private String author;

@Field("tags")
private List<String> tags;

@Field("visit_count")
private Long visitCount;

@Field("add_time")
private Date addTime;
//省略 set get 方法
}
  • 控制器 测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Autowired
MongoTemplate mongoTemplate;


@GetMapping("/batchSave")
public String batchSave() {
// 批量添加
List<Article> articles = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
Article article = new Article();
article.setTitle("MongoTemplate 的基本使用 ");
article.setAuthor("wuzhiyong");
article.setUrl("http://wu-zy.com/blog/" + i);
article.setTags(Arrays.asList("java", "mongodb", "spring"));
article.setVisitCount(0L);
article.setAddTime(new Date());
article.setId(Long.parseLong(i+""));
articles.add(article);
}
mongoTemplate.insert(articles, Article.class);
return "success";
}

运行项目访问接口:数据库已成功添加了

image-20200519113141558

修改数据操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@GetMapping("/update")
public String update() {
UpdateResult result = null;
Query query = Query.query(Criteria.where("author").is("wuzhiyong"));
Update update = Update.update("title", "MongoTemplate")
.set("visitCount", 10);
//更新 一条 数据
result = mongoTemplate.updateFirst(query, update, Article.class);

query = Query.query(Criteria.where("author").is("wuzhiyong"));
update = Update.update("title", "MongoTemplate").set("visitCount", 10);
//源码中实际上调用的方法为
//doUpdate(getCollectionName(entityClass), query, update, entityClass, false, true);
//最后面两个参数 final boolean upsert, final boolean multi
//upsert true 表示如果找数据就更新,否则 插入一条数据
//multi true 则修改所有符合条件的行,否则只修改第一条符合条件的行。
result = mongoTemplate.updateMulti(query, update, Article.class);

query = Query.query(Criteria.where("author").is("jason"));
update = Update.update("title", "MongoTemplate").set("visitCount", 10);
//有就更新 没有就插入一条
result = mongoTemplate.upsert(query, update, Article.class);

query = Query.query(Criteria.where("author").is("jason"));
update = Update.update("title", "MongoTemplate").set("money", 100);
result = mongoTemplate.updateMulti(query, update, Article.class);

query = Query.query(Criteria.where("author").is("jason"));
update = Update.update("title", "MongoTemplate")
.inc("money", 100); //算数操作 + 100
result = mongoTemplate.updateMulti(query, update, Article.class);

query = Query.query(Criteria.where("author").is("jason"));
update = Update.update("title", "MongoTemplate")
.rename("visitCount", "vc");//更新列名,
result = mongoTemplate.updateMulti(query, update, Article.class);

query = Query.query(Criteria.where("author").is("jason"));
update = Update.update("title", "MongoTemplate")
.unset("vc");//删除列 vc
result = mongoTemplate.updateMulti(query, update, Article.class);

query = Query.query(Criteria.where("author").is("wuzhiyong"));
update = Update.update("title", "MongoTemplate")
.pull("tags", "java");//删除 tags 数组里的 ‘Java’
result = mongoTemplate.updateMulti(query, update, Article.class);
return "success";
}

删除数据操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@GetMapping("/delete")
public String delete() {
DeleteResult result = null;
Query query = Query.query(Criteria.where("author").is("wuzhiyong"));
//通过 类 删除
result = mongoTemplate.remove(query, Article.class);

query = Query.query(Criteria.where("author").is("wuzhiyong"));
//通过表名 删除
result = mongoTemplate.remove(query, "article_info");

query = Query.query(Criteria.where("author").is("wuzhiyong"));
//查出数据并删除一条
Article article = mongoTemplate.findAndRemove(query, Article.class);

query = Query.query(Criteria.where("author").is("wuzhiyong"));
//查出数据并删除多条
List<Article> articles =
mongoTemplate.findAllAndRemove(query, Article.class);

//删除 表(集合)
mongoTemplate.dropCollection(Article.class);
mongoTemplate.dropCollection("article_info");
//删除库
mongoTemplate.getDb().drop();
return "success";

查询数据操作

  • mongoTemplate 查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@GetMapping("/query")
public String query() {
Query query = Query.query(Criteria.where("author").is("wuzhiyong"));
//条件查询
List<Article> articles = mongoTemplate.find(query, Article.class);

query = Query.query(Criteria.where("author").is("wuzhiyong"));
//查询一条
Article article = mongoTemplate.findOne(query, Article.class);
//查询所有
articles = mongoTemplate.findAll(Article.class);

query = Query.query(Criteria.where("author").is("wuzhiyong"));
//查询 count
long count = mongoTemplate.count(query, Article.class);
// 通过id 查询
article = mongoTemplate.findById(new ObjectId("5ec3511ea4a66f76d84a412e"), Article.class);

List<String> authors = Arrays.asList("wuzhiyong", "jason");
query = Query.query(Criteria.where("author").in(authors));
// where in 查询
articles = mongoTemplate.find(query, Article.class);

query = Query.query(Criteria.where("author")
.ne("wuzhiyong"));// 不等与
articles = mongoTemplate.find(query, Article.class);

query = Query.query(Criteria.where("visitCount")
.lt(10)); //小于
articles = mongoTemplate.find(query, Article.class);

query = Query.query(Criteria.where("visitCount")
.gt(5).lt(10));//大于5 且 小于 10
articles = mongoTemplate.find(query, Article.class);

query = Query.query(Criteria.where("author")
.regex("a"));//模糊查询 相当于 like 可传 正侧
articles = mongoTemplate.find(query, Article.class);

query = Query.query(Criteria.where("tags").size(3));// tags 数组为 3 的
articles = mongoTemplate.find(query, Article.class);

query = Query.query(Criteria.where("")
// or 逻辑条件查询。
.orOperator( Criteria.where("author").is("jason"), Criteria.where("visitCount").is(0)));
articles = mongoTemplate.find(query, Article.class);
return "success";
}
  • spring data 框架查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Repository("ArticleRepositor")
public interface ArticleRepositor extends PagingAndSortingRepository<Article, String> {
/** 分页查询*/
@Override
public Page<Article> findAll(Pageable pageable);

// 根据 author 查询
public List<Article> findByAuthor(String author);

// 根据作者和标题查询
public List<Article> findByAuthorAndTitle(String author, String title);

// 忽略参数大小写
public List<Article> findByAuthorIgnoreCase(String author);

// 忽略所有参数大小写
public List<Article> findByAuthorAndTitleAllIgnoreCase(String author, String title);

// 排序
public List<Article> findByAuthorOrderByVisitCountDesc(String author);

public List<Article> findByAuthorOrderByVisitCountAsc(String author);

// 自带排序条件
public List<Article> findByAuthor(String author, Sort sort);
}

索引

要给某个字段加索引就在字段上面加上 @Index 注解,里面可以填写对应的参数,在插入数据的时候,框架会自动根据配置的注解创建对应的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Document
@CompoundIndexes({
@CompoundIndex(name = "city_region_idx", def = "{'city': 1, 'region': 1}")
})
public class Person {
private String id;

@Indexed(unique = true)
private String name;

@Indexed(background = true)
private int age;

private String city;

private String region;
//省略 set get。。。
}
  • @CompoundIndexes 组合索引声明,内可以包含多个 @CompoundIndex
  • @CompoundIndex 组合索引
    • name 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
    • def = “{‘city’: 1, ‘region’: 1}” 定义:city与region组合, 1 表示顺序。 -1 表示倒序。
  • @Indexed 普通索引
    • unique = true 建立的索引是否唯一。指定为true创建唯一索引。默认值为 false.
    • 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 “background” 可选参数。 “background” 默认值为 false。

redis

集成配置


未完待续。。。


参考:

《spring cloud 微服务 入门、进阶与实战》

菜鸟教程

博客:https://www.yzlfxy.com/system/redis/345667.html