对于想了解SpringCloud.H+Nacos1.4.1+分布式事物Seata1.4.1的读者,本文将提供新的信息,我们将详细介绍29【详细版】,并且为您提供关于(七十七)springcloud+s
对于想了解SpringCloud.H + Nacos1.4.1 + 分布式事物Seata1.4.1的读者,本文将提供新的信息,我们将详细介绍29【详细版】,并且为您提供关于(七十七) springcloud+springcloud+vue+uniapp分布式微服务电商 商城之Spring Cloud集成Spring Data Redis、(四十五) springcloud 分布式微服务电商 商城之跟我学习 SpringCloud-Smconf(分布式配置管理框架)概述、java springcloud 版 b2b2c 社交电商 spring cloud 分布式微服务 (七)高可用的分布式配置中心 (Spring Cloud Config)、seata环境搭建(Nacos SpringCloud)的有价值信息。
本文目录一览:- SpringCloud.H + Nacos1.4.1 + 分布式事物Seata1.4.1(29)【详细版】(springcloud 分布式事务解决方案实例)
- (七十七) springcloud+springcloud+vue+uniapp分布式微服务电商 商城之Spring Cloud集成Spring Data Redis
- (四十五) springcloud 分布式微服务电商 商城之跟我学习 SpringCloud-Smconf(分布式配置管理框架)概述
- java springcloud 版 b2b2c 社交电商 spring cloud 分布式微服务 (七)高可用的分布式配置中心 (Spring Cloud Config)
- seata环境搭建(Nacos SpringCloud)
SpringCloud.H + Nacos1.4.1 + 分布式事物Seata1.4.1(29)【详细版】(springcloud 分布式事务解决方案实例)
1、启动nacos1.4.1平台
此刻我们在nacos创建一个SEATA-TEST的命名空间,GROUP为test,为seata提供使用
2、下载seata1.4.1
点击1.4.1版本链接,选择binary
3、运行1.4.1版本sql文件(与1.5.1版本表结构有区别)
创建本地数据库seata
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for branch_table -- ---------------------------- DROP TABLE IF EXISTS `branch_table`; CREATE TABLE `branch_table` ( `branch_id` bigint NOT NULL, `xid` varchar(128) NOT NULL, `transaction_id` bigint DEFAULT NULL, `resource_group_id` varchar(32) DEFAULT NULL, `resource_id` varchar(256) DEFAULT NULL, `lock_key` varchar(128) DEFAULT NULL, `branch_type` varchar(8) DEFAULT NULL, `status` tinyint DEFAULT NULL, `client_id` varchar(64) DEFAULT NULL, `application_data` varchar(2000) DEFAULT NULL, `gmt_create` datetime DEFAULT NULL, `gmt_modified` datetime DEFAULT NULL, PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ---------------------------- -- Table structure for global_table -- ---------------------------- DROP TABLE IF EXISTS `global_table`; CREATE TABLE `global_table` ( `xid` varchar(128) NOT NULL, `transaction_id` bigint DEFAULT NULL, `status` tinyint NOT NULL, `application_id` varchar(32) DEFAULT NULL, `transaction_service_group` varchar(32) DEFAULT NULL, `transaction_name` varchar(128) DEFAULT NULL, `timeout` int DEFAULT NULL, `begin_time` bigint DEFAULT NULL, `application_data` varchar(2000) DEFAULT NULL, `gmt_create` datetime DEFAULT NULL, `gmt_modified` datetime DEFAULT NULL, PRIMARY KEY (`xid`), KEY `idx_gmt_modified_status` (`gmt_modified`,`status`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ---------------------------- -- Table structure for lock_table -- ---------------------------- DROP TABLE IF EXISTS `lock_table`; CREATE TABLE `lock_table` ( `row_key` varchar(128) NOT NULL, `xid` varchar(96) DEFAULT NULL, `transaction_id` mediumtext, `branch_id` mediumtext, `resource_id` varchar(256) DEFAULT NULL, `table_name` varchar(32) DEFAULT NULL, `pk` varchar(36) DEFAULT NULL, `gmt_create` datetime DEFAULT NULL, `gmt_modified` datetime DEFAULT NULL, PRIMARY KEY (`row_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; SET FOREIGN_KEY_CHECKS = 1;
4、修改配置文件
将下载好的包解压,此时需要修改两个文件file.conf【数据库连接配置】和registry.conf【注册中心配置】
由于我们选择的是mysql持久化,所以只需要配置mysql就好,不需要的配置可以删掉,修改完成后的file.conf如下:
store { mode = "db" publicKey = "" db { datasource = "druid" dbType = "mysql" driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true" user = "root" password = "123456" minConn = 5 maxConn = 100 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } }
同理,修改registry.conf文件内容,如下:
registry { type = "nacos" loadBalance = "RandomLoadBalance" loadBalanceVirtualNodes = 10 nacos { # 注册到nacos使用的服务名称 application = "seata-server" serverAddr = "127.0.0.1:8848" # 刚刚第一步nacos创建的分组 group = "test" # 刚刚第一步nacos 创建命名空间SEATA-TEST的id namespace = "1adf5a6a-93e3-409c-9410-e9c7ae0e0067" cluster = "default" username = "nacos" password = "nacos" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "nacos" nacos { serverAddr = "127.0.0.1:8848" namespace = "1adf5a6a-93e3-409c-9410-e9c7ae0e0067" group = "test" username = "nacos" password = "nacos" } }
5、添加脚本文件
点击1.4.1版本链接,选择source
此刻我们需要用到源码中的nacos-config.sh 和config.txt两个文件,位置如图:
这一步的作用是通过脚本nacos-config.sh把config.txt一键同步到nacos的SEATA-TEST的test分组中去,脚本文件nacos-config.sh不需要修改,而config.txt内容过于多,我们也不需要那么多的配置,需要整理一下,如同第4步一样。
service.vgroupMapping.nacos-seata-account_tx_group=default service.vgroupMapping.nacos-seata-order_tx_group=default service.vgroupMapping.nacos-seata-storage_tx_group=default service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disableGlobalTransaction=false store.mode=db store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true store.db.user=root store.db.password=123456 store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000
config.txt有两个关键点需要注意:
第一个关键点:
store.db.driverClassName=com.mysql.cj.jdbc.Driver
这个代表的是mysql8,而官方提供的是mysql5.1的驱动版本,如果你本地mysql也是8的,需要下载mysql8的驱动,然后在seata-server-1.4.2下的lib文件中,把mysql5驱动替换,如下:
接下来就是第二个关键点:
service.vgroupMapping.nacos-seata-account_tx_group=default service.vgroupMapping.nacos-seata-order_tx_group=default service.vgroupMapping.nacos-seata-storage_tx_group=default
以上这三个是我们自己定义的,每个含义为事物分组,前缀service.vgroupMapping这个是固定的,而后面nacos-seata-account_tx_group这个名称是和我们每个服务的定义一致,这样当我们每个服务在全局事物的时候,可以通过这个事物分组定位(下面会介绍,我们会搭建三个服务,account、storge、order,每个服务对应一个)
nacos-config.sh和config.txt准备好以后,我们把nacos-config.sh放到bin文件下,config放到外层即可,如图:
将 config.txt 的数据注册到 nacos 中
sh nacos-config.sh -h 127.0.0.1 -p 8848 -g test -t 1adf5a6a-93e3-409c-9410-e9c7ae0e0067 -u nacos -w nacos
解释命令:
127.0.0.1为nacos的ip
8848为nacos的端口号
test为我们为seata创建的group
1adf5a6a-93e3-409c-9410-e9c7ae0e0067为我们创建的SEATA-TEST命名空间的id
查看自己的局域网ip,然后执行命令:
通过ifconfig查看自己的局域网ip,然后让nacos注册那里显示你的ip
ifconfig
启动 seata 服务端
sh seata-server.sh -p 8091 -h 10.100.23.111(自己局域网ip)
至于为什么使用局域网ip,而不是127.0.0.1,可见该博客说得很清楚===》点击查看详情,不然业务服务会找不到seata-server服务,这一步宣告我们的seata-server搭建结束。
6、创建业务服务
根据官方demo,我们需要准备三个模块服务,分别是account、order、storge,不过这三个服务需要feign,可能要贴的代码太多,所以就先不贴了,可以去gitee上查看==>链接,分支for_seata是1.4.1,但是bootstarp.yml里面的部分配置属性可能和博客不同,需要单拿出一个来举例,其他两个一样。【更改也是我们把seata的配置的namespace、group、application和seata-server中的refistry.conf同步一下就好】
server: port: 8061 spring: application: name: nacos-seata-account profiles: active: @spring.active@ cloud: nacos: username: nacos password: nacos discovery: namespace: 358de121-4068-4f63-8492-b5692bf9fc6f group: @spring.active@ server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 namespace: 358de121-4068-4f63-8492-b5692bf9fc6f group: @spring.active@ file-extension: yaml main: allow-bean-definition-overriding: true datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/seata-account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&useSSL=false username: root password: 123456 seata: application-id: ${spring.application.name} #事务群组(可以每个应用独立取名,也可以使用相同的名字),要与服务端nacos-config.txt中service.vgroup_mapping中存在,并且要保证多个群组情况下后缀名要保持一致-tx_group service: vgroup-mapping: nacos-seata-account_tx_group: default # 必须和config.txt里面配置的属性一致 tx-service-group: nacos-seata-account_tx_group config: # 必须写nacos,否则默认file type: nacos nacos: namespace: 1adf5a6a-93e3-409c-9410-e9c7ae0e0067 username: nacos password: nacos server-addr: localhost:8848 group: test registry: # 必须写nacos,否则默认file type: nacos nacos: server-addr: 127.0.0.1:8848 username: nacos password: nacos group: test namespace: 1adf5a6a-93e3-409c-9410-e9c7ae0e0067 application: seata-server # enabled: false # enable-auto-data-source-proxy: false mybatis-plus: configuration: log-prefix: mybatis log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl type-aliases-package: com.test.seataaccount.entity mapper-locations: classpath*:/mapper/*.xml # feign组件超时设置,用于查看seata数据库中的临时数据内容 feign: client: config: default: connect-timeout: 30000 read-timeout: 30000
7、流程测试
将三个服务分别启动,走order借口调试,由于超市链接,在mysql生成的数据会撤回,结束!
http://localhost:8062/test
相关文档:
Seata1.4.2-分布式事务-Seata实战
官方新人部署指南
seata-server注册ip需为可访问ip,不能为nacos暂时ip
seata1.2视频讲解
seata搭建问题注意事项
(七十七) springcloud+springcloud+vue+uniapp分布式微服务电商 商城之Spring Cloud集成Spring Data Redis
Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。
Redis 是一个高性能的 key-value 数据库,同时支持多种存储类型,包括 String(字符串)、List(链表)、Set(集合)、Zset(sorted set——有序集合)和 Hash(哈希类型)。
用 Redistemplate 操作 Redis
在 Java 中操作 Redis 我们可以用 Jedis,也可以用 Spring Data Redis。
本节我们基于 Spring Data Redis 操作 Redis,Spring Data Redis 也是基于 Jedis 来实现的,它在 Jedis 上面封装了一层,让我们操作 Redis 更加简单。
关于在 Spring Boot 中集成 Spring Data Redis 不做过多讲解,本节主要讲怎么用 Redistemplate 操作 Redis。
Redistemplate 是一个泛型类,可以指定 Key 和 Value 的类型,我们以字符串操作来讲解,可以直接用 StringRedisTemplate 来操作。
在使用的类中直接注入 StringRedisTemplate 即可,如下代码所示。
@Autowired
private StringRedisTemplate stringRedisTemplate;
StringRedisTemplate 中提供了很多方法来操作数据,主要有以下几种:
- opsForValue:操作 Key Value 类型
- opsForHash:操作 Hash 类型
- opsForList:操作 List 类型
- opsForSet:操作 Set 类型
- opsForZSet:操作 opsForZSet 类型
下面我们以 Key Value 类型来讲解,设置一个缓存,缓存时间为 1 小时,如下代码所示。
stringRedisTemplate.opsForValue().set("key", "鸿鹄云商", 1, TimeUnit.HOURS);
获取缓存,如下代码所示。
String value = stringRedisTemplate.opsForValue().get("key");
删除缓存,如下代码所示。
stringRedisTemplate.delete("key");
判断一个 key 是否存在,如下代码所示。
boolean exists = stringRedisTemplate.hasKey("key");
如果你不喜欢用这些封装好的方法,想要用最底层的方法来操作也是可以的。通过 StringRedisTemplate 可以拿到 RedisConnection,如下代码所示。
RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection();
用 Repository 操作 Redis
凡是 Spring Data 系列的框架,都是一种风格,我们都可以用 Repository 方式来操作数据。下面我们看下怎么使用 Repository。
定义一个数据存储的实体类,@Id 类似于数据库中的主键,能够自动生成,RedisHash 是 Hash 的名称,相当于数据库的表名,如下代码所示。
@Data
@RedisHash("persons")
public class Person {
@Id
String id;
String firstname;
String lastname;
}
定义 Repository 接口,代码如下所示。
public interface PersonRepository extends CrudRepository<Person, String> {
}
使用接口对数据进行增删改查操作,代码如下所示。
@Autowired
PersonRepository repo;
public void basicCrudOperations() {
Person person = new Person("张三", "zhangsan");
repo.save(person);
repo.findOne(person.getId());
repo.count();
repo.delete(person);
}
数据保存到 Redis 中会变成两部分,一部分是一个 set,里面存储的是所有数据的 ID 值,另一部分是一个 Hash,存储的是具体每条数据。
Spring Cache缓存数据
一般的缓存逻辑都是下面代码这样的方式,首先判断缓存中是否有数据,有就获取数据返回,没有就从数据库中查数据,然后缓存进去,再返回。
public Person get(String id) {
Person person = repo.findOne(id);
if (person != null) {
return person;
}
person = dao.findById(id);
repo.save(person);
return person;
}
首先这种方式在逻辑上是肯定没有问题的,大部分人也都是这么用的,不过当这种代码充满整个项目的时候,看起来就非常别扭了,感觉有点多余,不过通过 Spring Cache 就能解决这个问题。我们不需要关心缓存的逻辑,只需要关注从数据库中查询数据,将缓存的逻辑交给框架来实现。
Spring Cache 利用注解方式来实现数据的缓存,还具备相当的灵活性,能够使用 SpEL(Spring Expression Language)来定义缓存的 key,还能定义多种条件判断。
Spring Cache 的注解定义在 spring-context 包中,如图 1 所示。
常用的注解有 @Cacheable、@CachePut、@CacheEvict。
- @Cacheable:用于查询的时候缓存数据。
- @CachePut:用于对数据修改的时候修改缓存中的数据。
- @CacheEvict:用于对数据删除的时候清除缓存中的数据。
首先我们配置一下 Redistemplate,设置下序列化方式为 JSON,这样存在于 Redis 中的数据查看起来就比较方便了,如下代码所示。
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.afterPropertiesSet();
setSerializer(redisTemplate);
return redisTemplate;
}
private void setSerializer(RedisTemplate<String, String> template) {
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(jackson2JsonRedisSerializer);
}
}
除了 Json 序列化,还有很多其他的序列化方式,读者可以根据自己的需求来设置,序列化的类在 Spring-Data-Redis 包中,如图 2 所示。
除了配置序列化方式,我们还可以配置 CacheManager 来设置缓存的过期时间,代码如下所示。
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1)).disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
}
还可以配置缓存 Key 的自动生成方式,这里是用类名+方法名+参数来生成缓存的 Key,只有这样才能让 Key 具有唯一性,当然你也可以使用默认的 org.springframework.cache.interceptor.SimpleKeyGenerator,代码如下所示。
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(":" + method.getName());
for (Object obj : params) {
sb.append(":" + obj.toString());
}
return sb.toString();
}
};
}
接下来,我们改造一下上面定义的get方法,用注解的方式来使用缓存,具体代码如下所示。
@Cacheable(value="get", key="#id")
public Person get(String id) {
return findById(id);
}
@Cacheable 中的 value 我们可以定义成与方法名称一样。标识这个方法的缓存 key,会在 Redis 中存储一个 Zset,Zset 的 key 就是我们定义的 value 的值,Zset 中会存储具体的每个缓存的 key,也就是当调用 get("1001") 的时候,Zset 中就会存储 1001,同时 Redis 中会有一个单独的 String 类型的数据,Key 是 1001。
#id 是 SpEL 的语法,通过参数名来定义缓存 key,上面的 key 如果直接用参数 id 来定义的话会出问题的,比如当另一个缓存方法的参数也是 1001 的时候,这个 key 就会冲突。所以我们最好还是定义一个唯一的前缀,然后再加上参数,这样就不会有冲突了,比如:key="''get''+#id"。
除了用 SpEL 来自定义key,我们刚刚其实配置了一个 keyGenerator,它就是用来生成 key 的,使用方法代码如下所示。
@Cacheable(value = "get", keyGenerator = "keyGenerator")
public Person get(String id) {
return findById(id);
}
指定 keyGenerator 后,就可以不用配置 key,通过 keyGenerator 会自动生成缓存的 key,生成的规则就是我们配置中自己定义的,我们可以看到使用我们自定义的 keyGenerator 之后,存储在 Redis 中的数据的 key 是很长的一个字符串,规则也就是我们自定义的类名+方法名+参数。
@CachePut、@CacheEvict 的用法和 @Cacheable 一样,本节不做演示。
缓存异常处理
在用注解进行自动缓存的过程中,包括 Redis 的链接都是框架自动完成的,在缓存过程中如果 Redis 连接不上出现了异常,这时候整个请求都将失败。
缓存是一种辅助的手段,就算不能用,也不能影响正常的业务逻辑,如果我们直接用 Redis 的连接或者 Redistemplate 来操作的话可以通过异常捕获来解决这个问题,在用注解进行自动缓存的时候我们需要定义异常处理类来对异常进行处理。
@Configuration
public class CacheAutoConfiguration extends CachingConfigurerSupport {
private Logger logger = LoggerFactory.getLogger(CacheAutoConfiguration.class);
/**
* redis 数据操作异常处理, 这里的处理:在日志中打印出错误信息, 但是放行
* 保证 redis 服务器出现连接等问题的时候不影响程序的正常运行, 使得能够出问题时不用缓存 , 继续执行业务逻辑去查询 DB
*
* @return
*/
@Bean
public CacheErrorHandler errorHandler() {
CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key, Object value) {
logger.error("redis 异常:key=[{}]", key, e);
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
logger.error("redis 异常:key=[{}]", key, e);
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
logger.error("redis 异常:", e);
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
logger.error("redis 异常:", e);
};
return cacheErrorHandler;
}
}
}
通过上面的处理,即使 Redis 挂掉了,程序连接不上时也不会影响业务功能,而是会继续执行查询数据库的操作。
自定义缓存工具类
在上面代码中,我们首先判断缓存中是否有数据,如果没有就从数据库中查询,然后再缓存进行,这样操作比较麻烦,还得写判断。使用注解的方式相对来说就简单多了,如果你不想用注解的方式,还是想在代码层面自己做缓存控制,那么我们可以自己封装一个工具类来避免写大量的判断操作。笔者以字符串操作为例,不涉及反序列化为对象的封装,封装缓存的基本操作,读者可以自行扩展。
首先定义一个缓存操作的接口,提供获取缓存,删除缓存操作,具体代码如下所示。
public interface CacheService {
/**
* 设置缓存
*
* @param key 缓存 KEY
* @param value 缓存值
* @param timeout 缓存过期时间
* @param timeUnit 缓存过期时间单位
*/
public void setCache(String key, String value, long timeout, TimeUnit timeUnit);
/**
* 获取缓存
*
* @param key 缓存KEY
* @return
*/
public String getCache(String key);
public <V, K> String getCache(K key, Closure<V, K> closure);
public <V, K> String getCache(K key, Closure<V, K> closure, long timeout, TimeUnit timeUnit);
/**
* 删除缓存
*
* @param key缓存KEY
*/
public void deleteCache(String key);
}
实现类代码如下所示。
@Service
public class CacheServiceImpl implements CacheService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private long timeout = 1L;
private TimeUnit timeUnit = TimeUnit.HOURS;
@Override
public void setCache(String key, String value, long timeout, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
@Override
public String getCache(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
@Override
public void deleteCache(String key) {
stringRedisTemplate.delete(key);
}
@Override
public <V, K> String getCache(K key, Closure<V, K> closure) {
return doGetCache(key, closure, this.timeout, this.timeUnit);
}
@Override
public <V, K> String getCache(K key, Closure<V, K> closure, long timeout, TimeUnit timeUnit) {
return doGetCache(key, closure, timeout, timeUnit);
}
private <K, V> String doGetCache(K key, Closure<V, K> closure, long timeout, TimeUnit timeUnit) {
String ret = getCache(key.toString());
if (ret == null) {
Object r = closure.execute(key);
setCache(key.toString(), r.toString(), timeout, timeUnit);
return r.toString();
}
return ret;
}
}
定义一个方法回调的接口,用于执行回调的业务逻辑。代码如下所示。
public interface Closure<O, I> {
public O execute(I input);
}
下面我们对这个缓存操作类的代码进行讲解。简单的 set 和 get 缓存不做过多讲解,因为和直接使用 RedisTemplate 没什么区别,本节主要讲的是基于方法的回调实现缓存。
我们的目的也很简单,就是先去判断缓存中是否存在,不存在的话则从数据库查询,然后将插入到缓存中的逻辑统一,不用每个方法中都去写这个判断的逻辑。
方法的回调实现是基于 Closure 接口来做的,使用方法代码如下所示。
@Autowired
private CacheService cacheService;
public String get() {
String cacheKey = "1001";
return cacheService.getCache(cacheKey, new Closure<String, String>() {
@Override
public String execute(String id) {
// 执行你的业务逻辑
return userService.getById(id);
}
});
}
通过这样的封装,我们就不用在每个缓存的地方都去判断了,当缓存中有值的时候,getCache 方法会根据缓存的 key 去缓存中获取,然后返回;如果没有值的话会执行 execute 中的逻辑获取数据,然后缓存起来再返回。
推荐电子商务源码
(四十五) springcloud 分布式微服务电商 商城之跟我学习 SpringCloud-Smconf(分布式配置管理框架)概述
Smconf 专注于分布式环境下的配置的统一管理。采用 Java+Zookeeper+Mongodb+Spring Boot 开发。目前只支持 Java,其他的使用语言需要通过调用 REST API 来实现。
每个技术人都有一个开源的梦想,那就是自己也能开发出一个让很多人使用的框架。其实分享使用不是重点,重点是自己写的架构本身,你对它的各个方面都了如指掌,可以很方便添加新功能,比如加上一些适应公司内部需求的功能。
笔者之所以抛弃了 SpringCloud Config,一方面在于它的配置刷新这块不是很方便,需要集成消息总线加上 WebHook 才能完成。另一个原因就是一些特殊的需求实现起来没那么方便,比如推送配置到指定的节点。
Smconf 目前支持的功能如下:
- 提供配置的统一管理。
- 多个环境(生产环境为 prod,线上测试环境为 online,线下测试环境为 test,开发环境为 dev)。
- Web 后台配置管理。
- 配置修改后实时同步到使用的客户端。
- 无缝集成 Spring 和 Spring Boot 项目。
- 非 Spring 项目中也可以使用。
- Web 后台支持不同账号管理不同环境的配置。
- 支持水平扩容、负载,部署多个 Server、Client 自动发现。
- 支持配置更新回调接口做扩展。
- 支持手动触发推送配置到指定的节点。
- 修改配置可以选择推送的节点,可用于做灰度发布测试。
- 配置的历史修改记录。
Smconf 工作原理
如图 1 所示为 Smconf 的整个架构规划,Smconf 同样也分为服务端和客户端两个部分,服务端负责配置信息的管理,客户端负责拉取配置信息及上传配置信息。
1. 基本概念
在正式介绍 Smconf 的工作原理之前,我们先来看几个基本概念。
- 环境:在 Smconf 中总共分了 4 个环境,分别是生产环境(prod)、线上测试环境(online)、线下测试环境(test)、开发环境(dev),Smconf Client 会根据不同的环境加载不同的配置。
- 系统:这里的系统就是指服务的名称,能够直观反映这个配置是哪个服务在使用。
- 配置文件:一个系统中有多个配置文件,可以将同类的配置放到一个文件中,也可以只用一个配置文件,配置文件需要定义一个实体类。
- 配置 key:一个配置文件中有多个配置项,实体类的字段名称就是配置的 key,字段的值就是配置的 value。
2. 客户端讲解
客户端在启动的时候会将自身的信息注册到 Zookeeper 中,为每一个配置文件注册一个节点,添加 watcher 监听,同时获取 Server 的节点信息,然后连接 Smconf Server 初始化配置数据或者拉取最新的配置到本地。
配置信息只有在配置中心不存在时才会初始化配置,如存在,则拉取配置中心的配置覆盖本地的配置,当然也可以通过配置项来执行是否拉取远程配置。
在 Smconf 中每个配置都必须存在于某一个配置文件中,拉取的配置信息会注册到配置文件的实体类中,配置类是受 Spring 管理的,可以直接注入使用。
3. 服务端讲解
接着来看服务端,配置信息是存储在 Mongodb 中的,Mongodb 可以做副本集,不存在单点问题。可以启动多个 Smconf Server,并且不需要做负载均衡,在启动的时候它会将自身的信息注册到 Zookeeper 中。
当配置发生修改的时候,通过触发 Zookeeper 的 watcher 事件来通知 Smconf Client 配置有修改,需要重新加载配置。
Smconf 提供了友好的 Web 页面对配置信息进行管理,通过不同的账号、不同的权限控制,确保配置不被随便修改。
推荐电子商务源码
java springcloud 版 b2b2c 社交电商 spring cloud 分布式微服务 (七)高可用的分布式配置中心 (Spring Cloud Config)
Springcloud b2b2c 电子商务社交平台源码请加企鹅求求:一零三八七七四六二六。讲述了一个服务如何从配置中心读取文件,配置中心如何从远程 git 读取配置文件,当服务实例很多时,都从配置中心读取文件,这时可以考虑将配置中心做成一个微服务,将其集群化,从而达到高可用,架构图如下:
一、准备工作
继续使用上一篇文章的工程,创建一个 eureka-server 工程,用作服务注册中心。
在其 pom.xml 文件引入 Eureka 的起步依赖 spring-cloud-starter-eureka-server,代码如下:
<?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>com.forezp</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
在配置文件application.yml上,指定服务端口为8889,加上作为服务注册中心的基本配置,代码如下:
server:
port: 8889
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
入口类:
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
二、改造 config-server
在其 pom.xml 文件加上 EurekaClient 的起步依赖 spring-cloud-starter-eureka,代码如下:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
配置文件 application.yml,指定服务注册地址为 http://localhost:8889/eureka/,其他配置同上一篇文章,完整的配置如下:
spring.application.name=config-server
server.port=8888
spring.cloud.config.server.git.uri=https://github.com/forezp/SpringcloudConfig/
spring.cloud.config.server.git.searchPaths=respo
spring.cloud.config.label=master
spring.cloud.config.server.git.username= your username
spring.cloud.config.server.git.password= your password
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/
最后需要在程序的启动类 Application 加上 @EnableEureka 的注解。
三、改造 config-client
将其注册微到服务注册中心,作为 Eureka 客户端,需要 pom 文件加上起步依赖 spring-cloud-starter-eureka,代码如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
spring.application.name=config-client
spring.cloud.config.label=master
spring.cloud.config.profile=dev
#spring.cloud.config.uri= http://localhost:8888/
eureka.client.serviceUrl.defaultZone=http://localhost:8889/eureka/
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.serviceId=config-server
server.port=8881
需要 JAVASpring Cloud 大型企业分布式微服务云构建的 B2B2C 电子商务平台源码请加企鹅求求:一零三八七七四六二六
seata环境搭建(Nacos SpringCloud)
seata环境搭建(Nacos SpringCloud)
准备
文档
Seata部署指南
按上面的文档添加config.txt和nacos-config.sh,添加成功后的目录如下
│ config.txt
│
├─conf
│ │ file.conf.back
│ │ logback.xml
│ │ README.md
│ │ README-zh.md
│ │ file.conf.example
│ │ registry.conf.back
│ │ nacos-config.sh
│ │ registry.conf
│ │ file.conf
│ │
│ └─meta-inf
│ └─services
│
├─bin
│ │ seata-server.sh
│ │ seata-server.bat
│ │
│ ├─sessionStore
│ │ root.data
│ │
│ └─file_store
│ └─data
│ root.data
│
└─lib
版本
nacos版本1.4.0
seata版本1.3.0
SpringBoot与SpringCloud版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/>
</parent>
<!--版本说明:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E-->
<properties>
<spring.cloud.version>Hoxton.SR8</spring.cloud.version>
<spring.cloud.alibaba.version>2.2.4.RELEASE</spring.cloud.alibaba.version>
</properties>
执行seata sql脚本
1.修改file.conf
重点关注store.mode store.db
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"
## rsa decryption public key
publicKey = ""
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushdiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## MysqL/oracle/postgresql/h2/oceanbase etc.
dbType = "MysqL"
driverClassName = "com.MysqL.jdbc.Driver"
## if using MysqL to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
url = "jdbc:MysqL://ip:port/seata?rewriteBatchedStatements=true"
user = "root"
password = "root"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
## redis store property
redis {
## redis mode: single、sentinel
mode = "single"
## single mode property
single {
host = ""
port = "6379"
}
## sentinel mode property
sentinel {
masterName = ""
## such as "10.28.235.65:26379,10.28.235.65:26380,10.28.235.65:26381"
sentinelHosts = ""
}
password = ""
database = "0"
minConn = 1
maxConn = 10
maxTotal = 100
queryLimit = 100
}
}
2.修改registry.conf
重点关注
registry.type
config.nacos
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "ip:port"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = 0
password = ""
cluster = "default"
timeout = 0
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
aclToken = ""
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "ip:port"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
consul {
serverAddr = "127.0.0.1:8500"
aclToken = ""
}
apollo {
appId = "seata-server"
## apolloConfigService will cover apolloMeta
apolloMeta = "http://192.168.1.204:8801"
apolloConfigService = "http://192.168.1.204:8080"
namespace = "application"
apolloAccesskeySecret = ""
cluster = "seata"
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
nodePath = "/seata/seata.properties"
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
3.新建命名空间
nacos控制台 新建名为seata的namespace
记下命名空间ID
4.修改config.txt
修改 store.mode , store.db.url等配置
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=true
transport.threadFactory.bossthreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossthreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
store.mode=db
store.lock.mode=file
store.session.mode=file
store.publicKey=
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushdiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
store.db.dbType=MysqL
store.db.driverClassName=com.MysqL.jdbc.Driver
store.db.url=jdbc:MysqL://ip:port/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
执行以下命令将配置导入nacos配置中心
sh nacos-config.sh -h nacosIp -t 命名空间ID
5.启动seata-server
执行bin目录下脚本文件
到nacos控制台下看服务是否注册成功
6.客户端依赖与配置
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
application.yml中添加
seata:
enabled: true
enable-auto-data-source-proxy: true
tx-service-group: my_test_tx_group
registry:
type: nacos
nacos:
application: seata-server
server-addr: nacosIp:nacosPort
username: nacos
password: nh5[;e_b9Q=Z
config:
type: nacos
nacos:
server-addr: nacosIp:nacosPort
group: SEATA_GROUP
namespace: 命名空间ID
username: nacos
password: nacos
service:
vgroup-mapping:
my_test_tx_group: default
disable-global-transaction: false
client:
rm:
report-success-enable: false
7.启动Client
如果显示下面的日志说明 TM注册成功
register TM success. client version:1.3.0, server version:1.3
关于SpringCloud.H + Nacos1.4.1 + 分布式事物Seata1.4.1和29【详细版】的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于(七十七) springcloud+springcloud+vue+uniapp分布式微服务电商 商城之Spring Cloud集成Spring Data Redis、(四十五) springcloud 分布式微服务电商 商城之跟我学习 SpringCloud-Smconf(分布式配置管理框架)概述、java springcloud 版 b2b2c 社交电商 spring cloud 分布式微服务 (七)高可用的分布式配置中心 (Spring Cloud Config)、seata环境搭建(Nacos SpringCloud)的相关信息,请在本站寻找。
本文标签: