你有多久没有自己搭过项目了:搭建一套当下最新的Java微服务架构【持续更新中...】

255 阅读8分钟

基本信息

开发工具版本
操作系统macOS Ventura 13.2.1(Intel)
IDEAIntelliJ IDEA 2021.3.1
Mavenapache-maven-3.6.3
数据库管理工具DBeaver 22.3.5
Redis GUIMedis2

根据自己使用的IDEA版本,到maven.apache.org/docs/histor… Date**在IDEA版本之前的Maven版本下载,不然会有兼容性问题。

MacOS和Windows系统在后续配置上会有区别,文中已用💻标出请读者注意。

技术选型

微服务框架:SpringCloudAlibaba 2022.0.0.0-RC1 Spring 6.0.6

名称SpringCloudSpringCloudAlibaba
注册中心Eureka、ConsulNacos
配置中心SpringCloud ConfigNacos
网关SpringCloud ZuulSpringCloud Gateway
负载均衡RibbonLoadbalancer
熔断降级HystrixSentinel
服务调用FeignOpenFeign

SpringCloudAlibaba比SpringCloud和Dubbo具有更好的扩展性,其配套的Nacos也能简化注册中心和配置中心的维护和部署。

注册中心:Nacos 2.2.0.1

配置中心:Nacos 2.2.0.1

Nacos = Spring Cloud Eureka + Spring Cloud Config

Nacos兼具注册中心和配置中心的功能,大大降低开发成本

数据库:MySQL 8.0.32

Nacos需要使用MySQL作为数据存储,MySQL作为当今最流行的数据库,稳定性和扩展性已经得到充分验证。

缓存:Redis7.0.9 + Redisson

客户端使用Redisson,相比于Jedis和Lettuce在分布式系统中的表现,Redisson具有更好的支持,大部分情况下可以采用Jedis + Redisson或Lettuce + Redisson的组合,但是本例为了使开发者的关注点在业务逻辑上,仅使用Redisson。

消息中间件:Apache RocketMQ 5.1.0

目前业界使用的消息中间件有很多,如ActiveMQ、RabbitMQ、RocketMQ、Kafka、ZeroMQ等,还有付费的其他公司维护的中间件,这里我们只对比了互联网公司三大主流消息中间件:RabbitMQ、RocketMQ、Kafka,最终选择在数据量不大,主要功能为业务数据推送,要求可靠性高延时低的业务特点下,我们选择RocketMQ。

数据同步:Canal

搜索引擎【待更新】

集群配置【待更新】

搭建数据库

1. 下载安装MySQL

地址:dev.mysql.com/downloads/m…

选择x86架构即可,在低并发的情况下,ARM结构FS模式要比X86性能略强,但在高并发的情况下,MYSQL在X86要比ARM结构表现的更好。

2. 配置my.cnf

默认情况下mysql安装目录中不存在my.cnf文件,需要自己创建,使用以下命令可以查看mysql读取my.cnf的路径

mysql --help|grep 'my.cnf'

输出为:

order of preference, my.cnf, $MYSQL_TCP_PORT,
/etc/my.cnf /etc/mysql/my.cnf /usr/local/mysql/etc/my.cnf ~/.my.cnf

在mysql目录中创建etc文件夹,并创建my.cnf文件,文件内容如下:

# Example MySQL config file for medium systems.
#
# This is for a system with little memory (32M - 64M) where MySQL plays
# an important part, or systems up to 128M where MySQL is used together with
# other programs (such as a web server)
#
# MySQL programs look for option files in a set of
# locations which depend on the deployment platform.
# You can copy this option file to one of those
# locations. For information about these locations, see:
# http://dev.mysql.com/doc/mysql/en/option-files.html
#
# In this file, you can use all long options that a program supports.
# If you want to know which options a program supports, run the program
# with the "--help" option.
# The following options will be passed to all MySQL clients
[client]
default-character-set=utf8
#password = your_password
port = 3306
socket = /tmp/mysql.sock
# Here follows entries for some specific programs
# The MySQL server
[mysqld]
character-set-server=utf8
init_connect='SET NAMES utf8
port = 3306
socket = /tmp/mysql.sock
skip-external-locking
key_buffer_size = 16M
max_allowed_packet = 1M
table_open_cache = 64
sort_buffer_size = 512K
net_buffer_length = 8K
read_buffer_size = 256K
read_rnd_buffer_size = 512K
myisam_sort_buffer_size = 8M
character-set-server=utf8
init_connect='SET NAMES utf8'
# Don't listen on a TCP/IP port at all. This can be a security enhancement,
# if all processes that need to connect to mysqld run on the same host.
# All interaction with mysqld must be made via Unix sockets or named pipes.
# Note that using this option without enabling named pipes on Windows
# (via the "enable-named-pipe" option) will render mysqld useless!
#
#skip-networking

# Replication Master Server (default)
# binary logging is required for replication
log-bin=mysql-bin

# binary logging format - mixed recommended
binlog_format=mixed

# required unique id between 1 and 2^32 - 1
# defaults to 1 if master-host is not set
# but will not function as a master if omitted
server-id = 1

# Replication Slave (comment out master section to use this)
#
# To configure this host as a replication slave, you can choose between
# two methods :
#
# 1) Use the CHANGE MASTER TO command (fully described in our manual) -
# the syntax is:
#
# CHANGE MASTER TO MASTER_HOST=<host>, MASTER_PORT=<port>,
# MASTER_USER=<user>, MASTER_PASSWORD=<password> ;
#
# where you replace <host>, <user>, <password> by quoted strings and
# <port> by the master's port number (3306 by default).
#
# Example:
#
# CHANGE MASTER TO MASTER_HOST='125.564.12.1', MASTER_PORT=3306,
# MASTER_USER='joe', MASTER_PASSWORD='secret';
#
# OR
#
# 2) Set the variables below. However, in case you choose this method, then
# start replication for the first time (even unsuccessfully, for example
# if you mistyped the password in master-password and the slave fails to
# connect), the slave will create a master.info file, and any later
# change in this file to the variables' values below will be ignored and
# overridden by the content of the master.info file, unless you shutdown
# the slave server, delete master.info and restart the slaver server.
# For that reason, you may want to leave the lines below untouched
# (commented) and instead use CHANGE MASTER TO (see above)
#
# required unique id between 2 and 2^32 - 1
# (and different from the master)
# defaults to 2 if master-host is set
# but will not function as a slave if omitted
#server-id = 2
#
# The replication master for this slave - required
#master-host = <hostname>
#
# The username the slave will use for authentication when connecting
# to the master - required
#master-user = <username>
#
# The password the slave will authenticate with when connecting to
# the master - required
#master-password = <password>
#
# The port the master is listening on.
# optional - defaults to 3306
#master-port = <port>
#
# binary logging - not required for slaves, but recommended
#log-bin=mysql-bin

# Uncomment the following if you are using InnoDB tables
#innodb_data_home_dir = /usr/local/mysql/data
#innodb_data_file_path = ibdata1:10M:autoextend
#innodb_log_group_home_dir = /usr/local/mysql/data
# You can set .._buffer_pool_size up to 50 - 80 %
# of RAM but beware of setting memory usage too high
#innodb_buffer_pool_size = 16M
#innodb_additional_mem_pool_size = 2M
# Set .._log_file_size to 25 % of buffer pool size
#innodb_log_file_size = 5M
#innodb_log_buffer_size = 8M
#innodb_flush_log_at_trx_commit = 1
#innodb_lock_wait_timeout = 50

[mysqldump]
quick
max_allowed_packet = 16M

[mysql]
no-auto-rehash
# Remove the next comment character if you are not familiar with SQL
#safe-updates
default-character-set=utf8

[myisamchk]
key_buffer_size = 20M
sort_buffer_size = 20M
read_buffer = 2M
write_buffer = 2M

[mysqlhotcopy]
interactive-timeout

3. 启动MySQL

# 启动命令
sudo /usr/local/mysql/support-files/mysql.server start

# 查看状态
sudo /usr/local/mysql/support-files/mysql.server status

如果查看状态输出

SUCCESS! MySQL running (28921)

接下来需要配置MySQL允许远程连接

mysql -uroot -p

输入密码

use mysql;

查询host输入:

select user,host from user;

创建host(如果有"%"这个host值,则跳过这一步)

如果没有"%"这个host值,就执行下面这两句:

update user set host='%' where user='root';
flush privileges;

4. DBeaver连接

DBeaver下载地址:dbeaver.io/download/

新建连接选择MySQL,并配置本地服务器地址、端口号、用户名、密码、本地客户端,如果没有驱动点击编辑驱动设置可以自动下载驱动

搭建Nacos

1. 下载Nacos

下载地址:github.com/alibaba/nac…

选择nacos-server-2.2.0.1.tar.gz下载解压即可

2. 配置Nacos

打开conf/application.properties,修改以下内容

#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
### 这里需要设置为自己数据库的信息
db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
### 数据库的用户名
db.user.0=root
### 数据库的密码
db.password.0=12345678

### Connection pool configuration: hikariCP
db.pool.config.connectionTimeout=30000
db.pool.config.validationTimeout=10000
db.pool.config.maximumPoolSize=20
db.pool.config.minimumIdle=2

以下配置为鉴权信息,为了安全,建议配置

### If turn on auth system:
nacos.core.auth.enabled=true

### The default token (Base64 String):
nacos.core.auth.plugin.nacos.token.secret.key=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg

下面首先在DBeaver创建nacos_config数据库,然后进入nacos/conf文件夹,在DBeaver中执行文件夹中的mysql-schema.sql文件,执行成功后数据库结构如下:

3. 启动Nacos

执行命令

sh startup.sh -m standalone

可以使用

tail -f ~/nacos/nacos/logs/start.out

查看启动日志

打印出

2023-03-03 10:00:26,962 INFO Nacos started successfully in stand alone mode. use external storage

说明启动成功

浏览器输入localhost:8848/nacos,可以进入可视化管理页面,账号密码可以从user表查看,默认是nacos/nacos

SpringCloudAlibaba注册Nacos及使用Nacos Config

1. 搭建父子工程

一般情况下项目分为三级:总项目、父工程、子模块

总项目:通用版本管理,统一打包

父工程:微服务最小部署工程,引入所有子模块需要的包

子模块:微服务下模块,用以区分同业务下不同层级

IDEA新建Maven模块如下:tank-forum为总项目、tank-forum-common和tank-forum-user为父工程、tank-forum-user-api为子模块

tank-forum的pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.hanqing</groupId>
    <artifactId>tank-forum</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>tank-forum-user</module>
        <module>tank-forum-common</module>
    </modules>
    <!-- 通用版本标签 -->
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.boot.version>3.0.3</spring.boot.version>
        <spring.cloud.alibaba.version>2022.0.0.0-RC1</spring.cloud.alibaba.version>
        <spring.cloud.version>2022.0.1</spring.cloud.version>
        <mybatis.plus.version>3.5.3.1</mybatis.plus.version>
        <mysql.version>8.0.32</mysql.version>
        <jackson.version>2.14.2</jackson.version>
        <lombok.version>1.18.26</lombok.version>
    </properties>

    <!-- 引用依赖声明 -->
    <dependencyManagement>
        <dependencies>
            <!-- spring-boot相关依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--  spring-cloud相关依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--  spring-cloud-alibaba相关依赖 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring.cloud.alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- lombok依赖 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

tank-forum-user的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>tank-forum-user</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <parent>
        <artifactId>tank-forum</artifactId>
        <groupId>org.hanqing</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modules>
        <module>tank-forum-user-api</module>
    </modules>
    <dependencies>
        <!-- spring-boot-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- spring-boot-jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>

        <!-- nacos 注册发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- nacos 配置中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis.plus.version}</version>
        </dependency>
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

tank-forum-user-api的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <artifactId>tank-forum-user</artifactId>
        <groupId>org.hanqing</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>tank-forum-user-api</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hanqing</groupId>
            <artifactId>tank-forum-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

2. yml配置文件

在子模块的resources创建bootstrap.yml

server:
  port: 9090
spring:
  profiles:
    active: dev
  application:
    name: tank-forum-user
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        namespace: fc5caf99-7886-4250-bfd2-ee24f44fec0d
        group: user
      config:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        file-extension: yaml
        namespace: fc5caf99-7886-4250-bfd2-ee24f44fec0d
        group: user

3. Nacos创建命名空间、Config配置

打开Nacos → 命名空间 → 新建命名空间,输入命名空间名,会自动生成命名空间ID,对应的就是yml里的namespace

打开Nacos → 配置管理 → 配置列表,选择命名空间,创建配置,Data ID要严格遵照规则,Nacos服务匹配的时候会按照spring.application.name−{file-extension}.${profiles.active}来匹配文件,如果在dev里没有找到,则会匹配spring.application.name−{file-extension}。

4. 编写启动类和Controller测试类

启动类 TankForumUserApplication.java

package com.hanqing.tank.forum;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class TankForumUserApplication {
    public static void main(String[] args) {
        SpringApplication.run(TankForumUserApplication.class, args);
    }
}

测试Controller UserController.java

package com.hanqing.tank.forum.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RefreshScope
@RequestMapping("/user")
public class UserController {
    @Value("${user.name}")
    private String name;

    @GetMapping("getName")
    public String getUserName(){
        return "获取到Name:" + name;
    }
}

5. 启动测试

启动TankForumUserApplication.java,会打印出Nacos监听的相关信息

打开服务详情,浏览器输入192.168.234.38:9090/user/getName,打印出结果

SpringCloud集成MySQL和Mybatis-Plus

1. 添加yml配置

spring:
    datasource:
      # mysql8要使用cj.jdbc.Driver
      driver-class-name: com.mysql.cj.jdbc.Driver
      # 对应数据库地址,注意要加时区
      url: jdbc:mysql://localhost/tank_forum?serverTimezone=UTC%2B8&characterEncoding=UTF-8&allowMultiQueries=true
      username: root
      password: 123456
      
mybatis-plus:
  mapper-locations: classpath:mapper/**/*Mapper.xml
  global-config:
    db-config:
      # 主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID";
      id-type: assign_id
      # 默认数据库表下划线命名
      table-underline: true
  configuration:
    # 返回类型为Map,显示null对应的字段
    call-setters-on-nulls: true
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2. 添加数据库表数据

-- tank_forum.forum_user definition

CREATE TABLE `forum_user` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;

INSERT INTO tank_forum.forum_user
(id, name, age)
VALUES(1, '王二', 68);

3. 创建Service、Mapper、Domin结构

建议使用MybatisX自动生成,使用方法参考MybatisX快速开发插件,生成的相关结构如下:

User.java

package com.hanqing.tank.forum.domin;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("forum_user")
public class User {
    /** 主键 */
    @TableId
    private Long userId;

    /** 用户名称 */
    @TableField("name")
    private String userName;

    /** 年龄 */
    @TableField("age")
    private Integer userAge;
}

UserMapper.java

package com.hanqing.tank.forum.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hanqing.tank.forum.domin.User;
import java.util.List;

public interface UserMapper extends BaseMapper<User> {
    /** 自定义的查询 */
    List<User> selectTestFromUser();
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hanqing.tank.forum.mapper.UserMapper">
    <resultMap id="BaseResultMap" type="com.hanqing.tank.forum.domin.User">
        <id property="userId" column="id" jdbcType="BIGINT"/>
        <result property="userName" column="name" jdbcType="VARCHAR"/>
        <result property="userAge" column="age" jdbcType="INTEGER"/>
    </resultMap>
    <sql id="Base_Column_List">
        id,name,age
    </sql>
    <select id="selectTestFromUser" resultMap="BaseResultMap">
        SELECT
        <include refid="Base_Column_List"/>
        from forum_user
    </select>
</mapper>

UserService.java

package com.hanqing.tank.forum.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.hanqing.tank.forum.domin.User;
import java.util.List;

public interface UserService extends IService<User> {
    List<User> selectTestData();
}

UserServiceImpl.java

package com.hanqing.tank.forum.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hanqing.tank.forum.domin.User;
import com.hanqing.tank.forum.mapper.UserMapper;
import com.hanqing.tank.forum.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired
    UserMapper mapper;

    @Override
    public List<User> selectTestData() {
        return mapper.selectTestFromUser();
    }
}

改造UserController.java

package com.hanqing.tank.forum.controller;

import com.hanqing.tank.forum.service.UserService;
import com.hanqing.tank.forum.domin.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
@RefreshScope
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserService userService;

    @GetMapping("getName")
    public String getUserName() {
        List<User> userList = userService.selectTestData();
        return "获取到User:" + userList.toString();
    }
}

4. 启动测试

重启TankForumUserApplication.java,浏览器输入192.168.234.38:9090/user/getName,打印出结果

部署Redis + Redisson

1. 下载安装Redis

redis.io/download/,下…

2. 可视化Redis管理工具(💻Mac)

目前Mac系统使用率最高的是Medis2,Windows管理工具很多,可根据喜好下载

下载后解压可直接使用,连接默认本地Redis,可自动生成命令,个人学习使用免费版即可,30美元可升级付费版。

3. 添加Redisson集成SpringBoot依赖

在总工程tank-forum的pom中添加

<redisson.version>3.20.0</redisson.version>

<!-- redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>${redisson.version}</version>
</dependency>

bootstrap.yml里添加

这里其实还有很多配置项,但是为了简单就只配置了最基本的,并且host和port都是默认值

spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379

4. 使用Redisson测试

UserController.java里添加RedissonClient和测试方法:

@Autowired
RedissonClient redissonClient;

@GetMapping("testRedisson")
public String testRedisson() {
    // 此处一定要指定 new StringCodec()编码格式,不然Medis会有乱码的问题
    RBucket<Object> bucket = redissonClient.getBucket("USER_CACHE", new StringCodec());
    bucket.set("我是缓存的用户数据");
    return "存放用户缓存:" + bucket.get();
}

浏览器输入192.168.234.38:9090/user/testRedisson,并在Medis2里可以看到存放的数据

一般情况下,我们不会直接操作RedissonClient,而是在外直接封装一个RedisUtil来操作,并实现一些常用的Redis底层的功能,但是这相当于把Redisson对Redis的封装又拆开了。这也是为什么实际开发中一般会采用Jedis + Redisson的原因。所以实际开发中,建议Jedis进行Redis基础功能的操作,Redisson专注分布式环境下的一些操作,如各种锁等。

另外关于Redisson编码的问题,自3.19.0之后,Redisson默认编解码器变更为了org.redisson.codec.Kryo5Codec,编码格式不是UTF-8,这导致一些没有内置此编码器的可视化Redis工具看到的数据乱码。一般情况下我们不需要修改默认的编码格式,也是为了安全和稳定着想,本例中强制指定了用org.redisson.client.codec.StringCodec解码器,默认UTF-8存储,其实是不安全的一种做法,只是为了方便用Medis展示数据而已。

部署RocektMQ

1. 启动NameServer

下载地址:rocketmq.apache.org/zh/download 在无二次开发需求的情况下,直接下载Binary二进制文件即可。

解压后,找到解压包中bin目录下的mqnamesrv,双击启动,或使用命令行启动,显示The Name Server boot success.说明启动成功。

image.png

image.png

2. 启动Broker+Proxy

官网建议5.x版本的RocketMQ采用Broker和Proxy同进程的部署方式,所以这里我们使用命令行启动,可以指定启动方式。

如图,在bin目录输入命令:

sh mqbroker -n localhost:9876 --enable-proxy &

image.png

可以在~/logs/rocketmqlogs/proxy.log中查询日志,日过看到“The broker[brokerName,ip:port] boot success..”,这表明 broker 已成功启动。

3. RocketMQ可视化

RocketMQ的可视化工具原名rocketmq-console,后改名为 RocketMQ Dashboard 本质上是一个SpringBoot项目,配置好对应的RocketMQ之后启动即可使用。

下载地址:github.com/apache/rock… 将项目下载后在IDEA中打开,配置RocketMQ对应的地址和端口(默认都是127.0.0.1:9876),同时修改server:port:8082

运行App.java,启动成功后在浏览器输入127.0.0.1:8082,即可打开RocketMQ仪表盘,右上角可以转换成中文。

启动方式后续可以直接使用 java -jar,这里不做赘述,具体可以参考其中的README.md

image.png

image.png

4. 创建Topic

可以使用mqadmin命令创建,因为这里我们搭建了RocketMQ仪表盘,所以直接使用界面创建。

如果正常启动了NameServer、Broker+Proxy,默认集群名和BROKER名如图所示。这里我们创建一个用来进行用户修改密码通知的Topic。

image.png

点击提交,创建成功。

image.png

5. JavaSDK测试RocketMQ消息收发

5.1 引入jar包

在tank-forum的pom.xml里引入

<rocketmq.version>5.0.4</rocketmq.version>
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client-java</artifactId>
    <version>${rocketmq.version}</version>
</dependency> 

tank-forum-common的pom.xml里引入

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client-java</artifactId>
</dependency>

5.2 编写Producer生产者

在tank-forum-common中新建RocketMQUtil.java

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientConfigurationBuilder;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.message.Message;
import org.apache.rocketmq.client.apis.producer.Producer;
import org.apache.rocketmq.client.apis.producer.SendReceipt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RocketMQUtil {
    private static final Logger logger = LoggerFactory.getLogger(RocketMQUtil.class);

    public static void sendSimpleMsg(JSONObject msgJson, String topic) throws ClientException {
        // 接入点地址,需要设置成Proxy的地址和端口列表,一般是xxx:8081;xxx:8081。
        String endpoint = "127.0.0.1:8081";
        // 消息发送的目标Topic名称,需要提前创建。
        ClientServiceProvider provider = ClientServiceProvider.loadService();
        ClientConfigurationBuilder builder = ClientConfiguration.newBuilder().setEndpoints(endpoint);
        ClientConfiguration configuration = builder.build();
        // 初始化Producer时需要设置通信配置以及预绑定的Topic。
        Producer producer = provider.newProducerBuilder()
                .setTopics(topic)
                .setClientConfiguration(configuration)
                .build();
        // 普通消息发送。
        Message message = provider.newMessageBuilder()
                .setTopic(topic)
                // 设置消息索引键,可根据关键字精确查找某条消息。
                .setKeys("messageKey")
                // 设置消息Tag,用于消费端根据指定Tag过滤消息。
                .setTag("messageTag")
                // 消息体。
                .setBody(JSON.toJSONBytes(msgJson))
                .build();
        try {
            // 发送消息,需要关注发送结果,并捕获失败等异常。
            SendReceipt sendReceipt = producer.send(message);
            logger.info("Send message successfully, messageId={}", sendReceipt.getMessageId());
        } catch (ClientException e) {
            logger.error("Failed to send message", e);
        }
        // producer.close();
    }
}

在tank-forum-user-api中的UserController中新增方法

@GetMapping("resetPassword")
public String resetPassword() throws ClientException {
    String topic = "user_reset_password_topic";
    JSONObject json = new JSONObject();
    json.put("password", "123");
    RocketMQUtil.sendSimpleMsg(json, topic);
    return "修改密码成功";
}

浏览器访问192.168.12.38:9090/user/resetPassword,可以看到控制台打出发送成功的消息

image.png

同时我们打开RocketMQ仪表盘,点开消息一列,可以看到我们发送的消息,点开详情可以看到具体内容。

image.png

image.png

注:Apache RocketMQ 服务端5.x版本开始,生产者是匿名的,无需管理生产者分组(ProducerGroup);对于历史版本服务端3.x和4.x版本,已经使用的生产者分组可以废弃无需再设置,且不会对当前业务产生影响。

5.3 编写Consumer消费者

Apache RocketMQ 支持SimpleConsumer和PushConsumer两种消费者类型,SimpleConsumer属于主动拉取,适用于自定义业务流程,PushConsumer为回调推送,适用于被动通知,我们这里采用PushConsumer的方式进行消费,具体区别请参考官方文档:rocketmq.apache.org/zh/docs/fea…

首先要创建ConsumerGroup,用于管理消费组,如图创建名为user_reset_password_group的消费组。 image.png

然后在tank-forum-user中新建tank-forum-user-consumer子模块,引入jar包,用于用户相关消息的消费,编写UserConsumer.java

import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
import org.apache.rocketmq.client.apis.consumer.FilterExpression;
import org.apache.rocketmq.client.apis.consumer.FilterExpressionType;
import org.apache.rocketmq.client.apis.consumer.PushConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;

public class UserConsumer {
    private static final Logger logger = LoggerFactory.getLogger(UserConsumer.class);

    private UserConsumer() {
    }

    public static void main(String[] args) throws ClientException, IOException, InterruptedException {
        final ClientServiceProvider provider = ClientServiceProvider.loadService();
        // 接入点地址,需要设置成Proxy的地址和端口列表,一般是xxx:8081;xxx:8081。
        String endpoints = "127.0.0.1:8081";
        ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder()
                .setEndpoints(endpoints)
                .build();
        // 订阅消息的过滤规则,表示订阅所有Tag的消息。
        String tag = "*";
        FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);
        // 为消费者指定所属的消费者分组,Group需要提前创建。
        String consumerGroup = "user_reset_password_group";
        // 指定需要订阅哪个目标Topic,Topic需要提前创建。
        String topic = "user_reset_password_topic";
        // 初始化PushConsumer,需要绑定消费者分组ConsumerGroup、通信参数以及订阅关系。
        PushConsumer pushConsumer = provider.newPushConsumerBuilder()
                .setClientConfiguration(clientConfiguration)
                // 设置消费者分组。
                .setConsumerGroup(consumerGroup)
                // 设置预绑定的订阅关系。
                .setSubscriptionExpressions(Collections.singletonMap(topic, filterExpression))
                // 设置消费监听器。
                .setMessageListener(messageView -> {
                    // 处理消息并返回消费结果。
                    logger.info("Consume message successfully, messageId={}", messageView.getMessageId());
                    String str = StandardCharsets.UTF_8.decode(messageView.getBody()).toString();
                    logger.info("读取到的消息内容:{}", str);
                    return ConsumeResult.SUCCESS;
                })
                .build();
        Thread.sleep(Long.MAX_VALUE);
        // 如果不需要再使用 PushConsumer,可关闭该实例。
        // pushConsumer.close();
    }
}

启动main方法进行监听,可以看到控制台打出之前发送的消息 image.png

6. SpringCloudAlibaba集成RocketMQ

源码地址

gitee.com/hannaseyes/…